412 lines
11 KiB
Python
412 lines
11 KiB
Python
"""Predefined goals for GOAP agents.
|
|
|
|
Goals are organized into categories:
|
|
- Survival goals: Immediate needs (thirst, hunger, heat, energy)
|
|
- Resource goals: Building reserves
|
|
- Economic goals: Trading and wealth building
|
|
"""
|
|
|
|
from .goal import Goal, GoalType, create_survival_goal, create_resource_stock_goal, create_economic_goal
|
|
from .world_state import WorldState
|
|
|
|
|
|
# =============================================================================
|
|
# SURVIVAL GOALS
|
|
# =============================================================================
|
|
|
|
def _create_satisfy_thirst_goal() -> Goal:
|
|
"""Satisfy immediate thirst."""
|
|
return create_survival_goal(
|
|
goal_type=GoalType.SATISFY_THIRST,
|
|
name="Satisfy Thirst",
|
|
stat_name="thirst",
|
|
target_pct=0.5, # Want to get to 50%
|
|
base_priority=15.0, # Highest base priority - thirst is most dangerous
|
|
)
|
|
|
|
|
|
def _create_satisfy_hunger_goal() -> Goal:
|
|
"""Satisfy immediate hunger."""
|
|
return create_survival_goal(
|
|
goal_type=GoalType.SATISFY_HUNGER,
|
|
name="Satisfy Hunger",
|
|
stat_name="hunger",
|
|
target_pct=0.5,
|
|
base_priority=12.0,
|
|
)
|
|
|
|
|
|
def _create_maintain_heat_goal() -> Goal:
|
|
"""Maintain body heat."""
|
|
return create_survival_goal(
|
|
goal_type=GoalType.MAINTAIN_HEAT,
|
|
name="Maintain Heat",
|
|
stat_name="heat",
|
|
target_pct=0.5,
|
|
base_priority=10.0,
|
|
)
|
|
|
|
|
|
def _create_restore_energy_goal() -> Goal:
|
|
"""Restore energy when low."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
return state.energy_pct >= 0.4
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
if state.energy_pct >= 0.4:
|
|
return 0.0
|
|
|
|
# Priority increases as energy decreases
|
|
urgency = (0.4 - state.energy_pct) / 0.4
|
|
|
|
# But not if we have more urgent survival needs
|
|
max_vital_urgency = max(state.thirst_urgency, state.hunger_urgency, state.heat_urgency)
|
|
if max_vital_urgency > 1.5:
|
|
# Critical survival need - don't rest
|
|
return 0.0
|
|
|
|
base_priority = 6.0 * urgency
|
|
|
|
# Evening boost - want energy for night
|
|
if state.is_evening:
|
|
base_priority *= 1.5
|
|
|
|
return base_priority
|
|
|
|
return Goal(
|
|
goal_type=GoalType.RESTORE_ENERGY,
|
|
name="Restore Energy",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
target_state={"energy_pct": 0.6},
|
|
max_plan_depth=1, # Just rest
|
|
)
|
|
|
|
|
|
SURVIVAL_GOALS = [
|
|
_create_satisfy_thirst_goal(),
|
|
_create_satisfy_hunger_goal(),
|
|
_create_maintain_heat_goal(),
|
|
_create_restore_energy_goal(),
|
|
]
|
|
|
|
|
|
# =============================================================================
|
|
# RESOURCE GOALS
|
|
# =============================================================================
|
|
|
|
def _create_stock_water_goal() -> Goal:
|
|
"""Maintain water reserves."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
target = int(2 * (0.5 + state.hoarding_rate))
|
|
return state.water_count >= target
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
target = int(2 * (0.5 + state.hoarding_rate))
|
|
|
|
if state.water_count >= target:
|
|
return 0.0
|
|
|
|
deficit = target - state.water_count
|
|
base_priority = 4.0 * (deficit / max(1, target))
|
|
|
|
# Lower if urgent survival needs
|
|
if max(state.thirst_urgency, state.hunger_urgency) > 1.0:
|
|
base_priority *= 0.3
|
|
|
|
# Evening boost
|
|
if state.is_evening:
|
|
base_priority *= 2.0
|
|
|
|
return base_priority
|
|
|
|
return Goal(
|
|
goal_type=GoalType.STOCK_WATER,
|
|
name="Stock Water",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
max_plan_depth=2,
|
|
)
|
|
|
|
|
|
def _create_stock_food_goal() -> Goal:
|
|
"""Maintain food reserves (meat + berries)."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
target = int(3 * (0.5 + state.hoarding_rate))
|
|
return state.food_count >= target
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
target = int(3 * (0.5 + state.hoarding_rate))
|
|
|
|
if state.food_count >= target:
|
|
return 0.0
|
|
|
|
deficit = target - state.food_count
|
|
base_priority = 4.0 * (deficit / max(1, target))
|
|
|
|
# Lower if urgent survival needs
|
|
if max(state.thirst_urgency, state.hunger_urgency) > 1.0:
|
|
base_priority *= 0.3
|
|
|
|
# Evening boost
|
|
if state.is_evening:
|
|
base_priority *= 2.0
|
|
|
|
# Risk-takers may prefer hunting (more food per action)
|
|
base_priority *= (0.8 + state.risk_tolerance * 0.4)
|
|
|
|
return base_priority
|
|
|
|
return Goal(
|
|
goal_type=GoalType.STOCK_FOOD,
|
|
name="Stock Food",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
max_plan_depth=2,
|
|
)
|
|
|
|
|
|
def _create_stock_wood_goal() -> Goal:
|
|
"""Maintain wood reserves for fires."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
target = int(2 * (0.5 + state.hoarding_rate))
|
|
return state.wood_count >= target
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
target = int(2 * (0.5 + state.hoarding_rate))
|
|
|
|
if state.wood_count >= target:
|
|
return 0.0
|
|
|
|
deficit = target - state.wood_count
|
|
base_priority = 3.0 * (deficit / max(1, target))
|
|
|
|
# Higher priority if heat is becoming an issue
|
|
if state.heat_urgency > 0.5:
|
|
base_priority *= 1.5
|
|
|
|
# Lower if urgent survival needs
|
|
if max(state.thirst_urgency, state.hunger_urgency) > 1.0:
|
|
base_priority *= 0.3
|
|
|
|
return base_priority
|
|
|
|
return Goal(
|
|
goal_type=GoalType.STOCK_WOOD,
|
|
name="Stock Wood",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
max_plan_depth=2,
|
|
)
|
|
|
|
|
|
def _create_get_clothes_goal() -> Goal:
|
|
"""Get clothes for heat protection."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
return state.has_clothes
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
if state.has_clothes:
|
|
return 0.0
|
|
|
|
# Only pursue if we have hide
|
|
if state.hide_count < 1:
|
|
return 0.0
|
|
|
|
base_priority = 2.0
|
|
|
|
# Higher if heat is an issue
|
|
if state.heat_urgency > 0.3:
|
|
base_priority *= 1.5
|
|
|
|
return base_priority
|
|
|
|
return Goal(
|
|
goal_type=GoalType.GET_CLOTHES,
|
|
name="Get Clothes",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
max_plan_depth=1,
|
|
)
|
|
|
|
|
|
RESOURCE_GOALS = [
|
|
_create_stock_water_goal(),
|
|
_create_stock_food_goal(),
|
|
_create_stock_wood_goal(),
|
|
_create_get_clothes_goal(),
|
|
]
|
|
|
|
|
|
# =============================================================================
|
|
# ECONOMIC GOALS
|
|
# =============================================================================
|
|
|
|
def _create_build_wealth_goal() -> Goal:
|
|
"""Accumulate money through trading."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
return state.is_wealthy
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
if state.is_wealthy:
|
|
return 0.0
|
|
|
|
# Base priority scaled by wealth desire
|
|
base_priority = 2.0 * state.wealth_desire
|
|
|
|
# Only when survival is stable
|
|
max_urgency = max(state.thirst_urgency, state.hunger_urgency, state.heat_urgency)
|
|
if max_urgency > 0.5:
|
|
return 0.0
|
|
|
|
# Traders prioritize wealth more
|
|
if state.is_trader:
|
|
base_priority *= 2.0
|
|
|
|
return base_priority
|
|
|
|
return create_economic_goal(
|
|
goal_type=GoalType.BUILD_WEALTH,
|
|
name="Build Wealth",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
)
|
|
|
|
|
|
def _create_sell_excess_goal() -> Goal:
|
|
"""Sell excess resources on the market."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
# Satisfied if inventory is not getting full
|
|
return state.inventory_space > 3
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
if state.inventory_space > 5:
|
|
return 0.0 # Plenty of space
|
|
|
|
# Priority increases as inventory fills
|
|
fullness = 1.0 - (state.inventory_space / 12.0)
|
|
base_priority = 3.0 * fullness
|
|
|
|
# Low hoarders sell more readily
|
|
base_priority *= (1.5 - state.hoarding_rate)
|
|
|
|
# Only when survival is stable
|
|
max_urgency = max(state.thirst_urgency, state.hunger_urgency)
|
|
if max_urgency > 0.5:
|
|
base_priority *= 0.5
|
|
|
|
return base_priority
|
|
|
|
return create_economic_goal(
|
|
goal_type=GoalType.SELL_EXCESS,
|
|
name="Sell Excess",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
)
|
|
|
|
|
|
def _create_find_deals_goal() -> Goal:
|
|
"""Find good deals on the market."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
# This goal is never fully "satisfied" - it's opportunistic
|
|
return False
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
# Only pursue if we have money and market access
|
|
if state.money < 10:
|
|
return 0.0
|
|
|
|
# Check if there are deals available
|
|
has_deals = state.can_buy_water or state.can_buy_food or state.can_buy_wood
|
|
if not has_deals:
|
|
return 0.0
|
|
|
|
# Base priority from market affinity
|
|
base_priority = 2.0 * state.market_affinity
|
|
|
|
# Only when survival is stable
|
|
max_urgency = max(state.thirst_urgency, state.hunger_urgency)
|
|
if max_urgency > 0.5:
|
|
return 0.0
|
|
|
|
# Need inventory space
|
|
if state.inventory_space < 2:
|
|
return 0.0
|
|
|
|
return base_priority
|
|
|
|
return create_economic_goal(
|
|
goal_type=GoalType.FIND_DEALS,
|
|
name="Find Deals",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
)
|
|
|
|
|
|
def _create_trader_arbitrage_goal() -> Goal:
|
|
"""Trader-specific arbitrage goal (buy low, sell high)."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
return False # Always looking for opportunities
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
# Only for traders
|
|
if not state.is_trader:
|
|
return 0.0
|
|
|
|
# Need capital to trade
|
|
if state.money < 20:
|
|
return 1.0 # Low priority - need to sell something first
|
|
|
|
# Base priority for traders
|
|
base_priority = 5.0
|
|
|
|
# Only when survival is stable
|
|
max_urgency = max(state.thirst_urgency, state.hunger_urgency, state.heat_urgency)
|
|
if max_urgency > 0.3:
|
|
base_priority *= 0.5
|
|
|
|
return base_priority
|
|
|
|
return create_economic_goal(
|
|
goal_type=GoalType.TRADER_ARBITRAGE,
|
|
name="Trader Arbitrage",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
)
|
|
|
|
|
|
def _create_sleep_goal() -> Goal:
|
|
"""Sleep at night."""
|
|
def is_satisfied(state: WorldState) -> bool:
|
|
return not state.is_night # Satisfied when it's not night
|
|
|
|
def get_priority(state: WorldState) -> float:
|
|
if not state.is_night:
|
|
return 0.0
|
|
|
|
# Highest priority at night
|
|
return 100.0
|
|
|
|
return Goal(
|
|
goal_type=GoalType.SLEEP,
|
|
name="Sleep",
|
|
is_satisfied=is_satisfied,
|
|
get_priority=get_priority,
|
|
max_plan_depth=1,
|
|
)
|
|
|
|
|
|
ECONOMIC_GOALS = [
|
|
_create_build_wealth_goal(),
|
|
_create_sell_excess_goal(),
|
|
_create_find_deals_goal(),
|
|
_create_trader_arbitrage_goal(),
|
|
_create_sleep_goal(),
|
|
]
|
|
|
|
|
|
def get_all_goals() -> list[Goal]:
|
|
"""Get all available goals."""
|
|
return SURVIVAL_GOALS + RESOURCE_GOALS + ECONOMIC_GOALS
|
|
|