mirror of
https://github.com/tiennm99/gomoku.git
synced 2026-07-16 20:18:17 +00:00
23a15765be3ce00ef1ca6007f05a45d03e7d4c56
Defensive cleanup to address the reported symptom "can not play PVE after PVP". Server-side flow tests pass in every permutation, so the residue must be in the client scene lifecycle. game-state-service: - Reset isSpectating on GAME_STARTING, not just on CLIENT_EXIT. If a prior watch session ever left the flag set (e.g., the user left via a path that didn't emit CLIENT_EXIT), a fresh game-starting signal would incorrectly treat the new player as a spectator and block clicks via isMyTurn() returning false. game-scene._cleanup: - Explicitly drop Phaser input listeners (pointerdown, pointermove). Phaser's scene shutdown is supposed to tear down the input plugin, but an explicit off() makes the cleanup obvious and robust against any lifecycle quirk in scene restart. - Null out this.board / this.stones / this.lastMarker / this.hoverGraphic / this.hoverPos so the next create() starts from a known-clean state, and late-firing events can't accidentally dereference stale Phaser game objects that the scene already destroyed. No functional change to the happy path — just defensive teardown that makes a scene restart observably idempotent. npm run build green.
Gomoku (Five-in-a-Row)
Multiplayer online Gomoku with PVP, PVE (3 AI difficulties), and spectator mode. Go server + Phaser 3 browser client communicating over protobuf WebSocket binary frames.
Forked from ratel-online by ainilili and contributors. Card games removed; gomoku rewritten with a caro-compatible protobuf protocol and Phaser 3 client (feature set ported from tiennm99/caro).
Features
- PVP — two human players in a room
- PVE — three AI difficulties:
- Easy — random legal move
- Medium — heuristic threat scoring
- Hard — minimax depth 3 with alpha-beta pruning
- Spectator mode — watch any in-progress game in real time
- Owner-controlled start — room owner starts when ready; other players can join before start
- Live lobby — room list updates as rooms open / close
- Heartbeat / reconnect — client pings every 5 s; missed pings close the connection cleanly
Quick Start
Docker (recommended)
docker compose up -d
- Browser client: http://localhost:8080
- WebSocket endpoint: ws://localhost:1999/gomoku
Tear down:
docker compose down
Local Development
Prerequisites: Go 1.23+, Node 22+
# Terminal 1 — Go server
go -C server run . -p 1999
# Terminal 2 — Vite dev server
npm --prefix client install
npm --prefix client run dev
Open http://localhost:5173 in two browser tabs to play against yourself.
Architecture
Browser tab A ─────────────────────────────────────────────────┐
Browser tab B ──► http://localhost:8080 (nginx / client:8080) │
│ │
Phaser 3 + protobufjs │
│ WS binary frames │
▼ │
ws://localhost:1999/gomoku (Go server) │
│ │
┌──────────┴──────────┐ │
│ state machine │ one goroutine per player │
│ per-player │ lobby → waiting → game │
├─────────────────────┤ │
│ in-memory domain │ rooms, players (no DB) │
├─────────────────────┤ │
│ game engine │ 15×15 board, win detect │
└─────────────────────┘ │
│ │
common/proto/*.proto (single source of truth)────┘
Key design decisions:
- Server-authoritative — all move validation and win detection server-side; browser never modifies board locally
- Protobuf binary frames over WebSocket (
gorilla/websocket);common/proto/is the single source of truth - State machine per player —
lobby → waiting → game; states registered inserver/state/ - In-memory store — rooms and players live in hashmaps; restart loses all state (intentional — games are short-lived)
- Channel-based turn sync — game turns flow via
chanper player, no polling
Project Structure
gomoku/
├── server/ Go game server (WebSocket :1999, WS endpoint /gomoku)
│ ├── main.go
│ ├── consts/ state IDs, game constants
│ ├── database/ in-memory room + player store
│ ├── game/ gomoku engine (board, win detection, AI)
│ ├── network/ WebSocket server, connection handler
│ ├── protocol/ protobuf encode/decode helpers
│ ├── state/ state machine (lobby, waiting, game)
│ ├── Dockerfile multi-stage Go build → distroless runtime
│ └── Makefile build / test / docker targets
│
├── client/ Phaser 3 browser client (Vite, served by nginx :8080)
│ ├── src/
│ │ ├── scenes/ Phaser scenes (lobby, game, spectate)
│ │ ├── services/ connection, room, game, AI services
│ │ └── generated/ protobuf JS stubs (committed, no build-time protoc)
│ ├── Dockerfile Vite build → nginx:1.27-alpine
│ └── nginx.conf SPA fallback, gzip, aggressive asset caching
│
├── common/
│ └── proto/ request.proto / response.proto (shared by server + client)
│
├── docker-compose.yml two services: server :1999, client :8080
└── docs/ architecture, deployment, code standards
Game Rules
- Board: 15 × 15 intersections
- Black moves first
- First player to place 5 stones in a row (horizontal, vertical, or diagonal) wins
- Full board with no winner is a draw
Protocol
- Transport: WebSocket binary frames (
ws://<host>:1999/gomoku) - Encoding: protobuf (see
common/proto/request.proto,common/proto/response.proto) - Auth: client sends
AuthRequest{name}immediately after connect; server replies withAuthResponse{playerId, color} - Heartbeat: client →
HeartbeatRequestevery 5 s; server repliesHeartbeatResponse; 3 missed → disconnect - Auto-reconnect: client retries with exponential back-off on connection loss
Deployment Notes
See docs/deployment-guide.md for:
- Docker Compose quick start
- Building individual images
- Reverse proxy / TLS setup (Caddy, Traefik, nginx)
Credits
- ratel-online by ainilili — original Go game server framework (MIT)
- tiennm99/caro — feature-set reference: protobuf protocol, AI engine, Phaser 3 client
- Phaser 3 — HTML5 game framework
- gorilla/websocket — WebSocket library
- protobuf + protobufjs — binary protocol
Implementation history: plans/260411-1101-port-caro-feature-set/plan.md
License
Apache 2.0 — see LICENSE.
Description
A simple Gomoku game with networking. Base on [Ratel](https://github.com/ratel-online/)
Readme
Apache-2.0
1.3 MiB
Languages
JavaScript
73.4%
Go
25.3%
HTML
1%
Makefile
0.2%
Dockerfile
0.1%