Sugoroku双六

@johnmorrisdotca/sugoroku 1.0.0 · 5 entry points · 170 exports

@johnmorrisdotca/sugoroku

agreeDraw allHome applyMove BAR beaverDouble boardPoint canBeaver canDouble canPlayAll chanceOfWinning checkersOf choosePlay computerAction ComputerAction ComputerOptions concede concedeKind countAt Cube CUBE_LIMIT cubeInPlay cubeIsDead DEFAULT_RULES diceMustPlay DiceSource diceToPlay Direction dropDouble endTurn evaluate evaluateFor EVALUATION_WEIGHTS EvaluationWeights finishGame formatMove formatOpening formatPlay formatPosition formatRecordHeader formatResult formatTurn GameRecorder GameResult GameState Goal hashSeed Hop HowItEnded inContact inPlay isVariantKey keithCount legalMoves legalPlays legalPlaysOf Match mirrorPoint Move movesOfDie multiplierOf newGame NewGameOptions newMatch nextMoves offerDouble onBoard Opening openingFrom opponentAt otherSide parseHops parsePlay parsePosition Phase pipCount Play playMove playMoves playTurn POINTS pointsToGo Position positionFromId positionId positionKey Preset presetByKey presetByName PRESETS Random randomDice RECORD_VERSION RecordSeed ReplayedGame ReplayError ReplayOk replayRecord resolveRules restartTurn rollDice rollFrom rollOpening Rules rulesProblems seededDice seededRandom Settings settingsFor Side sideIndex SIDES sideToAct startCounts startGame startPosition startReserve stepComputer Strength STRENGTHS takeDouble turnIsPlayed undoMove VARIANT_KEYS VariantKey VARIANTS variantSpec VariantSpec VERSION wantsToDouble wantsToTake winChance winKind WinKind withCounts

function agreeDraw

agreeDraw(game: GameState): GameState

The game drawn by agreement.

function allHome

allHome(position: Position, side: Side): boolean

Whether every checker of side still in play is in its home board (own points 1 to 6): the condition for bearing off.

function applyMove

applyMove(spec: VariantSpec, position: Position, side: Side, move: Move): Position

The position after a move. Does not check that the move is legal.

const BAR

BAR: 25

The number a move's from has when a checker comes from the bar (or from off the board at the start of a race).

function beaverDouble

beaverDouble(game: GameState): GameState

Beaver: the side that was doubled redoubles and keeps the cube; the side that doubled must now take it or drop it.

function boardPoint

boardPoint(spec: VariantSpec, side: Side, own: number): number

The point on the board, 1 to 24 counting the way white counts, that is side's own point own. White's own numbers are the board's; black's are the board's reversed when the sides go opposite ways round.

function canBeaver

canBeaver(game: GameState): boolean

Whether the side that was doubled may beaver: redouble at once and keep the cube. Only where the rules allow it.

function canDouble

canDouble(game: GameState): boolean

Whether the side on turn may offer a double now.

function canPlayAll

canPlayAll(spec: VariantSpec, position: Position, side: Side, need: readonly number[], failed?: Set<string>): boolean

Whether every die of need (highest first) can be played from position, in some order.

function chanceOfWinning

chanceOfWinning(game: GameState, side: Side): number

The chance, 0 to 1, that side wins from here, by the evaluation, or by the Keith count's rule of thumb in a race (as 0.5 plus a share of the count's lead).

function checkersOf

checkersOf(position: Position, side: Side): number

The whole count of each side's checkers: on the board, on the bar, yet to enter, and off. Always the variant's checkers.

function choosePlay

choosePlay(game: GameState, options?: ComputerOptions): Play

The play the computer makes for the rest of the turn of the side on turn, from the plays the rules leave it.

function computerAction

computerAction(game: GameState, options?: ComputerOptions): ComputerAction | null

What the side that has to act in a game would do, if the computer were playing it. Nothing is done: the game is not changed. Null when the game is over.

type ComputerAction

type ComputerAction = | { kind: "throw" } | { kind: "double" } | { kind: "roll" } | { kind: "play"; play: Play } | { kind: "end" } | { kind: "take" } | { kind: "drop" };

What the computer does next in a game, for the side it plays.

type ComputerOptions

