Phaser AE

Dig and trap

Pro packv3.0.0-32DArcade

Holes dug in a level's floors, and everything that happens around them: enemies falling in and being held, climbing out, being crushed when the hole refills or knocked through to the floors below. Use the mechanic on its own in your own platformer, or as a complete game with levels, scoring and two classic-style presets.

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

Dig and trap: 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

digTrap

function digTrap(): Pack<{ digTrap: typeof api }>

Install the renderer-independent dig and trap API.

Helpers

classicLevels

function classicLevels(): ClassicLevel[]

The bundled classic levels with their original numbers.

createDigTrapGame

function createDigTrapGame(config: DigTrapGameConfig): DigTrapGame

Create a dig-and-trap game from a definition (a preset or your own).

createHoles

function createHoles(o: HolesOptions): Holes

Create the hole mechanic over a walker world.

digTrapDifficulty

function digTrapDifficulty(name: "easy" | "normal" | "arcade"): DigTrapDifficulty

Named difficulty presets.

LODE_RUNNER_TICKS

const LODE_RUNNER_TICKS: { readonly across: 5; readonly vertical: number; readonly dig: 11; readonly digLimit: 8; readonly open: 166; readonly closing: readonly [8, 8, 4]; readonly shake: 66; readonly reborn: 8; readonly guardMoves: readonly [0, 3, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]; }

Lode Runner timing, in game ticks, from Lode Runner Total Recall (AI version 4, which its author tuned to match the Apple II): the runner moves 8/40 of a tile across and 9/44 of a tile up or down per tick; a dig takes 11 ticks and a guard over the site cancels it during the first 8; a hole stays open 166 ticks and refills over 8 + 8 + 4; a trapped guard shakes for 51 + 5 x 3 ticks before climbing out; a reborn guard appears after 8 ticks. Guards share a fixed number of moves per 6 ticks: 3 for one guard, then 6, 8, 10, 12 ... (2 more for each guard).

lodeRunnerGuard

function lodeRunnerGuard(tile = 8, tickRate = 23): DigTrapKind

The Lode Runner guard kind: the runner's moves at the shared guard pace, the 1983 pursuit decision.

lodeRunnerLegend

function lodeRunnerLegend(): Record<string, LegendEntry>

The classic Lode Runner legend: ' ' '#' '@' 'H' '-' 'X' 'S' '$' '0' '&'.

lodeRunnerLevels

function lodeRunnerLevels(): DigTrapLevel[]

The bundled Apple II levels (1, 2, 3, 4, 5, 6, 10 and 20) as dig-trap levels.

lodeRunnerPreset

function lodeRunnerPreset(o: LodeRunnerOptions = {}): DigTrapGameConfig

Lode Runner (Broderbund, 1983): dig left or right into the brick beside and below (not under a ladder, bar, chest or guard), holes refill and crush, guards fall in and drop their gold, climb out, die in refilled holes and are reborn on the second row, runners walk over guards' heads, gold 250, guard trapped 75, killed 75, level 1500, five lives plus one per level, the hidden ladder appears when every chest is taken and the level ends at the top row.

lodeRunnerProfiles

function lodeRunnerProfiles(tile = 8, tickRate = 23): { runner: WalkerProfile; guard: WalkerProfile }

The runner and guard profiles at a tick rate: grid-aligned moves of one tile per 5 ticks across, 4.9 up and down.

parseLodeRunnerLevel

function parseLodeRunnerLevel(text: string | readonly string[], o: { width?: number; number?: number } = {}): DigTrapLevel

Import a level in the classic Lode Runner text format: one 448-character string (28 x 16, row by row, as Total Recall stores it), a newline-separated block, or an array of rows. '.' is read as empty space. Rows of any equal width and any count are accepted (custom sizes); short rows are padded with spaces. Throws on characters outside the format or a missing runner (a second runner is ignored, as in the original).

spacePanicKinds

function spacePanicKinds(tile = 8): Record<string, DigTrapKind>

The alien kinds: Monster (red, one floor), Boss (green, two), Don (white, three); chasing over floors and ladders with wandering spells.

spacePanicLegend

