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:
Vladyslav Doloman
2025-10-04 13:50:16 +03:00
commit 0703561446
28 changed files with 2523 additions and 0 deletions

98
src/shared/models.py Normal file
View 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