0% found this document useful (0 votes)
38 views11 pages

Buckblog - Maze Generation - Prim's Algorithm

The document discusses the implementation of Prim's algorithm for generating random mazes, detailing the process of growing a maze from a starting point by selecting random edges that connect to the frontier. It provides a step-by-step explanation of the algorithm, including code snippets in Ruby for managing the maze's grid and frontier cells. The author encourages readers to experiment with the algorithm and shares insights on the characteristics of mazes generated by this method.

Uploaded by

ahmedtheusual
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views11 pages

Buckblog - Maze Generation - Prim's Algorithm

The document discusses the implementation of Prim's algorithm for generating random mazes, detailing the process of growing a maze from a starting point by selecting random edges that connect to the frontier. It provides a step-by-step explanation of the algorithm, including code snippets in Ruby for managing the maze's grid and frontier cells. The author encourages readers to experiment with the algorithm and shares insights on the characteristics of mazes generated by this method.

Uploaded by

ahmedtheusual
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

The Buckblog

assorted ramblings by Jamis Buck

Home | RSS | Archives | Basil & Fabian


Announcements | Essays and Rants | Life | Metablog | Odds &
Ends | Projects | Redirect | Reviews | Spotlight | Tips & Tricks |
Under the Hood | Mazes for Programmers!

The maze book for


Maze Generation: Prim's programmers!
Algorithm mazesforprogrammers.com
Algorithms, circle
10 January 2011 — A method for generating mazes, hex grids,
masking, weaving,
random mazes using a simplified version of
braiding, 3D and 4D
Prim's algorithm — 5-minute read grids, spheres, and
more!
My last post was about using Kruskal’s algorithm DRM-Free Ebook
to generate random mazes. This article is about
using another minimal spanning tree algorithm to
do the same: Prim’s algorithm.

If you recall, the random variant of Kruskal’s algorithm worked by randomly


selecting edges from the graph, and adding them to the maze if they
connected disjoint trees. Visually, this had the effect of growing the maze
from many random points across the grid.

Prim’s approaches the problem from a different angle. Rather than working
edgewise across the entire graph, it starts at one point, and grows outward
from that point. The standard version of the algorithm works something like
this:

1. Choose an arbitrary vertex from G (the graph), and add it to some


(initially empty) set V.
2. Choose the edge with the smallest weight from G, that connects a
vertex in V with another vertex not in V.
3. Add that edge to the minimal spanning tree, and the edge’s other
vertex to V.
4. Repeat steps 2 and 3 until V includes every vertex in G.

And the result is a minimal spanning tree of G. Straightforward enough!

With one little change, it becomes a suitable method for generating mazes:
just change step 2 so that instead of selecting the edge with the smallest
weight, you select an edge at random, as long as it bridges the so-called
“frontier” of the maze (the set of edges that move from within the maze, to a
cell that is outside the maze).

As before, let’s walk through an example and see how it works in practice.

An example
Let’s start with a simple 3×3 grid:

Now, we choose a point at random and add it to the maze:

For efficiency, let’s call the set of all cells that are not yet in the maze, but
are adjacent to a cell that is in the maze, the “frontier”. We’ll color the
frontier cells red:
Now, we choose one of these frontier cells at random and carve a passage
from that frontier cell to whichever adjacent cell is already part of the maze.
Then we’ll mark the neighbors of the formerly frontier cell, as “frontier” cells
themselves.

Rinse and repeat:

Now, here’s an interesting bit. Look what happens if we (ahem, “randomly”)


choose the cell at (1,0) (the top middle). It is adjacent to two cells that are
already “in” the maze. The algorithm resolves this by simply choosing one of
the “in” neighbors at random. Prim’s doesn’t care which neighbor is picked,
only that each frontier cell eventually be connected to a cell already within
the maze.

Let’s just keep it going to the end, now, chug chug chug:
The algorithm terminates when there are no more frontier cells to choose
from, giving us the anticipated perfect maze.

Implementation
The largest bit of implementing Prim’s algorithm (for me) seems to go
toward managing the interactions with that frontier set. Maybe your
experience will be different. You basically need two operations: mark a cell
as “in” (which then marks the “out” neighbors as frontier cells), and one that
returns all the “in” neighbors of a given frontier cell. Something like this:
(and please excuse the apparent hand-waving around the add_frontier
method; it’s not complicated, just not entirely relevant. The full
implementation is given at the end of the article.)

