Files
gomoku/README.md
T
tiennm99 501ef1f1d2 docs: rewrite CLAUDE.md, README, architecture; trim historical baggage
The project-level docs described the pre-caro-port architecture (TCP on
9999, JSON {data:base64} wire format, web/ + core/ + api/ + bot/ dirs,
server flags -w/-t/-s, state strings like '1'/'2'/'7,7') and referenced
files that no longer exist. The README also described an owner-controlled
Start button, 5-second heartbeat, 'database/' directory, AuthRequest
handshake, and linked to now-deleted plan files.

CLAUDE.md
- Rewritten for the current architecture: single WS endpoint at
  :1999/gomoku, protobuf binary frames, server/{consts,game,lobby,
  network,state,pkg/log}, client/src/{config,scenes,services,ui,
  objects,generated}, auto-start PVP semantics, heartbeat 50s/deadline
  90s, ClientExit self-vs-peer helper, enum serialization notes, Hard
  AI description, and a "When editing" section covering proto regen,
  state flow tests, and scene cleanup patterns.

README.md
- Features: owner-controlled start → auto-start on 2nd join.
- Heartbeat: 5s → 50s; add server deadline + reconnect back-off.
- Project structure: database/ → lobby/; services list corrected.
- Protocol: replace AuthRequest story with real SetNickname handshake.
- Credits: trimmed from a multi-bullet block to a single attribution
  line ("Based on ratel-online by ainilili"). Removed caro references
  and the link to the deleted plans/ implementation history.

docs/system-architecture.md
- Fully rewritten. Drops old TCP+JSON protocol tables, CLI client box,
  obsolete state flow diagram. Adds: current state registry
  (welcome/setNickname/home/waiting/gamePvp/gamePve/gameover/watching),
  cross-goroutine sync channels (StartCh/GameOverCh/RematchCh), typed
  enum list, kickStaleRoomPeers note, ClientExit self-vs-peer semantics,
  and client module table.

docs/deployment-guide.md
- Remove stale pointer to deleted plans/ directory.

Code comment cleanup (user: "don't mention too much"):
- server/network/handlers_stateless.go handleHeartbeat: drop caro
  reference, explain behavior in terms of the reader loop.
- server/game/ai_eval.go: "ported from caro's GomokuAI scoring" →
  generic "threat-pattern weights used by the Hard AI".
- server/game/ai.go positionScore: drop caro attribution, describe
  the heuristic directly.
- server/lobby/room.go: drop "caro Java domain" annotation, describe
  what Room actually embeds.
- client/src/services/game-state-service.js WATCH_GAME_SUCCESS: drop
  historical rename comment.

go vet + go test ./... + npm run build all green.
2026-04-11 20:16:47 +07:00

6.3 KiB
Raw Blame History

Gomoku (Five-in-a-Row)

Multiplayer online Gomoku with PVP, PVE (3 AI difficulties), and spectator mode. Go server + Phaser 3 browser client over a typed protobuf WebSocket.


Features

  • PVP — two human players in a room. Game auto-starts as soon as the second player joins — no explicit "Start Game" click.
  • PVE — three AI difficulties:
    • Easy — random legal move
    • Medium — heuristic threat scoring (win > block > center-weighted)
    • Hard — minimax depth 3 with alpha-beta pruning
  • Spectator mode — watch any in-progress game in real time
  • Live lobby — room list refreshes on demand
  • Heartbeat / auto-reconnect — client pings every 50 s; server closes idle connections after 90 s; client reconnects with exponential back-off

Quick Start

docker compose up -d

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         │  home → waiting → game    │
              ├─────────────────────┤                           │
              │  in-memory lobby    │  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, enums replace stringly-typed fields
  • State machine per playerhome → waiting → game → gameover; states registered in server/state/
  • Auto-start PVP — handleJoinRoom flips the room to Playing the instant a second player joins; both goroutines transition via room.StartCh
  • In-memory store — rooms and players live in hashmaps; restart clears all state (intentional — games are short-lived)

Project Structure

gomoku/
├── server/              Go game server (:1999, endpoint /gomoku)
│   ├── main.go
│   ├── consts/          StateID enum, difficulty constants
│   ├── game/            pure game engine: Board, win detection, AI
│   ├── lobby/           in-memory Player + Room store
│   ├── network/         WS upgrade, reader/writer, dispatch
│   ├── protocol/        generated Go protobuf stubs
│   ├── state/           per-player state machine
│   ├── Dockerfile       multi-stage → distroless/static-debian12
│   └── Makefile         build / test / docker / proto targets
│
├── client/              Phaser 3 browser client (Vite, nginx :8080)
│   ├── src/
│   │   ├── config/          game-config, protocol-constants
│   │   ├── scenes/          boot, menu, game
│   │   ├── services/        event-bus, connection-service, game-state-service
│   │   ├── ui/              menu-ui, menu-ui-rooms, game-ui (DOM overlay)
│   │   ├── objects/         Phaser Board, Stone
│   │   └── generated/       committed protobuf JS stubs
│   ├── Dockerfile       Vite build → nginx:1.27-alpine
│   └── nginx.conf       SPA fallback, gzip, asset caching
│
├── common/proto/        request.proto / response.proto (shared wire format)
├── docker-compose.yml   two services: server :1999, client :8080
└── docs/                architecture + deployment guide

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 at ws://<host>:1999/gomoku
  • Encoding: protobuf (see common/proto/request.proto, common/proto/response.proto). The oneof payload case IS the event code.
  • Nickname handshake: on connect the server sends ClientConnectResponse then NicknameSetResponse{invalid_length: 0} as a prompt. The client replies with SetNicknameRequest{nickname}. On success the server sends NicknameSetResponse{invalid_length: 0} + ShowOptionsResponse and the client shows the lobby.
  • Heartbeat: client → HeartbeatRequest every 50 s. Server closes the WS if no frame is received within 90 s. Client auto-reconnects on close with exponential back-off (up to 10 attempts).

Deployment

See docs/deployment-guide.md for:

  • Docker Compose quick start
  • Building individual images
  • Reverse proxy / TLS setup (Caddy, Traefik, nginx)

Credits

Based on ratel-online by ainilili (MIT).

License

Apache 2.0 — see LICENSE.