Phaser AE

Shape packing puzzles

Pro packv3.0.0-2Grid

The rules of fit-the-pieces puzzles: a silhouette to fill, a set of pieces to fill it with, and a puzzle that knows when every piece is down and the shape is covered exactly. Square cells, hex cells and tangram polygons share one API, and a seeded generator deals puzzles with a solution it has already checked.

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

Released October 14, 2026 · updated October 19, 2026

Shape packing 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

shapePacking

function shapePacking(): Pack<{ shapePacking: typeof kit }>

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

Helpers

cellAt

function cellAt(mode: PackingConfig['mode'], point: Cell, step = 0.5): Cell

The board cell under a point in board units, for turning a pointer into a placement: the grid square containing it, the nearest hex cell (by cube rounding), or in polygon mode the point snapped to multiples of step (default 0.5, the lattice generated tangrams use). Throws on a non-finite point or a step that is not positive.

cellCentre

function cellCentre(mode: PackingConfig['mode'], cell: Cell): Cell

The centre of a board cell in board units: grid cells are unit squares (centre x + 0.5, y + 0.5), hex cells use hexCentre, and polygon mode's points are themselves.

createShapePacking

function createShapePacking(config: PackingConfig, certificate?: Placement[]): ShapePacking

Create a packing puzzle. Pieces may touch along edges; any overlap of area is refused. Throws on a bad mode, 0 or more than 32 pieces, duplicate ids, invalid cells or polygons (self-intersecting, repeated vertices, under 3 or over 64 corners), a rotation the mode does not allow, an invalid candidate, or a certificate that does not fill the target.

generateShapePacking

function generateShapePacking(options: PackingGenerationOptions = {}): PackingGeneration

Cut a seeded silhouette into connected pieces and return them with a replay-checked exact cover; it makes no uniqueness claim. Throws on out-of-range options, a layout the mode does not have, or a cut into more than the 32 pieces a puzzle may have.

hexCentre

function hexCentre(cell: Cell): Cell

The centre of hex cell (q, r) in board units, the distance between neighbouring centres being 1: pointy-top hexes, q across and r down-right, so { x: q + r / 2, y: r * sqrt(3) / 2 }. Scale and offset it to draw.

Classes

ShapePacking

class ShapePacking

A packing puzzle's rules: square, hex or polygon pieces placed into a silhouette, with undo, redo and a solver. Get one from createShapePacking.

  • constructor(config: PackingConfig, private certificate?: Placement[])
  • solveBest(maxStates = 20000): ReturnType<ShapePacking['solve']> — A winning continuation, without changing anything: the certificate's remaining placements when they still fit around the pieces already placed, otherwise solve(maxStates). Its first placement is the hint.
  • get state(): PackingState — A copy of the state: the placements and the move count.
  • get target(): Cell[] — A copy of the silhouette to fill.
  • get pieces(): PackingPiece[] — A copy of the pieces.
  • points(p: Placement): Cell[] — The cells (grid, hex) or vertices (polygon) a placement covers on the board: the piece is mirrored (if asked), rotated, moved so its smallest x and y are 0, then moved by x, y. Empty for an unknown piece.
  • preview(p: Placement): ActionResult<PackingEvent> — Check a placement without making it. The result is valid, or invalid with a reason: unknown-piece, invalid-transform, off-grid, outside-target or overlap.
  • place(p: Placement): ActionResult<PackingEvent> — Place a piece, or move it if it is already placed, as one move in the history. Returns { valid: true, events }, or { valid: false, reason, events: [] } for the reasons preview gives.
  • remove(id: string): ActionResult<PackingEvent> — Take a piece off the board as one move in the history. An id that is not placed is invalid with the reason not-placed.
  • get unfilled(): Cell[] — The target cells no piece covers yet; always empty in polygon mode.
  • get remainingArea(): number — Target area still uncovered: a count of cells, or an area in polygon mode.
  • get solved(): boolean — Whether every piece is placed and together they cover the target exactly.
  • solve(maxStates = 20000): SearchResult<Placement> | { status: 'unsupported'; actions: []; visited: 0 } — Find placements for the pieces not yet placed that cover the rest of the target, by breadth-first search over at most maxStates positions (default 20,000), placing pieces in config order; changes nothing. Grid and hex modes try every pose at every target cell; polygon mode searches only the config's candidates and reports unsupported without them. unsolvable is a proof only over those placements; budget-exhausted is no claim.
  • undo(): boolean — Undo the last move. Returns false when there is nothing to undo.
  • redo(): boolean — Redo the move last undone. Returns false when there is nothing to redo.
  • reset(): void — Return to the empty table and clear the history.

