Phaser AE

Sliding block puzzles

Pro packv3.0.0-3Grid

The rules of sliding-block puzzles: pieces of any shape slide through free cells until they leave by an exit or stand on a goal. One pack covers unblock-the-car puzzles, big-block escapes, the 15-puzzle, parking lots and coloured doors, and its generators measure difficulty exactly instead of guessing it.

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

Sliding block 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

slidingBlocks

function slidingBlocks(): Pack<{ slidingBlocks: SlidingBlocksApi }>

Install pure rules; no renderer, DOM or simulation hook is required.

Helpers

createSlidingBlocks

function createSlidingBlocks(config: SlidingConfig, knownSolution?: SlidingAction[]): SlidingBlocks

Construct a headless sliding puzzle.

generateSlidingExit

function generateSlidingExit(config: SlidingConfig, templateSolution: SlidingAction[], steps = 30, seed = 1, options: SlidingExitOptions = {}): SlidingExitGeneration

Build a seeded exit puzzle by making reversible legal moves from a proven template. The supplied solution is validated before generation, and the returned solution is replayed against the generated board before return. This proves solvability without depending on a search budget.

generateSlidingPuzzle

function generateSlidingPuzzle(options: SlidingPuzzleOptions = {}): SlidingExitGeneration

Generate a seeded puzzle from a preset: colour-exit (coloured doors), unblock (Rush Hour: free the red car) or parking (every car out by its own side). The solution is replayed before it is returned. Throws on out-of-range options, and when no candidate meets the difficulty within attempts (generateSlidingPuzzle: no ... deal met ...), never returning an easier board instead.

scrambleSliding

function scrambleSliding(config: SlidingConfig, steps = 30, seed = 1): { config: SlidingConfig; solution: SlidingAction[] }

Scramble a solved positional puzzle by reversible legal moves, returning a known solution.

shortenSlidingSolution

function shortenSlidingSolution(config: SlidingConfig, solution: SlidingAction[]): SlidingAction[]

Remove revisited board states from a verified certificate without guessing at legality.

transformSlidingTemplate

function transformSlidingTemplate(config: SlidingConfig, solution: SlidingAction[], variant: number): { config: SlidingConfig; solution: SlidingAction[] }

Rotate or reflect a complete template, including cells, axes, goals, doors and solution actions.

Classes

SlidingBlocks

class SlidingBlocks

Sliding and coloured-exit rules with transactional previews and bounded solving.

  • constructor(config: SlidingConfig, knownSolution?: SlidingAction[])
  • get config(): SlidingConfig — A copy of the configuration: the opening position, not the current one.
  • get state(): SlidingState — A copy of the current state.
  • get solved(): boolean — Whether every exiting piece has left and every goal piece stands on its goal.
  • legalActions(): SlidingAction[] — Every legal one-cell move and every legal exit from the current position.
  • legalSlides(): SlidingAction[] — Every legal slide from the current position: each reachable stopping point along a piece's permitted axes, however far, and every legal exit.
  • assessExitLayout(): SlidingExitLayout — Initial geometry, not the length of a supplied (possibly inefficient) solution.
  • proveFirstExitLowerBound(minimumSlides: number, maxStates = 20000): { status: SlidingLowerBound['status']; minimumSlides: number; visited: number } — Exhaustively reject any first removal reachable with fewer than N slides.
  • preview(a: SlidingAction): ActionResult<SlidingEvent> — What apply would answer for the action, without playing it: the position and history are untouched.
  • apply(a: SlidingAction): ActionResult<SlidingEvent> — Play an action. Returns { valid: true, events }, or { valid: false, reason, events: [] } with the position unchanged.
  • solve(maxStates = 20000): SearchResult<SlidingAction> — A winning plan from the current position: the known solution's remaining steps when the puzzle has one, else a bounded breadth-first search of at most maxStates positions (a whole number from 1 to 1,000,000; default 20,000, else it throws).
  • solveOptimal(maxStates = 20000): SearchResult<SlidingAction> — Exact shortest plan in whole-piece slides; a budget limit is never a difficulty proof.
  • solveGuided(maxStates = 20000): SearchResult<SlidingAction> — Fast best-first plan search. The result is verified and solvable, but not necessarily shortest.
  • solveBest(maxStates = 20000): SearchResult<SlidingAction> — Prefer a shorter guided plan while retaining the certified fallback.
  • analyze(maxStates = 20000): SlidingDifficulty — Report difficulty only when the bounded optimal search actually finishes.
  • proveRepositionLowerBound(minimumRepositionSlides: number, maxStates = 20000): SlidingLowerBound — Prove that no winning plan uses fewer than N movement slides; exits cost zero.
  • undo(): boolean — Step back one played action. Returns whether there was one to undo.
  • redo(): boolean — Replay one undone action. Returns whether there was one to redo.
  • reset(): void — Return to the opening position and clear both the undo and redo history.

Types

SlidingAction

type SlidingAction = { type: 'move'; id: string; dx: number; dy: number } | { type: 'exit'; id: string; exit: string }

