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 arewallthick.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, asroomDifficulty("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'screateRngstate, 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.