type ComputerOptions = { /** The strength. Default `strong`. */ strength?: Strength; /** Where its choices come from, for `random` and for choosing between plays that score the same. Default `Math.random`. */ random?: Random; /** * The longest, in milliseconds, it spends looking ahead at one move: it looks at its best plays in order and stops when the time is * up (always having looked at the best two). Default 60, …

function concede

concede(game: GameState, side: Side, kind?: WinKind): GameState

Give up the game: side loses. kind is what it is giving up, from concedeKind's worst down to a single game.

function concedeKind

concedeKind(game: GameState, side: Side): WinKind

What a side gives up if it gives up the game now, which is the worst the position could come to: a single game once it has borne a checker off; a gammon if nothing of the other side's can still hit it; otherwise a backgammon where it may have a checker caught in the other side's home board.

function countAt

countAt(position: Position, side: Side, own: number): number

How many of side's checkers stand on its own point own (1 to 24).

type Cube

type Cube = { readonly value: number; readonly owner: Side | null };

The doubling cube: what it shows, and who may turn it (null while it stands in the middle, for either).

const CUBE_LIMIT

CUBE_LIMIT: 64

The most the cube can show.

function cubeInPlay

cubeInPlay(rules: Rules, crawfordGame: boolean): boolean

Whether the cube is in play in a game of a match, given whether this is the Crawford game.

function cubeIsDead

cubeIsDead(game: GameState): boolean

The cube is dead when turning it could not matter: it already shows enough for either side to win the match with a win. Never in money play.

const DEFAULT_RULES

DEFAULT_RULES: Rules

The rules a variant is played with when nothing else is asked: a single game with no cube and no gammons.

function diceMustPlay

diceMustPlay(spec: VariantSpec, position: Position, side: Side, dice: readonly number[]): number[]

WHICH DICE MUST BE PLAYED. A side must play as many of its dice as it can; where it cannot play them all and could play either of two (or more) sets, it must play the higher: with two dice, if only one can be played, it is the larger one when it can. Returned highest first, from the dice as diceToPlay gives them.

type DiceSource

type DiceSource = () => number;

Where dice come from: each call is the number on one die, 1 to 6.

function diceToPlay

diceToPlay(spec: VariantSpec, roll: readonly number[]): number[]

The dice a roll gives to play, highest first: a double of a game that plays doubles four times is four of them.

type Direction

type Direction = /** Opposite ways round, as in backgammon: one side's 24-point is the other's 1-point. */ | "opposed" /** The same way round, from the same start, as in Tabula: both sides' own point 24 is the one place. */ | "same";

How the sides' tracks lie on the board.

function dropDouble

dropDouble(game: GameState): GameState

Drop the double that was offered: the side that drops it loses the game, at the cube's value before the double, or for a beaver at its value once the double was taken.

function endTurn

endTurn(game: GameState): GameState

End the side on turn's turn, once everything it had to play has been played. The other side is to move.

function evaluate

evaluate(spec: VariantSpec, position: Position, side: Side, weights?: EvaluationWeights): number

The score of a position for side, with the other side to move, as a game where bearing off first wins. Large positive or negative numbers are a game won or lost; evaluateFor turns it over for a game played to lose.

function evaluateFor

evaluateFor(spec: VariantSpec, position: Position, side: Side, weights?: EvaluationWeights): number

The score as side plays to win or to lose: a game played to lose is judged the other way up.

const EVALUATION_WEIGHTS

EVALUATION_WEIGHTS: EvaluationWeights

The weights the computer plays with: set by hand and then held against random changes of themselves, none of which won more often over two thousand games.

type EvaluationWeights

type EvaluationWeights = { /** What it is worth to have the roll, which the side that does not have it must give up. */ onRoll: number; /** Per checker still to bear off, in a race. */ raceChecker: number; /** What a made point in the home board is worth, by the point, 1 to 6. */ home: readonly [number, number, number, number, number, number]; /** A made point in the outfield (7 to 12), and in the other outfield (13…

The numbers the evaluation is made of, each a number of pips.

function finishGame

finishGame(match: Match, result: GameResult): Match

The match after a game's result: the score added up, the Crawford game accounted for, and the match over if it is decided.

function formatMove

formatMove(move: Move): string

One move as text: 24/18, bar/22*, 6/off.

function formatOpening

formatOpening: (throws: readonly [number, number]) => string

One line of a game: the opening throw.

function formatPlay

formatPlay(moves: readonly Move[]): string

The text of a whole turn: moves of one checker joined, equal moves counted, the farthest-back checker first. A turn of no moves is -.

function formatPosition

formatPosition(position: Position): string

A position as text, for any variant: each side's checkers as point:count in that side's own numbering, highest point first, then the bar, those yet to enter and those borne off, [white, black] each.

white=24:2,13:5,8:3,6:5 black=24:2,13:5,8:3,6:5 bar=0,0 reserve=0,0 off=0,0

function formatRecordHeader

formatRecordHeader(settings: Settings, seed?: RecordSeed): string[]

The header lines of a record: its version, the variant, the rules and the seed.

function formatResult

formatResult(result: GameResult): string

The line that closes a game, with what it came to.

function formatTurn

formatTurn: (side: Side, dice: readonly number[], moves: readonly Move[]) => string

One line of a game: a turn, with its dice and its play.

const GameRecorder

GameRecorder: typeof GameRecorder

A record written as a game is played. Call its methods as the game goes, in the order things happen, and text() is the record so far; a game not yet finished is a record that replays to a game still in play.

type GameResult

type GameResult = { /** The side that won, or null for a draw. */ readonly winner: Side | null; readonly how: HowItEnded; readonly kind: WinKind; /** 1 for a single win, 2 for a gammon, 3 for a backgammon, after the Jacoby rule and the choice of rules. */ readonly multiplier: number; /** The cube's value when it ended. */ readonly cube: number; /** What the winner scores: the multiplier times the cube. 0 for a draw.…

type GameState

type GameState = { readonly settings: Settings; /** Whether this is the Crawford game of a match: no cube. */ readonly crawford: boolean; /** The match score as the game began, `[white, black]`: what the dead cube is judged by. */ readonly score: readonly [number, number]; readonly position: Position; /** The side to move; null until the opening throw has decided. */ readonly turn: Side | null; readonly phase: Phase…

type Goal

type Goal = /** Bear off every checker first. */ | "first-off" /** Be last to bear off: whoever bears off every checker first loses (Anti-Backgammon). */ | "last-off";

What it takes to win.

function hashSeed

hashSeed(seed: number | string): number

A whole number from a seed given as a number or as text: the same seed is always the same number, in every browser and every Node.

type Hop

type Hop = { readonly from: number; readonly to: number };

A step of a written play: one checker, one die's distance (or off), as written.

type HowItEnded

type HowItEnded = /** Somebody bore off every checker. */ | "bear-off" /** A double was dropped. */ | "drop" /** A side gave up the game. */ | "concede" /** Neither side won. */ | "draw";

How a game came to its end.

function inContact

inContact(spec: VariantSpec, position: Position): boolean

Whether the two sides can still hit one another. Once every checker of one side has gone past every checker of the other there is nothing more to play for but the race, and the game is a race.

function inPlay

inPlay(position: Position, side: Side): number

Every checker of side that is still in the game: on the board, on the bar or yet to enter.

function isVariantKey

isVariantKey(key: unknown): key is VariantKey

Whether key names a variant.

function keithCount

keithCount(position: Position, side: Side, onRoll: boolean): number

The Keith count of a side, a pip count corrected for how badly the checkers are placed for bearing off: plus 2 for each checker beyond the first on the 1-point, 1 for each beyond the first on the 2-point and beyond three on the 3-point, and 1 for each empty point among the 4, 5 and 6, and then the side on roll adds a seventh (rounded down). For races only.

function legalMoves

legalMoves(game: GameState): Move[]

The moves the side on turn may make next. Empty when the turn is played out, or nothing can be played.

function legalPlays

legalPlays(spec: VariantSpec, position: Position, side: Side, roll: readonly number[]): Play[]

Every different turn a side may make with a roll: one for each position that playing the roll as the rules require can leave, with one way of getting there. A side that cannot move has the one play of no moves.

function legalPlaysOf

legalPlaysOf(game: GameState): Play[]

Every different way the side on turn may play out the rest of its turn: for the dice it has yet to play, from where the position stands now.

type Match

type Match = { readonly settings: Settings; /** The score, `[white, black]`. */ readonly score: readonly [number, number]; /** Every game finished so far. */ readonly results: readonly GameResult[]; /** Whether the next game is the Crawford game. */ readonly crawfordNext: boolean; /** Whether the Crawford game has been played: there is one in a match. */ readonly crawfordPlayed: boolean; /** Whether the match is dec…

A MATCH: games played one after another to a number of points, or without end in money play. It keeps the score and says which game is the Crawford game. Like everything here it is plain data, and never changed: each function gives back a new match.

function mirrorPoint

mirrorPoint(spec: VariantSpec, own: number): number

The other side's own number for the point that is side's own point own. With the sides going opposite ways round, a side's 24-point is the other's 1-point; going the same way, a point has the one number for both.

type Move

type Move = { readonly from: number; readonly to: number; /** The number on the die that moved it. A checker borne off with a larger die than it needed has `die` greater than `from`. */ readonly die: number; /** Whether it hit a checker, which went to the other side's bar. */ readonly hit: boolean; };

ONE CHECKER MOVED BY ONE DIE, in the mover's own numbering: from its own point from (or the BAR, 25, for a checker entering from the bar or from off the board at the start of a race) to its own point to (0 for borne off). A move that lands on a lone checker of the other side hits it.

function movesOfDie

movesOfDie(spec: VariantSpec, position: Position, side: Side, die: number): Move[]

Every move of one die that side may make from position, one for each point a checker can start from. A side with a checker on the bar must enter it first; a side that has not borne off may start a checker off the board (in a race) whenever it likes; and a side whose checkers are all home may bear off, by the exact die, or by a larger one from the highest point it has a checker on.

function multiplierOf

multiplierOf(game: GameState, kind: WinKind): number

What a win of kind is worth, after the rules: a single win when gammons do not count, or when the Jacoby rule holds them back because the cube was never turned.

function newGame

newGame(settings: Settings, options?: NewGameOptions): GameState

A new game, waiting for the opening throw.

type NewGameOptions

type NewGameOptions = { /** Whether it is the Crawford game of a match: played without the cube. */ crawford?: boolean; /** The score of the match so far, `[white, black]`. Default 0 to 0. */ score?: readonly [number, number]; };

Options for a new game.

function newMatch

newMatch(settings: Settings): Match

A match at 0 to 0.

function nextMoves

nextMoves(spec: VariantSpec, position: Position, side: Side, need: readonly number[]): Move[]

The moves that may be made next, by a side that still has the dice need (highest first) to play from position: every move that leaves the rest of need playable. Where one checker could be borne off by either of two dice, both moves are offered.

function offerDouble

offerDouble(game: GameState): GameState

Offer a double: the other side must now take it or drop it.

function onBoard

onBoard(position: Position, side: Side): number

How many checkers side has on the board's points, not counting the bar, the reserve and those borne off.

type Opening

type Opening = /** Each side throws one die; the higher plays both dice as the first roll of the game, as in backgammon. */ | "roll-off" /** Each side throws one die; the higher goes first and then rolls a roll of their own. */ | "die-each";

How a game is opened.

function openingFrom

openingFrom(source: DiceSource): [number, number]

An opening throw from a source: one die for white and one for black.

function opponentAt

opponentAt(spec: VariantSpec, position: Position, side: Side, own: number): number

How many of the other side's checkers stand on the point that is side's own point own.

function otherSide

otherSide(side: Side): Side

The other side.

function parseHops

parseHops(text: string): Hop[] | null

The hops a written play names, in the order written: a chain of three points is two hops, and 13/11(2) is two. Returns null for text that is not a play. -, pass, and the empty text are the play of no moves.

function parsePlay

parsePlay(spec: VariantSpec, position: Position, side: Side, roll: readonly number[], text: string): Move[] | null

The moves a written play makes with a roll, or null if it is not a legal play of that roll. Checks every rule: it is found by making each named move in some order, each one legal when it is made, until the roll is played as the rules require. A checker that plays both dice may be written as one move, 24/14 for a 6 and a 4, as well as through the point it passes.

function parsePosition

parsePosition(text: string): Position | null

The position that text of formatPosition's kind stands for, or null if it is not one. bar, reserve and off may be left out.

type Phase

type Phase = /** Nobody has the move yet: the sides are throwing a die each to see who starts. */ | "opening" /** The side on turn may double, or must roll. */ | "before-roll" /** The side on turn has offered a double, and the other must take it or drop it. */ | "double-offered" /** The side that was doubled has beavered (redoubled, keeping the cube) and the side on turn must take it or drop it. */ | "beaver-offered…

Where a game has got to.

function pipCount

pipCount(position: Position, side: Side): number

The pip count of a side: the sum of every checker's distance from being borne off. A checker on the bar or yet to enter counts 25.

type Play

type Play = { readonly moves: readonly Move[]; readonly position: Position; };

A WHOLE TURN: the moves a side makes with a roll, in the order it makes them, and the position they leave.

function playMove

playMove(game: GameState, step: { from: number; to: number; }): GameState

Make one move for the side on turn, given as the checker's own point it leaves and the one it goes to. The move must be one of legalMoves. Where a checker could be borne off by either of two dice, the smaller is used.

function playMoves

playMoves(spec: VariantSpec, position: Position, side: Side, moves: readonly Move[]): Position

The position after a list of moves, made one after another.

function playTurn

playTurn(game: GameState, moves: readonly Move[]): GameState

Make a whole play (the moves of legalPlays) for the side on turn and end the turn.

const POINTS

POINTS: 24

The points on the board.

function pointsToGo

pointsToGo(match: Match, side: Side): number

How many points a side still needs to win the match: 0 in money play, where there is no such thing.

type Position

type Position = { /** `[white, black]`, each 24 counts, index 0 being own point 1. */ readonly points: readonly [readonly number[], readonly number[]]; /** Checkers hit and waiting to enter, `[white, black]`. */ readonly bar: readonly [number, number]; /** Checkers that have never entered, `[white, black]`: all of them at the start of a race. */ readonly reserve: readonly [number, number]; /** Checkers borne off, `[…

WHERE THE CHECKERS ARE. Every count is in the side's own numbering: own point 1 is the one nearest its bearing-off and own point 24 the one farthest from it, so points[side][n - 1] is how many of that side's checkers stand on its own point n. A position is plain data and is never changed: every function that moves a checker returns a new one.

function positionFromId

positionFromId(id: string, onRoll: Side, checkers?: number): Position | null

The position an ID stands for, with onRoll to move, or null if the text is not an ID. The ID does not say how many checkers each side began with: pass the variant's (15 for the standard board), and what is not on the board is counted as borne off.

function positionId

positionId(position: Position, onRoll: Side): string | null

The ID of a position with onRoll to move, or null if it cannot be written in this form.

function positionKey

positionKey(position: Position): string

A position as a short string that is the same for the same position, for keeping a set of them.

type Preset

type Preset = { /** In kebab case. */ key: string; variant: VariantKey; rules: Rules; /** The names this goes by, as the sites print them. */ names: readonly string[]; };

A NAMED WAY OF PLAYING: a variant with its rules. The names on the sites these games were played on (ItsYourTurn.com and GoldToken.com) each lead to one of these through presetByName, and docs/VARIANTS.md says how each was read.

function presetByKey

presetByKey(key: string): Preset | undefined

The preset with this key, or undefined.

function presetByName

presetByName(name: string): Preset | undefined

The preset a site's printed name leads to, or undefined. Case and spacing around the name do not matter.

const PRESETS

PRESETS: readonly Preset[]

Every named way of playing, in the order of docs/VARIANTS.md.

type Random

type Random = () => number;

A source of random numbers from 0 up to but not including 1.

function randomDice

randomDice(): DiceSource

Dice that are not seeded: from the device's own random numbers.

const RECORD_VERSION

RECORD_VERSION: 1

The record format's version, the number after sugoroku on the first line.

type RecordSeed

type RecordSeed = number | string;

A seed in a record: all digits means a number, anything else is text.

type ReplayedGame

type ReplayedGame = { readonly result: GameResult; /** The turns each side took. */ readonly turns: readonly [number, number]; /** The position the game ended in. */ readonly position: Position; };

One game of a replayed record, finished.

type ReplayError

type ReplayError = { readonly ok: false; /** The line the record went wrong on, counting from 1. */ readonly line: number; readonly reason: string; };

type ReplayOk

type ReplayOk = { readonly ok: true; readonly settings: Settings; readonly seed: RecordSeed | null; /** The match as it stands after every finished game. */ readonly match: Match; /** Every finished game, in order. */ readonly games: readonly ReplayedGame[]; /** The game still in play when the record ends, or null if the last was finished. */ readonly current: GameState | null; };

function replayRecord

replayRecord(text: string): ReplayOk | ReplayError

Replay a record against the rules. Every move is checked to be legal when it was made, every double to have been allowed, and each game's result and the match's score are worked out, not read: a result line is only checked. Where the record has a seed, the dice are checked to be the seed's. Gives the games and the match, or the first line that goes wrong, and why.

function resolveRules

resolveRules(rules?: Partial<Rules>, variant?: VariantSpec): Rules

The rules with anything left out filled from DEFAULT_RULES. Throws if the result is not a set of rules that can be played.

function restartTurn

restartTurn(game: GameState): GameState

Take back every move of the turn.

function rollDice

rollDice(game: GameState, dice: readonly number[]): GameState

Roll the dice for the side on turn, which must not be waiting on an answer to a double.

function rollFrom

rollFrom(source: DiceSource, count?: number): number[]

A roll of count dice from a source.

function rollOpening

rollOpening(game: GameState, throws: readonly [number, number]): GameState

The opening throw: one die for white and one for black. The higher starts. A tie changes nothing but opening, and the sides throw again. In a game opened with a roll-off the starter plays the two dice as the first roll; otherwise the starter then rolls as usual.

type Rules

type Rules = { /** * The match length in points. 1 is a single game. 0 is money play: games * go on one after another, each scored alone, and nothing ends the session. */ points: number; /** Whether the doubling cube is played. Never in a one-point game, which is its own Crawford game. */ cube: boolean; /** The Crawford rule: the game after a side first comes within a point of winning the match is played without the…

HOW A GAME IS SCORED AND STAKED, beside the variant's board. A variant says where the checkers stand and how they move; these say what a win is worth.

function rulesProblems

rulesProblems(rules: Rules, variant?: VariantSpec): string[]

What goes wrong with a set of rules, as sentences; empty when they are fine.

function seededDice

seededDice(seed: number | string): DiceSource

A SEEDED DICE SEQUENCE: the same seed is the same dice, one die after another, in every browser and every Node, so a game can be played again from its seed and a server can tell whether the dice a game says it had are the dice it should have had. Dice are taken in the order a game asks for them: the opening throw (white's die, then black's, and again for a tie), then every roll in turn, each as many dice as the variant rolls.

function seededRandom

seededRandom(seed: number | string): Random

The mulberry32 stream: the same seed makes the same numbers everywhere.

type Settings

type Settings = { variant: VariantSpec; rules: Rules; };

The settings of a game: the variant's row and the rules. What every game and match is made from.

function settingsFor

settingsFor(variant: VariantKey | VariantSpec, rules?: Partial<Rules>): Settings

Settings from a variant key (or a row) and rules, left-out rules filled in.

type Side

type Side = "white" | "black";

THE TWO SIDES. A game has a white and a black player. Every count in a position is kept per side, in that side's own numbering of the points: a side's own point 1 is the one nearest its bearing-off, its own point 24 the one farthest from it, whichever way the board is drawn.

function sideIndex

sideIndex(side: Side): 0 | 1

0 for white and 1 for black: where a side's counts are kept in a position.

const SIDES

SIDES: readonly [Side, Side]

Both sides, white first.

function sideToAct

sideToAct(game: GameState): Side | null

Whether a side is the one the computer would act for at this moment of a game: the side on turn, or the side that must answer a double.

function startCounts

startCounts(spec: VariantSpec): number[]

Which points a side's checkers start on, as an array indexed by own point 1 to 24 (index 0 is point 1).

function startGame

startGame(match: Match): GameState

The first game, or the next game, of a match.

function startPosition

startPosition(spec: VariantSpec): Position

The position a variant starts from.

function startReserve

startReserve(spec: VariantSpec): number

How many checkers each side begins off the board, to be entered with the dice.

function stepComputer

stepComputer(game: GameState, dice: DiceSource, options?: ComputerOptions): { game: GameState; action: ComputerAction; } | null

Carry out one step of what the computer would do, with dice from dice. Returns the game after it, and what was done, so a caller can write it down or show it. Which side plays is up to the caller: this plays for whichever side is to act.

type Strength

type Strength = "random" | "greedy" | "careful" | "strong";

THE COMPUTER PLAYER, in four strengths. Each is the one before it with more care, and none of them is slow: a move takes a few milliseconds, and the strongest well under a tenth of a second in a browser.

- random: any legal play, with equal chance. It never doubles, and takes every double. - greedy: the play that leaves the best position by the evaluation (evaluate), looking no further. - careful: weighs its best four plays by what the other side's best reply to each roll would leave. - strong: the same over its best ten plays.

Doubling is simple, and works as documented in the README: in a race the Keith count (see keithCount), where the rules are Keith's: double when the count of the side on roll is within 4 of the other's, redouble within 3, take when the doubler's is 2 or more above the taker's. In a position where the sides are still in contact (careful and strong only), the evaluation is turned into a chance of winning (winChance) and a side doubles at 70% and takes at 25%.

const STRENGTHS

STRENGTHS: readonly Strength[]

The strengths, weakest first.

function takeDouble

takeDouble(game: GameState): GameState

Take the double that was offered. The cube turns and goes to the side that took it, and the side on turn rolls. (A beaver taken turns it twice, and leaves it with the side that beavered.)

function turnIsPlayed

turnIsPlayed(game: GameState): boolean

Whether the turn is played out and the side on turn has only to end it.

function undoMove

undoMove(game: GameState): GameState

Take back the last move made this turn. Does nothing if none has been made.

const VARIANT_KEYS

VARIANT_KEYS: readonly VariantKey[]

The variants' keys, in the order the demo lists them.

type VariantKey

type VariantKey = "backgammon" | "backgammon-race" | "anti-backgammon" | "nackgammon" | "long-gammon" | "hypergammon" | "tabula";

THE VARIANTS, AS ROWS OF SETTINGS. Nothing in the engine branches on a variant's name: it reads the row. A new game on the same board is a new row.

const VARIANTS

VARIANTS: Readonly<Record<VariantKey, VariantSpec>>

Every variant. Where each comes from, and where the sources disagree or say nothing, is in docs/VARIANTS.md.

function variantSpec

variantSpec(key: VariantKey): VariantSpec

The row for a variant.

type VariantSpec

type VariantSpec = { readonly key: VariantKey; /** Checkers each side has. */ readonly checkers: number; /** Where the checkers are at the start, as `[own point, how many]`. Empty for a start with every checker off the board. */ readonly layout: readonly (readonly [number, number])[]; /** How many dice a roll is: two, or three for Tabula. */ readonly dice: 2 | 3; /** Whether doubles are played four times (`four`) or…

const VERSION

VERSION: "1.0.0"

The package's version.

function wantsToDouble

wantsToDouble(game: GameState, options?: ComputerOptions): boolean

Whether the computer, to move before it rolls, should offer a double.

function wantsToTake

wantsToTake(game: GameState, options?: ComputerOptions): boolean

Whether the computer, doubled, should take.

function winChance

winChance(score: number): number

A chance of winning, 0 to 1, that a score in pips stands for: the logistic curve, which a lead of 25 pips in a position of this kind takes to about three in four.

function winKind

winKind(game: GameState, winner: Side): WinKind

The kind of win a game that winner has won by bearing off is: single, gammon or backgammon, before the rules say what they count for.

type WinKind

type WinKind = "single" | "gammon" | "backgammon";

What kind of win it was, which is how many times the cube's value it scores.

function withCounts

withCounts(position: Position, changes: { points?: [number[], number[]]; bar?: [number, number]; reserve?: [number, number]; off?: [number, number]; }): Position

A copy of a position with some counts changed.

@johnmorrisdotca/sugoroku/draw

BoardLayout BoardOptions boardSize Box colourProperty colourStyle drawSugoroku MOST_DRAWN Orientation SUGOROKU_BOARD_NAMES SUGOROKU_BOARDS SUGOROKU_CHECKER_SET_NAMES SUGOROKU_CHECKER_SETS SUGOROKU_COLOUR_NAMES SUGOROKU_STYLE SugorokuBoardName SugorokuCheckerSetName SugorokuColours SugorokuDrawOptions

const BoardLayout

BoardLayout: typeof BoardLayout

The layout of a board, a function of its options: where each point, the bar, each tray, the rail and the dice sit, all in landscape units.

type BoardOptions

type BoardOptions = { /** Whether the board has a rail for the doubling cube. */ cube: boolean; /** Which side's home board is at the bottom of the drawing. */ view: Side; /** Which end the trays are at. */ home: "left" | "right"; orientation: Orientation; };

function boardSize

boardSize(options: Pick<BoardOptions, "cube" | "orientation">): { width: number; height: number; }

The width and height the board is drawn in, which is the other way about when it stands up.

type Box

type Box = { x: number; y: number; w: number; h: number };

A box: where something is, in landscape units before any turning.

function colourProperty

colourProperty(name: keyof SugorokuColours): string

The custom property a colour is set on.

function colourStyle

colourStyle(options: Pick<SugorokuDrawOptions, "board" | "checkers" | "colours">): string

The inline custom properties of a drawing: the named board and checkers, then any colour given.

function drawSugoroku

drawSugoroku(position: Position, options?: SugorokuDrawOptions): string

The board as SVG text for position. The variant says how the sides' points line up; pass the same one the game is played with.

const MOST_DRAWN

MOST_DRAWN: 5

How many checkers are drawn on a stack before the last one carries the count.

type Orientation

type Orientation = "landscape" | "portrait";

Whether the board lies across the page or stands up in it.

const SUGOROKU_BOARD_NAMES

SUGOROKU_BOARD_NAMES: readonly SugorokuBoardName[]

The names of the boards.

const SUGOROKU_BOARDS

SUGOROKU_BOARDS: Readonly<Record<SugorokuBoardName, Pick<SugorokuColours, "frame" | "felt" | "pointA" | "pointB" | "bar" | "tray">>>

The boards the drawing knows by name, set against any theme. Each changes only the surfaces: the frame, the felt, the points, the bar and the trays.

const SUGOROKU_CHECKER_SET_NAMES

SUGOROKU_CHECKER_SET_NAMES: readonly SugorokuCheckerSetName[]

The names of the checker sets.

const SUGOROKU_CHECKER_SETS

SUGOROKU_CHECKER_SETS: Readonly<Record<SugorokuCheckerSetName, Pick<SugorokuColours, "white" | "black" | "whiteEdge" | "blackEdge">>>

The checkers the drawing knows by name. contrast is for readers who tell colours apart with difficulty: pale and near-black, with strong rims.

const SUGOROKU_COLOUR_NAMES

SUGOROKU_COLOUR_NAMES: readonly (keyof SugorokuColours)[]

The names of the colours, as the custom properties spell them (--sg- and the name in kebab case).

const SUGOROKU_STYLE

SUGOROKU_STYLE: "\n.sugoroku {\n --sg-frame: #5b3a1f; --sg-felt: var(--felt, #2f5d4a); --sg-point-a: #e7d8b1; --sg-point-b: #8e3a2b; --sg-bar: #4a2f19; --sg-tray: #24493a;\n --sg-white: #f6f0df; --sg-white-edge: #b3a888; --sg-black: #2b2724; --sg-black-edge: #0d0b0a;\n --sg-number: #e8dcc0; --sg-selected: #ffd23f; --sg-target: #7fe3a1; --sg-die: #fbf8f1; --sg-pip: #1f2320; --sg-cube: #fbf8f1; --sg-cube-ink: #1f2320;…

THE STYLE a Sugoroku board is drawn with: colours as custom properties (--sg-felt, --sg-white and the rest of SugorokuColours), light by default and dark when the device is, and the one rule that matters for a game played with fingers and a mouse: nothing on the board can be selected, dragged or double-tapped.

Put it in the page once. Every colour is a custom property on .sugoroku, so a page's own style needs only to set the ones it wants different; the felt follows --felt where a page defines one.

type SugorokuBoardName

type SugorokuBoardName = "green" | "blue" | "red" | "black" | "wood";

A named board, a set of colours for the board and its points.

type SugorokuCheckerSetName

type SugorokuCheckerSetName = "classic" | "red-and-white" | "gold-and-blue" | "contrast";

A named pair of checker colours.

type SugorokuColours

type SugorokuColours = { /** The frame the board is set in. */ frame: string; /** The playing surface. */ felt: string; /** The points, alternately. */ pointA: string; pointB: string; /** The bar, and the trays' floor. */ bar: string; tray: string; /** The checkers: the face and the rim of each side's. */ white: string; whiteEdge: string; black: string; blackEdge: string; /** The numbers on the points, and a count o…

THE COLOURS OF A BOARD. Every colour the drawing uses is one of these, set as a custom property on the drawing (--sg-felt and so on), so a page's own style may change any of them, and drawSugoroku takes any of them as an option. Where a colour is left out the drawing uses the theme's: light by default, dark when the device is (see SUGOROKU_STYLE).

type SugorokuDrawOptions

type SugorokuDrawOptions = { /** The variant, for how its sides' points line up. Default `backgammon`. */ variant?: VariantKey | VariantSpec; /** Whose home board is at the bottom of the drawing. Default white. */ view?: Side; /** Which end of the board the trays are at. Default right. */ home?: "left" | "right"; /** Lying across the page (default) or standing up, a better shape for a phone. */ orientation?: Orienta…

What to draw with.

@johnmorrisdotca/sugoroku/play

createSounds ensureSugorokuPlayStyle mountSugoroku packagedSounds SoundName SoundPlayer SoundUrls SUGOROKU_PLAY_STYLE SUGOROKU_STRINGS SugorokuEventDetail SugorokuLanguage sugorokuLanguageOf SugorokuLook SugorokuMount SugorokuMountOptions sugorokuSay

function createSounds

createSounds(urls: SoundUrls): SoundPlayer

A player for the sounds, which makes its audio only when first asked to play, as browsers insist on a gesture first. Plays nothing where there is no audio.

function ensureSugorokuPlayStyle

ensureSugorokuPlayStyle(host: Element): void

Put the style in the page once: in the document's head, or in the shadow root the host is in.

function mountSugoroku

mountSugoroku(host: HTMLElement, options?: SugorokuMountOptions): SugorokuMount

Mount a board into host.

function packagedSounds

packagedSounds(): SoundUrls

The sounds' addresses beside the package's code, or an empty set where import.meta.url is not an address (some test runners).

type SoundName

type SoundName = "clack" | "hit" | "dice" | "cube";

THE SOUNDS OF A BOARD, optional: a checker set down, a checker hit, the dice and the cube. The files are in the package's sounds/ folder (short WAV clips from Kenney's "Casino Audio", Creative Commons Zero: see sounds/CREDITS.txt) and are found beside the code with import.meta.url, so a bundler that follows that pattern ships them. A page that serves them from somewhere else gives their addresses.

type SoundPlayer

type SoundPlayer = { play: (name: SoundName) => void; /** Stop and let go of everything. */ destroy: () => void; };

type SoundUrls

type SoundUrls = Partial<Record<SoundName, string>>;

The address of each sound.

const SUGOROKU_PLAY_STYLE

SUGOROKU_PLAY_STYLE: "\n.sugoroku {\n --sg-frame: #5b3a1f; --sg-felt: var(--felt, #2f5d4a); --sg-point-a: #e7d8b1; --sg-point-b: #8e3a2b; --sg-bar: #4a2f19; --sg-tray: #24493a;\n --sg-white: #f6f0df; --sg-white-edge: #b3a888; --sg-black: #2b2724; --sg-black-edge: #0d0b0a;\n --sg-number: #e8dcc0; --sg-selected: #ffd23f; --sg-target: #7fe3a1; --sg-die: #fbf8f1; --sg-pip: #1f2320; --sg-cube: #fbf8f1; --sg-cube-ink: #1f…

THE STYLE a playable Sugoroku board wears (mountSugoroku, <sugoroku-board>): the drawing's own (SUGOROKU_STYLE), the board's box, its buttons and its lines of words. Colours are custom properties on .sugoroku-play (--sgp-ink, --sgp-muted, --sgp-rule, --sgp-surface, --sgp-accent, --sgp-good) so a page sets only what it wants different.

Nothing moves when something happens: the board is one box of one shape (set from the drawing's own proportions), the line of words keeps room for two lines, and the buttons are three of one size in the same places whatever they say. Nothing the player touches can be selected.

const SUGOROKU_STRINGS

SUGOROKU_STRINGS: Record<SugorokuLanguage, Record<string, string>>

type SugorokuEventDetail

type SugorokuEventDetail = { /** The side it is about; null for the opening throw. */ side: Side | null; game: GameState; match: Match; /** Dice: for a roll, those rolled; for a turn, the roll played. */ dice?: readonly number[]; /** A move in standard notation, or a turn's play. */ notation?: string; move?: Move; moves?: readonly Move[]; /** What was done to the cube. */ action?: "double" | "take" | "drop" | "beave…

What every event of the board carries.

type SugorokuLanguage

type SugorokuLanguage = "en" | "ja";

THE WORDS A SUGOROKU BOARD SAYS, in English and Japanese: the buttons, the line that says whose turn it is and what to do, and the result of a game. Plain data, so a page can read them, replace a few or add a language of its own beside these two.

{name} in a line is a value filled in. The Japanese is a first draft that no native reader has yet checked.

function sugorokuLanguageOf

sugorokuLanguageOf(element: Element | null): SugorokuLanguage

The language an element or its page is in: its own lang, or the nearest above, and English unless that begins with ja.

type SugorokuLook

type SugorokuLook = { board?: SugorokuBoardName; checkers?: SugorokuCheckerSetName; colours?: Partial<SugorokuColours>; theme?: "auto" | "light" | "dark"; /** Which end of the board the trays are at. */ home?: "left" | "right"; /** Whose home board is at the bottom: `auto` (the default) is the side a person plays when the computer plays the other, otherwise white. */ view?: Side | "auto"; /** Numbers on the points. …

How the board looks, apart from the game.

type SugorokuMount

type SugorokuMount = { readonly host: HTMLElement; /** The game as it stands. */ game: () => GameState; match: () => Match; /** The match so far as a record a server can replay (see `replayRecord`). */ record: () => string; /** Roll for the side that is to: with no dice, from the seed or at random; with them, the dice the page gives (two for the opening throw, one for white and one for black). */ roll: (dice?: reado…

type SugorokuMountOptions

type SugorokuMountOptions = SugorokuLook & { /** The variant. Default `backgammon`. */ variant?: VariantKey; /** The rules left out are a single game with no cube. */ rules?: Partial<Rules>; /** A named way of playing (a `PRESETS` key, such as `backgammon-5`) instead of `variant` and `rules`. */ preset?: string; /** Dice from a seed, so the game can be played again; left out they are random. */ seed?: RecordSeed; /*…

function sugorokuSay

sugorokuSay(language: SugorokuLanguage, key: string, values?: Record<string, string | number>): string

A line in a language with its {name}s filled in. A name left unfilled stays as written, so a missing value is visible.

@johnmorrisdotca/sugoroku/element

SugorokuBoard

const SugorokuBoard

SugorokuBoard: typeof SugorokuBoard

@johnmorrisdotca/sugoroku/element/define