Phaser AE

Hex stack sorting

Pro packv3.0.0-0GridNew

The rules of the hex stack sorting puzzle, with the mechanics found in games like Hexa Sort and Hexa Stack. You draw the board and the stacks; the pack decides every merge, every clear and every chain, and hands you the events in order.

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

Released October 24, 2026

Hex stack sorting: 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

hexaSort

function hexaSort(): Pack<{ hexaSort: typeof kit; }>

Compose the hexa sort rules, hex board helpers and hint player under game.hexaSort.

Helpers

createHexaSort

function createHexaSort(config: HexaSortConfig): HexaSort

Make a hexa sort level from a config. Throws, naming the field, on a config it cannot play.

HEX_DIRECTIONS

const HEX_DIRECTIONS: readonly HexCell[]

The six axial neighbour offsets, clockwise from the right on a pointy layout.

hexagonCells

function hexagonCells(radius: number): HexCell[]

Every cell within radius steps (0 to 12) of the centre: a hexagonal board of 3r(r+1)+1 cells.

hexaHint

function hexaHint(game: HexaSort): HexaHint | null

The placement worth making now: the most tiles cleared, then the most tiles gathered, then the emptiest board, then the placement whose top colour matches a stack nearby. A one-move greedy look, not a solver; pure.

hexaSortLevel

function hexaSortLevel(n: number, options: HexaLevelOptions): HexaSortConfig

The n-th level (1 to 999) of a ladder: the board grows, colours are added, the goal rises, and from level 4 some cells open only as tiles are cleared and a few stacks start on the board. A preset of the configuration, not a rule: every field it sets is the game's to change.

hexAt

function hexAt(x: number, y: number, size: number, orientation: HexOrientation = 'pointy'): HexCell

The cell under a point, for hexes of circumradius size: the inverse of hexPoint, rounded to the nearest cell.

hexDistance

function hexDistance(a: HexCell, b: HexCell): number

Steps between two cells.

hexFromKey

function hexFromKey(key: string): HexCell

The cell a key names.

hexKey

const hexKey = (q: number, r: number): string=> …

The text key a cell is stored under: "q,r".

hexNeighbours

function hexNeighbours(q: number, r: number): HexCell[]

The six neighbours of a cell, in HEX_DIRECTIONS order (whether or not they are on a board).

hexPoint

function hexPoint(q: number, r: number, size: number, orientation: HexOrientation = 'pointy'): { x: number; y: number }

The centre of a cell in world units for hexes of circumradius size, with the cell 0,0 at the origin.

playHexa

function playHexa(config: HexaSortConfig, options: { maxMoves?: number } = {}): HexaPlay

Play a level with hexaHint until it is won or lost, or maxMoves (default 2000) stacks are placed.

rectangleCells

function rectangleCells(width: number, height: number): HexCell[]

A rectangular board width × height (1 to 20 each) for a pointy layout, every other row shifted half a cell right (odd rows), in axial coordinates.

Classes

HexaSort

class HexaSort

A hexa sort level in play: place dealt stacks of hex tiles on the board; touching stacks with the same top colour gather those tiles onto one stack, chain on as new tops are uncovered, and a top run of clearAt tiles clears. Turn based and seeded, so a placement resolves at once into a list of events to animate. Get one from createHexaSort.

  • constructor(config: HexaSortConfig)
  • get config(): HexaSortConfig — A copy of the config.
  • get state(): HexaSortState — A copy of the state: everything a save needs.
  • get status(): HexaSortState['status'] — 'playing', 'won' or 'lost'.
  • get hand(): (string[] | null)[] — The hand: a copy of each slot's stack, bottom first, or null for a used slot.
  • get cleared(): number — Tiles cleared.
  • get score(): number — Points.
  • get toGo(): number | null — Tiles still to clear for the goal, or null when endless.
  • stackAt(q: number, r: number): string[] | null | undefined — The stack on a cell (bottom first), null when the cell is empty, undefined when it is not on the board.
  • lockedAt(q: number, r: number): number — Tiles cleared before a shut cell opens, or 0 when it is open or not on the board.
  • openCells(): HexCell[] — The open, empty cells a stack could go on now.
  • refusal(slot: number, q: number, r: number): HexaRefusal | null — Why placing hand slot slot on q,r would be refused, or null if it would be accepted.
  • place(slot: number, q: number, r: number): HexaResult — Place hand slot slot on cell q,r and resolve every merge and clear it sets off.
  • preview(slot: number, q: number, r: number): { events: HexaEvent[]; state: HexaSortState } | null — What place would do, without doing it: the events and the state after. Null when it would be refused.
  • hammer(q: number, r: number): HexaResult — Booster: take the stack off a cell, tiles and all.
  • reroll(): HexaResult — Booster: deal a whole new hand.
  • continueRun(count = 3): HexaResult — After a loss: take the count tallest stacks off the board (1 to 20, default 3) and play on.
  • restore(state: HexaSortState): void — Put back a saved state of this level. Throws on one that does not fit.