1 def mark(x, y, grid, frontier)


2 grid[y][x] |= IN
3 add_frontier(x-1, y, grid, frontier)
4 add_frontier(x+1, y, grid, frontier)
5 add_frontier(x, y-1, grid, frontier)
6 add_frontier(x, y+1, grid, frontier)
7 end
8
9 def neighbors(x, y, grid)
10 n = []
11
12 n << [x-1, y] if x > 0 && grid[y][x-1] & IN != 0
13 n << [x+1, y] if x+1 < grid[y].length && grid[y][x+1] & IN != 0
14 n << [x, y-1] if y > 0 && grid[y-1][x] & IN != 0
15 n << [x, y+1] if y+1 < grid.length && grid[y+1][x] & IN != 0
16
17 n
18 end

Once you’ve got that implemented (along with the necessary supporting
methods and data structures), the algorithm itself is remarkably
straightforward.

You start by marking a random cell:

1 mark(rand(width), rand(height), grid, frontier)


Then, you simply iterate until the frontier set is empty:

1 until frontier.empty?
2 # ...
3 end

Within the loop, we choose a frontier cell at random:

1 x, y = frontier.delete_at(rand(frontier.length))

Then we choose a random “in” neighbor of that frontier cell:

1 n = neighbors(x, y, grid)
2 nx, ny = n[rand(n.length)]

Then, we record a passage from the neighbor cell to the frontier cell:

1 dir = direction(x, y, nx, ny)


2 grid[y][x] |= dir
3 grid[ny][nx] |= OPPOSITE[dir]

And finally, we mark the frontier cell as being “in” the maze (and add any of
its outside neighbors to the frontier):

1 mark(x, y, grid, frontier)

And you’re done! For those of you not using IE (which will make a total
mess of this), here are two demos you can play with to see Prim’s in action:
Reset Step Run Reset Step Run

My complete implementation (in Ruby) is here:

