Add web client with WebSocket support

Implemented browser-based web client alongside existing pygame desktop client
with dual-protocol server architecture supporting both TCP and WebSocket.

Backend Changes:
- Refactored GameServer for dual-protocol support (TCP + WebSocket)
- Added WebSocketHandler for handling WebSocket connections
- Added HTTPServer using aiohttp for serving web client files
- Updated protocol handling to work with both connection types
- Server tracks clients with protocol metadata (TCP/WebSocket)
- Protocol-agnostic message sending and broadcasting
- Added WebSocket port (8889) and HTTP port (8000) configuration

Web Client:
- Complete HTML5/CSS/JavaScript implementation
- Responsive dark-themed UI
- HTML5 Canvas rendering matching pygame visual style
- WebSocket connection with auto-detected server URL
- Real-time multiplayer gameplay in browser
- Player list with scores and status
- Mobile-friendly responsive design

Deployment Options:
- Development: Built-in HTTP server for local testing
- Production: Disable HTTP server, use nginx/Apache for static files
- Flexible server configuration (--no-http, --no-websocket flags)
- Comprehensive nginx/Apache deployment documentation

New Files:
- src/server/websocket_handler.py - WebSocket connection handler
- src/server/http_server.py - Static file server
- web/index.html - Web client interface
- web/style.css - Responsive styling
- web/protocol.js - Protocol implementation
- web/game.js - Game client with Canvas rendering
- web/README.md - Deployment documentation

Updated Files:
- requirements.txt - Added websockets and aiohttp dependencies
- src/server/game_server.py - Dual-protocol support
- src/shared/constants.py - WebSocket and HTTP port constants
- run_server.py - Server options for web support
- README.md - Web client documentation
- CLAUDE.md - Architecture documentation

Features:
- Web and desktop clients can play together simultaneously
- Same JSON protocol for both client types
- Independent server components (disable what you don't need)
- Production-ready with reverse proxy support

🤖 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 14:39:13 +03:00
parent 0703561446
commit ec8e9cd5fb
13 changed files with 1454 additions and 86 deletions

View File

@@ -2,8 +2,10 @@
import asyncio
import argparse
from pathlib import Path
from src.server.game_server import GameServer
from src.shared.constants import DEFAULT_HOST, DEFAULT_PORT
from src.server.http_server import HTTPServer
from src.shared.constants import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_WS_PORT, DEFAULT_HTTP_PORT
async def main() -> None:
@@ -18,7 +20,24 @@ async def main() -> None:
"--port",
type=int,
default=DEFAULT_PORT,
help=f"Port number to bind to (default: {DEFAULT_PORT})",
help=f"TCP port number (default: {DEFAULT_PORT})",
)
parser.add_argument(
"--ws-port",
type=int,
default=DEFAULT_WS_PORT,
help=f"WebSocket port (default: {DEFAULT_WS_PORT}, 0 to disable)",
)
parser.add_argument(
"--http-port",
type=int,
default=DEFAULT_HTTP_PORT,
help=f"HTTP server port (default: {DEFAULT_HTTP_PORT}, 0 to disable)",
)
parser.add_argument(
"--web-dir",
default="web",
help="Directory containing web client files (default: web)",
)
parser.add_argument(
"--name",
@@ -30,17 +49,48 @@ async def main() -> None:
action="store_true",
help="Disable multicast discovery beacon",
)
parser.add_argument(
"--no-websocket",
action="store_true",
help="Disable WebSocket server",
)
parser.add_argument(
"--no-http",
action="store_true",
help="Disable HTTP server (for production with external web server)",
)
args = parser.parse_args()
# Determine WebSocket port
ws_port = None if args.no_websocket or args.ws_port == 0 else args.ws_port
# Create game server
server = GameServer(
host=args.host,
port=args.port,
server_name=args.name,
enable_discovery=not args.no_discovery,
ws_port=ws_port,
)
await server.start()
# Start HTTP server if enabled
http_task = None
if not args.no_http and args.http_port > 0:
web_dir = Path(args.web_dir)
if web_dir.exists():
http_server = HTTPServer(web_dir, args.http_port, "0.0.0.0")
await http_server.start()
http_task = asyncio.create_task(asyncio.Future()) # Keep running
else:
print(f"Warning: Web directory '{web_dir}' not found. HTTP server disabled.")
# Start game server
try:
await server.start()
finally:
if http_task:
http_task.cancel()
if __name__ == "__main__":