147 lines
4.7 KiB
Python
147 lines
4.7 KiB
Python
"""World container for the Village Simulation."""
|
|
|
|
import random
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
from backend.domain.agent import Agent, Position, Profession
|
|
|
|
|
|
class TimeOfDay(Enum):
|
|
"""Current time of day in the simulation."""
|
|
DAY = "day"
|
|
NIGHT = "night"
|
|
|
|
|
|
@dataclass
|
|
class WorldConfig:
|
|
"""Configuration for the world."""
|
|
width: int = 20
|
|
height: int = 20
|
|
initial_agents: int = 8
|
|
day_steps: int = 10
|
|
night_steps: int = 1
|
|
|
|
|
|
@dataclass
|
|
class World:
|
|
"""Container for all entities in the simulation."""
|
|
config: WorldConfig = field(default_factory=WorldConfig)
|
|
agents: list[Agent] = field(default_factory=list)
|
|
current_turn: int = 0
|
|
current_day: int = 1
|
|
step_in_day: int = 0
|
|
time_of_day: TimeOfDay = TimeOfDay.DAY
|
|
|
|
# Statistics
|
|
total_agents_spawned: int = 0
|
|
total_agents_died: int = 0
|
|
|
|
def spawn_agent(
|
|
self,
|
|
name: Optional[str] = None,
|
|
profession: Optional[Profession] = None,
|
|
position: Optional[Position] = None,
|
|
) -> Agent:
|
|
"""Spawn a new agent in the world."""
|
|
# All agents are now generic villagers - profession is not used for decisions
|
|
if profession is None:
|
|
profession = Profession.VILLAGER
|
|
|
|
if position is None:
|
|
position = Position(
|
|
x=random.randint(0, self.config.width - 1),
|
|
y=random.randint(0, self.config.height - 1),
|
|
)
|
|
|
|
agent = Agent(
|
|
name=name or f"Villager_{self.total_agents_spawned + 1}",
|
|
profession=profession,
|
|
position=position,
|
|
)
|
|
|
|
self.agents.append(agent)
|
|
self.total_agents_spawned += 1
|
|
return agent
|
|
|
|
def get_agent(self, agent_id: str) -> Optional[Agent]:
|
|
"""Get an agent by ID."""
|
|
for agent in self.agents:
|
|
if agent.id == agent_id:
|
|
return agent
|
|
return None
|
|
|
|
def remove_dead_agents(self) -> list[Agent]:
|
|
"""Remove all dead agents from the world. Returns list of removed agents.
|
|
Note: This is now handled by the engine's corpse system for visualization.
|
|
"""
|
|
dead_agents = [a for a in self.agents if not a.is_alive() and not a.is_corpse()]
|
|
# Don't actually remove here - let the engine handle corpse visualization
|
|
return dead_agents
|
|
|
|
def advance_time(self) -> None:
|
|
"""Advance the simulation time by one step."""
|
|
self.current_turn += 1
|
|
self.step_in_day += 1
|
|
|
|
total_steps = self.config.day_steps + self.config.night_steps
|
|
|
|
if self.step_in_day > total_steps:
|
|
self.step_in_day = 1
|
|
self.current_day += 1
|
|
|
|
# Determine time of day
|
|
if self.step_in_day <= self.config.day_steps:
|
|
self.time_of_day = TimeOfDay.DAY
|
|
else:
|
|
self.time_of_day = TimeOfDay.NIGHT
|
|
|
|
def is_night(self) -> bool:
|
|
"""Check if it's currently night."""
|
|
return self.time_of_day == TimeOfDay.NIGHT
|
|
|
|
def get_living_agents(self) -> list[Agent]:
|
|
"""Get all living agents (excludes corpses)."""
|
|
return [a for a in self.agents if a.is_alive() and not a.is_corpse()]
|
|
|
|
def get_statistics(self) -> dict:
|
|
"""Get current world statistics."""
|
|
living = self.get_living_agents()
|
|
total_money = sum(a.money for a in living)
|
|
|
|
profession_counts = {}
|
|
for agent in living:
|
|
prof = agent.profession.value
|
|
profession_counts[prof] = profession_counts.get(prof, 0) + 1
|
|
|
|
return {
|
|
"current_turn": self.current_turn,
|
|
"current_day": self.current_day,
|
|
"step_in_day": self.step_in_day,
|
|
"time_of_day": self.time_of_day.value,
|
|
"living_agents": len(living),
|
|
"total_agents_spawned": self.total_agents_spawned,
|
|
"total_agents_died": self.total_agents_died,
|
|
"total_money_in_circulation": total_money,
|
|
"professions": profession_counts,
|
|
}
|
|
|
|
def get_state_snapshot(self) -> dict:
|
|
"""Get a full snapshot of the world state for API."""
|
|
return {
|
|
"turn": self.current_turn,
|
|
"day": self.current_day,
|
|
"step_in_day": self.step_in_day,
|
|
"time_of_day": self.time_of_day.value,
|
|
"world_size": {"width": self.config.width, "height": self.config.height},
|
|
"agents": [a.to_dict() for a in self.agents],
|
|
"statistics": self.get_statistics(),
|
|
}
|
|
|
|
def initialize(self) -> None:
|
|
"""Initialize the world with starting agents."""
|
|
for _ in range(self.config.initial_agents):
|
|
self.spawn_agent()
|
|
|