Clears the quadrille, then generates a perfect maze at the current dimensions, filling wall cells with value — the maze analogue of the zero-arg fill() chessboard. The generator is an iterative randomized depth-first search over the room lattice. Walls are filled cells and corridors are empty cells, so reach and path work on the result out of the box. Rooms sit at even (row, col) indices, cell (0, 0) is always open, and the maze is borderless: the quadrille edge bounds it. Perfect means any two empty cells are joined by exactly one path, so no start or end cells are needed. Returns the quadrille (chainable).

Example#

(click to regenerate; press any key to toggle fat ⇄ thin)

code
Quadrille.cellLength = 30;
let board;
let wall;
let thin = false;
const THIN = {
  colorDisplay: Quadrille.thinWall, imageDisplay: Quadrille.thinWall,
  stringDisplay: Quadrille.thinWall, numberDisplay: Quadrille.thinWall,
  tileDisplay: null, outlineWeight: 7
};

function setup() {
  createCanvas(11 * Quadrille.cellLength, 15 * Quadrille.cellLength);
  wall = color('#0b332b');
  board = createQuadrille(11, 15).maze(wall);
}

function draw() {
  background('#138a72');
  drawQuadrille(board, thin ? { ...THIN, outline: '#0b332b' } : { outlineWeight: 0.5 });
}

function mousePressed() {
  board.maze(wall); // fresh level, one call
}

function keyPressed() {
  key.length === 1 && (thin = !thin); // fat ⇄ thin: a param swap
}

Regeneration, restyling, and the fat ⇄ thin toggle never touch storage: walls stay whatever they are, and the thin look is a display swap at the draw site via Quadrille.thinWall — installed as every display with tileDisplay: null, styled by the draw call’s own outline and outlineWeight.

Odd dimensions are canonical. On even ones a warning is issued and the trailing even row/col — which holds no rooms — seals as a wall strip on that side.

value goes through fill internals, so the Quadrille.factory contract is honored: a plain function is stored as a display-contract wall drawing, whereas a factory-tagged one is evaluated per wall cell — e.g. board.maze(Quadrille.factory(() => random(['🧱', '🪨']))) yields varied wall tiles.

Recipes

  • Braiding: board.maze(wall).rand(k) clears k random filled cells, turning a perfect maze into a loopy one.
  • Level authoring: createQuadrille(w, h).maze(wall)toBigInt → ship the result as a const bitboard; decode via createQuadrille(w, h, LEVEL, wall) with the same dimensions and endianness: a mismatched decode shifts the pattern.
  • Pathfinding: reach and path treat any filled cell as an obstacle — no maze required.

Why depth-first and not Prim’s

Any spanning tree of the room lattice is a perfect maze, so the generator’s choice is one of texture, not of correctness. Measured over 400 seeds on an 11×15 board, classifying each room by how many of its four wall slots are carved:

generatordead endscorridorsjunctions
randomized depth-first search (this)13.6%77.1%9.4%
randomized Prim’s32.5%42.4%25.1%

Depth-first carries a single frontier and backtracks only when stuck, so it lays long winding corridors and few decisions. Prim’s grows from a randomly chosen frontier cell each step, so it spreads outward as a bushy tree — 2.4× the dead ends and barely half the pass-through corridors. Kruskal’s differs again; Wilson’s and Aldous–Broder sample spanning trees uniformly. Corridors read as a maze to a player where Prim’s reads as a blob with holes, which is why maze runs the depth-first one.

For deterministic (repeatable) mazes, explicitly call randomSeed(seed) before maze(value).

Syntax#

maze(value)

Parameters#

ParamDescription
valueAny: wall value — a literal (color, string, image, …), a display-contract function (stored as-is), or a Quadrille.factory-tagged function (evaluated per wall cell)