192 lines
6.4 KiB
Python

"""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,
}