View: Multi-Page Single Page

API Reference

The public surface: data, generators, solvers, assets, and the demo layer.

Assemblies & layout

  • SimpleKlotski — the core: data model, solvers, generators, ScriptableObjects, presentation helpers. Pure C# apart from the asset types; no package dependencies.
  • SimpleKlotski.Demo — the playable demo layer (renderer, referee, input, Canvas menu/HUD). Uses TextMesh Pro and the Input System.
  • SimpleKlotski.Editor — the Generate window, inspectors, drawing and the demo builders. Editor-only.

Namespaces mirror that: SimpleKlotski, SimpleKlotski.Demo, SimpleKlotski.Editor.

Enums

  • KlotskiModeGridlock, Klotski, Clear.
  • SlideDirNone, Up, Down, Left, Right, In, Out. In and Out are the depth axis of 3D Clear boards only (In = deeper, Out = toward layer 0).

KlotskiPuzzle

The board. [Serializable], so it stores inside a ScriptableObject; pure C#, so it runs headless.

  • string Seed · KlotskiMode Mode
  • int Width, Height, DepthDepth is 1 for flat boards, 2+ for a 3D Clear lattice.
  • Piece[] Pieces · int[] Obstacles · int[] Checkpoints — cell indices are y * Width + x (3D uses Cell3).
  • SlideDir ExitSide, int ExitIndex — Gridlock's opening. int TargetX, TargetY — Klotski's goal.
  • Persisted verdict: bool Solvable, int MinMoves (the par), int SolutionCount, int Difficulty, bool Unique.
  • GenerationSettings Settings — the recipe that made it (used by the inspector's Re-roll).
  • Transient: KlotskiAnalysis Analysis, KlotskiReport Report — recomputed, never serialized.

Helpers: CellCount, LayerCount, Is3D, PieceCount, HeroIndex, InBounds(x,y), Cell3(x,y,z), ObstacleMask, CheckpointMask, PieceMask(i,x,y), Validate(out string error), and the labels PieceTag(i) ("B7") / PieceName(i) ("Block 7").

IsObstacle(cell) answers the same question as ObstacleMask but at any board size. Those masks are indexed by CELL, so they only hold while a board fits in 64 cells — and past that they do not fail loudly, they wrap. Clear boards reach 16 a side, so the Clear path asks this instead.

Piece & Move

Piece — one rectangular block:

  • int X, Y, W, H — top-left cell and size. int Z, D — layer and depth extent on 3D boards (DepthExtent normalises older data to 1).
  • bool IsHero · SlideDir Direction — the arrow, used by Clear; other modes leave it None.
  • Contains(cx,cy), Contains3(cx,cy,cz), AxisAllows(dir) — the Gridlock movement law: wide blocks slide horizontally, tall ones vertically, squares freely.

Moveint Piece, SlideDir Dir, int Dist. One move is one block sliding any distance in one direction, which is the genre-standard count (an L-turn is two moves). In Clear, Dist is the travel to leave the board.

Snake bodies

A snake is a Clear piece bent into a rope that slithers out head first along its own body. Piece is unchanged — bodies live in a flat side table on the puzzle, and an empty table means an ordinary rectangle, so every board saved before snakes existed reads exactly as it always did.

  • int[] BodyCells — every snake's cells, tail to head, back to back. int[] BodyStart / int[] BodyCount slice it per piece.
  • HasSnakes · IsSnake(i) · BodyLength(i) · BodyCell(i,k) (k counts from the TAIL) · HeadCell(i) · TailCell(i).
  • HeadDirection(i) — derived from the last body segment, so the arrow can never disagree with the shape it is drawn on. Returns the piece's own Direction for rectangles, so callers need no branch.
  • PieceContains(i,x,y) and BodyMask(i) — footprint questions that answer correctly for both kinds.
  • SetBodies(int[][] perPiece) — replace every body; a null entry leaves that piece a rectangle. ReverseBody(i) flips which end is the head.

A snake's X/Y is only a tail anchor and its W/H are 1, so Piece.Contains cannot answer for a rope's body — the struct has no access to the table describing its shape. Ask the puzzle (PieceContains / BodyMask) whenever a board might hold snakes.

Why a flat table rather than an array on Piece: Piece is a struct and gets copied constantly, so an array field would make every copy share one reference — mutating a copy would reach back into the board. Unity also serialises flat arrays far better than an array nested inside a struct inside an array.

SnakeShaper

SnakeShaper.Grow(occupied, width, height, start, maxCells, bendiness, rng) grows one rope as a self-avoiding walk through free cells and returns it tail to head, or null when the start cell is taken or it could not reach two cells.

It knows nothing about boards, pieces, solving or removal order — it takes an occupancy grid and hands back a shape, which is what lets hand-authoring use it as readily as generation does. Self-avoidance is by construction: a cell is marked used the moment it is taken, so a rope cannot cross itself.

GenerationSettings

Inputs to every generator. Clamp() coerces everything into legal ranges, and Clone() gives you a safe copy.

  • Mode, Width, Height3–16 for flat Clear, 3–8 for every other mode, clamped per mode because the sliding modes search a state space and already cost seconds a board at 8×8. Depth (1 = flat; 2–8 forces Clear), Seed.
  • PieceCount (0 = auto), ObstacleCount, CheckpointCount (0–4).
  • MinMoves / MaxMoves — the accept window (chain depth in Clear); TargetMoves demands an exact value and is reconciled into the window.
  • RequireUnique — hunt for a single-solution board.
  • HeroWidth / HeroHeight (1–2, Klotski).
  • ClearMaxLength (1–3) and ClearFill (0 = auto ≈0.6, up to 1 = packed solid).
  • SnakeRatio (0–1) — share of Clear pieces grown as ropes. At 0 the generator draws no random numbers for snakes at all, which is what keeps every existing seed reproducing the board it always did. SnakeMaxLength (0 = auto 0.4) is a multiple of (Width + Height), resolved by MaxSnakeCells(w,h); SnakeBendiness (0–1) shapes the rope without capping its bends. WantsSnakes is the convenience test.
  • MaxAttempts, and OnAttempt — a [NonSerialized] progress hook called with the attempt number; throw from it to abort a run (that is how the window's Cancel works).

Generators

All four share one entry point shape and always return a board — a matched one, or the closest near-miss with Report.Matched == false.

  • GridlockGenerator.GenerateVerified(settings)
  • KlotskiGenerator.GenerateVerified(settings) — reverse-BFS depth engineering, re-verified forwards.
  • ClearGenerator.GenerateVerified(settings) — greedy witness order; solvable by construction.
  • Clear3DGenerator.GenerateVerified(settings) — the same for lattices (Depth ≥ 2).

The returned puzzle carries its Analysis, Report and a scrubbed copy of the settings that made it. They are thread-safe pure C#: the Generate window runs them on a worker thread.

KlotskiBatch

Generating many boards, on or off the main thread. Picking a generator by mode is a rule, not a decision, so it lives in one place: KlotskiBatch routes by Mode (and by Depth > 1 for 3D), then adds batching, progress and cancellation on top.

  • Generate(settings) — one board, from whichever generator the settings call for.
  • Generate(settings, count, progress, cancel) — a batch on the calling thread. Each board is generated from its own seed, derived as "<seed>-<index>", so no two boards in a batch come out the same. Give the settings a Seed and the whole batch repeats exactly; leave it empty and a fresh random root is minted per call, so two un-seeded batches never collide.
  • GenerateAsync(settings, count, progress, cancel) — the same on a background thread, returning a Task<List<KlotskiPuzzle>>. This is what a loading screen wants.

Progress is per ATTEMPT, not per board. A demanding move window can spend hundreds of attempts on a single board, and a bar that only moves once per finished board sits still exactly when the work is hardest.

BatchProgressMeaning
Board / TotalBoards1-based board, and how many were asked for.
AttemptAttempt within the current board.
MaxAttemptsThe ceiling after clamping — what the generator will really stop at, not what you asked for.
BoardCompleteSet on the report sent when a board lands; Attempt then holds the attempt it actually finished on.
Fraction0–1 across the whole batch, counting a part-finished board by its attempt progress.

The progress callback arrives on the worker thread: not the main thread. Store the values in plain fields and read them back in Update() — that is what the demo does — or wrap the callback in a System.Progress<T> created on the main thread. Touching a UnityEngine object straight from it is a hard bug to trace.

Cancellation is checked between boards and between attempts — the token is tested inside OnAttempt, the same per-attempt hook the Generate window's Cancel button uses. A cancelled run throws OperationCanceledException rather than finishing the board it was on, so wrap the call in a try if you cancel deliberately.

KlotskiAnalysis

  • bool Solvable · int MinMoves (the par; in Clear, the block count) · int SolutionCount · bool CountComplete · bool IsUnique (solvable, exactly one, and the count was complete).
  • List<Move> BestSolution — one proven-shortest solution (in Clear, a valid removal order).
  • int ChainDepth — Clear's difficulty signal.
  • long StatesExplored · int Difficulty (1–5★) · int ChallengeScore.
  • List<string> Warnings and DesignNotes — the human-readable verdict the editor shows.

KlotskiReport carries AttemptsUsed, LayoutRejects, QualityRejects and Matchedfalse means you got an honest fallback.

Solvers

KlotskiSolver — breadth-first search for Gridlock and Klotski:

  • Analyze(puzzle) / Analyze(puzzle, maxStates) — proven par plus shortest-solution counting. States are canonicalised so identical blocks are interchangeable, which is what keeps dense boards searchable.
  • MaxSlide(p, codec, others, fromCell, piece, dir)the movement law. The demo referee calls this exact method, so a demo move can never disagree with the solver.
  • IsWin(p, hero, heroCell) and IsWinState(p, codec, cells) — hero position, and the full test including checkpoint coverage.
  • LegalMoves(...), MaxStates, SolutionCountCap.

ClearSolver — dependency analysis, not search:

  • Analyze(puzzle) — layered peel, chain depth, and an exact count of removal orders up to MaxCountPieces (16).
  • Removable(puzzle, i, remaining) — the tap test the demo uses.
  • ExitPath(p,i) / ExitDistance(p,i), and the 3D pair ExitPath3 / ExitDistance3 plus CellMap3.
  • AnalyzeGraph(ClearGraph) — the core in its raw form: a blocker graph in, a verdict out. BuildGraph / BuildGraph3 reduce a board to one.

Difficulty

Difficulty.GridlockStars(a), Difficulty.KlotskiStars(a), Difficulty.ClearStars(a) — 1–5★ from an analysis (0 = unrated or unsolvable). Banded on the proven par for the sliding modes and on chain depth for Clear; the thresholds are listed in Generating.

ScriptableObjects

  • KlotskiPuzzleAsset — one board.
  • KlotskiPuzzlePack — an ordered list; Count, Get(index), Puzzles.
  • IPuzzleProvider — implemented by both, with AppendTo(List<KlotskiPuzzle> levels), so one inspector list can accept either.
  • PuzzleAssets.CreatePuzzle(...) / CreatePack(...) — what the Save menu calls.

Demo components

  • KlotskiGame — the referee. LoadLevel(puzzle), TrySlide(piece, dir, dist), TryRemove(piece), Undo(), PlayHint(), StartAutoSolve(delay), CancelAutoSolve(), ResetLevel(), Unload(); state via MoveCount, Par, RemainingCount, CoveredCheckpointCount; events LevelLoaded / LevelReset / LevelSolved / Moved / MoveBlocked / Undone.
  • KlotskiLevelRenderer — builds the board in 3D. Two prefab slots (piece, hero) and three arrow-glyph slots; empty slots fall back to primitives and generated glyphs. SetPieceCell, SlideOff, RestorePiece, SetCheckpointHeld, BoardCenter, BoardExtent.
  • KlotskiInput — drag-to-slide, click-to-clear, R/Z/H/F keys.
  • KlotskiOrbitCamera — 3D navigation: orbit, pan, zoom. KlotskiCameraFramer frames each new board.
  • KlotskiDemo — the flow (mode, endless vs pack, next level). KlotskiHud / KlotskiMenu — Canvas UI. KlotskiBeeper — procedural SFX, no audio files.

IPuzzleSession

What the UI needs from whatever is refereeing, so the HUD is not welded to one class: IsSolved, AutoSolving, MoveCount, Par, Difficulty, ObjectiveText, CounterText, ControlsText, the verbs ResetLevel / StartAutoSolve / CancelAutoSolve / Undo / PlayHint, and the events LevelLoaded / LevelReset / LevelSolved.

KlotskiGame implements it; KlotskiHud.Bind(session) points the UI at one. Implement it yourself if you write your own referee and want to reuse the HUD.