1 # -------------------------------------------------------------------
2 # An implementation of Prim's algorithm for generating mazes.
3 # This is a pretty fast algorithm, when implemented well, since it
4 # only needs random access to the list of frontier cells. It does
5 # require space proportional to the size of the maze, but even worse-
6 # case, it won't be but a fraction of the size of the maze itself.
7 # As with Kruskal's, this algorithm tends to generate mazes with many
8 # short cul-de-sacs.
9 # -------------------------------------------------------------------
10 # NOTE: the display routine used in this script requires a terminal
11 # that supports ANSI escape sequences. Windows users, sorry. :(
12 # -------------------------------------------------------------------
13
14 # -------------------------------------------------------------------
15 # 1. Allow the maze to be customized via command-line parameters
16 # -------------------------------------------------------------------
17
18 width = (ARGV[0] || 10).to_i
19 height = (ARGV[1] || width).to_i
20 seed = (ARGV[2] || rand(0xFFFF_FFFF)).to_i
21
22 srand(seed)
23
24 # -------------------------------------------------------------------
25 # 2. Set up constants to aid with describing the passage directions
26 # -------------------------------------------------------------------
27
28 N, S, E, W = 1, 2, 4, 8
29 IN = 0x10
30 FRONTIER = 0x20
31 OPPOSITE = { E => W, W => E, N => S, S => N }
32
33 # -------------------------------------------------------------------
34 # 3. Data structures and methods to assist the algorithm
35 # -------------------------------------------------------------------
36
37 grid = Array.new(height) { Array.new(width, 0) }
38 frontier = []
39
40 def add_frontier(x, y, grid, frontier)
41 if x >= 0 && y >= 0 && y < grid.length && x < grid[y].length && grid
42 grid[y][x] |= FRONTIER
43 frontier << [x,y]
44 end
45 end
46
47 def mark(x, y, grid, frontier)
48 grid[y][x] |= IN
49 add_frontier(x-1, y, grid, frontier)
50 add_frontier(x+1, y, grid, frontier)
51 add_frontier(x, y-1, grid, frontier)
52 add_frontier(x, y+1, grid, frontier)
53 end
54
55 def neighbors(x, y, grid)
56 n = []
57
58 n << [x-1, y] if x > 0 && grid[y][x-1] & IN != 0
59 n << [x+1, y] if x+1 < grid[y].length && grid[y][x+1] & IN != 0
60 n << [x, y-1] if y > 0 && grid[y-1][x] & IN != 0
61 n << [x, y+1] if y+1 < grid.length && grid[y+1][x] & IN != 0
62
63 n
64 end
65
66 def direction(fx, fy, tx, ty)
67 return E if fx < tx
68 return W if fx > tx
69 return S if fy < ty
70 return N if fy > ty
71 end
72
73 # -------------------------------------------------------------------
74 # 4. Routines for displaying the maze
75 # -------------------------------------------------------------------
76
77 def empty?(cell)
78 cell == 0 || cell == FRONTIER
79 end
80
81 def display_maze(grid)
82 print "\e[H" # move to upper-left
83 puts " " + "_" * (grid[0].length * 2 - 1)
84 grid.each_with_index do |row, y|
85 print "|"
86 row.each_with_index do |cell, x|
87 print "\e[41m" if cell == FRONTIER
88 if empty?(cell) && y+1 < grid.length && empty?(grid[y+1][x])
89 print " "
90 else
91 print((cell & S != 0) ? " " : "_")
92 end
93 print "\e[m" if cell == FRONTIER
94
95 if empty?(cell) && x+1 < row.length && empty?(row[x+1])
96 print((y+1 < grid.length && (empty?(grid[y+1][x]) || empty?
97 elsif cell & E != 0
98 print(((cell | row[x+1]) & S != 0) ? " " : "_")
99 else
100 print "|"
101 end
102 end
103 puts
104 end
105 end
106
107 # -------------------------------------------------------------------
108 # 5. Prim's algorithm
109 # -------------------------------------------------------------------
110
111 print "\e[2J" # clear the screen
112
113 mark(rand(width), rand(height), grid, frontier)
114 until frontier.empty?
115 x, y = frontier.delete_at(rand(frontier.length))
116 n = neighbors(x, y, grid)
117 nx, ny = n[rand(n.length)]
118
119 dir = direction(x, y, nx, ny)
120 grid[y][x] |= dir
121 grid[ny][nx] |= OPPOSITE[dir]
122
123 mark(x, y, grid, frontier)
124
125 display_maze(grid)
126 sleep 0.01
127 end
128
129 display_maze(grid)
130
131 # -------------------------------------------------------------------
132 # 6. Show the parameters used to build this maze, for repeatability
133 # -------------------------------------------------------------------
134
135 puts "#{$0} #{width} #{height} #{seed}"
prims.rb hosted with by GitHub view raw

Conclusion
Mazes generated by Prim’s algorithm share many of the characteristics of
those created via Kruskal’s algorithm, such as having an abundance of
short cul-de-sacs. Such an aesthetic appeals to some, and not to others,
but it definitely has this to say for it: for large mazes, the short cul-de-sacs
make the maze harder to puzzle out at a glance!

Whether you enjoy the look of these mazes or not, I encourage you to try
your hand at Prim’s algorithm. It’s a fun algorithm to code, not least of all
because it comes together so easily. Personally, I enjoy watching the
animation: it puts me in mind of a flame consuming a sheet of paper.

Please do share what you come up with. Have fun!

← Maze Generation: Maze Generation: Recursive


Posted in
Kruskal's Algorithm Division →
Under the Hood
(3 Jan 2011) (12 Jan 2011)

Reader Comments

Really liked your javascript implementation! Any gist to that?

Lucas Prim
12 Jan 2011

Thanks, @Lucas! The sources are actually on github:


http://github.com/jamis/csmazes. You can get a sneak peek there of all
the algorithms I intend to cover on my blog, since I’ve got them all
implemented there. :)

Jamis
12 Jan 2011

This actually runs just fine on IE8. (Our network blocks connections
from other programs.)
Paul
27 Jan 2011

These articles lovingly written and prepared for your reading pleasure by Jamis Buck
<[email protected]>. Follow me on Twitter and stalk my code at GitHub.

This work is licensed under a Creative Commons Attribution-NonCommercial-


ShareAlike 4.0 International License.

You might also like