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>— Whatapplywould 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 mostmaxStatespositions (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 (eachidnames a piece without anexit). 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'sexitrefers 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 + lengthmust not pass the end of the edge.key?: string— Colour key: only a piece with thiskeymay 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 forcreateSlidingBlocks.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 legalexitactions.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: numbervisited: 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 andSlidingState.removedrefer to it.x: number— Board column of the piece's origin (0 at the left); the cells'xoffsets are added to it. A whole number.y: number— Board row of the piece's origin (0 at the top); the cells'yoffsets 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 biggersearchBudget.exitRow?: number— Unblock and Parking: the row Red drives along and leaves by, 0 toboard - 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.


