"""Data models shared between client and server.""" from dataclasses import dataclass, field from typing import List, Tuple @dataclass class Position: """Represents a position on the game grid.""" x: int y: int def __add__(self, other: Tuple[int, int]) -> "Position": """Add a direction tuple to position.""" return Position(self.x + other[0], self.y + other[1]) def to_tuple(self) -> Tuple[int, int]: """Convert to tuple for serialization.""" return (self.x, self.y) @classmethod def from_tuple(cls, pos: Tuple[int, int]) -> "Position": """Create Position from tuple.""" return cls(pos[0], pos[1]) @dataclass class Snake: """Represents a snake in the game.""" player_id: str body: List[Position] = field(default_factory=list) direction: Tuple[int, int] = (1, 0) # Default: moving right alive: bool = True score: int = 0 stuck: bool = False # True when snake is blocked and shrinking color_index: int = 0 # Index in COLOR_SNAKES array for persistent color player_name: str = "" # Human-readable player name input_buffer: List[Tuple[int, int]] = field(default_factory=list) # Buffer for pending direction changes (max 3) def get_head(self) -> Position: """Get the head position of the snake.""" return self.body[0] if self.body else Position(0, 0) def to_dict(self) -> dict: """Convert to dictionary for serialization.""" return { "player_id": self.player_id, "body": [pos.to_tuple() for pos in self.body], "direction": self.direction, "alive": self.alive, "score": self.score, "stuck": self.stuck, "color_index": self.color_index, "player_name": self.player_name, "input_buffer": self.input_buffer, } @classmethod def from_dict(cls, data: dict) -> "Snake": """Create Snake from dictionary.""" snake = cls(player_id=data["player_id"]) snake.body = [Position.from_tuple(pos) for pos in data["body"]] snake.direction = tuple(data["direction"]) snake.alive = data["alive"] snake.score = data["score"] snake.stuck = data.get("stuck", False) # Default to False for backward compatibility snake.color_index = data.get("color_index", 0) # Default to 0 for backward compatibility snake.player_name = data.get("player_name", "") # Default to empty string for backward compatibility snake.input_buffer = [tuple(d) for d in data.get("input_buffer", [])] # Default to empty list for backward compatibility return snake @dataclass class Food: """Represents food on the game grid.""" position: Position def to_dict(self) -> dict: """Convert to dictionary for serialization.""" return {"position": self.position.to_tuple()} @classmethod def from_dict(cls, data: dict) -> "Food": """Create Food from dictionary.""" return cls(position=Position.from_tuple(data["position"])) @dataclass class GameState: """Represents the complete game state.""" snakes: List[Snake] = field(default_factory=list) food: List[Food] = field(default_factory=list) game_running: bool = False def to_dict(self) -> dict: """Convert to dictionary for serialization.""" return { "snakes": [snake.to_dict() for snake in self.snakes], "food": [f.to_dict() for f in self.food], "game_running": self.game_running, } @classmethod def from_dict(cls, data: dict) -> "GameState": """Create GameState from dictionary.""" state = cls() state.snakes = [Snake.from_dict(s) for s in data["snakes"]] state.food = [Food.from_dict(f) for f in data["food"]] state.game_running = data["game_running"] return state