The pack
deduce
function deduce(): Pack<{ deduce: DeduceApi }>
The deduce pack. Pure logic, so it needs no backend and runs headless.
Helpers
createFlood
function createFlood(cfg: FloodConfig): Flood
Creates a Flood-It board: random colours, and a move limit the greedy hint is known to finish within unless you set one.
createLightsOut
function createLightsOut(cfg: LightsOutConfig): LightsOut
Creates a Lights Out board, scrambled from all-off so a solution always exists.
createNonogram
function createNonogram(cfg: NonogramConfig): Nonogram
Creates a nonogram puzzle, a generated one with a picture that line logic alone can solve whenever generation finds one (check lineSolvable).
Classes
Flood
class Flood
A Flood-It board. Pick colours to grow one region until it covers the board. Get one from createFlood.
readonly cols: number— The number of columns in the board.readonly rows: number— The number of rows in the board.readonly colors: number— The number of colours in play.readonly origin: XY— The cell where the flood region starts.moveLimit: number— The number of moves the player can use to win.moves: number— The number of moves used so far. Starts at 0.constructor(cfg: FloodConfig)colorAt(x: number, y: number): number— Returns the colour index of the cell at (x, y).inRegion(x: number, y: number): boolean— Returns true when the cell at (x, y) is part of the flood region.get currentColor(): number— The colour index of the flood region, read from the origin cell.preview(color: number): number— How many cells pickingcolorwould add to the region now; 0 for the region's own colour.pick(color: number): { color: number; absorbed: XY[]; won: boolean; movesLeft: number }— Recolour the region tocolor(a move) and absorb the touching cells of that colour. Picking the region's own colour is free and does nothing. Throws on a colour outside 0 tocolors - 1.get won(): boolean— True when the region covers the whole board.get lost(): boolean— True when the game is not won and the moves used reach the move limit.findMoves(): { color: number; gain: number }[]— Every other colour with what it would gain now, most first: the greedy hint, not an optimal plan.
LightsOut
class LightsOut
A Lights Out board. Press cells to toggle a stamp of lights until all are off. Get one from createLightsOut.
readonly cols: number— The number of columns in the board.readonly rows: number— The number of rows in the board.moves: number— The number of presses made so far. Starts at 0.constructor(cfg: LightsOutConfig)light(x: number, y: number): boolean— Returns true when the light at (x, y) is on.press(x: number, y: number): XY[]— Press cell (x, y): toggle the stamp around it and count a move. Returns the cells toggled, for the presenter.get solved(): boolean— True when every light is off.get litCount(): number— The number of lights that are on.solution(): XY[]— The presses that turn every light off, each cell at most once (pressing twice undoes itself and order does not matter), computed by Gaussian elimination over GF(2):light ^= stampis a system of linear equations, so this is a calculation, not a search. Every board this class can reach is solvable, because it was scrambled from all off by presses and the player only adds presses. When the board has more than one solution (a 5 x 5 cross board has four) it returns one with the fewest presses, as long as the system has at mostMAX_FREE_PRESSESfree variables; above that it returns a solution without the guarantee. Every square board up to 20 x 20 with the cross stamp is within it (19 x 19 has 16 free variables and takes about a fifth of a second).
Nonogram
class Nonogram
A nonogram (Picross) puzzle; a generated one is line-solvable unless its lineSolvable says otherwise. Get one from createNonogram.
readonly cols: number— The number of columns in the grid.readonly rows: number— The number of rows in the grid.rowClues: Clue[]— The run-length clues for each row, top to bottom.colClues: Clue[]— The run-length clues for each column, left to right.readonly lineSolvable: boolean— True when line logic alone (each row's and column's forced cells, repeated to a fixed point) solves this board, sosolveStepcan always make progress until it is done. A generated board is line-solvable unless generation ran out of attempts, which is possible on a large or sparse board; check this flag, because a board that is not line-solvable needs guesses. For asolutionyou supply, it says whether your picture is line-solvable.constructor(cfg: NonogramConfig)set(x: number, y: number, mark: NonoMark): void— Mark cell (x, y) as the player does:'fill','cross'(known empty) or'unknown'. Marks are not checked against the picture.mark(x: number, y: number): NonoMark— Returns the player mark at (x, y).lineSatisfied(axis: 'row' | 'col', i: number): boolean— Whether row or columni's filled cells now read exactly as its clue (for dimming a finished clue).get solved(): boolean— True when every row and column reads as its clue; another picture with the same clues also counts.solutionAt(x: number, y: number): boolean— Whether cell (x, y) is filled in the puzzle's own picture (for checking a mark or revealing the answer).solveStep(): { at: XY; mark: NonoMark }[]— The hint: mark the forced cells of the first row (then column) that has any, and return them; [] when line logic can go no further. Takes the player's current marks as known, so a wrong mark can mislead it.
Types
Clue
type Clue = number[]
Run-length clues for one line, e.g. [3,1] = a run of 3 then a run of 1.
DeduceApi
type DeduceApi = typeof kit
game.deduce — every function and type this pack carries, as one namespace.
FloodConfig
interface FloodConfig
Configures a Flood-It board for createFlood.
cols: number— The number of columns in the board.rows: number— The number of rows in the board.colors?: number— Colours in play, at least 2. Default 6.seed?: number— Seed of the board generator, an unsigned 32-bit integer. Default 1.moveLimit?: number— Moves allowed. Default: the moves the greedy hint needs to finish this board, plus 30% and 2, so the board can always be won within it.origin?: XY— The cell the flood grows from. Default the top-left corner.
LightsOutConfig
interface LightsOutConfig
Configures a Lights Out board for createLightsOut.
cols: number— The number of columns in the board.rows: number— The number of rows in the board.seed?: number— The seed for the random generator. Default 1.stamp?: 'cross' | '3x3' | XY[]— The cells a press toggles, relative to the pressed cell:'cross'(the default: itself and its four neighbours),'3x3'(itself and all eight neighbours) or your own offsets.scrambles?: number— Random presses that scramble the board from all lights off, which is why it is always solvable. Default cols * rows.wrap?: boolean— Whether the stamp wraps round the edges (a torus). Default false: off-board cells are skipped.
NonogramConfig
interface NonogramConfig
Configures a nonogram puzzle for createNonogram.
cols: number— The number of columns in the grid.rows: number— The number of rows in the grid.seed?: number— Seed of the picture generator, an unsigned 32-bit integer; the same seed and size give the same picture. Default 1.density?: number— Chance that a generated cell is filled, 0 to 1. Default 0.55; sparser pictures are less often line-solvable.solution?: boolean[][]— Your own picture,rowsarrays ofcolsbooleans (true = filled), instead of a generated one;lineSolvablethen says whether logic alone solves it.
NonoMark
type NonoMark = 'unknown' | 'fill' | 'cross'
The state of one nonogram cell as the player marks it.
XY
interface XY
A grid position: x is the column index and y is the row index, 0 at the top left.
x: number— The column index, 0 at the left.y: number— The row index, 0 at the top.


