Initial commit: Multiplayer Snake game with server discovery
Implemented a complete network multiplayer Snake game with the following features: Core Game: - Client-server architecture using asyncio for networking - Pygame-based rendering at 60 FPS - Server-authoritative game state with 10 TPS - Collision detection (walls, self, other players) - Food spawning and score tracking - Support for multiple players with color-coded snakes Server Discovery: - UDP multicast-based automatic server discovery (239.255.0.1:9999) - Server beacon broadcasts presence every 2 seconds - Client discovery with 3-second timeout - Server selection UI for multiple servers - Auto-connect for single server - Graceful fallback to manual connection Project Structure: - src/shared/ - Protocol, models, constants, discovery utilities - src/server/ - Game server, game logic, server beacon - src/client/ - Game client, renderer, discovery, server selector - tests/ - Unit tests for game logic, models, and discovery Command-line interface with argparse for both server and client. Comprehensive documentation in README.md and CLAUDE.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
98
src/shared/models.py
Normal file
98
src/shared/models.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""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
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@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"]
|
||||
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
|
||||
Reference in New Issue
Block a user