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.
What it owns
Section titled “What it owns”- Game state machine. A
Gameowns the board, the players, the current turn, and the activeRulesBaseimplementation. 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.
RulesBaseis abstract;ChineseRulesis 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 to use it
Section titled “When to use it”- 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.
Game API
Section titled “Game API”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}| Member | Description |
|---|---|
new Game(sizeOrState, black, white, rules?) | sizeOrState is a board dimension (e.g. 19). rules defaults to ChineseRules. |
board: Board | Public, mutated in place as moves are applied. |
getBoard(): Board | Same reference as .board; kept for callers that prefer methods. |
getCurrentTurn(): StoneColor | BLACK or WHITE. |
makeMove(move): MoveReturn | Validates against rules + turn, returns {success, message}. |
pass(): void | Advances the turn. Used by endGame to settle consecutive passes. |
endGame(): void | Marks the game ended; subsequent moves return GAME_ALREADY_ENDED. |
saveState(): string | JSON snapshot — pairs with JSON.parse to reconstruct. |
isGameEnded: boolean (getter) | True once endGame has been called. |
Models
Section titled “Models”// StoneColor — string enum exported from src/models/Stone.tsexport enum StoneColor { BLACK = "BLACK", WHITE = "WHITE",}
// Player — src/models/Player.ts:3export type Player = { name: string; color: StoneColor; capturedStones: number;};
// Move — src/models/Move.ts:4export 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): voidCoordinates 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.
Messages
Section titled “Messages”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/:
pnpm --dir packages/weiqi-engine test# vitest watch mode; pass `run` for one-shotNotable 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.
Public exports
Section titled “Public exports”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";