JavaScript API (DiceChess)
The Dice Chess Engine exposes the DiceChess object to JavaScript consumers (like the dicechess-lab PWA frontend). This API provides functions for move generation, validation, and game state transitions.
Two entry points
Section titled “Two entry points”The npm package has two entries, and every function on this page that needs only the rules is on both:
import { DiceChess } from '@fortemate/dicechess-engine'; // everythingimport { DiceChess } from '@fortemate/dicechess-engine/rules'; // rules only, ~420 KB smallerThe ./rules subpath carries getLegalUciMoves, generateMoves, applyMove, endTurn, perft,
getPieceFromDice and canonicalKey — identical in name and behaviour — and nothing that reaches
the search package. Everything else below (getLegalTurnTree, getPlayableDice, getBestMove,
bot discovery, time policies, the doubling and draw decisions, estimateEquity) lives on the full
entry only; getLegalTurnTree and getPlayableDice are there because they are built from
TurnGenerator, which ./rules does not carry. See Published Artifacts
for the sizes and the reason the WebAssembly package has no such subpath.
DiceChess
Section titled “DiceChess”The primary interface for interacting with the engine from JavaScript/TypeScript.
getLegalUciMoves
Section titled “getLegalUciMoves”Returns all legal moves for a given position and a set of available dice rolls as a flat array of UCI strings (e.g., ["e2e4", "e7e8q"]).
function getLegalUciMoves(dfen: string): string[]Returns: An array of full UCI move strings. If a pawn promotion is legal, the 5th character contains the target piece notation (e.g., "e7e8q").
These are the legal first actions of a turn from this position, and the call judges the position
in isolation. Asked again after each micro-move, it no longer knows how many dice the whole turn
could have used, so it can admit a continuation the turn does not allow. To follow a turn one action
at a time, walk getLegalTurnTree instead.
getLegalTurnTree
Section titled “getLegalTurnTree”Returns every legal turn of the rolled position as a prefix tree of UCI micro-moves. Full entry only.
interface MoveTree { [uci: string]: MoveTree}
function getLegalTurnTree(dfen: string): MoveTreeThe tree is made of plain nested objects keyed by micro-move, with children in UCI order — the
shape of dicechess-play-api’s MoveTree, so JSON.stringify gives the same string the server
sends for the same roll:
{ "e2e3": { "e3e4": {} }, "e2e4": { "e4e5": {} } }- A node with no children is a complete legal turn, and every complete legal turn is such a leaf. A turn that captures the king always ends at a leaf, even when dice remain; every other turn spends the most dice the roll allows (the Maximum Micro-moves Rule, castling counting as two).
- The first level equals
getLegalUciMoves(dfen). Deeper levels can be narrower thangetLegalUciMovesasked again after each micro-move. - An empty object means the roll has no legal move (the player passes). An invalid DFEN or a
DFEN without dice also returns
{}.
The difference matters in positions where a first action is legal only because a later one takes
the king. With 8/8/8/2k5/8/1N6/2P5/K7 w - - 0 1 NPP, c2c4 is legal because Nb3xc5 follows;
after it, getLegalUciMoves admits every knight move, but a quiet one would end a two-dice turn
while c2c3, c3c4, Nb3xc5 uses all three. The tree offers b3c5 alone:
const tree = DiceChess.getLegalTurnTree('8/8/8/2k5/8/1N6/2P5/K7 w - - 0 1 NPP')Object.keys(tree.c2c4) // ["b3c5"]A client walks the tree alongside applyMove: play an action that is a key of the
current node and descend into its child. An empty child completes the turn: call endTurn, unless
the last action captured the king, which ends the game.
getPlayableDice
Section titled “getPlayableDice”Returns the dice that a legal turn can still spend, given the micro-moves already played. Full entry only.
function getPlayableDice(dfen: string, moves?: string[]): string | undefinedA die is playable while at least one legal turn that begins with moves spends it after them.
A client can dim every other die. No turn left can use it, and none will for the rest of the turn,
because each action played only narrows the turns that can follow.
dfenis the roll: the position at the start of the turn with its dice, as forgetLegalTurnTree.movesare the micro-moves played since, in UCI and in order: a path from the root of the tree. Omitted, they are none.- The result is the playable dice as the DFEN dice field writes them: ascending by face
(
PNBRQK), upper case for White and lower case for Black. A face appears as often as the most dice showing it that one legal turn spends, so"NN"means both knight dice can be used. ""means that no legal turn continues: the turn is complete, a king capture included, the roll has no legal move, or the DFEN has no dice.undefinedmeans an invalid DFEN,movesthat are not an array of UCI strings, or moves that are not the beginning of a legal turn.
The answer is about the whole turn, not about the next action. In the start position with the dice
queen, rook and knight, only a knight can move first. After b1a3, though, the rook can go
a1b1, and every legal turn is “knight, then rook”. The rook die is therefore playable although
the rook cannot move first:
const roll = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 QRN'DiceChess.getPlayableDice(roll) // "NR": only the queen die is lostDiceChess.getPlayableDice(roll, ['b1a3']) // "R"DiceChess.getPlayableDice(roll, ['b1a3', 'a1b1']) // "": the turn is completePass the roll and the moves, not the DFEN after them. The Maximum Micro-moves Rule counts the
dice the whole turn could use, and a turn that takes the king ends there. The DFEN after an action
records neither. With 8/8/8/2k5/8/1N6/2P5/K7 w - - 0 1 NPP, Nb3xc5 takes the king and ends the
turn, yet the DFEN it leaves still carries both pawn dice:
const roll = '8/8/8/2k5/8/1N6/2P5/K7 w - - 0 1 NPP'DiceChess.getPlayableDice(roll, ['b3c5']) // "": the turn is overDiceChess.getPlayableDice('8/8/8/2N5/8/8/2P5/K7 w - - 0 1 PP') // "PP": asked afresh, and wrongWhen to call it. A client that already holds the tree knows the answer without a call in the two common cases, at the roll and after each action alike:
- The current node is empty, so no legal turn continues: no die is playable.
- Some path below the current node has as many actions as there are dice left. Every action spends at least one die, so that path spends them all, and every die left is playable.
Only in between does it need the call: with three dice, when the longest path below the current node has one or two actions. A castling path can have fewer actions than the dice it spends, and then the call simply answers that every die is playable.
Cost. Each call enumerates the legal turns of the roll and replays their moves, still less work
than getLegalTurnTree, which builds the tree. Following the rule above keeps that cost off the large
trees: they almost always have a path that spends every die, while a call after every action would
enumerate them again each time.
applyMove
Section titled “applyMove”Applies a micro-move to the given DFEN and returns the resulting board state. This function acts as the Single Source of Truth for chess rules, ensuring correct handling of castling rights, en passant, and pawn promotions.
[!NOTE]
Because a Dice Chess turn consists of multiple micro-moves,applyMovedoes not transition the turn to the opponent. The active color and full-move number remain unchanged. To formally end a turn, you must callendTurn.
function applyMove(dfen: string, from: string, to: string, promotion?: string): string | undefinedReturns: The updated DFEN after the move is applied, or undefined if the move is pseudo-illegal, no die in the pool allows it, or an argument is invalid.
The returned DFEN keeps the dice the move did not spend, so the next call sees exactly the dice the
rest of the turn may use: playing e2e4 from a position with PPN leaves PN, and castling spends
both the king and the rook die. Pass it on unchanged: while dice remain, appending dice to it gives an
eight-field DFEN, which every function rejects.
When the DFEN carries dice, the move must spend one of them. A move that no die in the pool allows —
a pawn move with dice NNN, or castling without a rook die — returns undefined, as a pseudo-illegal
move does. A position without dice, such as a board editor’s, accepts any pseudo-legal move and stays
without dice.
applyMove checks the dice, not the whole turn. It does not know which dice the turn started with,
so it cannot apply the Maximum Micro-moves Rule, and once the last die is spent the DFEN it returns has
no dice: DFEN cannot tell spent dice from dice not yet rolled, so a further move on it is accepted.
Walk getLegalTurnTree, and call endTurn at an empty child.
[!CAUTION] Behaviour change (#279). Up to 0.12.3,
applyMoveemptied the dice pool after every move and did not check the dice, and clients removed the played die themselves. A client that appends or re-attaches the remaining dice afterapplyMovemust stop doing so, and a move the dice do not allow now returnsundefined.
endTurn
Section titled “endTurn”Explicitly ends the current player’s turn. This function is critical for the micro-move architecture. It performs three vital operations:
- Toggles the active color to the opponent.
- Increments the full-move number (if the current player was Black).
- Clears any stale en-passant targets from the previous turn, preventing illegal captures.
function endTurn(dfen: string): string | undefinedReturns: The updated FEN string for the next player’s turn, or undefined if the FEN is invalid.
getAvailableBots
Section titled “getAvailableBots”Returns all available bots (search algorithms) supported by the engine.
function getAvailableBots(): { id: string, name: string, description: string, difficulty: number, isExperimental: boolean}[]Returns: An array of bot metadata objects, which can be used to dynamically populate UI selection menus.
getAvailableTimePolicies
Section titled “getAvailableTimePolicies”Returns the stable IDs of the built-in time-management policies.
type TimePolicyId = "empirical-v1" | "legacy-linear-v1"
function getAvailableTimePolicies(): TimePolicyId[]"empirical-v1" is calibrated from production Dice Chess games and is the default.
"legacy-linear-v1" preserves the original chess-inspired allocation for rollback and A/B tests.
getBestMove
Section titled “getBestMove”Computes the best sequence of micro-moves for the given position and available dice using the engine’s search algorithms.
interface ClockStateOptions { remainingMs: number incrementMs?: number moveNumber?: number movesToGo?: number}
interface BestMoveOptions { algorithm?: string clock?: ClockStateOptions timePolicy?: TimePolicyId timeBudgetMs?: number}
function getBestMove(dfen: string, options?: BestMoveOptions): { moves: { from: string, to: string, promotion?: string }[], score: number, timeTakenMs: number, budgetMs: number}options.algorithm: The bot ID to use. Built-in algorithms are"random","checkmate-aware","greedy","greedy-v2","aggressive", and"monte-carlo". Defaults to"greedy".options.clock: The live game clock. The engine converts it into a per-turn budget for algorithms that support deadlines.options.timePolicy: The allocation policy to use withclock. Defaults to"empirical-v1"; an unknown ID also falls back to the default.options.timeBudgetMs: An advanced precomputed per-turn budget. It bypasses time management and is ignored when a validclockis present; malformed or non-finite clocks fall back to this value.budgetMs: The effective search budget. It is0when no time budget was applied.
const result = DiceChess.getBestMove(dfen, { algorithm: "monte-carlo", clock: { remainingMs: 180_000, incrementMs: 2_000, moveNumber: 8 }, timePolicy: "empirical-v1"})See Time Management for the allocation formula, safeguards, and guidance on selecting a policy.
getPieceFromDice
Section titled “getPieceFromDice”Returns the piece type notation associated with a dice roll.
function getPieceFromDice(dice: number): string | null1→"p"(Pawn)2→"n"(Knight)3→"b"(Bishop)4→"r"(Rook)5→"q"(Queen)6→"k"(King)
Doubling Cube Functions
Section titled “Doubling Cube Functions”shouldBotOfferDouble
Section titled “shouldBotOfferDouble”Evaluates whether the bot should offer a double before its turn.
function shouldBotOfferDouble(dfen: string, currentStake: number, options?: { algorithm?: string }): booleandfen: The current game state in DFEN format.currentStake: The current stake value.options.algorithm: The bot ID to use for evaluation. Defaults to"greedy".
shouldBotAcceptDouble
Section titled “shouldBotAcceptDouble”Evaluates whether the bot should accept a double offered by the opponent.
function shouldBotAcceptDouble(dfen: string, newStake: number, options?: { algorithm?: string }): booleandfen: The current game state in DFEN format.newStake: The new stake value after accepting the double.options.algorithm: The bot ID to use for evaluation. Defaults to"greedy".
Draw Offer Functions
Section titled “Draw Offer Functions”shouldBotOfferDraw
Section titled “shouldBotOfferDraw”Evaluates whether the bot should offer a draw.
function shouldBotOfferDraw(dfen: string, options?: { algorithm?: string }): booleandfen: The current game state in DFEN format.options.algorithm: The bot ID to use for evaluation. Defaults to"greedy".
shouldBotAcceptDraw
Section titled “shouldBotAcceptDraw”Evaluates whether the bot should accept a draw offered by the opponent.
function shouldBotAcceptDraw(dfen: string, options?: { algorithm?: string }): booleandfen: The current game state in DFEN format.options.algorithm: The bot ID to use for evaluation. Defaults to"greedy".