"""Action definitions for the Village Simulation. Action configurations are loaded dynamically from the global config. """ from dataclasses import dataclass, field from enum import Enum from typing import Optional, TYPE_CHECKING from .resources import ResourceType if TYPE_CHECKING: from backend.config import SimulationConfig class ActionType(Enum): """Types of actions an agent can perform.""" SLEEP = "sleep" # Night action - restores energy REST = "rest" # Day action - restores some energy HUNT = "hunt" # Produces meat and hide GATHER = "gather" # Produces berries CHOP_WOOD = "chop_wood" # Produces wood GET_WATER = "get_water" # Produces water WEAVE = "weave" # Produces clothes from hide BUILD_FIRE = "build_fire" # Consumes wood, provides heat TRADE = "trade" # Market interaction CONSUME = "consume" # Consume resource from inventory @dataclass class ActionConfig: """Configuration for an action type.""" energy_cost: int # Negative = cost, positive = restoration success_chance: float = 1.0 # 0.0 to 1.0 min_output: int = 0 max_output: int = 0 output_resource: Optional[ResourceType] = None secondary_output: Optional[ResourceType] = None secondary_min: int = 0 secondary_max: int = 0 requires_resource: Optional[ResourceType] = None requires_quantity: int = 0 def get_action_config() -> dict[ActionType, ActionConfig]: """Get action configurations from the global config. This function dynamically builds ACTION_CONFIG from config.json values. """ # Import here to avoid circular imports from backend.config import get_config config = get_config() actions = config.actions return { ActionType.SLEEP: ActionConfig( energy_cost=actions.sleep_energy, # Restores energy ), ActionType.REST: ActionConfig( energy_cost=actions.rest_energy, # Restores some energy ), ActionType.HUNT: ActionConfig( energy_cost=actions.hunt_energy, success_chance=actions.hunt_success, min_output=actions.hunt_meat_min, max_output=actions.hunt_meat_max, output_resource=ResourceType.MEAT, secondary_output=ResourceType.HIDE, secondary_min=actions.hunt_hide_min, secondary_max=actions.hunt_hide_max, ), ActionType.GATHER: ActionConfig( energy_cost=actions.gather_energy, success_chance=1.0, min_output=actions.gather_min, max_output=actions.gather_max, output_resource=ResourceType.BERRIES, ), ActionType.CHOP_WOOD: ActionConfig( energy_cost=actions.chop_wood_energy, success_chance=actions.chop_wood_success, min_output=actions.chop_wood_min, max_output=actions.chop_wood_max, output_resource=ResourceType.WOOD, ), ActionType.GET_WATER: ActionConfig( energy_cost=actions.get_water_energy, success_chance=1.0, min_output=1, max_output=1, output_resource=ResourceType.WATER, ), ActionType.WEAVE: ActionConfig( energy_cost=actions.weave_energy, success_chance=1.0, min_output=1, max_output=1, output_resource=ResourceType.CLOTHES, requires_resource=ResourceType.HIDE, requires_quantity=1, ), ActionType.BUILD_FIRE: ActionConfig( energy_cost=actions.build_fire_energy, success_chance=1.0, requires_resource=ResourceType.WOOD, requires_quantity=1, ), ActionType.TRADE: ActionConfig( energy_cost=actions.trade_energy, ), ActionType.CONSUME: ActionConfig( energy_cost=0, ), } # Lazy-loaded action config cache _action_config_cache: Optional[dict[ActionType, ActionConfig]] = None def get_cached_action_config() -> dict[ActionType, ActionConfig]: """Get cached action config (rebuilds if config changes).""" global _action_config_cache if _action_config_cache is None: _action_config_cache = get_action_config() return _action_config_cache def reset_action_config_cache() -> None: """Reset the action config cache (call when config changes).""" global _action_config_cache _action_config_cache = None # For backwards compatibility - this is a property-like access # that returns fresh config each time (use get_cached_action_config for performance) class _ActionConfigAccessor: """Accessor class that provides dict-like access to action configs.""" def __getitem__(self, action_type: ActionType) -> ActionConfig: return get_cached_action_config()[action_type] def get(self, action_type: ActionType, default=None) -> Optional[ActionConfig]: return get_cached_action_config().get(action_type, default) def __contains__(self, action_type: ActionType) -> bool: return action_type in get_cached_action_config() def items(self): return get_cached_action_config().items() def keys(self): return get_cached_action_config().keys() def values(self): return get_cached_action_config().values() # This provides the same interface as before but loads from config ACTION_CONFIG = _ActionConfigAccessor() @dataclass class ActionResult: """Result of executing an action.""" action_type: ActionType success: bool energy_spent: int = 0 resources_gained: list = field(default_factory=list) resources_consumed: list = field(default_factory=list) heat_gained: int = 0 message: str = "" def to_dict(self) -> dict: """Convert to dictionary for API serialization.""" return { "action_type": self.action_type.value, "success": self.success, "energy_spent": self.energy_spent, "resources_gained": [ {"type": r.type.value, "quantity": r.quantity} for r in self.resources_gained ], "resources_consumed": [ {"type": r.type.value, "quantity": r.quantity} for r in self.resources_consumed ], "heat_gained": self.heat_gained, "message": self.message, }