"""Centralized configuration for the Village Simulation.""" from dataclasses import dataclass, field, asdict from typing import Optional import json from pathlib import Path @dataclass class AgentStatsConfig: """Configuration for agent vital stats.""" # Maximum values max_energy: int = 50 max_hunger: int = 100 max_thirst: int = 100 max_heat: int = 100 max_faith: int = 100 # NEW: Religious faith level # Starting values start_energy: int = 50 start_hunger: int = 80 start_thirst: int = 80 start_heat: int = 100 start_faith: int = 50 # NEW: Start with moderate faith # Decay rates per turn energy_decay: int = 2 hunger_decay: int = 2 thirst_decay: int = 2 heat_decay: int = 2 faith_decay: int = 1 # NEW: Faith decays slowly without religious activity # Thresholds critical_threshold: float = 0.25 low_energy_threshold: int = 15 @dataclass class ResourceConfig: """Configuration for resource properties.""" # Decay rates (turns until spoilage, 0 = infinite) meat_decay: int = 8 berries_decay: int = 25 clothes_decay: int = 50 oil_decay: int = 0 # NEW: Oil doesn't decay fuel_decay: int = 0 # NEW: Refined fuel doesn't decay # Resource effects meat_hunger: int = 30 meat_energy: int = 5 berries_hunger: int = 8 berries_thirst: int = 3 water_thirst: int = 50 fire_heat: int = 15 fuel_heat: int = 35 # NEW: Fuel provides more heat than wood oil_energy: int = 0 # NEW: Raw oil has no direct use fuel_energy: int = 8 # NEW: Refined fuel provides energy @dataclass class ActionConfig: """Configuration for action costs and outcomes.""" # Energy costs (positive = restore, negative = spend) sleep_energy: int = 60 rest_energy: int = 10 hunt_energy: int = -15 gather_energy: int = -5 chop_wood_energy: int = -10 get_water_energy: int = -5 weave_energy: int = -8 build_fire_energy: int = -5 trade_energy: int = -1 # NEW: Oil industry actions drill_oil_energy: int = -10 refine_energy: int = -8 # NEW: Religious actions pray_energy: int = -2 preach_energy: int = -4 # NEW: Diplomatic actions negotiate_energy: int = -3 declare_war_energy: int = -5 make_peace_energy: int = -3 # Success chances (0.0 to 1.0) hunt_success: float = 0.7 chop_wood_success: float = 0.9 drill_oil_success: float = 0.6 # NEW: Harder to extract oil # Output quantities hunt_meat_min: int = 1 hunt_meat_max: int = 3 hunt_hide_min: int = 0 hunt_hide_max: int = 1 gather_min: int = 2 gather_max: int = 5 chop_wood_min: int = 1 chop_wood_max: int = 2 # NEW: Oil output drill_oil_min: int = 1 drill_oil_max: int = 3 # NEW: Religious action effects pray_faith_gain: int = 25 preach_faith_spread: int = 15 preach_convert_chance: float = 0.15 @dataclass class WorldConfig: """Configuration for world properties.""" width: int = 20 height: int = 20 initial_agents: int = 8 day_steps: int = 10 night_steps: int = 1 # Agent configuration inventory_slots: int = 10 starting_money: int = 100 # NEW: World features oil_fields_count: int = 3 # Number of oil field locations temple_count: int = 2 # Number of temple/religious locations @dataclass class MarketConfig: """Configuration for market behavior.""" turns_before_discount: int = 3 discount_rate: float = 0.15 base_price_multiplier: float = 1.2 @dataclass class EconomyConfig: """Configuration for economic behavior and agent trading.""" energy_to_money_ratio: float = 1.5 wealth_desire: float = 0.3 buy_efficiency_threshold: float = 0.7 min_wealth_target: int = 50 max_price_markup: float = 2.0 min_price_discount: float = 0.5 # NEW: Oil economy oil_base_price: int = 25 # Oil is valuable fuel_base_price: int = 40 # Refined fuel is more valuable @dataclass class ReligionConfig: """Configuration for religion system.""" num_religions: int = 3 # Number of different religions conversion_resistance: float = 0.5 # How hard to convert agents zealot_threshold: float = 0.80 # Faith level for zealot behavior faith_trade_bonus: float = 0.10 # Bonus when trading with same religion same_religion_bonus: float = 0.15 # General bonus with same religion different_religion_penalty: float = 0.10 # Penalty with different religion holy_war_threshold: float = 0.90 # Faith level to trigger religious conflict @dataclass class DiplomacyConfig: """Configuration for diplomacy and faction system.""" num_factions: int = 4 # Number of factions starting_relations: int = 50 # Neutral starting relations (0-100) alliance_threshold: int = 75 # Relations needed for alliance war_threshold: int = 25 # Relations below this = hostile relation_decay: int = 1 # Relations decay towards neutral trade_relation_boost: int = 2 # Trading improves relations war_damage_multiplier: float = 1.5 # Extra damage during war peace_treaty_duration: int = 20 # Turns peace treaty lasts war_exhaustion_rate: int = 2 # How fast war exhaustion builds @dataclass class SimulationConfig: """Master configuration containing all sub-configs.""" agent_stats: AgentStatsConfig = field(default_factory=AgentStatsConfig) resources: ResourceConfig = field(default_factory=ResourceConfig) actions: ActionConfig = field(default_factory=ActionConfig) world: WorldConfig = field(default_factory=WorldConfig) market: MarketConfig = field(default_factory=MarketConfig) economy: EconomyConfig = field(default_factory=EconomyConfig) religion: ReligionConfig = field(default_factory=ReligionConfig) # NEW diplomacy: DiplomacyConfig = field(default_factory=DiplomacyConfig) # NEW # Simulation control auto_step_interval: float = 1.0 def to_dict(self) -> dict: """Convert to dictionary.""" return { "agent_stats": asdict(self.agent_stats), "resources": asdict(self.resources), "actions": asdict(self.actions), "world": asdict(self.world), "market": asdict(self.market), "economy": asdict(self.economy), "religion": asdict(self.religion), "diplomacy": asdict(self.diplomacy), "auto_step_interval": self.auto_step_interval, } @classmethod def from_dict(cls, data: dict) -> "SimulationConfig": """Create from dictionary.""" return cls( agent_stats=AgentStatsConfig(**data.get("agent_stats", {})), resources=ResourceConfig(**data.get("resources", {})), actions=ActionConfig(**data.get("actions", {})), world=WorldConfig(**data.get("world", {})), market=MarketConfig(**data.get("market", {})), economy=EconomyConfig(**data.get("economy", {})), religion=ReligionConfig(**data.get("religion", {})), diplomacy=DiplomacyConfig(**data.get("diplomacy", {})), auto_step_interval=data.get("auto_step_interval", 1.0), ) def save(self, path: str = "config.json") -> None: """Save configuration to JSON file.""" with open(path, "w") as f: json.dump(self.to_dict(), f, indent=2) @classmethod def load(cls, path: str = "config.json") -> "SimulationConfig": """Load configuration from JSON file.""" try: with open(path, "r") as f: data = json.load(f) return cls.from_dict(data) except FileNotFoundError: return cls() # Global configuration instance _config: Optional[SimulationConfig] = None def get_config() -> SimulationConfig: """Get the global configuration instance.""" global _config if _config is None: _config = load_config() return _config def load_config(path: str = "config.json") -> SimulationConfig: """Load configuration from JSON file, falling back to defaults.""" try: config_path = Path(path) if not config_path.is_absolute(): workspace_root = Path(__file__).parent.parent config_path = workspace_root / path if config_path.exists(): with open(config_path, "r") as f: data = json.load(f) return SimulationConfig.from_dict(data) except (FileNotFoundError, json.JSONDecodeError) as e: print(f"Warning: Could not load config from {path}: {e}") return SimulationConfig() def set_config(config: SimulationConfig) -> None: """Set the global configuration instance.""" global _config _config = config def reset_config() -> SimulationConfig: """Reset configuration to defaults.""" global _config _config = SimulationConfig() _reset_all_caches() return _config def reload_config(path: str = "config.json") -> SimulationConfig: """Reload configuration from file and reset all caches.""" global _config _config = load_config(path) _reset_all_caches() return _config def _reset_all_caches() -> None: """Reset all module caches that depend on config values.""" try: from backend.domain.action import reset_action_config_cache reset_action_config_cache() except ImportError: pass try: from backend.domain.resources import reset_resource_cache reset_resource_cache() except ImportError: pass