go-engine
packages/go-engine wraps the goban-engine UMD bundle (goban-engine on
npm) and exposes a small, typed API: GoGame, StoneColor, position
serialisation, and a few state helpers. It exists because
weiqi-engine is intentionally minimal (no superko,
no territory scoring) — go-engine is what you reach for when full rules
fidelity matters.
When to use it
Section titled “When to use it”- Analysis modules.
weiqi-serverusesGoGameto reconstruct positions for KataGo snapshots — territory scoring and superko must be correct or the replay diverges from KataGo’s view of the board. - Replay flows. Any UI that needs to walk through a stored game one ply at
a time and show captured stones + dead stones uses
GoGame. - Anything that needs to interop with third-party SGF tools.
goban-engineimplements the WGo / KGS conventions; the wrapper inherits that.
For lightweight validation in tests or client previews, prefer
weiqi-engine.
Loading the UMD bundle
Section titled “Loading the UMD bundle”goban-engine ships as a UMD bundle. go-game.ts walks the namespace
import looking for the constructor in three plausible locations (default,
"goban-engine", top-level). That logic is the reason for the verbose
unwrapping in packages/go-engine/src/go-game.ts:5-65 — Vite and Node ESM
shim the bundle differently.
If you see the error goban-engine: could not resolve GobanEngine constructor,
your bundler is wrapping the module twice. Check optimizeDeps.include for
Vite, or pin the resolution in your tsconfig.
GoGame API
Section titled “GoGame API”import { GoGame, StoneColor } from "@workspace/go-engine";
const game = new GoGame({ boardSize: 19, rules: "chinese" });game.playMove(3, 3, StoneColor.BLACK);const snapshot = game.serializePosition();const state = game.getGameState(); // { isGameEnded, consecutivePasses, winner? }| Member | Description |
|---|---|
new GoGame({ boardSize, rules }) | rules is "chinese" by default. Other options: "aga", "japanese". |
playMove(x, y, color): void | Throws on illegal move (suicide, ko, occupied). Caller should try/catch. |
pass(color): void | Advances the turn; two consecutive passes usually end the game. |
setDeadStones(points): void | Used during scoring to mark dead groups before territory is computed. |
getGameState(): GameState | { isGameEnded, consecutivePasses, winner?: StoneColor | "tie" }. |
serializePosition(): string | Stable string used by KataGo snapshots and SGF exports. |
getCaptures(color): number | Per-color capture count. |
getScore(): ScoreResult | Returns { BLACK: number, WHITE: number } territory + captures after end. |
Exports
Section titled “Exports”export { GoGame } from "./go-game";export { StoneColor } from "./go-game";export type { Position, Player, ScoreResult, GameState } from "./go-game";export { serializePosition } from "./serialize-position";StoneColor here is the string-enum form ("black" | "white") — different
from weiqi-engine’s uppercase form (StoneColor.BLACK = "BLACK"). When
bridging between the two engines, normalise via:
const normalized = goEngineColor === "black" ? weiqiEngine.StoneColor.BLACK : weiqiEngine.StoneColor.WHITE;Relationship to weiqi-server
Section titled “Relationship to weiqi-server”weiqi-server imports GoGame (not Game) for analysis and replay paths
because the bot ladder needs territory scoring and superko to be authoritative.
Move validation on the hot path (BotGameService.makeMove) goes through the
lightweight weiqi-engine Game instead — see
Bot Ladder.
packages/go-engine ships with a small smoke test suite under tests/.
Run via:
pnpm --dir packages/go-engine test