Game-family-specific logic. Each plugin handles all variants within its family through configuration, not code branching.
A plugin encapsulates the rules for a game family: legal move generation, capture mechanics, win/draw detection, and turn flow. The engine has 14 built-in plugins: backgammon, chess, draughts, go, hex, hop, the Landlord's Game, mancala, morris, race, reversi, shogi, tableau and xiangqi. Families without a plugin of their own name one of these in their rulebook; see Families on Shared Plugins.
Plugins receive configuration (from frontmatter) and produce behaviour. The same chess plugin handles standard, Fischer Random, Capablanca, and 72 other variants through different config parameters.
A plugin is a factory function that returns a plugin object:
export function createMyPlugin(config = {}, context = {}) {
return {
sliceName: 'myGame',
init(pluginConfig, { provide, request }) {
// Set up initial state, register capabilities
return { /* initial state */ }
},
getLegalMoves(state, topology, playerIndex) {
// Return array of legal moves
return []
},
applyMove(state, move, topology) {
// Apply move, return new state
// Return { state, continueTurn: true } for multi-action turns
return { state: newState }
},
checkWin(state, topology) {
// Return winner index, 'draw', or null
return null
},
}
}
All plugins implement from the same set of 11 hooks. No game-specific hooks exist.
| Hook | Purpose |
|---|---|
init | Initialise state, register capabilities |
getLegalMoves | Generate all legal moves for current player |
applyMove | Execute a move, return new state |
checkWin | Detect win, draw, or ongoing |
validateMove | Check if a specific move is legal |
getScore | Numeric evaluation for AI |
serialize | State to notation string |
deserialize | Notation string to state |
getMetadata | Piece lists, captured pieces, etc. |
onTurnEnd | Post-turn cleanup (timers, clocks) |
onGameEnd | Final state processing |
Each plugin owns a named state slice (its sliceName). Any plugin can read any slice, but only the owner can write to it. State must be JSON-serialisable.
The continueTurn mechanism allows compound turns: Morris mill removal, Backgammon multi-dice moves, Draughts forced capture chains.
Plugins register at packages/plugins/registry.js:
import { register, get, has, createFactory } from 'packages/plugins/registry.js'
// Register a custom plugin
register('myGame', { factory: createMyPlugin })
// Retrieve a factory
const factory = createFactory('chess')
const plugin = factory(variantConfig, context)
All 13 built-ins auto-register when packages/plugins/index.js is imported.
packages/plugins/my-game/ with src/my-game-plugin.js and index.jsindex.jspackages/plugins/index.jspackages/plugins/my-game/__tests__/register('myGame', { factory }) at startup without modifying any files.