Types

PackingConfig

interface PackingConfig

A puzzle: the silhouette to fill, the pieces to fill it with and the moves allowed.

  • mode: 'grid' | 'hex' | 'polygon' — The kind of board: square cells, hex cells (axial q/r) or continuous polygons.
  • target: Cell[] — The silhouette to fill: its cells (grid, hex) or the vertices of a simple polygon (3 to 64 corners).
  • pieces: PackingPiece[] — The pieces, 1 to 32, with unique ids.
  • rotations?: number[] — Rotations allowed, in degrees; default 0, 90, 180 and 270 (hex: every 60). Grid needs multiples of 90 and hex of 60; polygons take any finite angle. Must not be empty.
  • reflect?: boolean — Whether pieces may be mirrored; default false.
  • candidates?: Placement[] — Placements solve searches among instead of generating them; polygon mode needs these to be solved at all. Each must be valid on an empty table.

PackingEvent

type PackingEvent ={ type: 'placed'; placement: Placement } | { type: 'removed'; id: string }

What a successful move reports: a piece placed (with its placement) or removed (by id).

PackingGeneration

interface PackingGeneration

A generated puzzle: the configuration, a winning set of placements and what was measured.

  • config: PackingConfig — The puzzle, ready for createShapePacking; pieces are shuffled and shown in a random pose.
  • solution: Placement[] — One placement per piece that fills the target exactly; replayed before it is returned.
  • seed: number — The seed asked for.
  • metrics: { /** Pieces. */ pieces: number; /** Target cells, or the target's area in polygon mode. */ area: number; /** Poses each piece can take: rotations, times 2 with reflection. */ orientations: number; /** Pieces with distinct stored shapes. */ uniqueShapes: number; } — The puzzle's structure; not a uniqueness or human-difficulty claim.

PackingGenerationOptions

interface PackingGenerationOptions

Options for generateShapePacking; every field may be left out.

  • mode?: PackingConfig['mode'] — The kind of board; default hex.
  • difficulty?: 'easy' | 'medium' | 'hard' | 'very-hard' — Sets the default size, piece size and reflection; default medium. Anything else throws.
  • seed?: number — Seed of the random stream, an unsigned 32-bit whole number; default 1.
  • layout?: string — The silhouette: grid rectangle (default), cross or steps; hex hexagon (default), diamond or triangle; polygon square (default) or diamond. Ignored when target is given.
  • size?: number — Side (grid) or radius (hex) of the layout in cells: 2 to 12 for grid (default 4, 5, 6, 7 by difficulty) and 1 to 6 for hex (default 2, 2, 3, 3). Polygon ignores it.
  • target?: Cell[] — Grid and hex: your own silhouette, as unique whole-number cells (hex: axial q/r), instead of layout and size. A part with fewer cells than minPieceSize becomes a smaller piece.
  • pieceSize?: number — Cells a piece grows to, 1 to 12; default 5, 5, 4 and 4 by difficulty. Pieces alternate between this and one less, and merging leftovers can make one larger. Polygon ignores it.
  • minPieceSize?: number — Fewest cells in a piece, 1 to pieceSize: smaller leftovers merge into a neighbour. Default 3 (or pieceSize, if smaller). Polygon ignores it.
  • rotate?: boolean — False fixes every piece's orientation (the only rotation is 0); default true.
  • reflect?: boolean — Whether pieces may be mirrored; default true from hard up.

PackingPiece

interface PackingPiece

A piece of the puzzle, described in its own local coordinates.

  • id: string — Unique id, non-empty; placements refer to it.
  • cells?: Cell[] — Grid and hex modes: the cells the piece covers, in whole-number coordinates (hex: axial q/r).
  • polygon?: Cell[] — Polygon mode: the vertices of a simple polygon with 3 to 64 corners and no holes.

PackingState

interface PackingState

The puzzle's state, plain data.

  • placements: Placement[] — The pieces now placed, at most one each, in the order they were placed.
  • moves: number — Moves made so far, counting each placement and removal.

Placement

interface Placement

How a piece lies on the target.

  • id: string — Id of the piece placed.
  • x: number — Position across of the transformed piece's minimum-x corner: a whole number of cells in grid and hex modes.
  • y: number — Position down of the transformed piece's minimum-y corner: a whole number of cells in grid and hex modes.
  • rotation?: number — Rotation in degrees; default 0. It must be one of the configuration's rotations.
  • mirrored?: boolean — Whether the piece is reflected before it is rotated; default false. It needs the configuration's reflect.

ShapePackingApi

type ShapePackingApi = typeof kit

Public headless shape packing puzzles helpers.