SDK

Every playable family is reached through one SDK, @moddable/engine/play. The tools server and other sites use it to list games, play them headless, ask the AI for a move and draw a position. The table below is measured by running the SDK against each family whenever the site is built.

Installing

The package is @moddable/engine, pure ES modules with no build step. Link a checkout of the repository, as the tools server does:

{
  "dependencies": { "@moddable/engine": "file:../moddable-engine" }
}

Families and variants

import { getFamilies, listVariants } from '@moddable/engine/play'

getFamilies()            // every family the engine plays
listVariants('tafl')     // its variants, with their labels and groups

Playing a game

import { createGameForFamily } from '@moddable/engine/play'

const game = createGameForFamily('senet', { variant: 'standard', rngSeed: 7 })
game.getLegalMoves()     // moves are plain objects; pass one back to apply it
game.applyMove(game.getLegalMoves()[0])
game.checkWin()          // a seat index, 'draw', or null
game.getState()          // serialisable; game.loadState(state) restores it

A game with dice, sticks or shells throws from its own seeded generator, so the same rngSeed replays the same game. A game with hidden hands answers game.viewForSeat(seat) with only what that seat may see.

The AI

import { createAI } from '@moddable/engine/play'

const ai = createAI('pachisi', 'standard', { difficulty: 'medium' })
const state = game.getState()
const move = ai.pickMove(state.slice, state.players.currentIndex)

createAI chooses the right search for the family: minimax, Monte Carlo tree search, or the family plugin's own policy for games of chance and hidden hands. Call it rather than a search directly: a minimax search cannot play a game whose next position depends on a throw.

Drawing a position

import { renderStateAsSvg } from '@moddable/engine/play'

const gallery = await fetch('https://engine.moddable.games/pieces/gallery-index.json').then(r => r.json())
const svg = renderStateAsSvg('tafl', game.getState(), {
  variant: 'brandubh',
  gallery,                                        // piece and card artwork
  assetBase: 'https://engine.moddable.games/',    // where artwork paths resolve
  highlights: ['d4', { key: 'd5', color: '#f5c542' }],
})

A position is drawn the way the play page draws it: the variant's own board, surface and piece set. A board is written back to the variant's setup notation, and a table of cards, tiles or dice is drawn as it looks to one seat (seat, default 0), with that seat's hand face up and the others face down. Highlights name cells by the ids the board draws them with (e4, point-6, n1); a square grid also takes an index. A variant that names a piece set needs gallery, and the call throws without it rather than drawing a board with its pieces missing.

A variant whose board is a data file names it under content.source, and the SDK leaves reading it to you so it runs in a browser as well as Node. The Landlord's Game is one: fetch https://engine.moddable.games/data/landlords-game-boards.json and pass it as content. Without it the call throws and names the file, where it used to draw an empty board.

Card and tile artwork

import { cardArtwork } from '@moddable/engine/play'
import { createDeck } from '@moddable/engine/component-deck'

const art = cardArtwork('standard-52', { variant: 'hearts', gallery, assetBase: 'https://engine.moddable.games/' })
art.cardUrl(createDeck('standard-52')[0])   // .../pieces/sets/letele-cards/S-A.svg
art.backUrl()                               // .../pieces/sets/letele-cards/B-1.svg

Artwork belongs to the family, not the deck: each family's frontmatter names the set its cards, tiles or dominoes are drawn from, and cardArtwork resolves through it as the play page does.

What each family supports

FamilyPlugincreateAIrenderStateAsSvgDiceHidden handsDocs
AgonchessminimaxyesFamilies on Shared Plugins
AsaltohoppolicyyesHopping Games
Backgammonbackgammonpolicyyes✓Backgammon
Bavarian 32-Card Gamesbavarian-32policyyes✓Cards, Tiles and Dice
Chauparracepolicyyes✓Race Games
ChesschessminimaxyesChess
Dou Shou Qi (斗兽棋 / Jungle)chessminimaxyesFamilies on Shared Plugins
Double-Six Dominoesdouble-six-dominoespolicyyes✓Cards, Tiles and Dice
DraughtsdraughtsminimaxyesDraughts
FanoronadraughtsminimaxyesFamilies on Shared Plugins
Flower 48flower-48policyyes✓Cards, Tiles and Dice
GogomctsyesGo
HalmahoppolicyyesHopping Games
HexhexmctsyesHex
The Landlord's Gamelandlordsminimaxyes✓Landlord's Game
Mahjongmahjongpolicyyes✓Cards, Tiles and Dice
MancalamancalaminimaxyesMancala
Nine Men's MorrismorrisminimaxyesMorris
Nyout (유놓이 / Yut Nori)racepolicyyes✓Race Games
Pachisiracepolicyyes✓Race Games
ReversireversiminimaxyesReversi
Royal Game of Urracepolicyyes✓Race Games
Senetracepolicyyes✓Race Games
ShogishogiminimaxyesShogi
Standard 52-Card Deckstandard-52policyyes✓Cards, Tiles and Dice
Standard Dicestandard-dicepolicyyes✓Cards, Tiles and Dice
Stern-HalmahoppolicyyesHopping Games
SurakartachessminimaxyesFamilies on Shared Plugins
TaflchessminimaxyesFamilies on Shared Plugins
Xiangqixiangqiminimaxyes✓Xiangqi