Chinese chess on a 9×10 board: generals confined to their palaces, a river the elephants cannot cross, and a cannon that captures only by jumping a screen.
<iframe
src="https://engine.moddable.games/play/?embed=1&family=xiangqi&variant=standard"
style="width:100%;max-width:560px;aspect-ratio:9/10;border:none"
title="Play Xiangqi"
></iframe>
The parameters, commands and events are identical to every other family. See the Go page for the full table; substitute family=xiangqi.
Parameters follow the xiangqi hub in moddable-rules, and each setup is copied verbatim from the variant's frontmatter — the same string the published board diagram is drawn from.
| Key | Board | Distinguishing rule |
|---|---|---|
standard | 9×10 | River, palace confinement, flying general, cannon screen capture |
minixiangqi | 7×7 | Reduced board, no river; elephants and advisors move freely |
xiangqi-42 | 7×6 | Minimal board (42 points), fast games |
Three variants are registered. Six more exist in the hub and are held back for the reasons under known gaps.
The plugin declares one vocabulary, and it is the same vocabulary the variant frontmatter uses. Symbols are the canonical Xiangqi FEN letters, so a position read from the rules parses here and renders through the piece mapping the board diagram already uses. Red is player 0 and takes the uppercase letter.
| Type | Red | Black | Movement |
|---|---|---|---|
general | K | k | One step orthogonally, never leaving the palace |
advisor | A | a | One step diagonally, never leaving the palace |
elephant | E | e | Two points diagonally, blocked at the midpoint, never crossing the river |
horse | H | h | Knight's move, blocked by a piece on the leg it leaves along |
chariot | R | r | Slides orthogonally, as a rook |
cannon | C | c | Slides orthogonally to move; captures only by jumping exactly one screen |
soldier | P | p | One step forward; gains sideways steps after crossing the river |
The soldier is P rather than S because that is what canonical Xiangqi FEN uses. Getting this wrong is not cosmetic: the symbol a piece serialises to is what the renderer resolves against the piece set, so a piece emitting an unmapped symbol is simply not drawn.
The plugin is parametric, with no per-variant code:
| Parameter | Default | Effect |
|---|---|---|
rows / cols | 10 / 9 | Board dimensions |
hasRiver | true | Elephants may not cross; soldiers widen after crossing |
flyingGeneralRule | true | The generals may not face each other down an open file |
cannonJumpToMove | false | When true the cannon must jump a screen to move as well as to capture |
passAllowed | false | Adds { action: 'pass' } to the move list |
The plugin composes the rules constraint.region, capture.screen-jump, constraint.facing, check and checkmate.
Xiangqi uses the move interaction model: click to select one of your own pieces, click again to commit. Selection is rejected on empty points and on the opponent's pieces, and every generated move is filtered through the check test before it is offered, so the flying general rule constrains the move list rather than being caught after the fact.
import { interactionModelFor } from 'moddable-engine/play'
const model = interactionModelFor('xiangqi') // the 'move' model
model.handleClick(from, ctx) // { type: 'select', pos }
model.handleClick(to, ctx) // { type: 'move', move }
import { createGame, getLegalMoves, getGameStatus, createAI, listVariants } from 'moddable-engine/play'
listVariants('xiangqi') // [{ key, label, group, board, description }]
const game = createGame('xiangqi', 'standard')
game.getLegalMoves() // [{ from, to }, ...]
game.applyMove({ from: 84, to: 63 })
getGameStatus('xiangqi', 'standard', game.getState())
const ai = createAI('xiangqi', 'standard', { difficulty: 'hard' })
ai.pickMove(game.getState().slice, 0)
Difficulty accepts beginner, easy, medium, hard and expert, as it does for every family.
Six hub variants are not registered. Each is documented in the rulebook frontmatter (engine.unsupported) with its reason: