Powered by AppSignal & Oban Pro

Gallery: Maze Showroom

livebooks/gallery/maze_gallery.livemd

Gallery: Maze Showroom

Mix.install([
  {:yog_ex, "~> 0.98"},
  {:kino_vizjs, "~> 0.8.0"}
])

Introduction

This gallery showcases the diverse textures, biases, and visual styles of all the maze generation algorithms supported by Yog.Generator.Maze.

Each section highlights a different generator, offering a clean look at the Unicode rendering, a pathfinding solution (from the top-left S to bottom-right E), and the underlying spanning tree network.

Set up

defmodule MazeGalleryHelper do
  def render_maze_tabs(maze_name, maze) do
    # 1. Unicode String
    unicode_string = Yog.Render.ASCII.grid_to_string_unicode(maze)

    # 2. Solved Maze
    graph = Yog.Builder.GridGraph.to_graph(maze)
    start_node = Yog.Builder.GridGraph.coord_to_id(maze, 0, 0)
    end_node = Yog.Builder.GridGraph.coord_to_id(maze, maze.rows - 1, maze.cols - 1)

    solved_string =
      case Yog.Pathfinding.Dijkstra.shortest_path(graph, start_node, end_node) do
        {:ok, path} ->
          occupants = 
            path.nodes 
            |> Map.new(fn id -> {id, "•"} end)
            |> Map.put(start_node, "S")
            |> Map.put(end_node, "E")

          Yog.Render.ASCII.grid_to_string_unicode(maze, occupants)
        :error ->
          "Error: Pathfinding failed."
      end

    # 3. Graphviz DOT
    # Use a smaller version for DOT visualization to keep it readable
    small_maze = apply(Yog.Generator.Maze, String.to_atom(Macro.underscore(maze_name)), [6, 6])
    dot_source = Yog.Render.DOT.to_dot(Yog.Builder.GridGraph.to_graph(small_maze))

    IO.puts("### #{maze_name} Maze (#{maze.rows}x#{maze.cols})")

    Kino.Layout.tabs([
      "Unicode Layout": Kino.Markdown.new("```\n" <> unicode_string <> "\n```"),
      "Solved Path": Kino.Markdown.new("```\n" <> solved_string <> "\n```"),
      "Graph Network (6x6)": Kino.VizJS.render(dot_source, height: "800px")
    ])
  end
end

Classic Maze Algorithms

These popular algorithms are frequently used in game design due to their balance of speed and maze complexity.

Binary Tree

The fastest algorithm. It carves north or east for each cell, resulting in a very strong diagonal bias.

maze = Yog.Generator.Maze.binary_tree(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("BinaryTree", maze)

Sidewinder

A row-based generator similar to Binary Tree but with less diagonal bias. It creates distinct vertical corridors.

maze = Yog.Generator.Maze.sidewinder(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("Sidewinder", maze)

Recursive Backtracker

A Depth-First Search walk that backtracks when stuck. It creates very winding, challenging paths with long corridors and few branches.

maze = Yog.Generator.Maze.recursive_backtracker(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("RecursiveBacktracker", maze)

Hunt and Kill

Similar to Recursive Backtracker but hunts sequentially across the grid for adjacent visited cells when stuck. It produces winding corridors with no recursion stack.

maze = Yog.Generator.Maze.hunt_and_kill(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("HuntAndKill", maze)

Unbiased Random Spanning Trees

These algorithms generate mathematically unbiased perfect mazes: all possible mazes are equally likely. They have no directional bias.

Wilson’s

Uses loop-erased random walks to build a perfect spanning tree. It creates beautifully balanced mazes with highly organic corridors.

maze = Yog.Generator.Maze.wilson(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("Wilson", maze)

Aldous-Broder

Performs a true random walk over the entire grid. It is slower but guarantees completely uniform randomness.

maze = Yog.Generator.Maze.aldous_broder(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("AldousBroder", maze)

Partition & Tree-based Algorithms

These algorithms treat maze generation as minimum spanning trees or geometric subdivisions.

Kruskal’s

Treats every cell as a separate set and shuffles all potential passages, merging them using a Disjoint Set Union (DSU) until one spanning tree remains.

maze = Yog.Generator.Maze.kruskal(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("Kruskal", maze)

Prim’s (Simplified)

Starts from a single cell and grows outwards by adding random adjacent frontier cells, producing high branch densities and many short dead ends.

maze = Yog.Generator.Maze.prim_simplified(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("PrimSimplified", maze)

Growing Tree

A highly customizable algorithm that can mimic either Prim’s (radial) or Recursive Backtracker (winding) depending on how it selects cells from its active list.

maze = Yog.Generator.Maze.growing_tree(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("GrowingTree", maze)

Recursive Division

A fractal-like algorithm that divides the chamber with walls, adding a single passage through the wall, and repeating the process recursively.

maze = Yog.Generator.Maze.recursive_division(15, 15, seed: 42)
MazeGalleryHelper.render_maze_tabs("RecursiveDivision", maze)