Python 四大迷宫生成算法实现(3): 递归分割算法
递归分割算法简介
先介绍下算法使用的地图,地图最外围默认是一圈墙,其中白色单元是迷宫单元,黑色单元是墙。示例地图的宽度和长度都为11。
- 第一个图用十字将地图分割成4个小矩阵,可以看到这个十字交点所在的位置(WALL_X, WALL_Y),在这个图中为(6,4),注意WALL_X, WALL_Y都必须是偶数。同时迷宫的长度和宽度必须为奇数。
- 第二图在4个小矩阵的相邻四条边上随机选择三条边来打通(每条边上去掉一个单元的墙),使得这4个小矩阵连通。
这是一个递归算法,重复调用算法直到矩阵的长度或宽度<=1:
1 算法调用参数是一个矩阵的位置(x, y, width, height)
2 判断矩阵是否不能继续分割(长度或宽度<=1)
- 如果能继续分割
- 随机选择十字交点的位置(WALL_X, WALL_Y), 将这条十字所在的单元都设为墙,分割成4个更小的矩阵.
- 十字所在的四条边中随机选择三条边来打通(每条边上去掉一个单元的墙)
- 对于4个小矩阵递归调用算法
- 否则,直接返回
关键代码介绍
保存基本信息的地图类
地图类用于保存和获取迷宫算法使用的基础地图信息。
- 先创建一个map类, 初始化参数设置地图的长度和宽度,并设置保存地图信息的二维数据map的值为0, 值为0表示该单元可以移动,值为1表示该单元是墙。定义在算法中用到的Enum 类MAP_ENTRY_TYPE 和 WALL_DIRECTION。
class MAP_ENTRY_TYPE(Enum):
MAP_EMPTY = 0,
MAP_BLOCK = 1,
class WALL_DIRECTION(Enum):
WALL_LEFT = 0,
WALL_UP = 1,
WALL_RIGHT = 2,
WALL_DOWN = 3,
class Map():
def __init__(self, width, height):
self.width = width
self.height = height
self.map = [[0 for x in range(self.width)] for y in range(self.height)]
- 在map类中添加将一个map单元设置为某个值的函数,判断某个单元是否是墙的函数,和是否是有效单元的函数。
def setMap(self, x, y, value):
if value == MAP_ENTRY_TYPE.MAP_EMPTY:
self.map[y][x] = 0
elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
self.map[y][x] = 1
def isMovable(self, x, y):
return self.map[y][x] != 1
def isValid(self, x, y):
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return False
return True
- 在map类中添加一个显示地图的函数,可以看到,这边只是简单的打印出所有节点的值,值为1时打印’#"表示强,或1的意思上面已经说明,在后面显示寻路算法结果时,会使用到值2,表示一条从开始节点到目标节点的路径。
def showMap(self):
for row in self.map:
s = ''
for entry in row:
if entry == 0:
s += ' 0'
elif entry == 1:
s += ' #'
else:
s += ' X'
print(s)
算法主函数介绍
doRecursiveDivision 函数 先调用setMap函数将地图最外围四条边都设置为墙。传入recursiveDivision 函数的参数是矩阵的位置(x, y, width, height),假如地图的宽度是31,长度是21,则传入参数为(1,1,29,19)。
def doRecursiveDivision(map):
# draw four margin wall lines
for x in range(0, map.width):
map.setMap(x, 0, MAP_ENTRY_TYPE.MAP_BLOCK)
map.setMap(x, map.height-1, MAP_ENTRY_TYPE.MAP_BLOCK)
for y in range(0, map.height):
map.setMap(0, y, MAP_ENTRY_TYPE.MAP_BLOCK)
map.setMap(map.width-1, y, MAP_ENTRY_TYPE.MAP_BLOCK)
recursiveDivision(map, 1, 1, map.width - 2, map.height - 2, MAP_ENTRY_TYPE.MAP_BLOCK)
recursiveDivision 函数就是上面算法主循环的实现。
# recursive division algorithm
def recursiveDivision(map, x, y, width, height, wall_value):
if width <= 1 or height <= 1:
return
#generate a row and a column wall index, they must be even number
wall_x, wall_y = (getWallIndex(x, width), getWallIndex(y, height))
#set horizontal and vertical lines to wall
for i in range(x, x+width):
map.setMap(i, wall_y, wall_value)
for i in range(y, y+height):
map.setMap(wall_x, i, wall_value)
#create three holes
generateHoles(map, x, y, width, height, wall_x, wall_y)
recursiveDivision(map, x, y, wall_x - x, wall_y - y, wall_value)
recursiveDivision(map, x, wall_y + 1, wall_x - x, y + height - wall_y -1, wall_value)
recursiveDivision(map, wall_x + 1, y, x + width - wall_x -1, wall_y - y, wall_value)
recursiveDivision(map, wall_x + 1, wall_y + 1, x + width - wall_x -1, y + height - wall_y -1, wall_value)
getWallIndex函数获取指定范围的随机偶数。
generateHoles函数,选择三条边打通,有个特别要注意的是,当外层的矩阵和十字墙相邻的单元是连通的情况下,比如下图所示的红色单元,这时候红色单元必须要打通,不然会导致迷宫不连通。
# start must be a odd number, wall_index must be a even number
def getWallIndex(start, length):
assert length >= 3
wall_index = randint(start + 1, start + length - 2)
if wall_index % 2 == 1:
wall_index -= 1
return wall_index
# must check adjacent entry of four margin entries,
# if adjacent entry is movable, must set the margin entry as the hole
def generateHoles(map, x, y, width, height, wall_x, wall_y):
holes = []
hole_entrys = [(randint(x, wall_x -1), wall_y), (randint(wall_x + 1, x + width -1), wall_y),
(wall_x, randint(y, wall_y -1)), (wall_x, randint(wall_y + 1, y + height - 1))]
margin_entrys = [(x, wall_y), (x+width-1, wall_y), (wall_x, y), (wall_x, y + height-1)]
adjacent_entrys = [(x-1, wall_y), (x+width, wall_y), (wall_x, y - 1), (wall_x, y + height)]
for i in range(4):
adj_x, adj_y = (adjacent_entrys[i][0], adjacent_entrys[i][1])
if map.isValid(adj_x, adj_y) and map.isMovable(adj_x, adj_y):
map.setMap(margin_entrys[i][0], margin_entrys[i][1], MAP_ENTRY_TYPE.MAP_EMPTY)
else:
holes.append(hole_entrys[i])
ignore_hole = randint(0, len(holes)-1)
for i in range(0, len(holes)):
if i != ignore_hole:
map.setMap(holes[i][0], holes[i][1], MAP_ENTRY_TYPE.MAP_EMPTY)
代码的初始化
可以调整地图的长度,宽度,注意长度和宽度必须为奇数。
def run():
WIDTH = 31
HEIGHT = 21
map = Map(WIDTH, HEIGHT)
doRecursiveDivision(map)
map.showMap()
if __name__ == "__main__":
run()
执行的效果图如下,迷宫中’#‘表示墙,空格’ '表示通道。
完整代码
使用python3.7编译
from random import randint, choice
from enum import Enum
class MAP_ENTRY_TYPE(Enum):
MAP_EMPTY = 0,
MAP_BLOCK = 1,
class WALL_DIRECTION(Enum):
WALL_LEFT = 0,
WALL_UP = 1,
WALL_RIGHT = 2,
WALL_DOWN = 3,
class Map():
def __init__(self, width, height):
self.width = width
self.height = height
self.map = [[0 for x in range(self.width)] for y in range(self.height)]
def setMap(self, x, y, value):
if value == MAP_ENTRY_TYPE.MAP_EMPTY:
self.map[y][x] = 0
elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
self.map[y][x] = 1
def isMovable(self, x, y):
return self.map[y][x] != 1
def isValid(self, x, y):
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return False
return True
def showMap(self):
for row in self.map:
s = ''
for entry in row:
if entry == 0:
s += ' '
elif entry == 1:
s += ' #'
else:
s += ' X'
print(s)
# recursive division algorithm
def recursiveDivision(map, x, y, width, height, wall_value):
# start must be a odd number, wall_index must be a even number
def getWallIndex(start, length):
assert length >= 3
wall_index = randint(start + 1, start + length - 2)
if wall_index % 2 == 1:
wall_index -= 1
return wall_index
# must check adjacent entry of four margin entries,
# if adjacent entry is movable, must set the margin entry as the hole
def generateHoles(map, x, y, width, height, wall_x, wall_y):
holes = []
hole_entrys = [(randint(x, wall_x -1), wall_y), (randint(wall_x + 1, x + width -1), wall_y),
(wall_x, randint(y, wall_y -1)), (wall_x, randint(wall_y + 1, y + height - 1))]
margin_entrys = [(x, wall_y), (x+width-1, wall_y), (wall_x, y), (wall_x, y + height-1)]
adjacent_entrys = [(x-1, wall_y), (x+width, wall_y), (wall_x, y - 1), (wall_x, y + height)]
for i in range(4):
adj_x, adj_y = (adjacent_entrys[i][0], adjacent_entrys[i][1])
if map.isValid(adj_x, adj_y) and map.isMovable(adj_x, adj_y):
map.setMap(margin_entrys[i][0], margin_entrys[i][1], MAP_ENTRY_TYPE.MAP_EMPTY)
else:
holes.append(hole_entrys[i])
ignore_hole = randint(0, len(holes)-1)
for i in range(0, len(holes)):
if i != ignore_hole:
map.setMap(holes[i][0], holes[i][1], MAP_ENTRY_TYPE.MAP_EMPTY)
if width <= 1 or height <= 1:
return
#generate a row and a column wall index, they must be even number
wall_x, wall_y = (getWallIndex(x, width), getWallIndex(y, height))
#set horizontal and vertical lines to wall
for i in range(x, x+width):
map.setMap(i, wall_y, wall_value)
for i in range(y, y+height):
map.setMap(wall_x, i, wall_value)
#create three holes
generateHoles(map, x, y, width, height, wall_x, wall_y)
recursiveDivision(map, x, y, wall_x - x, wall_y - y, wall_value)
recursiveDivision(map, x, wall_y + 1, wall_x - x, y + height - wall_y -1, wall_value)
recursiveDivision(map, wall_x + 1, y, x + width - wall_x -1, wall_y - y, wall_value)
recursiveDivision(map, wall_x + 1, wall_y + 1, x + width - wall_x -1, y + height - wall_y -1, wall_value)
def doRecursiveDivision(map):
# draw four margin wall lines
for x in range(0, map.width):
map.setMap(x, 0, MAP_ENTRY_TYPE.MAP_BLOCK)
map.setMap(x, map.height-1, MAP_ENTRY_TYPE.MAP_BLOCK)
for y in range(0, map.height):
map.setMap(0, y, MAP_ENTRY_TYPE.MAP_BLOCK)
map.setMap(map.width-1, y, MAP_ENTRY_TYPE.MAP_BLOCK)
recursiveDivision(map, 1, 1, map.width - 2, map.height - 2, MAP_ENTRY_TYPE.MAP_BLOCK)
def run():
WIDTH = 31
HEIGHT = 21
map = Map(WIDTH, HEIGHT)
doRecursiveDivision(map)
map.showMap()
if __name__ == "__main__":
run()