function spacePanicLegend(): Record<string, LegendEntry>

The Space Panic legend: '%' diggable girder (one-way floor), 'h' ladder through a girder, 'H' ladder, '#' ground, 'P' player, 'E' alien entry.

spacePanicLevels

function spacePanicLevels(screen: string[] = spacePanicScreen()): DigTrapLevel[]

The level table: aliens released per level and their speed (later levels add Bosses and Dons and run faster).

spacePanicPreset

function spacePanicPreset(o: SpacePanicOptions = {}): DigTrapGameConfig

Space Panic (Universal, 1980): girder floors joined by ladders, no jump; dig a hole with several strokes, aliens that fall in are held head-down in the girder, hit them through it (three hits) and they fall; a Monster dies after one floor, a Boss after two, a Don after three, scoring more for longer drops (Monster 100-800, Boss 300-1,200, Don 800-2,000 as the operator's manual lists), and one alien landing on another kills both. Left too long, an alien climbs out, fills its hole and turns into the next kind. Falling into a hole or touching an alien costs a life, and so does running out of oxygen; the oxygen left at the end of a level scores 10 a second. Three lives, an extra life at 3,000.

spacePanicScreen

function spacePanicScreen(): string[]

The Space Panic screen: 24 x 30 cells of 8 px (the 192 x 256 portrait screen less the score line), five girder floors over the ground joined by staggered ladders, aliens entering at the top. Redrawn from screenshots.

validateDigTrapGame

function validateDigTrapGame(c: DigTrapGameConfig): void

Check a definition; throws a clear error for anything unsupported.

validateHoleRules

function validateHoleRules(r: HoleRules): void

Validate hole rules; throws a clear error for an unsupported configuration.

Types

ClassicLevel

interface ClassicLevel

A classic level: its number in the 150-level set and its rows.

  • number: number — Level number in the Apple II set.
  • rows: string[] — 16 rows of 28 characters.

DifficultyInput

type DifficultyInput = "easy" | "normal" | "arcade" | DigTrapDifficulty

Difficulty by name or explicit values.

DigTrapDifficulty

interface DigTrapDifficulty

The difficulty knobs every preset scales. "arcade" values are all 1 (the original tables).

  • speed: number — Enemy speed multiplier.
  • hold: number — Multiplier on how long prisoners stay in holes before climbing out.
  • open: number — Multiplier on how long holes stay open before closing.
  • oxygen: number — Multiplier on the oxygen supply.
  • maxEnemies: number — Most enemies on screen at once (applied after the level's own count); Infinity for no cap.
  • promote: boolean — Escaped prisoners are promoted to their kind's promote.

DigTrapEnemyPhase

type DigTrapEnemyPhase = "active" | "trapped" | "climbing" | "knocked" | "reborn"

What an enemy is doing, for renderers.

DigTrapEnemyView

interface DigTrapEnemyView

An enemy as the rules see it.

  • id: string — Body id (stable for its life; a promotion makes a new one).
  • kind: string — Kind.
  • phase: DigTrapEnemyPhase — Phase.
  • carrying: boolean — Carrying an item.
  • timer: number — Seconds left in the hole before climbing out (trapped; -1 when prisoners never climb out), or before reappearing (reborn).

DigTrapEvent

type DigTrapEvent = | { kind: "level-start"; level: number; number: number } | { kind: "item"; by: string; col: number; row: number; points: number } | { kind: "item-dropped"; by: string; col: number; row: number } | { kind: "item-lost"; by: string } | { kind: "revealed"; cells: number } | { kind: "dug"; col: number; row: number } | { kind: "stroke"; col: number; row: number; strokes: number; of: number } | { kind: "filled"; col: number; row: number; cause: string } | { kind: "trapped"; id: string; col: number; row: number; points: number } | { kind: "escaped"; id: string; promotedTo: string } | { kind: "hit"; id: string; hits: number; of: number } | { kind: "knocked"; id: string; col: number; row: number } | { kind: "enemy-killed"; id: string; enemy: string; cause: "crushed" | "knocked" | "together"; floors: number; points: number; x: number; y: number } | { kind: "enemy-survived"; id: string; floors: number; needed: number } | { kind: "enemy-released"; id: string; enemy: string } | { kind: "respawned"; id: string; col: number; row: number } | { kind: "player-died"; cause: "enemy" | "crushed" | "fell-in" | "oxygen" | "suicide" | "fall" | "out-of-bounds"; x: number; y: number } | { kind: "level-complete"; level: number; points: number; bonus: number } | { kind: "extra-life"; lives: number } | { kind: "game-over"; score: number }

Events from drain().

DigTrapGame

interface DigTrapGame

A running dig-and-trap game.

  • readonly config: DigTrapGameConfig — The definition (difficulty applied).
  • readonly map: PlatformMap — The current level's map.
  • readonly world: WalkerWorld — The current walker world.
  • readonly enemies: EnemyDirector — The current enemy director.
  • readonly holes: Holes — The current hole mechanic.
  • readonly player: string — Player body id ('player').
  • readonly state: DigTrapState — A copy of the state.
  • readonly build: number — Increments whenever the level is rebuilt (renderers snap every sprite).
  • step(input: DigTrapInput): void — One fixed step.
  • tick(seconds: number, input: DigTrapInput): number — Run whole fixed steps for elapsed seconds (the remainder carries to the next call; at most 8 steps of backlog); returns the number run. A dig or fill press is held until the next step runs and applies to that step only.
  • newGame(level?: number): void — Start a new game (optionally at a level index).
  • suicide(): void — Lose the life in play (Lode Runner's abort key).
  • kindOf(id: string): string — Kind of an enemy body id, or ''.
  • drain(): DigTrapEvent[] — Events since the last drain.
  • snapshot(): DigTrapSnapshot — Serialisable state.
  • restore(s: DigTrapSnapshot): void — Restore a snapshot taken from this game.

DigTrapGameConfig

interface DigTrapGameConfig

A complete dig-and-trap game definition.

  • name: string — Free name ('lode-runner', 'space-panic', ...).
  • tile: number — Tile size in world units.
  • legend: Readonly<Record<string, LegendEntry>> — Map legend. Markers: player, enemy, item. Tags the rules read: reveal (hidden until every item is taken).
  • bounds?: PlatformMapDef["bounds"] — Map bounds (default sides and bottom solid, top open).
  • levels: DigTrapLevel[] — The levels, played in order.
  • loopTo?: number — Level index play continues from after the last level. Default 0.
  • player: WalkerProfile — The player's movement profile.
  • digMode: "sides" | "facing" — How the player digs: 'sides' (two dig buttons, left and right: Lode Runner) or 'facing' (one button digs ahead: Space Panic).
  • kinds: Record<string, DigTrapKind> — Enemy kinds by name.
  • markerKind: string — Kind placed at enemy markers when a level lists no enemies.
  • maxEnemies: number — Most enemies a level starts with (extra markers are ignored, as Lode Runner caps its guards).
  • pace?: number[] — Enemy pace by enemy count: entry n is the speed multiplier with n enemies on the level (the last repeats). Lode Runner shares a fixed number of guard moves among its guards, so each slows as more are added. Default [1].
  • release?: { initial: number; every: number } — Release schedule: enemies at the start, then one every every seconds at the enemy markers in turn. Default: all at once.
  • holes: HoleRules — Hole rules (blockers are added by the game: items block a dig beneath them).
  • objective: "collect" | "clear" — The objective: 'collect' every item then reach exitRow (Lode Runner), or 'clear' every enemy (Space Panic).
  • exitRow?: number — Row the player must reach once every item is taken ('collect'). Default 0.
  • enemyCarry: [number, number] | null — Enemies pick up items they pass and carry them for a random number of cells in [min, max] before dropping them. null: they ignore items.
  • respawn: { row: number; delay: number } | null — Dead enemies respawn: in a random free cell of row (scanning down when a row is full) after delay seconds; null: they stay dead.
  • contact: { x: number; y: number; trappedHarmless: boolean } — Contact kills the player when centres are within these fractions of a tile on both axes. trappedHarmless: prisoners never kill.
  • scoring: { /** Item taken. */ item: number; /** Enemy trapped in a hole. */ trapped: number; /** Enemy crushed by a closing hole. */ crushed: number; /** Level completed. */ level: number; /** Points per second of oxygen left at a level's end. */ oxygenBonus: number; } — Points.
  • lives: number — Starting lives.
  • lifePerLevel: number — Lives gained per completed level.
  • extraLives: number[] — Scores that each award one extra life.
  • oxygen: number | null — Oxygen seconds per level (null: no timer). Running out costs a life.
  • step?: number — Fixed step, seconds. Default 1/60.
  • timing: { ready: number; dying: number; complete: number } — Phase lengths, seconds: before play, after a death, after a level.
  • difficulty: DigTrapDifficulty — Difficulty.
  • startLevel?: number — Level to start on (index). Default 0.
  • seed: number — Seed.

DigTrapInput

interface DigTrapInput

What the player does this step.

  • x: number — -1 left .. 1 right.
  • y: number — -1 up .. 1 down.
  • dig?: number | boolean — Dig: -1 left, 1 right ('sides'), or true / any non-zero for ahead ('facing').
  • fill?: number | boolean — Fill (Space Panic): -1 / 1 / true like dig.

DigTrapKind

interface DigTrapKind

An enemy kind in a dig-and-trap game: a platform-enemies kind plus how holes and falls treat it.

  • enemy: EnemyKind — The enemy (profile and movement) for platform-enemies.
  • floors?: number — Floors a knocked body must fall through in one drop to die (Space Panic: Monster 1, Boss 2, Don 3). Default 1.
  • knockScore?: number[] — Points for a knock kill by floors fallen: entry floors - 1, the last entry repeating. Default [0].
  • promote?: string — Kind it turns into after climbing out of a hole ('' or absent: none).

DigTrapLevel

interface DigTrapLevel

One authored level.

  • rows: string[] — Rows through the game's legend (top first).
  • number?: number — Number shown for the level (default index + 1).
  • enemies?: { kind: string; count: number }[] — Enemies released over time, by kind (default: one markerKind at each enemy marker).
  • speed?: number — Enemy speed multiplier for this level (default 1).
  • oxygen?: number — Oxygen seconds for this level (default the game's).
  • hold?: number — Multiplier on how long prisoners stay in holes (default 1).

DigTrapSnapshot

interface DigTrapSnapshot

Serialisable game snapshot.

  • state: DigTrapState — Rules state.
  • map: PlatformMapSnapshot — Map.
  • world: WalkerWorldSnapshot — Walker world.
  • enemies: EnemiesSnapshot — Enemies.
  • holes: HolesSnapshot — Holes.
  • extra: { carry: [string, number][]; last: [string, string][]; reborn: [string, number][]; release: number; rng: RngState; marker: number; events: DigTrapEvent[]; items: string[]; acc: number } — Private: carry counters, last cells, reborn timers, release timer, rng.

DigTrapState

interface DigTrapState

Serialisable state, for renderers and snapshots.

  • status: DigTrapStatus — Status.
  • timer: number — Seconds in the status.
  • level: number — Level index.
  • levelNumber: number — Level number shown.
  • score: number — Score.
  • lives: number — Lives left (including the one in play).
  • items: { col: number; row: number }[] — Items lying on the map.
  • itemsLeft: number — Items not yet taken (lying or carried).
  • exitOpen: boolean — Hidden cells revealed and the exit open ('collect').
  • oxygen: number | null — Oxygen seconds left (null without a timer).
  • oxygenMax: number | null — Oxygen at the level's start.
  • enemies: DigTrapEnemyView[] — Enemies on the level.
  • pending: string[] — Enemies still to be released.
  • killed: number — Enemies killed this level.
  • awarded: number — Extra-life thresholds already awarded.
  • cause: string — Why the last life was lost ('' none).

DigTrapStatus

type DigTrapStatus = "ready" | "playing" | "dying" | "complete" | "over"

Game status.

Hole

interface Hole

One hole (a dug or partly dug cell).

  • col: number — Column.
  • row: number — Row of the dug cell.
  • state: HoleState — Stage.
  • strokes: number — Dig strokes completed (equals rules.strokes once open).
  • fill: number — Fill strokes applied to an empty open hole.
  • hits: number — Hits applied to the prisoner.
  • timer: number — Seconds in the current stage.
  • stage: number — Closing stage index (state 'closing').
  • occupant: string — Body id held in the hole, or ''.
  • digger: string — Body id digging it now, or ''.

HoleEvent

type HoleEvent = | { kind: "stroke"; digger: string; col: number; row: number; strokes: number; of: number } | { kind: "dug"; digger: string; col: number; row: number } | { kind: "dig-cancelled"; digger: string; col: number; row: number; by: string } | { kind: "filled"; by: string; col: number; row: number; cause: "hand" | "closed" | "escape" | "knock" } | { kind: "closing"; col: number; row: number; stage: number } | { kind: "trapped"; id: string; col: number; row: number } | { kind: "climbing"; id: string; col: number; row: number } | { kind: "escaped"; id: string; col: number; row: number } | { kind: "crushed"; id: string; col: number; row: number } | { kind: "fell-in"; id: string; col: number; row: number } | { kind: "hit"; by: string; id: string; col: number; row: number; hits: number; of: number } | { kind: "knocked"; by: string; id: string; col: number; row: number } | { kind: "landed"; id: string; floors: number; col: number; fromRow: number; row: number; on: string }

Events from drain().

HoleRules

interface HoleRules

How holes are dug, how long they last, what they hold and what closing them does.

  • strokes: number — Dig strokes needed to open a hole (Lode Runner 1, Space Panic several).
  • strokeTime: number — Seconds one stroke keeps the digger busy (intents ignored).
  • interruptBefore: number — Fraction of a stroke (0..1) before which a body arriving over the dig site cancels the stroke (Lode Runner 8/11). 0: never.
  • open: number | null — Seconds a fully open hole stays before it starts closing; null: it stays until filled by hand or by an escaping prisoner.
  • closing: number[] — Closing stages after open, seconds each; the hole refills at the end of the last.
  • fillStrokes: number — Strokes needed to fill an empty hole by hand (each also removes a stroke from a partly dug hole). 0: holes cannot be filled by hand.
  • knockStrokes: number — Strokes (dig or fill, see knockWith) on an occupied hole that knock the prisoner through the floor. 0: never.
  • knockWith: "dig" | "fill" | "either" — Which strokes count as hits on an occupied hole.
  • hold: number | null — Seconds a trapped body stays before climbing out. null: it never climbs out.
  • climbSpeed: number — Climb-out speed, world units per second (up, then one cell sideways).
  • escapeFills: boolean — The hole refills as soon as an escaping prisoner has left it (Space Panic).
  • sink: "bottom" | "head" — Where a prisoner is pinned: 'bottom' (feet on the hole's floor, Lode Runner) or 'head' (head level with the floor's top, so others walk over it, Space Panic).
  • trapTags: string[] — Bodies with any of these tags are trapped (held) by holes.
  • deadlyTags: string[] — Bodies with any of these tags are reported fell-in when they drop into an open hole (Space Panic's spaceman).
  • heads: "none" | "trapped" | "all" — Which trappable bodies others may stand on: none, trapped prisoners only, or all of them (Lode Runner: runners walk on guards' heads).
  • blockers?: (col: number, row: number) => boolean — Cells above a dig site must be free of these (in addition to solid cells, ladders and bars).

Holes

interface Holes extends WalkerController, SurfaceProvider

The hole mechanic: a walker controller and surface provider.

  • readonly id: string — Controller / provider id.
  • readonly rules: HoleRules — The rules (live; edit with setRules).
  • holes(): Hole[] — Copies of the holes.
  • hole(col: number, row: number): Hole | null — The hole at a cell, or null.
  • prisoners(): Prisoner[] — Copies of the prisoners.
  • prisoner(id: string): Prisoner | null — A body's prisoner record, or null.
  • busy(id: string): number — Whether a body is busy with a stroke; seconds left.
  • canDig(id: string, side: -1 | 1): { col: number; row: number; ok: boolean; reason: string } — Which cell a dig from this body toward side would hit, and whether it may: 'ok' or a reason.
  • dig(id: string, side: -1 | 1): boolean — One dig stroke beside-and-below toward side (or a hit on an occupied hole). Returns whether a stroke started.
  • fill(id: string, side: -1 | 1): boolean — One fill stroke toward side (fills an empty hole, un-digs a partial one, or hits a prisoner).
  • open(col: number, row: number, by?: string): boolean — Open a hole at once (scripted levels, tests).
  • close(col: number, row: number, cause?: "hand" | "closed" | "escape" | "knock"): boolean — Close a hole at once (crushing what is inside).
  • release(id: string): void — Release a body from any hold (for a game that removes or respawns it).
  • setTargets(ids: string[]): void — Set the bodies prisoners climb out toward.
  • setRules(patch: Partial<HoleRules>): void — Patch the rules (difficulty changes).
  • drain(): HoleEvent[] — Events since the last drain.
  • snapshot(): HolesSnapshot — Serialisable state.
  • restore(s: HolesSnapshot): void — Restore (restore the map and world first).

HolesOptions

interface HolesOptions

Options for createHoles.

  • world: WalkerWorld — The walker world (its map is dug).
  • rules: HoleRules — Rules.
  • enemies?: EnemyDirector — Enemy director: trapped enemies are suspended and resumed. Optional.
  • targets?: string[] — Ids of bodies prisoners climb out toward (the player).
  • attach?: boolean — Register as a walker controller and surface provider. Default true.
  • id?: string — Controller and provider id. Default 'dig-trap'.

HolesSnapshot

interface HolesSnapshot

Serialisable state.

  • holes: Hole[] — Holes.
  • prisoners: Prisoner[] — Prisoners.
  • busy: { id: string; left: number; total: number; col: number; row: number; kind: "dig" | "fill" }[] — Busy diggers: id, seconds left, total, hole key, kind.
  • pinned: [string, boolean][] — Pinned bodies' original flight setting.
  • events: HoleEvent[] — Undrained events.
  • targets: string[] — Targets.

HoleState

type HoleState = "digging" | "open" | "closing"

A hole's life stage.

LodeRunnerOptions

interface LodeRunnerOptions

Options for lodeRunnerPreset.

  • difficulty?: DifficultyInput — Difficulty (default 'normal'); 'arcade' is the original timing.
  • levels?: DigTrapLevel[] — Levels (default the bundled Apple II levels). Use parseLodeRunnerLevel to import more.
  • startLevel?: number — Level index to start on.
  • lives?: number — Starting lives (default 5).
  • maxGuards?: number — Most guards on a level (default 5; Total Recall's cap, extra guard markers are ignored).
  • tickRate?: number — Game ticks per second (default 23, Total Recall's NORMAL speed; 18 SLOW, 29 FAST).
  • tile?: number — Tile size in world units (default 8).
  • holes?: Partial<HoleRules> — Hole overrides (open time, closing, hold ...), in seconds.
  • seed?: number — Seed (guard respawn columns, gold-carry lengths).

Prisoner

interface Prisoner

A trapped, climbing or knocked body.

  • id: string — Body id.
  • col: number — Hole column.
  • row: number — Hole row.
  • phase: "held" | "up" | "side" | "knocked" — 'held' (in the hole), 'up' / 'side' (climbing out), 'knocked' (falling through the floor).
  • timer: number — Seconds in the phase.
  • dir: -1 | 1 — Climb-out direction.

SpacePanicOptions

interface SpacePanicOptions

Options for spacePanicPreset.

  • difficulty?: DifficultyInput — Difficulty (default 'normal'); 'arcade' is the reference timing.
  • levels?: DigTrapLevel[] — Levels (default spacePanicLevels()).
  • startLevel?: number — Level index to start on.
  • lives?: number — Starting lives (default 3; the cabinet's DIP switch offers 3 or 4).
  • extraLife?: number — Extra life score (default 3000; DIP 3000 or 5000).
  • oxygen?: number — Oxygen seconds per level (default 90).
  • tile?: number — Tile size (default 8).
  • holes?: Partial<HoleRules> — Hole overrides.
  • seed?: number — Seed.