Files
claudePySnake/src/server/server_beacon.py
Vladyslav Doloman 0703561446 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>
2025-10-04 13:50:16 +03:00

126 lines
3.9 KiB
Python

"""Server beacon for multicast discovery."""
import asyncio
import socket
from typing import Callable
from ..shared.discovery import (
ServerInfo,
DiscoveryMessage,
create_multicast_socket,
get_local_ip,
)
from ..shared.constants import MULTICAST_GROUP, MULTICAST_PORT, BEACON_INTERVAL
class ServerBeacon:
"""Broadcasts server presence and responds to discovery requests."""
def __init__(
self,
server_name: str,
server_port: int,
get_player_count: Callable[[], int],
):
"""Initialize the server beacon.
Args:
server_name: Name of the server
server_port: TCP port the game server is listening on
get_player_count: Callable that returns current player count
"""
self.server_name = server_name
self.server_port = server_port
self.get_player_count = get_player_count
self.running = False
self.sock: socket.socket | None = None
self.local_ip = get_local_ip()
def create_server_info(self) -> ServerInfo:
"""Create ServerInfo with current server state.
Returns:
Current server information
"""
return ServerInfo(
host=self.local_ip,
port=self.server_port,
server_name=self.server_name,
players_count=self.get_player_count(),
)
async def start(self) -> None:
"""Start the beacon service."""
self.running = True
self.sock = create_multicast_socket(bind=True)
self.sock.setblocking(False)
print(f"Server beacon started on {MULTICAST_GROUP}:{MULTICAST_PORT}")
print(f"Server IP: {self.local_ip}, Port: {self.server_port}")
# Run both listener and broadcaster concurrently
await asyncio.gather(
self.listen_for_discovery(),
self.broadcast_presence(),
)
async def listen_for_discovery(self) -> None:
"""Listen for DISCOVER messages and respond."""
loop = asyncio.get_event_loop()
while self.running:
try:
# Receive data (non-blocking with asyncio)
data, addr = await loop.sock_recvfrom(self.sock, 1024)
# Parse message
msg_type, msg_data = DiscoveryMessage.parse(data)
if msg_type == DiscoveryMessage.DISCOVER:
# Respond to discovery request
await self.send_announce(addr)
except Exception as e:
# Ignore errors and continue listening
await asyncio.sleep(0.1)
async def send_announce(self, addr: tuple) -> None:
"""Send SERVER_ANNOUNCE message to a specific address.
Args:
addr: (host, port) tuple to send to
"""
try:
server_info = self.create_server_info()
message = DiscoveryMessage.create_announce(server_info)
# Send directly to the requester
self.sock.sendto(message, addr)
print(f"Sent announcement to {addr}")
except Exception as e:
print(f"Error sending announcement: {e}")
async def broadcast_presence(self) -> None:
"""Periodically broadcast server presence to multicast group."""
while self.running:
try:
server_info = self.create_server_info()
message = DiscoveryMessage.create_announce(server_info)
# Broadcast to multicast group
self.sock.sendto(message, (MULTICAST_GROUP, MULTICAST_PORT))
except Exception as e:
print(f"Error broadcasting presence: {e}")
# Wait before next broadcast
await asyncio.sleep(BEACON_INTERVAL)
async def stop(self) -> None:
"""Stop the beacon service."""
self.running = False
if self.sock:
self.sock.close()
print("Server beacon stopped")