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