Снесарев Максим 308f738c37 [new] add goap agents
2026-01-19 20:45:35 +03:00

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,
)