A move slides piece id by dx columns or dy rows (exactly one non-zero) through free cells; an exit takes it out of the board by the named exit.

SlidingBlocksApi

type SlidingBlocksApi = typeof kit & { /** Same seeded generator off-thread; frame-delivered results require a started game. */ generateSlidingPuzzleAsync(options?: kit.SlidingPuzzleOptions, job?: { signal?: AbortSignal }): Promise<kit.SlidingExitGeneration>; }

Public headless sliding block puzzles helpers.

SlidingConfig

interface SlidingConfig

One puzzle: the board, its pieces and walls, the exits and the goals that win it.

  • cols: number — Board width in cells: a whole number from 1 to 32.
  • rows: number — Board height in cells: a whole number from 1 to 32.
  • pieces: SlidingPiece[] — The pieces at their starting positions: at least one, with unique ids, and no cell shared with another piece or a wall.
  • walls?: Cell[] — Cells that are permanently blocked.
  • exits?: SlidingExit[] — The doorways in the board's edges, with unique ids.
  • goals?: { id: string; x: number; y: number }[] — Target origins for pieces that stay on the board (each id names a piece without an exit). A puzzle needs at least one goal or one exiting piece.

SlidingDifficulty

type SlidingDifficulty = | { status: 'solved'; minimumSlides: number; repositionSlides: number; distinctMovers: number; immediateExits: number; solution: SlidingAction[]; visited: number } | { status: 'unsolvable' | 'budget-exhausted'; minimumSlides: null; repositionSlides: null; distinctMovers: null; immediateExits: number; solution: []; visited: number }

The result of analyze: when solved, the exact shortest plan in whole-piece slides with its measures; otherwise no measure is claimed.

SlidingEvent

type SlidingEvent = { type: 'moved'; id: string; from: Cell; to: Cell } | { type: 'exited'; id: string; exit: string }

What an applied action did: a piece moved from one origin to another, or exited by an exit.

SlidingExit

interface SlidingExit

A doorway in the board's edge through which a matching piece leaves.

  • id: string — Unique, non-empty name of the exit; a piece's exit refers to it.
  • side: 'left' | 'right' | 'top' | 'bottom' — The board edge it opens in.
  • at: number — Position of its first cell along that edge: a row for left and right, a column for top and bottom. A whole number from 0.
  • length: number — Cells wide along the edge, 1 or more; at + length must not pass the end of the edge.
  • key?: string — Colour key: only a piece with this key may leave by it. Omit to admit any piece.

SlidingExitGeneration

interface SlidingExitGeneration

A generated exit puzzle: the board, a replayable solution, the seeds that reproduce it and what was proven about it.

  • config: SlidingConfig — The generated puzzle, ready for createSlidingBlocks.
  • solution: SlidingAction[] — A replayable solution from the generated configuration, including every exit.
  • scrambleMoves: number — How many reversible moves were made from the template to scramble it.
  • requestedSeed: number — Seed supplied by the caller; replay this with the same options to reproduce the deal.
  • seed: number — Candidate seed accepted after bounded attempts.
  • blockedExits: number — Exit pieces which cannot be removed immediately in the generated state.
  • variant: number — Applied board symmetry: 0–3 clockwise rotations; 4–7 also reflect left/right first.
  • difficulty?: { repositionLowerBound: number; visited: number; optimal?: Extract<SlidingDifficulty, { status: 'solved' }>; layout?: SlidingExitLayout; firstExitLowerBound?: number; parking?: { minimumStagingSlides: number; initialExitChoices: number; candidates: number }; unblock?: { blockersMoved: number; repeatedBlockers: number; componentStates: number; candidates: number } } — Certified lower bound, and an exact optimal assessment when requested.

SlidingExitLayout

interface SlidingExitLayout

The geometry of an exit puzzle's opening position, from assessExitLayout.

  • occupiedCells: number — Cells covered by all the pieces together.
  • misalignedPieces: number — Exit pieces whose span across their doorway's lane lies outside the doorway.
  • directExits: number — Exit pieces already aligned with their doorway that can slide straight to the edge and leave, ignoring the others.
  • immediateExits: number — Exits that can be taken right now: the legal exit actions.
  • geometryLowerBound: number — Sum of independently necessary axis slides, ignoring blockers.

SlidingExitOptions

interface SlidingExitOptions

Requirements and limits for generateSlidingExit; a deal that misses one is rejected, and generation throws if no candidate meets them.

  • minBlockedExits?: number — Least number of exit pieces that cannot leave at once in the generated deal. A whole number from 0 up to the number of exit pieces; default 0.
  • attempts?: number — Seeded candidates tried before generation throws. A whole number from 1 to 1000; default 1.
  • minSlides?: number — Reject candidates whose proven shortest plan needs fewer total slides.
  • minRepositionSlides?: number — Reject candidates with too few required repositioning slides (exits excluded).
  • minPiecesMoved?: number — Require the shortest plan to move at least this many different pieces.
  • searchBudget?: number — Exact-search budget per candidate; exhausted searches are rejected, never accepted.
  • symmetry?: 'none' | 'rotations' | 'dihedral' — Seed-selected rotations or reflections change the location of the template's exits.

