Go back to the main README
When creating mazes for use in games you will frequently want to have big, empty rooms included within the maze. The DungeonRooms algorithm was included for just this purpose. It is a simple variation on the classic Hunt-and-Kill algorithm, but it accepts information about open rooms you want included in the maze.
To open up rooms in a maze, DungeonRooms accepts two optional input parameters:
- rooms: List(List(tuple, tuple))
- A list of lists, containing the top-left and bottom-right corners of the rooms you want to create. For best results, the corners of each room shouldhave odd-numbered coordinates.
- grid: 2D NumPy array, of booleans, all set to
True
- A pre-built maze array filled with one, or many, rooms.
Let's do an example of each method for defining input rooms:
Here we create a 24x33 maze with one rectangular 4x4 room, open between the corners (3, 3) and (7, 7):
m = Maze()
m.generator = DungeonRooms(24, 33, rooms=[[(3,3), (7,7)]])
m.generate()
Here we create a 4x4 maze with one rectangular 2x2 room, open between the corners (5, 5) and (7, 7):
import numpy as np
g = np.ones((9, 9), dtype=np.bool_)
g[5] = array('b', [1,1,1,0,0,0,1])
a[6] = array('b', [1,1,1,0,0,0,1])
g[7] = array('b', [1,1,1,0,0,0,1])
m = Maze()
m.generator = DungeonRooms(4, 4, grid=g)
m.generate()
Perhaps you want more control over your maze. You have ideas in you imagine spiral mazes, or circular mazes with a room at the very center. The Perturbation algorithm will allow you to do all of these things.
First, start with a simple spiral maze (which is trivial to solve):
import numpy as np
g = np.ones((11, 11), dtype=np.bool_)
g[1] = array('b', [1,0,0,0,0,0,0,0,0,0,1])
g[2] = array('b', [1,1,1,1,1,1,1,1,1,0,1])
g[3] = array('b', [1,0,0,0,0,0,0,0,1,0,1])
g[4] = array('b', [1,0,1,1,1,1,1,0,1,0,1])
g[5] = array('b', [1,0,1,0,0,0,1,0,1,0,1])
g[6] = array('b', [1,0,1,0,1,1,1,0,1,0,1])
g[7] = array('b', [1,0,1,0,0,0,0,0,1,0,1])
g[8] = array('b', [1,0,1,1,1,1,1,1,1,0,1])
g[9] = array('b', [1,0,0,0,0,0,0,0,0,0,1])
m = Maze()
m.generator = Perturbation(grid=g, repeat=1, new_walls=3)
m.generate()
m.start = (1, 0)
m.end = (5, 5)
The end result is a maze that is almost a spiral, but enough different to still make a decent maze.
WORK IN PROGRESS
For the rest of this section, let us assume we have already generated a maze:
from mazelib import *
m = Maze()
m.generator = Prims(27, 34)
m.generate()
If you want a low-key, fast way to view the maze you've generated, just use the library's built-in tostring
method:
print(m.tostring()) # print walls only
print(m.tostring(True)) # print walls and entrances
print(m.tostring(True, True)) # print walls, entrances, and solution
print(str(m)) # print everything that is available
The above print
calls would generate these types of plots, respectively:
########### ########### ###########
# # # # S # # # S*# # #
# ### # # # # ### # # # #*### # # #
# # # # # # # # #*# # #
# ### ##### # ### ##### #*### #####
# # # # #*** #
### ####### ### ####### ###*#######
# E # E # *******E
########### ########### ###########
Sometimes it is hard to see the finer points of a maze in plain text. You may want to see at a glance if the maze has unreachable sections, if it has loops or free walls, if it is obviously too easy, etcetera.
import matplotlib.pyplot as plt
def showPNG(grid):
"""Generate a simple image of the maze."""
plt.figure(figsize=(10, 5))
plt.imshow(grid, cmap=plt.cm.binary, interpolation='nearest')
plt.xticks([]), plt.yticks([])
plt.show()
CSS and HTML are universal and easy to use. Here is a simple example to display your maze in CSS and HTML:
def toHTML(grid, start, end, cell_size=10):
row_max = grid.height
col_max = grid.width
html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' + \
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' + \
'<html xmlns="http://www.w3.org/1999/xhtml"><head>' + \
'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />' + \
'<style type="text/css" media="screen">' + \
'#maze {width: ' + str(cell_size * col_max) + 'px;height: ' + \
str(cell_size * row_max) + 'px;border: 3px solid grey;}' + \
'div.maze_row div{width: ' + str(cell_size) + 'px;height: ' + str(cell_size) + 'px;}' + \
'div.maze_row div.bl{background-color: black;}' + \
'div.maze_row div.wh{background-color: white;}' + \
'div.maze_row div.rd{background-color: red;}' + \
'div.maze_row div.gr{background-color: green;}' + \
'div.maze_row div{float: left;}' + \
'div.maze_row:after{content: ".";height: 0;visibility: hidden;display: block;clear: both;}' + \
'</style></head><body>' + \
'<div id="maze">'
for row in xrange(row_max):
html += '<div class="maze_row">'
for col in xrange(col_max):
if (row, col) == start:
html += '<div class="gr"></div>'
elif (row, col) == end:
html += '<div class="rd"></div>'
elif grid[row][col]:
html += '<div class="bl"></div>'
else:
html += '<div class="wh"></div>'
html += '</div>'
html += '</div></body></html>'
return html
Chances are, if you're reading this you probably like XKCD. So, let's make the maze look like Randal drew it.
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import matplotlib.pyplot as plt
def plotXKCD(grid):
""" Generate an XKCD-styled line-drawn image of the maze. """
H = len(grid)
W = len(grid[0])
h = (H - 1) // 2
w = (W - 1) // 2
with plt.xkcd():
fig = plt.figure()
ax = fig.add_subplot(111)
vertices = []
codes = []
# loop over horizontals
for r,rr in enumerate(xrange(1, H, 2)):
run = []
for c,cc in enumerate(xrange(1, W, 2)):
if grid[rr-1,cc]:
if not run:
run = [(r,c)]
run += [(r,c+1)]
else:
use_run(codes, vertices, run)
run = []
use_run(codes, vertices, run)
# grab bottom side of last row
run = []
for c,cc in enumerate(xrange(1, W, 2)):
if grid[H-1,cc]:
if not run:
run = [(H//2,c)]
run += [(H//2,c+1)]
else:
use_run(codes, vertices, run)
run = []
use_run(codes, vertices, run)
# loop over verticles
for c,cc in enumerate(xrange(1, W, 2)):
run = []
for r,rr in enumerate(xrange(1, H, 2)):
if grid[rr,cc-1]:
if not run:
run = [(r,c)]
run += [(r+1,c)]
else:
use_run(codes, vertices, run)
run = []
use_run(codes, vertices, run)
# grab far right column
run = []
for r,rr in enumerate(xrange(1, H, 2)):
if grid[rr,W-1]:
if not run:
run = [(r,W//2)]
run += [(r+1,W//2)]
else:
use_run(codes, vertices, run)
run = []
use_run(codes, vertices, run)
vertices = np.array(vertices, float)
path = Path(vertices, codes)
# for a line maze
pathpatch = PathPatch(path, facecolor='None', edgecolor='black', lw=2)
ax.add_patch(pathpatch)
# hide axis and labels
ax.axis('off')
#ax.set_title('XKCD Maze')
ax.dataLim.update_from_data_xy([(-0.1,-0.1), (h + 0.1, w + 0.1)])
ax.autoscale_view()
plt.show()
def use_run(codes, vertices, run):
"""Helper method for plotXKCD. Updates path with newest run."""
if run:
codes += [Path.MOVETO] + [Path.LINETO] * (len(run) - 1)
vertices += run
plotXKCD(m.grid)