Files
the-island/backend/app/server.py
empty 714b5824ba feat: initialize interactive live-stream game backend MVP
- Add FastAPI backend with WebSocket support
- Implement ConnectionManager for client connections
- Create GameEngine with async game loop (2s tick)
- Add RuleBasedAgent for keyword-based responses
- Define Pydantic schemas for GameEvent protocol
- Create debug frontend dashboard for testing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 14:58:38 +08:00

91 lines
2.7 KiB
Python

"""
WebSocket connection manager and server utilities.
Handles client connections, disconnections, and message broadcasting.
"""
import logging
from typing import Any
from fastapi import WebSocket
from .schemas import GameEvent
logger = logging.getLogger(__name__)
class ConnectionManager:
"""
Manages WebSocket connections for real-time communication.
Handles connection lifecycle and provides broadcast capabilities
to all connected clients.
"""
def __init__(self) -> None:
"""Initialize the connection manager with an empty connection list."""
self._active_connections: list[WebSocket] = []
@property
def connection_count(self) -> int:
"""Return the number of active connections."""
return len(self._active_connections)
async def connect(self, websocket: WebSocket) -> None:
"""
Accept and register a new WebSocket connection.
Args:
websocket: The WebSocket connection to accept
"""
await websocket.accept()
self._active_connections.append(websocket)
logger.info(f"Client connected. Total connections: {self.connection_count}")
def disconnect(self, websocket: WebSocket) -> None:
"""
Remove a WebSocket connection from the active list.
Args:
websocket: The WebSocket connection to remove
"""
if websocket in self._active_connections:
self._active_connections.remove(websocket)
logger.info(f"Client disconnected. Total connections: {self.connection_count}")
async def broadcast(self, event: GameEvent) -> None:
"""
Send a GameEvent to all connected clients.
Args:
event: The GameEvent to broadcast
"""
if not self._active_connections:
return
message = event.model_dump_json()
disconnected: list[WebSocket] = []
for connection in self._active_connections:
try:
await connection.send_text(message)
except Exception as e:
logger.warning(f"Failed to send to client: {e}")
disconnected.append(connection)
# Clean up failed connections
for conn in disconnected:
self.disconnect(conn)
async def send_personal(self, websocket: WebSocket, event: GameEvent) -> None:
"""
Send a GameEvent to a specific client.
Args:
websocket: The target WebSocket connection
event: The GameEvent to send
"""
try:
await websocket.send_text(event.model_dump_json())
except Exception as e:
logger.warning(f"Failed to send personal message: {e}")
self.disconnect(websocket)