- TypeScript 83.9%
- CSS 12.6%
- Python 2.8%
- Dockerfile 0.3%
- HTML 0.3%
- Other 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| design | ||
| docs | ||
| e2e | ||
| public | ||
| scripts | ||
| server | ||
| shared | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| compose.yaml | ||
| Dockerfile | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.server.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
Topology Gomoku · 拓扑五子棋 · 拓撲五子棋
Gomoku across seven surfaces, with synchronized 2D and interactive Three.js boards. Play locally, against the computer, through a private invitation, or in the public online lobby. The interface supports English, Simplified Chinese and Traditional Chinese.
Run locally
Use Node.js 24 or newer, npm, and Git LFS:
git lfs pull
npm ci
npm run build
npm start
Open http://127.0.0.1:2567. The root redirects to the preferred locale. Local
sessions use non-Secure cookies; production requires HTTPS and sets Secure cookies.
By default the server expects the exact origin http://127.0.0.1:2567. Set
PUBLIC_ORIGIN when using another hostname or port.
For development with Vite hot reload, use two terminals after the initial build:
PUBLIC_ORIGIN=http://localhost:5173 npm run dev:server
npm run dev
Open http://localhost:5173. Vite proxies API, lobby and room connections to port
2567. SQLite is stored under data/, which is ignored by Git.
Languages and HTML source
The stable locale URLs are /en/, /zh-Hans/, and /zh-Hant/. Lobby URLs add
lobby; invitations add room/<match-id>. Switching the language preserves the
current match, board view and music. The server supplies the correct <html lang>,
page title, description and Open Graph metadata in the original HTML response,
including for lobby and invitation URLs. This also works in View Source and with
JavaScript disabled. Vite builds a localized HTML entry for each language.
An explicit locale URL takes precedence. At /, a saved locale cookie wins over
Accept-Language. Chinese browser locales honor explicit Hans/Hant script tags
before their region; TW/HK/MO default to Traditional, CN/SG to Simplified. Other
languages fall back to English. Translations include rule descriptions, topology
names, notifications, music titles and accessible board labels.
Online play
- Open the online lobby and choose a display name, surface and board size.
- Create a public table, or a private table and copy its invitation link.
- The second player joins; the server assigns Black and White and starts play.
- Undo requires the other player's agreement. Both players must request a rematch; colors swap in the next round. Resignation ends the round immediately.
Only public tables are listed in the live lobby. Private match IDs contain 128 bits of randomness and act as invitation capabilities: anyone with the link can claim an available seat. Once seated, each player must present their original HttpOnly guest-session cookie. Display names never authorize a seat. A guest may have one unfinished match at a time. “Your recent games” allows returning to saved matches, including private ones.
The server validates membership, session expiry, turn order, board bounds, occupancy, match phase and command revision. Accepted commands have durable IDs, so retries cannot place a second stone. Cross-origin mutation and socket requests are rejected; invitations work because both browsers connect to the game's own origin. Raw Colyseus create/join endpoints cannot bypass the application's guards.
Moves pause for up to 90 seconds after a player disconnects. AI then takes over that seat using the shared topology rules. The returning player immediately resumes control from the current position; stale AI calculations are discarded. Undo and rematches still require both human players to be connected.
When both players are absent, play pauses and AI stops. Paused games remain recoverable for 30 days, matching guest-session retention, then close as abandoned draws. An empty waiting table closes after two minutes away (or 30 minutes total). “Close table” confirms closure with the server before returning to the lobby.
Browser reloads and outage recovery reclaim seats with the HttpOnly guest cookie, without reusing stale Colyseus reconnection tokens. Retries use capped backoff and durable command IDs. Clearing cookies loses access to the previous guest identity.
The public lobby has 8 games per page and recent games 5 per page, with
independent navigation and live updates. Filtering runs on the server before
pagination, so every matching game remains reachable. History is scoped to the
current guest. The API accepts page, pageSize (up to 50), and topology.
Rules
Black moves first. Connect five or more distinct intersections horizontally, vertically, or diagonally to win. Overlines count; there are no forbidden moves.
| Surface | Left / right edges | Top / bottom edges |
|---|---|---|
| Plane | Stop | Stop |
| Cylinder | Same-direction connection | Stop |
| Torus | Same-direction connection | Same-direction connection |
| Möbius strip | Reversed connection | Stop |
| Klein bottle | Reversed connection | Same-direction connection |
| Projective plane | Reversed connection | Reversed connection |
| Sphere | Top connects to left | Right connects to bottom |
Crossing a seam transforms both position and direction. Loops count each intersection once. On the sphere and projective plane, a diagonal that crosses two edges at exactly the same corner stops there. Visual self-intersections of the Klein bottle and Roman surface do not introduce additional connections.
Local games are saved separately by topology, board size and mode. Against the computer you play Black; undo retracts a full round. Demonstrations remain separate from the current game. Online games share the exact same pure rules.
Structure
shared/ Pure topology/rules engine, protocol types and locale metadata
server/ Colyseus rooms, HTTP/SSE routes, SQLite persistence and validation
src/i18n/ Three translation catalogs and language switching
src/online/ Lobby, online controls and recoverable client connections
src/game/ Local game controller, AI, audio; shared rules re-exports
src/components/ Shared 2D/3D game UI
src/scene/ Three.js scene, picking, camera and resource lifecycle
scripts/ Build, Docker image script and Blender asset creation
public/ Models, coordinates, textures and eight original music tracks
AI uses topology-aware iterative deepening, alpha-beta pruning and extended forcing-line search. Local play runs in a reusable browser worker; undo, restart and switching games cancel obsolete work. Servers use up to two reusable worker threads, with at most 16 outstanding jobs and per-dispatch watchdogs. Every result must match the current round, revision and seat control before it can be saved. Player returns actively cancel the search. AI moves use the same transactional event history as human moves. See AI design and verification for search budgets, tactical tests and reproducible legacy-opponent benchmarks.
Room updates use one small, atomic Colyseus snapshot per change so the board, turn and outcome cannot be rendered from different revisions. Rendering and music remain client-local. The shared engine imports neither React nor browser APIs. Three.js and the online client are loaded on demand.
Verify
npm run typecheck
npm test
npm run verify:ai
npm run build
npx playwright install chromium
npm run test:e2e
Tests cover all seven topology rules and all three board sizes, AI behavior,
localization coverage and HTML source, concurrent seat claims and moves,
idempotent retries, invalid/session-expired requests, rate limits, undo/rematch
consent, disconnection/reconnection, atomic persistence rollback, actual server
restart recovery, and real two-browser workflows. Browser tests also exercise
mixed languages, refresh recovery, public/private visibility and mobile layout.
Coverage also includes AI takeover/return races, paused games, topology-specific
AI wins, migration of existing databases, pagination past the old list limits,
and repeated reconnections without accumulating reserved seats.
Screenshots and traces are written under /tmp, outside the repository.
Deploy
./scripts/build-image.sh
Builds Linux amd64 and loads
git.qqs.tw/bobmaster/topology-gomoku:latest. Add --push to publish after registry
login. compose.yaml includes no Nginx service; it uses the host's Nginx and a
persistent database volume. See deployment instructions
for the origin setting, Nginx proxy, updates, rate limits, retention and backups.
Assets and music
Seven surfaces and stone meshes are generated from design/topology-gomoku.blend
and scripts/build_assets.py. Rebuild on a machine with Blender using
blender --background --python scripts/build_assets.py; npm run assets uses
the macOS application path. Model coordinate JSON comes from the same surface
parameterizations. Media files use Git LFS and must be downloaded before builds.
Eight original m4a tracks play in a shuffled sequence, with 30% initial volume. The note button controls music independently from stone sounds. Browser autoplay restrictions may require a click before playback. Failed tracks are skipped; manual pause stays paused. Switching languages or games keeps the current music.
The existing Three.js engine chunk produces an expected size advisory at build time; it remains separate and loads only for the 3D board.
