Plugins

Game-family-specific logic. Each plugin handles all variants within its family through configuration, not code branching.

What plugins are

A plugin encapsulates the rules for a game family: legal move generation, capture mechanics, win/draw detection, and turn flow. The engine has 13 built-in plugins covering chess, go, draughts, reversi, mancala, backgammon, morris, hex, halma, shogi, xiangqi, race games, and Big 2.

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.

Plugin structure

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
    },
  }
}

Universal hooks

All plugins implement from the same set of 11 hooks. No game-specific hooks exist.

HookPurpose
initInitialise state, register capabilities
getLegalMovesGenerate all legal moves for current player
applyMoveExecute a move, return new state
checkWinDetect win, draw, or ongoing
validateMoveCheck if a specific move is legal
getScoreNumeric evaluation for AI
serializeState to notation string
deserializeNotation string to state
getMetadataPiece lists, captured pieces, etc.
onTurnEndPost-turn cleanup (timers, clocks)
onGameEndFinal state processing

State ownership

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.

Registry

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.

Adding a plugin

  1. Create packages/plugins/my-game/ with src/my-game-plugin.js and index.js
  2. Export your factory from index.js
  3. Add the import + register call to packages/plugins/index.js
  4. Write tests in packages/plugins/my-game/__tests__/
Or use the runtime API: call register('myGame', { factory }) at startup without modifying any files.