Phaser AE

Maze room shooter

Pro packv3.0.0-12DArcade

A room-to-room maze shooter: lethal walls, robots that hunt and shoot along eight lines, an exit that closes behind you, and a pursuer who cannot be destroyed and arrives sooner the faster you kill. The 1980 arcade game is a preset, and a second, different game ships beside it.

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

Released October 19, 2026 · updated October 20, 2026

Maze room shooter: 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

mazeRooms

function mazeRooms(): Pack<{ mazeRooms: MazeRoomsApi }>

Install the renderer-independent maze-rooms rules (Berzerk). Projectiles are flown by projectile-patterns.

Helpers

berzerkLevels

function berzerkLevels(): RobotLevel[]

The RC31 robot escalation table (MAME driver notes, from The Cutting Room Floor).

berzerkMaze

function berzerkMaze(x: number, y: number, pillars = 8, multiplier = 7, increment = 0x3153): { arms: Arm[]; seed: number }

The original maze for room (x, y): pillar arms in row-major order. With pillars = 8 this is Berzerk's room; any count works. Returns the arms and the RNG seed after generation (robot placement continues it).

berzerkPreset

function berzerkPreset(o: BerzerkPresetOptions = {}): MazeRoomsConfig

Berzerk (Stern, 1980; RC31 rules) as a maze-rooms definition.

createMazeRooms

function createMazeRooms(config: MazeRoomsConfig): MazeRooms

Create a maze-rooms game.

mazeHuntPreset

function mazeHuntPreset(o: MazeHuntPresetOptions = {}): MazeRoomsConfig

A materially different maze-rooms game: a 7x4 grid of smaller cells with hashed mazes, only left and right exits, one robot per cell that may spawn, a gentler escalation table (more but slower bolts, starting armed), faster robots from the first room, a faster pursuer that spares robots, held-fire auto-repeat and extra lives every 3000 points up to four.

roomDifficulty

function roomDifficulty(name: "easy" | "normal" | "arcade"): RoomDifficulty

Named difficulty settings for maze rooms.

validateMazeRooms

function validateMazeRooms(c: MazeRoomsConfig): void

Throw a descriptive error for an unsupported configuration.

Classes

MazeRooms

class MazeRooms

The maze-rooms rules owner. Create with createMazeRooms.

  • readonly config: MazeRoomsConfig — The definition.
  • readonly projectiles: Projectiles — The projectile owner that flies every bolt.
  • readonly difficulty: RoomDifficulty — The difficulty in force.
  • constructor(config: MazeRoomsConfig)
  • get state(): MazeRoomsState — A read-only copy of the state.
  • get bounds(): { left: number; top: number; right: number; bottom: number } — Room box: the outer wall lines' extents.
  • boltPosition(id: number): { x: number; y: number; vx: number; vy: number } | null — Bolt projectile position by id.
  • levelFor(score: number): RobotLevel — The robot level for a score.
  • armsFor(x: number, y: number): Arm[] — Pillar arms for any room (from the authored table or the generator).
  • reset(): void — New game.
  • tick(seconds: number, input: MazeRoomsInput = {}): void — Advance by real seconds in whole frames (at most 8 per call).
  • step(input: MazeRoomsInput = {}): void — One frame.
  • drain(): MazeRoomsEvent[] — Drain events.
  • snapshot() — Serializable snapshot.
  • restore(snap: ReturnType<MazeRooms["snapshot"]>): void — Restore a snapshot from the same configuration.

Types

Arm

type Arm = "N" | "S" | "E" | "W" | "."

A pillar's wall arm: north, south, east, west, or none (authored rooms only).

BerzerkPresetOptions

interface BerzerkPresetOptions

Options for berzerkPreset.

  • lives?: number — Starting lives. Original: 3.
  • extraLives?: number[] — Extra lives (DIP): [5000] (default), [5000, 10000] or [].
  • otto?: boolean — Pursuer on.
  • startRoom?: { x: number; y: number } — Start room coordinates. Default: the original cold start (x 0x53, y 0x31).
  • difficulty?: "easy" | "normal" | "arcade" | RoomDifficulty — Difficulty: "arcade" (default, the original), "normal", "easy", or explicit values.
  • seed?: number — Seed.

MazeGenerator

type MazeGenerator = /** The original: seed = Y*256 + X, seed = seed*7 + 0x3153 twice per pillar, arm = high byte & 3 (N,S,E,W). */ | "berzerk" /** A well-mixed hash of seed and coordinates (every layout reachable, no repeats every four rooms). */ | "hashed"

Maze generation.

MazeHuntPresetOptions

interface MazeHuntPresetOptions

