203 lines
7.3 KiB
Python
203 lines
7.3 KiB
Python
|
|
from typing import Dict, List, Optional, Set, Tuple
|
||
|
|
from server.models import Player, Building, BuildingType, BUILDING_CONFIGS
|
||
|
|
import time
|
||
|
|
|
||
|
|
class GameState:
|
||
|
|
"""Manages the complete game state"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.players: Dict[str, Player] = {}
|
||
|
|
self.buildings: Dict[Tuple[int, int], Building] = {}
|
||
|
|
self.road_network: Set[Tuple[int, int]] = set()
|
||
|
|
self.connected_zones: List[Set[Tuple[int, int]]] = []
|
||
|
|
|
||
|
|
def get_or_create_player(self, nickname: str, player_id: str) -> Player:
|
||
|
|
"""Get existing player or create new one"""
|
||
|
|
if player_id in self.players:
|
||
|
|
player = self.players[player_id]
|
||
|
|
player.is_online = True
|
||
|
|
player.last_online = time.time()
|
||
|
|
return player
|
||
|
|
|
||
|
|
player = Player(
|
||
|
|
player_id=player_id,
|
||
|
|
nickname=nickname,
|
||
|
|
last_online=time.time()
|
||
|
|
)
|
||
|
|
self.players[player_id] = player
|
||
|
|
return player
|
||
|
|
|
||
|
|
def place_building(self, player_id: str, building_type: str, x: int, y: int) -> dict:
|
||
|
|
"""Place a building on the map"""
|
||
|
|
# Check if tile is occupied
|
||
|
|
if (x, y) in self.buildings:
|
||
|
|
return {"success": False, "error": "Tile already occupied"}
|
||
|
|
|
||
|
|
# Get player
|
||
|
|
player = self.players.get(player_id)
|
||
|
|
if not player:
|
||
|
|
return {"success": False, "error": "Player not found"}
|
||
|
|
|
||
|
|
# Get building config
|
||
|
|
try:
|
||
|
|
b_type = BuildingType(building_type)
|
||
|
|
config = BUILDING_CONFIGS[b_type]
|
||
|
|
except (ValueError, KeyError):
|
||
|
|
return {"success": False, "error": "Invalid building type"}
|
||
|
|
|
||
|
|
# Check if player can afford
|
||
|
|
if not player.can_afford(config.cost):
|
||
|
|
return {"success": False, "error": "Not enough money"}
|
||
|
|
|
||
|
|
# Check requirements
|
||
|
|
if config.requires_population > player.population:
|
||
|
|
return {"success": False, "error": f"Requires {config.requires_population} population"}
|
||
|
|
|
||
|
|
if config.power_required and not self._has_power_plant(player_id):
|
||
|
|
return {"success": False, "error": "Requires power plant"}
|
||
|
|
|
||
|
|
# Place building
|
||
|
|
building = Building(
|
||
|
|
building_type=b_type,
|
||
|
|
x=x,
|
||
|
|
y=y,
|
||
|
|
owner_id=player_id,
|
||
|
|
placed_at=time.time()
|
||
|
|
)
|
||
|
|
|
||
|
|
self.buildings[(x, y)] = building
|
||
|
|
player.deduct_money(config.cost)
|
||
|
|
|
||
|
|
# Update road network if it's a road
|
||
|
|
if b_type == BuildingType.ROAD:
|
||
|
|
self.road_network.add((x, y))
|
||
|
|
self._update_connected_zones()
|
||
|
|
|
||
|
|
return {"success": True, "building": building.to_dict()}
|
||
|
|
|
||
|
|
def remove_building(self, player_id: str, x: int, y: int) -> dict:
|
||
|
|
"""Remove a building"""
|
||
|
|
building = self.buildings.get((x, y))
|
||
|
|
|
||
|
|
if not building:
|
||
|
|
return {"success": False, "error": "No building at this location"}
|
||
|
|
|
||
|
|
if building.owner_id != player_id:
|
||
|
|
return {"success": False, "error": "You don't own this building"}
|
||
|
|
|
||
|
|
# Remove building
|
||
|
|
del self.buildings[(x, y)]
|
||
|
|
|
||
|
|
# Update road network if it was a road
|
||
|
|
if building.building_type == BuildingType.ROAD:
|
||
|
|
self.road_network.discard((x, y))
|
||
|
|
self._update_connected_zones()
|
||
|
|
|
||
|
|
return {"success": True}
|
||
|
|
|
||
|
|
def edit_building_name(self, player_id: str, x: int, y: int, name: str) -> dict:
|
||
|
|
"""Edit building name"""
|
||
|
|
building = self.buildings.get((x, y))
|
||
|
|
|
||
|
|
if not building:
|
||
|
|
return {"success": False, "error": "No building at this location"}
|
||
|
|
|
||
|
|
if building.owner_id != player_id:
|
||
|
|
return {"success": False, "error": "You don't own this building"}
|
||
|
|
|
||
|
|
building.name = name
|
||
|
|
return {"success": True}
|
||
|
|
|
||
|
|
def _has_power_plant(self, player_id: str) -> bool:
|
||
|
|
"""Check if player has a power plant"""
|
||
|
|
for building in self.buildings.values():
|
||
|
|
if (building.owner_id == player_id and
|
||
|
|
building.building_type == BuildingType.POWER_PLANT):
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
def _update_connected_zones(self):
|
||
|
|
"""Update connected zones based on road network using flood fill"""
|
||
|
|
if not self.road_network:
|
||
|
|
self.connected_zones = []
|
||
|
|
return
|
||
|
|
|
||
|
|
visited = set()
|
||
|
|
self.connected_zones = []
|
||
|
|
|
||
|
|
for road_pos in self.road_network:
|
||
|
|
if road_pos in visited:
|
||
|
|
continue
|
||
|
|
|
||
|
|
# Flood fill to find connected zone
|
||
|
|
zone = set()
|
||
|
|
stack = [road_pos]
|
||
|
|
|
||
|
|
while stack:
|
||
|
|
pos = stack.pop()
|
||
|
|
if pos in visited:
|
||
|
|
continue
|
||
|
|
|
||
|
|
visited.add(pos)
|
||
|
|
zone.add(pos)
|
||
|
|
|
||
|
|
# Check adjacent positions
|
||
|
|
x, y = pos
|
||
|
|
for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
|
||
|
|
adj_pos = (x + dx, y + dy)
|
||
|
|
if adj_pos in self.road_network and adj_pos not in visited:
|
||
|
|
stack.append(adj_pos)
|
||
|
|
|
||
|
|
self.connected_zones.append(zone)
|
||
|
|
|
||
|
|
def get_building_zone_size(self, x: int, y: int) -> int:
|
||
|
|
"""Get the size of the connected zone for a building"""
|
||
|
|
# Find adjacent roads
|
||
|
|
for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, 1), (1, -1), (-1, -1)]:
|
||
|
|
road_pos = (x + dx, y + dy)
|
||
|
|
if road_pos in self.road_network:
|
||
|
|
# Find which zone this road belongs to
|
||
|
|
for zone in self.connected_zones:
|
||
|
|
if road_pos in zone:
|
||
|
|
return len(zone)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
def get_player_buildings(self, player_id: str) -> List[Building]:
|
||
|
|
"""Get all buildings owned by a player"""
|
||
|
|
return [b for b in self.buildings.values() if b.owner_id == player_id]
|
||
|
|
|
||
|
|
def get_state(self) -> dict:
|
||
|
|
"""Get complete game state for broadcasting"""
|
||
|
|
return {
|
||
|
|
"players": {pid: p.to_dict() for pid, p in self.players.items()},
|
||
|
|
"buildings": {f"{x},{y}": b.to_dict() for (x, y), b in self.buildings.items()}
|
||
|
|
}
|
||
|
|
|
||
|
|
def load_state(self, state: dict):
|
||
|
|
"""Load game state from database"""
|
||
|
|
if not state:
|
||
|
|
return
|
||
|
|
|
||
|
|
# Load players
|
||
|
|
for player_data in state.get("players", []):
|
||
|
|
player = Player(**player_data)
|
||
|
|
player.is_online = False
|
||
|
|
self.players[player.player_id] = player
|
||
|
|
|
||
|
|
# Load buildings
|
||
|
|
for building_data in state.get("buildings", []):
|
||
|
|
building = Building(
|
||
|
|
building_type=BuildingType(building_data["type"]),
|
||
|
|
x=building_data["x"],
|
||
|
|
y=building_data["y"],
|
||
|
|
owner_id=building_data["owner_id"],
|
||
|
|
name=building_data.get("name"),
|
||
|
|
placed_at=building_data.get("placed_at", 0.0)
|
||
|
|
)
|
||
|
|
self.buildings[(building.x, building.y)] = building
|
||
|
|
|
||
|
|
if building.building_type == BuildingType.ROAD:
|
||
|
|
self.road_network.add((building.x, building.y))
|
||
|
|
|
||
|
|
self._update_connected_zones()
|