Skip to content

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.

  • Analysis modules. weiqi-server uses GoGame to 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-engine implements the WGo / KGS conventions; the wrapper inherits that.

For lightweight validation in tests or client previews, prefer weiqi-engine.

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.

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? }
MemberDescription
new GoGame({ boardSize, rules })rules is "chinese" by default. Other options: "aga", "japanese".
playMove(x, y, color): voidThrows on illegal move (suicide, ko, occupied). Caller should try/catch.
pass(color): voidAdvances the turn; two consecutive passes usually end the game.
setDeadStones(points): voidUsed during scoring to mark dead groups before territory is computed.
getGameState(): GameState{ isGameEnded, consecutivePasses, winner?: StoneColor | "tie" }.
serializePosition(): stringStable string used by KataGo snapshots and SGF exports.
getCaptures(color): numberPer-color capture count.
getScore(): ScoreResultReturns { BLACK: number, WHITE: number } territory + captures after end.
packages/go-engine/src/index.ts
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;

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:

Terminal window
pnpm --dir packages/go-engine test