mirror of
https://github.com/tiennm99/caro.git
synced 2026-05-22 10:24:56 +00:00
17 KiB
17 KiB
System Architecture
High-Level Overview
Caro is a client-server multiplayer game with dual-protocol networking:
┌──────────────┐ WebSocket ┌──────────────────────────────┐
│ Web Client │◄────/JSON───►│ │
│ (Phaser 3) │ │ Java Netty Server │
└──────────────┘ │ Port 1024: TCP/Protobuf │
│ Port 1025: WebSocket/JSON │
┌──────────────┐ TCP │ Port 1025: HTTP (static) │
│ CLI Client │◄────PB──────►│ │
│ (Java) │ │ Game Logic: │
└──────────────┘ │ - Room Management │
│ - Move Validation │
│ - AI (3 difficulties) │
│ - Win Detection │
└──────────────────────────────┘
Component Architecture
1. Server (Java Netty)
File: landlords-server/src/main/java/org/nico/ratel/landlords/server/
Responsibilities:
- Listen on TCP (1024) and WebSocket (1025) simultaneously
- Parse incoming messages (Protobuf or JSON)
- Execute game logic (move validation, win checks)
- Broadcast state updates to all connected clients
- Run AI for PVE games
- Manage room lifecycle (create, join, spectate, cleanup)
Key Classes:
SimpleServer— Entry point, starts Netty bootstrap for both portsServerEventListener— Base class for event handlersServerEventListener_CODE_*— Individual handlers for each ServerEventCodeProtobufTransferHandler— Netty pipeline handler for TCPWebsocketTransferHandler— Netty pipeline handler for WebSocketStaticFileHandler— HTTP file serving (index.html, CSS, JS, etc.)ProtobufProxy/WebsocketProxy— Send messages back to clients
Event Codes (ServerEventCode) — sent by clients:
CODE_CLIENT_EXIT Player disconnected or left
CODE_CLIENT_OFFLINE Network timeout
CODE_CLIENT_INFO_SET Set client metadata
CODE_CLIENT_NICKNAME_SET Set player display name
CODE_CLIENT_HEAD_BEAT Keep-alive heartbeat
CODE_ROOM_CREATE Create PVP room
CODE_ROOM_CREATE_PVE Create PVE room (with AI)
CODE_GET_ROOMS Request room list
CODE_ROOM_JOIN Join existing room
CODE_GAME_STARTING Request game start (ready)
CODE_GAME_READY Player ready signal
CODE_GAME_MOVE Make a move (row, col)
CODE_GAME_RESET Reset game state
CODE_GAME_WATCH Spectate a game
CODE_GAME_WATCH_EXIT Stop spectating
2. Web Client (Phaser 3 + Vite)
File: web-client/src/
Responsibilities:
- Render 15x15 game board with wood texture
- Display pieces as gradient-colored stones
- Handle user input (mouse clicks, keyboard)
- Animate stone placement and drop effects
- Display game menus, lobbies, room lists
- Show move history and turn indicator
- Play sound effects (Web Audio API)
- Manage WebSocket connection with heartbeat/reconnect
Architecture:
web-client/src/
├── main.js # Phaser boot, create game instance
├── config/
│ ├── game-config.js # Phaser config (resolution, scale, physics)
│ └── protocol-constants.js # ServerEventCode & ClientEventCode enums
├── scenes/
│ ├── boot-scene.js # Initialize, connect to server
│ ├── menu-scene.js # DOM overlay menus (nickname, lobby, rooms)
│ └── game-scene.js # Main gameplay (board, pieces, input, HUD)
├── services/
│ ├── event-bus.js # Pub/sub dispatcher for loose coupling
│ ├── connection-service.js # WebSocket connection & heartbeat logic
│ └── game-state-service.js # Client-side state container (board, room, players)
├── objects/
│ ├── board.js # 15x15 grid rendering, wood texture
│ └── stone.js # Individual stone sprite with animations
└── ui/
├── menu-ui.js # Nickname input, room creation, settings
└── game-ui.js # HUD (move history, turn indicator, game over)
Key Patterns:
- Event Bus: Decouples scenes, services, UI components. Emit
event→ listeners respond - Game State Service: Single source of truth for board, room, players
- Connection Service: Handles reconnect logic and heartbeat (30-second interval)
- WebSocket Message Format:
{ code: "CODE_GAME_MOVE", data: "{...}", info: "" }
3. CLI Client (Java)
File: landlords-client/src/main/java/org/nico/ratel/landlords/client/
Responsibilities:
- Connect to server via TCP (Protobuf) or WebSocket
- Parse command-line arguments (-h host, -p port, -ptl protocol)
- Read moves from stdin (format:
row,colorexit) - Display board state in terminal
- Handle disconnection gracefully
Key Classes:
SimpleClient— Entry point, arg parsing, connection setupClientEventListener— Base for event handlersClientEventListener_CODE_*— Individual handlersProtobufTransferHandler/WebsocketTransferHandler— Protocol handlersProtobufProxy/WebsocketProxy— Send moves to server
4. Common Library (Shared Code)
File: landlords-common/src/main/java/org/nico/ratel/landlords/
Responsibilities:
- Define shared entities (Board, Room, GameMove)
- Define shared enums (ServerEventCode, ClientEventCode, PieceType, GameResult)
- Implement game logic (move validation, win detection, AI)
- Utilities (JSON, List, Options, Time helpers)
Key Classes:
Board— 15x15 grid, move validation, win/draw detectionRoom— Encapsulates game state, players, spectatorsGameMove— Represents single move (row, col, piece type)ServerTransferData/ClientTransferData— Network message wrappersGomokuHelper— Win detection (4 directions: horizontal, vertical, 2 diagonals)GomokuAI— AI move selection (Easy, Medium, Hard difficulties)- Enums:
ServerEventCode,ClientEventCode,PieceType,GameResult,RoomType,RoomStatus
Network Protocol
Message Format
WebSocket (JSON):
{
"code": "CODE_GAME_MOVE",
"data": "{\"row\": 7, \"col\": 7}",
"info": ""
}
TCP (Protobuf): Binary format (serialized via Protobuf 3).
Connection Flow
Client Server
│
├─ (1) Connect ──────────────►│
│
├─ (2) CODE_CLIENT_NICKNAME_SET ──────────────►│
│ │ Validate, store
│◄─────────── CODE_CLIENT_CONNECT ──│ (send list of existing rooms)
│
├─ (3) CODE_ROOM_CREATE_PVP ─────────────────────►│
│ │ Create room, assign player
│◄─────────── CODE_ROOM_CREATE_SUCCESS ──────────│
│
├─ (4) [Other client joins room]
│ │ Both ready
│◄─────────── CODE_GAME_STARTING ──│ (send board, initial state)
│
├─ (5) CODE_GAME_MOVE ────────────────────────────►│
│ │ Validate, apply, check win
│◄─────────── CODE_GAME_MOVE_SUCCESS ───────────│
│ │ Broadcast to both clients
│◄─────────── (move update) ────────│
│
├─ ... [repeating moves] ...
│
├─ (N) [Winning move] ────────────────────────────►│
│ │ Check win condition
│◄─────────── CODE_GAME_WIN ────────│ (or CODE_GAME_LOSE for opponent)
│◄─────────── CODE_GAME_OVER ───────│ (final state)
│
└─ Disconnect
Key Event Codes (Server → Client)
CODE_CLIENT_CONNECT Login successful, rooms list
CODE_SHOW_ROOMS Room list updated
CODE_ROOM_CREATE_SUCCESS Room created
CODE_ROOM_JOIN_SUCCESS Joined room, waiting for players
CODE_GAME_STARTING All players ready, game begins
CODE_GAME_MOVE_SUCCESS Move valid, board updated
CODE_GAME_MOVE_INVALID Move failed (validation error)
CODE_GAME_WIN You won
CODE_GAME_LOSE You lost
CODE_GAME_DRAW Draw (board full)
CODE_GAME_OVER Game ended; payload: { result, winnerNickname }
CODE_CLIENT_KICK Disconnected by server
Game Logic Flow
Move Validation & Execution
Player submits move (row, col)
│
▼
[Validate]
- Row/Col in [0, 14]?
- Position empty?
- Game still in progress?
│ YES ▼
[Execute]
- Place piece on board
- Increment moveCount
- Check win (4 directions from position)
│ ▼
[Determine Outcome]
- Win? → Send CODE_GAME_WIN / CODE_GAME_LOSE
- Draw? (moveCount == 225) → Send CODE_GAME_DRAW
- Continue? → Wait for opponent move
Win Detection (GomokuHelper)
Checks 4 directions from last placed stone:
- Horizontal — count left/right until edge or different piece
- Vertical — count up/down
- Diagonal ↘ — count up-left/down-right
- Diagonal ↙ — count up-right/down-left
Win if count ≥ 5.
AI Move Selection (GomokuAI)
Three difficulties:
| Difficulty | Logic | Speed |
|---|---|---|
| Easy | Random valid move | Instant |
| Medium | Try to win, then block opponent, else random | < 100ms |
| Hard | Minimax scoring at depth 3 | < 1 sec |
Module Dependencies
landlords-common (shared lib)
├── entities (Board, Room, GameMove, etc.)
├── enums (ServerEventCode, ClientEventCode, GameResult, etc.)
├── game logic (GomokuHelper, GomokuAI)
└── utilities (JSON, ListUtils, TimeHelper, etc.)
landlords-server (depends on landlords-common)
├── Netty server bootstrap
├── TCP handler (Protobuf codec)
├── WebSocket handler
├── Static file handler (HTTP)
├── Event listeners (game logic)
└── Room/player managers
landlords-client (depends on landlords-common)
├── Netty client bootstrap
├── TCP or WebSocket handler
├── Event listeners (local display)
└── Terminal UI
web-client (no dependencies except Phaser 3, Vite)
├── Phaser game instance
├── WebSocket connection
├── Event-driven scenes/services
└── Canvas rendering (board, stones, UI)
Data Structures
Board
- Type:
PieceType[][](15 x 15) - Values:
EMPTY,BLACK,WHITE - Accessed:
board[row][col]
Room
- ID: Unique identifier
- Type:
RoomType.PVPorRoomType.PVE - Status:
RoomStatus.WAITING,PLAYING,FINISHED - Players: List of 2 ClientSide objects (player 1 & 2)
- Spectators: List of additional ClientSide objects
- Board: Current game board
- MoveHistory: List of GameMove objects
GameMove
- row, col: Position (0-14)
- piece:
PieceType.BLACKorWHITE - timestamp: When move was made
File Serving (Static Web UI)
Flow:
- Client makes HTTP request (e.g.,
GET /index.html) StaticFileHandlerintercepts in Netty pipeline- If path is
/ratel, pass to WebSocket handler - Otherwise, map to classpath resource:
static/{path} - Look up MIME type (html, css, js, png, svg, etc.)
- Return 200 OK with file content
Supported Extensions:
.html,.css,.js,.json— text with charset UTF-8.mp3— audio/mpeg.png,.jpg,.svg,.ico— images
Security: Path traversal (..) rejected, returns 403 Forbidden.
Concurrency & Synchronization
Server
- Netty Threading: Each connection has dedicated event loop thread
- Room State: Synchronized via
ServerContainssingleton (all rooms in memory) - Thread Safety: No explicit locks; Netty guarantees sequential processing per connection
- AI Moves: Executed in event loop thread (blocking for < 1 sec)
Client
- Web Client: Async via promises (WebSocket events trigger state updates)
- CLI Client: Blocking on stdin, concurrent with network reads
Error Handling
Server-Side
- Invalid moves →
CODE_GAME_MOVE_INVALIDwith reason - Room not found →
CODE_ROOM_PLAY_FAIL_BY_INEXIST - Room full →
CODE_ROOM_JOIN_FAIL_BY_FULL - Disconnection →
CODE_CLIENT_OFFLINEevent, auto-cleanup after timeout
Client-Side
- WebSocket close → Reconnect with exponential backoff
- Protocol error → Log, show user "Connection error" toast
- Invalid state (e.g., can't move during opponent's turn) → Reject locally
Performance Considerations
| Component | Target | Actual |
|---|---|---|
| Server latency | < 50ms per move | ~10-20ms (Netty, in-memory) |
| Network latency | < 500ms round-trip | Depends on client location |
| AI response (Hard) | < 1 second | ~800ms (depth 3 minimax) |
| Web client load | < 2 seconds | ~500ms (Vite optimized) |
| Concurrent players | 100+ | Tested to 50+, no issues |
Security Considerations
Current Status
- No authentication — all players anonymous (nickname only)
- No encryption — TCP and WebSocket unencrypted
- Input validation — Move coordinates validated, nicknames sanitized
Recommendations (Not Implemented)
- Use TLS/WSS for encrypted connections
- Add user account + token-based auth
- Rate-limit API endpoints
- Implement server-side state validation (no client-side cheating)
- Sanitize HTML from nicknames before broadcast
Deployment Architecture
┌────────────────────────────────────┐
│ GitHub Actions (CI/CD) │
│ ├─ build.yml: mvn + npm build │
│ ├─ Test: Run 37 unit tests │
│ └─ Deploy: Push web-client/ to │
│ GitHub Pages │
└────────────────────────────────────┘
│
┌───────────┴──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ JAR (Release) │ │ GitHub Pages │
│ landlords-server │ │ Web UI │
│ (Java 8+) │ │ Static files │
└──────────────────┘ └─────────────────┘
│
├─ java -jar ... -p 1024
│ ↓
└─ Listens on :1024 (TCP), :1025 (WS + HTTP)
Key Files Summary
| File | Purpose |
|---|---|
SimpleServer.java |
Server entry point |
SimpleClient.java |
CLI client entry point |
main.js |
Web client entry point (Phaser) |
Board.java |
Game board state + validation |
GomokuHelper.java |
Win detection algorithm |
GomokuAI.java |
AI move selection (3 difficulties) |
Room.java |
Game room state container |
ServerEventListener_*.java |
Event handlers (game logic) |
game-scene.js |
Web client main gameplay scene |
connection-service.js |
WebSocket client |
protocol-constants.js |
Event code enums |
Future Architectural Improvements
- Database integration — Persist games, leaderboards, accounts
- Message broker (Kafka/RabbitMQ) — Decouple game logic from network I/O
- Microservices — Separate room manager, AI service, auth service
- Load balancing — Multiple server instances with session affinity
- Spectator streaming — Publish game state to viewers without load
- Replay system — Record move history, allow playback
- Mobile app — Native iOS/Android clients instead of web-only