yog/generator/maze

Maze generation algorithms for creating perfect mazes.

Perfect mazes are spanning trees on a grid where every cell connects to every other cell via exactly one path (no loops, no isolated areas).

Based on “Mazes for Programmers” by Jamis Buck.

Quick Start

// Generate a maze using the Recursive Backtracker algorithm
let maze = maze.recursive_backtracker(20, 20, seed: Some(42))
// Use ascii renderer
grid_to_string(maze) |> io.println

Algorithms

AlgorithmSpeedBiasBest For
binary_treeO(N)DiagonalSimplest, fastest
sidewinderO(N)VerticalMemory constrained
recursive_backtrackerO(N)CorridorsGames, roguelikes
hunt_and_killO(V²)WindingFew dead ends
aldous_broderO(V²)NoneUniform randomness
wilsonO(V) avgNoneEfficient uniform
kruskalO(N log N)NoneBalanced corridors
prim_simplifiedO(N log N)RadialMany dead ends
prim_trueO(N log N)JigsawDense texture
ellersO(N)HorizontalInfinite height mazes
growing_treeO(N)VariesVersatility
recursive_divisionO(N log N)RectangularRooms, fractal feel

Output Format

All algorithms return a yog/builder/grid.Grid that can be:

References

Types

Direction bias for the Binary Tree algorithm.

pub type BinaryTreeBias {
  NorthEast
  NorthWest
  SouthEast
  SouthWest
}

Constructors

  • NorthEast
  • NorthWest
  • SouthEast
  • SouthWest

Selector strategy for the Growing Tree algorithm.

pub type GrowingTreeSelector {
  Last
  First
  Random
  Middle
  Mix(GrowingTreeSelector, Float)
}

Constructors

Scan mode for the Hunt-and-Kill algorithm.

pub type HuntScanMode {
  ScanSequential
  ScanRandom
}

Constructors

  • ScanSequential
  • ScanRandom

Direction for the Sidewinder algorithm to carve.

pub type SidewinderDirection {
  North
  South
  East
  West
}

Constructors

  • North
  • South
  • East
  • West

Values

