Skip to content

weiqi-engine

packages/weiqi-engine is the authoritative Go rules and state machine used by weiqi-server and any code that needs to validate moves offline (e.g. the agent module replaying a game for analysis). It has no I/O: no network, no DB, no environment variables. Everything it needs is passed in via constructor arguments.

  • Game state machine. A Game owns the board, the players, the current turn, and the active RulesBase implementation. It exposes move application, pass, end-of-game, and JSON-serialisable state snapshots.
  • Board, stone, and move models. Pure data classes in src/models/.
  • Rule plug-ins. RulesBase is abstract; ChineseRules is the only concrete implementation and ships as the default. Suicides are forbidden; positional superko is enforced.
  • Localised messages. GameMessages (src/lib/messages.ts) is the single source of truth for user-facing move feedback.
  • When you need to validate a single move without a database round-trip (e.g. an MCP tool replaying a turn client-side).
  • When you need to serialise a game snapshot to JSON for persistence.
  • When you want to write deterministic tests against Go rules.

When you need full GTP-compatible play, captures, superko detection, and scoring tuned for third-party interop, use go-engine instead — it wraps the goban-engine UMD bundle.

The class lives in packages/weiqi-engine/src/engine/Game.ts:14.

import { Game } from "@workspace/weiqi-engine";
import { Player } from "@workspace/weiqi-engine/models/Player";
import { StoneColor } from "@workspace/weiqi-engine/models/Stone";
const black: Player = { name: "B", color: StoneColor.BLACK, capturedStones: 0 };
const white: Player = { name: "W", color: StoneColor.WHITE, capturedStones: 0 };
const game = new Game(19, black, white); // 19x19 board, Chinese rules
const result = game.makeMove({ x: 3, y: 3, color: StoneColor.BLACK });
if (!result.success) {
// See GameMessages for stable codes: NOT_YOUR_TURN,
// YOU_CANT_PLACE_STONE_ON_THIS_POSITION, GAME_ALREADY_ENDED, ...
throw new Error(result.message);
}
if (game.isGameEnded) {
const json = game.saveState(); // serialise for persistence / replay
}
MemberDescription
new Game(sizeOrState, black, white, rules?)sizeOrState is a board dimension (e.g. 19). rules defaults to ChineseRules.
board: BoardPublic, mutated in place as moves are applied.
getBoard(): BoardSame reference as .board; kept for callers that prefer methods.
getCurrentTurn(): StoneColorBLACK or WHITE.
makeMove(move): MoveReturnValidates against rules + turn, returns {success, message}.
pass(): voidAdvances the turn. Used by endGame to settle consecutive passes.
endGame(): voidMarks the game ended; subsequent moves return GAME_ALREADY_ENDED.
saveState(): stringJSON snapshot — pairs with JSON.parse to reconstruct.
isGameEnded: boolean (getter)True once endGame has been called.
// StoneColor — string enum exported from src/models/Stone.ts
export enum StoneColor {
BLACK = "BLACK",
WHITE = "WHITE",
}
// Player — src/models/Player.ts:3
export type Player = {
name: string;
color: StoneColor;
capturedStones: number;
};
// Move — src/models/Move.ts:4
export type Move = { x: number; y: number; color: StoneColor };
// Board — src/models/Board.ts:4
// getStone(x, y): StoneColor | null
// placeStone(move): void
// removeStone(x, y): void
// forEachCell(cb): void

Coordinates are 0-indexed with (0, 0) at the top-left corner. The engine does not enforce board orientation (no flip / mirror) — that is the caller’s responsibility.

GameMessages (src/lib/messages.ts) is the only place stable strings live:

export const GameMessages = {
STONE_PLACED: "Stone placed",
NOT_YOUR_TURN: "Not your turn",
GAME_ALREADY_ENDED: "Game has already ended",
PASS: "Pass",
INVALID_MOVE: "Invalid move",
POSITION_OCCUPIED: "Position is already occupied",
YOU_CANT_PLACE_STONE_ON_THIS_POSITION:
"You can't place a stone on this position",
} as const;

Tests should match on these strings rather than on the literal English text — the strings are the contract.

Engine tests live in packages/weiqi-engine/tests/ and mirror src/:

Terminal window
pnpm --dir packages/weiqi-engine test
# vitest watch mode; pass `run` for one-shot

Notable suites:

  • tests/engine/rules/base/canPlaceStone.test.ts — covers legal/illegal placement.
  • tests/engine/game/makeMove.test.ts — turn rotation, pass behaviour.
  • tests/models/board/placement.test.ts — board mutators.

packages/weiqi-engine re-exports from its src/index.ts:

export { Game } from "./engine/Game";
export type { StoneColor as StoneColorType } from "./models/Stone";
export { StoneColor } from "./models/Stone";
export { GameMessages } from "./lib/messages";