villsim/backend/domain/resources.py

181 lines
5.9 KiB
Python

"""Resource definitions for the Village Simulation.
Resource effects and decay rates are loaded dynamically from the global config.
"""
from dataclasses import dataclass
from enum import Enum
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from backend.config import SimulationConfig
class ResourceType(Enum):
"""Types of resources available in the simulation."""
MEAT = "meat"
BERRIES = "berries"
WATER = "water"
WOOD = "wood"
HIDE = "hide"
CLOTHES = "clothes"
@dataclass
class ResourceEffect:
"""Effects applied when consuming a resource."""
hunger: int = 0
thirst: int = 0
heat: int = 0
energy: int = 0
def get_resource_effects() -> dict[ResourceType, ResourceEffect]:
"""Get resource effects from the global config."""
# Import here to avoid circular imports
from backend.config import get_config
config = get_config()
resources = config.resources
return {
ResourceType.MEAT: ResourceEffect(
hunger=resources.meat_hunger,
energy=resources.meat_energy,
),
ResourceType.BERRIES: ResourceEffect(
hunger=resources.berries_hunger,
thirst=resources.berries_thirst,
),
ResourceType.WATER: ResourceEffect(
thirst=resources.water_thirst,
),
ResourceType.WOOD: ResourceEffect(), # Used as fuel, not consumed directly
ResourceType.HIDE: ResourceEffect(), # Used for crafting
ResourceType.CLOTHES: ResourceEffect(), # Passive heat reduction effect
}
def get_resource_decay_rates() -> dict[ResourceType, Optional[int]]:
"""Get resource decay rates from the global config."""
# Import here to avoid circular imports
from backend.config import get_config
config = get_config()
resources = config.resources
return {
ResourceType.MEAT: resources.meat_decay if resources.meat_decay > 0 else None,
ResourceType.BERRIES: resources.berries_decay if resources.berries_decay > 0 else None,
ResourceType.WATER: None, # Infinite
ResourceType.WOOD: None, # Infinite
ResourceType.HIDE: None, # Infinite
ResourceType.CLOTHES: resources.clothes_decay if resources.clothes_decay > 0 else None,
}
def get_fire_heat() -> int:
"""Get heat provided by building a fire."""
from backend.config import get_config
return get_config().resources.fire_heat
# Cached values for performance
_resource_effects_cache: Optional[dict[ResourceType, ResourceEffect]] = None
_resource_decay_cache: Optional[dict[ResourceType, Optional[int]]] = None
def get_cached_resource_effects() -> dict[ResourceType, ResourceEffect]:
"""Get cached resource effects."""
global _resource_effects_cache
if _resource_effects_cache is None:
_resource_effects_cache = get_resource_effects()
return _resource_effects_cache
def get_cached_resource_decay_rates() -> dict[ResourceType, Optional[int]]:
"""Get cached resource decay rates."""
global _resource_decay_cache
if _resource_decay_cache is None:
_resource_decay_cache = get_resource_decay_rates()
return _resource_decay_cache
def reset_resource_cache() -> None:
"""Reset resource caches (call when config changes)."""
global _resource_effects_cache, _resource_decay_cache
_resource_effects_cache = None
_resource_decay_cache = None
# Accessor classes for backwards compatibility
class _ResourceEffectsAccessor:
"""Accessor that provides dict-like access to resource effects."""
def __getitem__(self, resource_type: ResourceType) -> ResourceEffect:
return get_cached_resource_effects()[resource_type]
def get(self, resource_type: ResourceType, default=None) -> Optional[ResourceEffect]:
return get_cached_resource_effects().get(resource_type, default)
def __contains__(self, resource_type: ResourceType) -> bool:
return resource_type in get_cached_resource_effects()
class _ResourceDecayAccessor:
"""Accessor that provides dict-like access to resource decay rates."""
def __getitem__(self, resource_type: ResourceType) -> Optional[int]:
return get_cached_resource_decay_rates()[resource_type]
def get(self, resource_type: ResourceType, default=None) -> Optional[int]:
return get_cached_resource_decay_rates().get(resource_type, default)
def __contains__(self, resource_type: ResourceType) -> bool:
return resource_type in get_cached_resource_decay_rates()
# These provide the same interface as before but load from config
RESOURCE_EFFECTS = _ResourceEffectsAccessor()
RESOURCE_DECAY_RATES = _ResourceDecayAccessor()
@dataclass
class Resource:
"""A resource instance in the simulation."""
type: ResourceType
quantity: int = 1
created_turn: int = 0
@property
def decay_rate(self) -> Optional[int]:
"""Get the decay rate for this resource type."""
return get_cached_resource_decay_rates()[self.type]
@property
def effect(self) -> ResourceEffect:
"""Get the effect of consuming this resource."""
return get_cached_resource_effects()[self.type]
def is_expired(self, current_turn: int) -> bool:
"""Check if the resource has decayed."""
if self.decay_rate is None:
return False
return (current_turn - self.created_turn) >= self.decay_rate
def turns_until_decay(self, current_turn: int) -> Optional[int]:
"""Get remaining turns until decay, None if infinite."""
if self.decay_rate is None:
return None
remaining = self.decay_rate - (current_turn - self.created_turn)
return max(0, remaining)
def to_dict(self) -> dict:
"""Convert to dictionary for API serialization."""
return {
"type": self.type.value,
"quantity": self.quantity,
"created_turn": self.created_turn,
"decay_rate": self.decay_rate,
}