Phaser AE

Deduction puzzles

Pro packv3.0.0-32DGrid

Three logic puzzles with seeded generators and hints you can trust: picture-logic nonograms, light-toggling boards and colour floods. The nonogram and toggle hints are computed exactly, so a hint is never a guess.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 20, 2026

Deduction puzzles: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

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 picking color would 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 to color (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 to colors - 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 ^= stamp is 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 most MAX_FREE_PRESSES free 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, so solveStep can 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 a solution you 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 column i'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, rows arrays of cols booleans (true = filled), instead of a generated one; lineSolvable then 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.