Types

HexaEvent

type HexaEvent = | { type: 'placed'; cell: string; slot: number; tiles: string[] } | { type: 'moved'; from: string; to: string; color: string; count: number } | { type: 'cleared'; cell: string; color: string; count: number; chain: number; points: number } | { type: 'unlocked'; cell: string } | { type: 'dealt'; slots: number[] } | { type: 'removed'; cell: string; tiles: string[] } | { type: 'won' } | { type: 'lost' }

What a placement or booster did, in order. Cells are keys ("q,r").

HexaHint

interface HexaHint

A suggested placement, from hexaHint.

  • slot: number — Hand slot.
  • q: number — Cell column.
  • r: number — Cell row.
  • clears: number — Tiles it would clear.
  • moves: number — Tiles it would move between stacks.

HexaLevelOptions

interface HexaLevelOptions

Options for hexaSortLevel.

  • colors: string[] — The colours to draw from, in the order levels add them; at least 3.
  • shape?: 'hexagon' | 'rectangle' — Board shape; default 'hexagon'.
  • seed?: number — Seed for the level's own choices (locked cells, starting stacks, deals); default the level number.

HexaLockedCell

interface HexaLockedCell extends HexCell

A cell that opens once enough tiles are cleared.

  • unlockAt: number — Tiles cleared before it opens, 1 to 100000.

HexaPlay

interface HexaPlay

How a played level ended, from playHexa.

  • won: boolean — Whether the goal was met.
  • moves: number — Stacks placed.
  • cleared: number — Tiles cleared.
  • score: number — Points.

HexaRefusal

type HexaRefusal = 'not-playing' | 'bad-slot' | 'empty-slot' | 'off-board' | 'locked' | 'occupied' | 'empty-cell' | 'not-lost' | 'bad-count'

Why an action was refused.

HexaResult

type HexaResult = { valid: true; reason?: undefined; events: HexaEvent[] } | { valid: false; reason: HexaRefusal; events: [] }

The answer to an action: done with its events, or refused with a reason and nothing changed.

HexaSortConfig

interface HexaSortConfig

A hexa sort level.

  • cells: HexCell[] — The board's cells, 1 to 400, distinct. hexagonCells and rectangleCells make the usual shapes.
  • locked?: HexaLockedCell[] — Cells that stay shut until enough tiles are cleared. Each must be one of cells.
  • stacks?: HexaStartStack[] — Stacks on the board at the start, on distinct open cells.
  • colors: string[] — The colours deals draw from, CSS colour strings, 1 to 12, distinct.
  • schedule?: { at: number; colors: number }[] — How many of colors deals use, rising with tiles cleared: { at, colors } steps; default all from the start.
  • hand?: number — Stacks offered at once, 1 to 5; default 3.
  • refill?: 'all' | 'each' — 'all' (default): a new hand once every stack is placed. 'each': a placed stack is replaced at once.
  • height?: [number, number] — Tiles in a dealt stack, [min, max] from 1 to 20; default [2, 6].
  • runs?: [number, number] — Colour runs in a dealt stack, [min, max] from 1 to 6; default [1, 3].
  • clearAt?: number — A top run of one colour this long clears, 2 to 40; default 10.
  • target?: HexaTarget — Which stack gathers matching tiles; default 'most' (the one with the longest top run, the placed one on a tie).
  • goal?: number — Tiles to clear to win, 0 to 100000; default 0, endless.
  • seed?: number — Seed for the deals, 0 to 4294967295; default 1.

HexaSortState

interface HexaSortState

Everything that changes in play; a save holds exactly this.

  • stacks: Record<string, string[]> — Stacks on the board by cell key ("q,r"), bottom first.
  • hand: (string[] | null)[] — The hand: a stack per slot, bottom first, or null for a used slot.
  • locked: Record<string, number> — Cells still shut, by key, with the tiles cleared they wait for.
  • cleared: number — Tiles cleared.
  • score: number — Points: each clear scores its tiles times its place in the placement's chain of clears.
  • moves: number — Stacks placed.
  • bestChain: number — Longest chain of clears from one placement.
  • status: 'playing' | 'won' | 'lost' — 'playing', 'won' (the goal is met) or 'lost' (no open cell for the next stack).
  • rng: number[] — The deals' random stream.

HexaStartStack

interface HexaStartStack extends HexCell

A stack already on the board when the level starts.

  • tiles: string[] — Its tiles, bottom first, each a colour; 1 to 40.

HexaTarget

type HexaTarget = 'most' | 'placed'

Which stack gathers the tiles when stacks with the same top colour touch.

HexCell

interface HexCell

A hex cell in axial coordinates: q across, r down the diagonal rows.

  • q: number — Axial column.
  • r: number — Axial row.

HexOrientation

type HexOrientation = 'pointy' | 'flat'

How hexes are drawn: 'pointy' (a point at the top, rows run across) or 'flat' (a flat top, columns run down).