pub fn aldous_broder(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Aldous-Broder algorithm.

Produces a truly uniform spanning tree using a random walk.

Characteristics

  • Time: O(N²) worst case
  • Space: O(1) auxiliary
  • Bias: None
  • Texture: Completely unbiased, uniform corridors

When to Use

  • When mathematical lack of bias is required
  • Small grids

When NOT to Use

  • Large grids (highly inefficient)
pub fn binary_tree(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Binary Tree algorithm.

The simplest maze algorithm. For each cell, randomly carves a passage to either the north or east neighbor (or other chosen bias). Creates an unbroken corridor along two boundaries.

Characteristics

  • Time: O(N) where N = rows × cols
  • Space: O(1) auxiliary
  • Bias: Strong diagonal (NE by default)
  • Texture: Distinctive diagonal corridors

When to Use

  • When speed is critical
  • For educational demonstrations
  • When predictable texture is acceptable

When NOT to Use

  • When uniform randomness is required
  • When aesthetic variety matters

Examples

let m = maze.binary_tree(5, 5, seed: Some(42))
// m.rows == 5
pub fn binary_tree_with_options(
  rows: Int,
  cols: Int,
  bias: BinaryTreeBias,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Binary Tree algorithm with custom bias.

pub fn ellers(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using Eller’s algorithm.

A row-by-row algorithm that uses constant memory relative to width.

Characteristics

  • Time: O(N)
  • Space: O(cols)
  • Bias: Horizontal
  • Texture: Balanced, consistent corridors

When to Use

  • Generating mazes of infinite/very large height
  • Memory-critical environments
pub fn growing_tree(
  rows: Int,
  cols: Int,
  selector: GrowingTreeSelector,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Growing Tree algorithm.

A versatile algorithm that can simulate others depending on selection strategy.

Characteristics

  • Time: O(N)

  • Space: O(N) for active list

  • Bias: Varies by strategy

  • Texture: Varies by strategy

  • Last: Simulates Recursive Backtracker

  • First: Simulates very long, straight corridors

  • Random: Simulates Simplified Prim’s

  • Middle: Unique hybrid texture

pub fn hunt_and_kill(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Hunt-and-Kill algorithm.

Performs a random walk until stuck, then hunts for an unvisited cell adjacent to the visited region.

Characteristics

  • Time: O(N²) worst case
  • Space: O(1) auxiliary
  • Bias: High winding, dense
  • Texture: Uniform but with fewer dead ends than others

When to Use

  • When you want a maze with few dead ends
  • When space is critical but time is less so

When NOT to Use

  • Very large grids (O(N²) can be slow)
pub fn hunt_and_kill_with_options(
  rows: Int,
  cols: Int,
  scan_mode: HuntScanMode,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Hunt-and-Kill algorithm with custom options.

pub fn kruskal(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using randomized Kruskal’s algorithm.

Produces a uniform spanning tree by randomly merging disjoint sets.

Characteristics

  • Time: O(N log N)
  • Space: O(N) for set tracking
  • Bias: None
  • Texture: Balanced corridors, uniform distribution

When to Use

  • When you want unbiased mazes on non-grid topologies
  • For a balanced roguelike feel
pub fn prim_simplified(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Simplified Prim’s algorithm.

Creates mazes with strong radial texture and many dead ends.

Characteristics

  • Time: O(N log N)
  • Space: O(N) for frontier list
  • Bias: Radial (spreads from start)
  • Texture: Jigsaw-like, very dense with short corridors

When to Use

  • When you want a dense maze with many branches
  • To create paths that feel “grown” from a source
pub fn prim_true(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using True Prim’s algorithm.

Uses random weights for each cell to produce a jigsaw-like texture.

Characteristics

  • Time: O(N log N)
  • Space: O(N) for weights and frontier
  • Bias: None
  • Texture: Jigsaw texture, balanced but dense
pub fn recursive_backtracker(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Recursive Backtracker algorithm (DFS).

Performs a random walk avoiding visited cells, backtracking when stuck. Creates twisty mazes with long corridors - the most popular algorithm for games.

Characteristics

  • Time: O(N) where N = rows × cols
  • Space: O(N) for the explicit stack
  • Bias: Twisty passages, long corridors
  • Texture: Classic “roguelike” maze aesthetic

When to Use

  • Games and roguelikes (most popular choice)
  • When you want twisty, exploratory mazes
  • Longest path puzzles

When NOT to Use

  • If memory is extremely limited (N can be large)

Examples

let m = maze.recursive_backtracker(20, 20, seed: Some(123))
// Produces twisty corridors
pub fn recursive_division(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Recursive Division algorithm.

Starts with a full grid and adds walls.

Characteristics

  • Time: O(N log N)
  • Space: O(log N) for recursion stack
  • Bias: Rectangular
  • Texture: Room-like, fractal aesthetic

When to Use

  • When you want a maze that feels “built”
  • Creating architectural layouts
pub fn sidewinder(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Sidewinder algorithm.

Row-based algorithm that creates vertical corridors.

Characteristics

  • Time: O(N) where N = rows × cols
  • Space: O(cols) - only tracks current run
  • Bias: Vertical corridors (north-south)
  • Texture: Long vertical passages with horizontal “rungs”

When to Use

  • When you want vertical maze progression
  • Memory-constrained environments
  • Creating “floor” separation in games

When NOT to Use

  • When you need horizontal bias
pub fn sidewinder_with_options(
  rows: Int,
  cols: Int,
  direction: SidewinderDirection,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using the Sidewinder algorithm with custom direction.

pub fn wilson(
  rows: Int,
  cols: Int,
  seed seed: option.Option(Int),
) -> grid.Grid(Nil, Int)

Generates a maze using Wilson’s algorithm.

Produces a uniform spanning tree using loop-erased random walks.

Characteristics

  • Time: O(N) average
  • Space: O(N) for current walk
  • Bias: None
  • Texture: Completely unbiased, elegant aesthetic

When to Use

  • When you want a truly unbiased maze efficiently
  • Generating perfect test data

When NOT to Use

  • When you want specific hallway or corridor textures
Search Document