"""Goal definitions for GOAP planning. Goals represent what an agent wants to achieve. Each goal has: - A name/type for identification - A condition that checks if the goal is satisfied - A priority function that determines how important the goal is - Optional target state values for the planner """ from dataclasses import dataclass, field from enum import Enum from typing import Callable, Optional from .world_state import WorldState class GoalType(Enum): """Types of goals agents can pursue.""" # Survival goals - highest priority when needed SATISFY_THIRST = "satisfy_thirst" SATISFY_HUNGER = "satisfy_hunger" MAINTAIN_HEAT = "maintain_heat" RESTORE_ENERGY = "restore_energy" # Resource goals - medium priority STOCK_WATER = "stock_water" STOCK_FOOD = "stock_food" STOCK_WOOD = "stock_wood" GET_CLOTHES = "get_clothes" # Economic goals - lower priority but persistent BUILD_WEALTH = "build_wealth" SELL_EXCESS = "sell_excess" FIND_DEALS = "find_deals" TRADER_ARBITRAGE = "trader_arbitrage" # Night behavior SLEEP = "sleep" @dataclass class Goal: """A goal that an agent can pursue. Goals are the driving force of GOAP. The planner searches for action sequences that transform the current world state into one where the goal condition is satisfied. Attributes: goal_type: The type of goal (for identification) name: Human-readable name is_satisfied: Function that checks if goal is achieved in a state get_priority: Function that calculates goal priority (higher = more important) target_state: Optional dict of state values the goal aims for max_plan_depth: Maximum actions to plan for this goal """ goal_type: GoalType name: str is_satisfied: Callable[[WorldState], bool] get_priority: Callable[[WorldState], float] target_state: dict = field(default_factory=dict) max_plan_depth: int = 3 def satisfied(self, state: WorldState) -> bool: """Check if goal is satisfied in the given state.""" return self.is_satisfied(state) def priority(self, state: WorldState) -> float: """Get the priority of this goal in the given state.""" return self.get_priority(state) def __repr__(self) -> str: return f"Goal({self.name})" def create_survival_goal( goal_type: GoalType, name: str, stat_name: str, target_pct: float = 0.6, base_priority: float = 10.0, ) -> Goal: """Factory for creating survival-related goals. Survival goals have high priority when the relevant stat is low. Priority scales with urgency. """ urgency_name = f"{stat_name}_urgency" pct_name = f"{stat_name}_pct" def is_satisfied(state: WorldState) -> bool: return getattr(state, pct_name) >= target_pct def get_priority(state: WorldState) -> float: urgency = getattr(state, urgency_name) pct = getattr(state, pct_name) if urgency <= 0: return 0.0 # No need to pursue this goal # Priority increases with urgency # Critical urgency (>1.0) gives very high priority priority = base_priority * urgency # Extra boost when critical if pct < state.critical_threshold: priority *= 2.0 return priority return Goal( goal_type=goal_type, name=name, is_satisfied=is_satisfied, get_priority=get_priority, target_state={pct_name: target_pct}, max_plan_depth=2, # Survival should be quick ) def create_resource_stock_goal( goal_type: GoalType, name: str, resource_name: str, target_count: int, base_priority: float = 5.0, ) -> Goal: """Factory for creating resource stockpiling goals. Resource goals have moderate priority and aim to maintain reserves. """ count_name = f"{resource_name}_count" def is_satisfied(state: WorldState) -> bool: return getattr(state, count_name) >= target_count def get_priority(state: WorldState) -> float: current = getattr(state, count_name) if current >= target_count: return 0.0 # Already have enough # Priority based on how far from target deficit = target_count - current priority = base_priority * (deficit / target_count) # Lower priority if survival is urgent max_urgency = max(state.thirst_urgency, state.hunger_urgency, state.heat_urgency) if max_urgency > 0.5: priority *= 0.5 # Hoarders prioritize stockpiling more priority *= (0.8 + state.hoarding_rate * 0.4) # Evening boost - stock up before night if state.is_evening: priority *= 1.5 return priority return Goal( goal_type=goal_type, name=name, is_satisfied=is_satisfied, get_priority=get_priority, target_state={count_name: target_count}, max_plan_depth=3, ) def create_economic_goal( goal_type: GoalType, name: str, is_satisfied: Callable[[WorldState], bool], get_priority: Callable[[WorldState], float], ) -> Goal: """Factory for creating economic/trading goals.""" return Goal( goal_type=goal_type, name=name, is_satisfied=is_satisfied, get_priority=get_priority, max_plan_depth=2, )