Options for mazeHuntPreset.

  • cols?: number — Cells across (default 7).
  • rows?: number — Cells down (default 4).
  • lives?: number — Starting lives (default 4).
  • seed?: number — Seed.

MazeRoomsApi

type MazeRoomsApi = typeof rooms & { boxHitsRect: typeof boxHitsRect; dir8: typeof dir8; lcg16: typeof lcg16; overlaps: typeof overlaps }

game.mazeRooms: the maze-rooms rules owner, its presets and helpers.

MazeRoomsConfig

interface MazeRoomsConfig

The complete maze-rooms definition. berzerkPreset supplies the 1980 values.

  • fps: number — Frames per second. Original: 60.
  • room: { /** Cells across. Original: 5. */ cols: number; /** Cells down. Original: 3. */ rows: number; /** Cell width. Original: 48. */ cellW: number; /** Cell height. Original: 68. */ cellH: number; /** Wall thickness. Original: 4. */ wall: number; /** Left edge of the first wall line. */ left: number; /** Top edge of the first wall line. */ top: number; /** Exit sides that exist (all four in the original). */ exits: RoomSide[]; /** Close the exit the player came in by. Original: true. */ closeEntry: boolean; } — Room geometry: a cols x rows grid of cells; wall lines are wall thick.
  • maze: { /** Generator. */ generator: MazeGenerator; /** Authored rooms by "x,y": one letter per pillar in row-major order (N, S, E, W, or . for none). */ authored?: Record<string, string>; /** LCG multiplier and increment for the berzerk generator. Original: 7, 0x3153. */ multiplier: number; /** LCG increment. */ increment: number; } — Maze arm generation.
  • startRoom: { x: number; y: number } — Starting room coordinates (0..255 each). The cold-start room of the original is (0x53, 0x31).
  • worldSize: number — Room coordinates wrap at this size. Original: 256.
  • player: { /** Hit box. */ w: number; /** Hit box. */ h: number; /** Vector ticks per pixel step. Original: 2. */ tprime: number; /** Starting lives. Original: 3. */ lives: number; /** Extra-life scores (each awarded once). Original DIP options: [5000], [5000, 10000] or []. */ extraLives: number[]; /** Most player bolts on screen. */ bolts: number; /** Player bolt speed (px per frame). */ boltSpeed: number; /** Frames between held-fire shots in the same direction (0 = press or re-aim to fire again). */ autoFire: number; /** Distance from the entry wall at which the player appears. */ entryInset: number; } — The player.
  • robot: { /** Hit box. */ w: number; /** Hit box. */ h: number; /** Points per robot, however it dies. Original: 50. */ score: number; /** Room bonus per robot when all are destroyed. Original: 10. */ bonusPerRobot: number; /** Starting vector ticks per step; one faster per room entered, down to \minSpeed`. Original: 5 → 1. / speed: number; /* Fastest. Original: 1. / minSpeed: number; /* * Spawn slots (top-left corners) and the jitter added to each (0..jitter-1 px). "cells" uses one slot per * cell, away from the entry cell. Original: eleven slots with 32 px of jitter. / slots: { x: number; y: number }[] | "cells"; /* Jitter range in pixels. / jitter: number; /* Chance each slot spawns a robot, cycling with every room entered (the original's BCD density counter). / chances: number[]; /* Score-based escalation (the RC31 table). / levels: RobotLevel[]; /* Slow and fast bolt speeds (px per frame). / boltSpeed: number; /* Fast bolt speed. / fastBoltSpeed: number; /* Least wake delay on room entry, in frames. Original: 30. / minWake: number; /* Frames a robot stands after shooting. Original: 10. / shootPause: number; /* Alignment tolerances for shooting (px): horizontal, vertical, diagonal. */ aim: { horizontal: number; vertical: number; diagonal: number }; }` — Robots.
  • otto: { /** Enabled. */ enabled: boolean; /** Hit box. */ w: number; /** Hit box. */ h: number; /** Vector ticks per step. Original: 2. */ tprime: number; /** Appearance delay unit in frames. Original: 40. */ unit: number; /** Extra units per robot destroyed. Original: 2. */ perKill: number; /** Bounce heights (px) cycled per step. */ bounce: number[]; /** Kills robots it touches. */ killsRobots: boolean; } — The pursuer (Evil Otto).
  • deathFrames: number — Frames of the death sequence.
  • exitFrames: number — Frames of the room-change transition.
  • vectorRate: number — Vector updates per frame. Original: 1 (the bottom-of-screen interrupt).
  • difficulty?: RoomDifficulty — Difficulty shaping (default: the original, as roomDifficulty("arcade")).
  • seed?: number — Seed for the non-ROM random choices and the time-advanced room RNG.

MazeRoomsEvent

type MazeRoomsEvent = | { kind: "room-enter"; x: number; y: number; robots: number; entry: RoomSide | "" } | { kind: "robot-killed"; id: string; x: number; y: number; by: "player" | "robot" | "wall" | "collision" | "otto"; score: number } | { kind: "room-bonus"; score: number } | { kind: "fired"; owner: string; x: number; y: number } | { kind: "otto"; x: number; y: number } | { kind: "player-killed"; x: number; y: number; by: "wall" | "robot" | "bolt" | "otto" } | { kind: "room-exit"; side: RoomSide; chicken: boolean } | { kind: "extra-life"; lives: number } | { kind: "game-over"; score: number }

Events for presenters.

MazeRoomsInput

interface MazeRoomsInput

Input for one frame.

  • move?: Dir8 — Eight-way stick.
  • fire?: boolean — Fire button held: the player stands and shoots along the stick (or facing).

MazeRoomsState

interface MazeRoomsState

The whole serialisable state.

  • status: RoomStatus — Phase.
  • statusFrames: number — Frames in this phase.
  • frame: number — Total frames.
  • roomX: number — Room coordinates.
  • roomY: number — Room coordinates.
  • rooms: number — Rooms entered this game.
  • entry: RoomSide | "" — Side the player entered by ("" at a game start or after a death).
  • exit: RoomSide | "" — Side the player left by.
  • arms: Arm[] — Pillar arms in row-major order.
  • walls: WallRect[] — Wall rectangles of the current room, door included.
  • score: number — Score.
  • lives: number — Lives including the one in play.
  • extrasAwarded: number — Extra lives awarded.
  • player: { x: number; y: number; alive: boolean; facing: Dir8; moving: boolean; shooting: number; time: number; anim: number; fireWas: boolean; aimWas: Dir8; held: number } — The player.
  • robots: RoomRobot[] — Robots.
  • robotsAtStart: number — Robots in the room at entry (bonus basis).
  • level: RobotLevel — The level in force for this room.
  • robotSpeed: number — Robot speed (vector ticks per step) for this room.
  • fireCooldown: number — Robot shot cooldown (frames).
  • bolts: RoomBolt[] — Bolts.
  • otto: { active: boolean; x: number; y: number; bounce: number; countdown: number; unitFrames: number; time: number; startX: number; startY: number } — The pursuer.
  • rr: number — Round-robin pointer over robots + pursuer.
  • bonus: number — Bonus paid in this room (0 = not yet).
  • lcg: number — The original's 16-bit room RNG.
  • random: number[] — Seeded random state (non-ROM choices): four words of the kernel's createRng state, advanced in place.
  • nextId: number — Next id.

RobotLevel

interface RobotLevel

One robot level, chosen by score when a room is entered.

  • from: number — Applies from this score.
  • colour: string — Presentation colour.
  • bolts: number — Most robot bolts on screen at once (0 = robots never fire).
  • fast: boolean — Fast bolts.
  • delay: number — Frames before robots wake on entering a room, and frames between robot shots.

RoomBolt

interface RoomBolt

A bolt with its owner (projectile position lives in projectiles).

  • id: number — Projectile id.
  • owner: string — "player" or a robot id.
  • dir: Dir8 — Direction.
  • fast: boolean — Fast robot bolt.

RoomDifficulty

interface RoomDifficulty

Difficulty shaping over the robot table. roomDifficulty("arcade") is all-neutral and plays the original exactly; every other value is a departure a consumer opts into.

  • population: number — Multiplier on each room's robot spawn chance (at most 1 after scaling). Arcade: 1.
  • fire: number — Robot fire-rate scale: the level's delay between robot shots is divided by it. Arcade: 1.
  • boltSpeed: number — Robot bolt speed scale. Arcade: 1.
  • wake: number — Extra frames before robots wake on entering a room. Arcade: 0.

RoomRobot

interface RoomRobot

A robot.

  • id: string — Unique id.
  • x: number — Centre.
  • y: number — Centre.
  • dir: Dir8 — Current step direction.
  • facing: Dir8 — Facing for drawing (last non-zero direction or shot direction).
  • wake: number — Frames until it wakes.
  • pause: number — Frames it stands after a shot.
  • time: number — Vector countdown.
  • anim: number — Steps taken (animation).

RoomSide

type RoomSide = "left" | "right" | "top" | "bottom"

A side of a room.

RoomStatus

type RoomStatus = "playing" | "dying" | "exiting" | "game-over"

Game phase.