186 lines
5.3 KiB
Python
186 lines
5.3 KiB
Python
"""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,
|
|
)
|
|
|