SlidingLowerBound

type SlidingLowerBound = { … }

The result of a lower-bound proof: proven (no plan is shorter than the bound), easier (a shorter plan exists) or budget-exhausted (no claim is made).

  • status: 'proven' | 'easier' | 'budget-exhausted'
  • minimumRepositionSlides: number
  • visited: number

SlidingPiece

interface SlidingPiece

Pieces use an origin plus local occupied cells; holes in a shape stay empty.

  • id: string — Unique, non-empty name of the piece; actions, goals and SlidingState.removed refer to it.
  • x: number — Board column of the piece's origin (0 at the left); the cells' x offsets are added to it. A whole number.
  • y: number — Board row of the piece's origin (0 at the top); the cells' y offsets are added to it. A whole number.
  • cells: Cell[] — The cells it occupies, as offsets from its origin; at least one, none repeated.
  • axis?: 'x' | 'y' | 'both' — The directions it may slide: 'x' (left and right only), 'y' (up and down only) or 'both'. Default 'both'.
  • fixed?: boolean — True for a piece that never moves and cannot leave. Default false.
  • key?: string — Colour key: the piece may leave only by an exit with the same key, or by an exit that has no key.
  • exit?: string — The id of the exit this piece must leave by to win the puzzle. Omit for a piece that stays on the board.

SlidingPuzzleOptions

interface SlidingPuzzleOptions

Options for generateSlidingPuzzle; every limit left out takes the default of the chosen preset and difficulty.

  • preset?: 'colour-exit'|'unblock'|'parking' — Which reference puzzle to build: 'colour-exit' (the default), 'unblock' or 'parking'.
  • difficulty?: 'easy'|'medium'|'hard'|'very-hard' — The tier whose default limits apply: 'easy', 'medium' (the default), 'hard' or 'very-hard'.
  • seed?: number — Seed that reproduces the board: a whole number from 0 to 4294967295. Default 1.
  • board?: number — Board size in cells per side. Colour Exit accepts 6, 7 or 8 (its 3 x 3 dissection of the board); Unblock and Parking any of 4 to 12. Default 6. Larger Unblock and Parking boards have far more positions, so they need a bigger searchBudget.
  • exitRow?: number — Unblock and Parking: the row Red drives along and leaves by, 0 to board - 1. Default the row above the middle (2 on a 6 x 6 board, as in Rush Hour).
  • minRepositionSlides?: number — Least number of repositioning slides (exits excluded) the board must need. Unblock's default is 2, 8, 16, 24 by tier; crossed Colour Exit's is 1, 10, 20, 25; easy Colour Exit's is 1.
  • searchBudget?: number — Search states allowed for each proof or solve. Defaults: 20,000 for Unblock and crossed Colour Exit, 50,000 for easy Colour Exit, 2,000 for Parking.
  • attempts?: number — Candidate boards tried before generation throws. Defaults: 1200 for Unblock, 80 for Colour Exit, 24 for Parking.
  • minMisalignedPieces?: number — Colour Exit: least pieces whose span lies outside their door's lane. Default 0, 5, 9, 12 by tier; setting it selects the crossed-door generator.
  • maxDirectExits?: number — Most pieces that can leave at the start by a clear straight route. Colour Exit defaults to 8, 2, 0, 0 by tier (setting it selects the crossed-door generator); Parking to 6, 4, 2, 1.
  • minFirstExitSlides?: number — Colour Exit: slides that must be played before any piece can first leave, proven exhaustively. Default 0, 1, 3, 4 by tier; setting it selects the crossed-door generator.
  • minStagingSlides?: number — Parking: fewest staging slides (moving a car aside before another can be driven out; driving a clear car out is free) the board must need, 0 to 100. Default 0, 2, 5, 9 by tier.
  • maxStagingSlides?: number — Parking: most staging slides (moving a car aside before it can be extracted) the board may need. Default the larger of the minimum and 1, 4, 8, 16 by tier.
  • maxRepositionSlides?: number — Unblock: most repositioning slides (the final exit excluded) the board may need. Default the larger of the minimum and 5, 12, 22, 40 by tier.
  • minBlockersMoved?: number — Unblock: least blockers (bars other than Red) moved in the returned optimal plan. Default 1, 3, 6, 7 by tier.
  • minRepeatedBlockers?: number — Unblock: least blockers moved more than once in the returned optimal plan. Default 0, 1, 3, 4 by tier.
  • blockerCount?: number — Unblock: how many blocker bars besides Red, a whole number from 3 to 12 (more on a board larger than 6: up to a third of its cells). Default a seeded 9 to 12 on 6 x 6, scaled to the board's area.

SlidingState

interface SlidingState

Where a puzzle stands.

  • pieces: SlidingPiece[] — The pieces still on the board, at their current positions.
  • removed: string[] — Ids of the pieces that have left by their exits.
  • moves: number — Actions played since the opening position, moves and exits both.