前言
通过prim可以生成不同的地图,通过A*算法解决迷宫问题
一、Prim算法生成地图
Prim算法生成地图,通过A*算法解决,但是搞了几天,A *算法没有搞出来,所以换了个算法,深度优先算法(DFS)。
二、实现迷宫
2.1 深度优先算法
深度优先搜索属于图算法的一种,英文缩写为DFS(Depth First Search.)其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。如下例:
这种算法相对于A* 算法,比较繁琐,消耗比A*多,但是容易理解,从生活中举例:我在家里丢了一把钥匙,但是我并不知道钥匙的具体位置,钥匙会在任何位置,这时候,我就需要去翻看每个角落,而且去过的角落都记在了脑海里,不会再去看,这种地毯式搜索,直到找到钥匙位置。
2.2引入库
这次引用了pyxel库,Pyxel是一个python的经典像素风游戏制作引擎。
可以使迷宫可视化。
2.3实现过程
Prim生成迷宫
仍是采用Prim生成迷宫,我们定义一个 Maze 类,用二维数组表示迷宫地图,其中 1 表示墙壁,0 表示道路,然后初始化左上角为入口,右下角为出口,最后定义下方向向量。
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.map = [[0 if x % 2 == 1 and y % 2 == 1 else 1 for x in range(width)] for y in range(height)]
self.map[1][0] = 0 # 入口
self.map[height - 2][width - 1] = 0 # 出口
self.visited = []
self.dx = [1, 0, -1, 0]
self.dy = [0, -1, 0, 1]
接下来就是生成迷宫的主函数。该函数里面有两个主要有get_neighbor_road(point) 和 deal_with_not_visited(),前者会获得传入坐标点 point 的邻路节点,返回值是一个二维数组,后者 deal_with_not_visited() 从列表里随机选一面墙,如果这面墙分隔的两个单元格只有一个单元格被访问过那就从列表里移除这面墙,同时把墙打通将单元格标记为已访问
将未访问的单元格的的邻墙加入列表 wall。
def generate(self):
start = [1, 1]
self.visited.append(start)
wall_list = self.get_neighbor_wall(start)
while wall_list:
wall_position = random.choice(wall_list)
neighbor_road = self.get_neighbor_road(wall_position)
wall_list.remove(wall_position)
self.deal_with_not_visited(neighbor_road[0], wall_position, wall_list)
self.deal_with_not_visited(neighbor_road[1], wall_position, wall_list)
Prim 算法是随机的从列表中的所有的单元格进行随机选择,新加入的单元格和旧加入的单元格没有差别,都是50%的概率,这样一样迷宫生成就自然些
DFS走出迷宫
实现DFS就是要实现一个递归程序。当该节点坐标越界、该节点被访问过或者该节点是墙壁的时候,直接返回,因为不是走出迷宫的路径,如果是,就将它加入访问过的节点和路径。
然后如果该节点是出口则表示程序执行结束,找到了通路。不然就遍历四个方向向量,将节点的邻路传入函数 dfs 继续以上步骤,直到找到出路或者程序所有节点都遍历完成。
def dfs(self, x, y, path, visited=[]):
# outOfIndex
if self.is_out_of_index(x, y):
return False
# visited or is wall
if [x, y] in visited or self.get_value([x, y]) == 1:
return False
visited.append([x, y])
path.append([x, y])
# end...
if x == self.width - 2 and y == self.height - 2:
return True
# recursive
for i in range(4):
if 0 < x + self.dx[i] < self.width - 1 and 0 < y + self.dy[i] < self.height - 1 and \
self.get_value([x + self.dx[i], y + self.dy[i]]) == 0:
if self.dfs(x + self.dx[i], y + self.dy[i], path, visited):
return True
elif not self.is_out_of_index(x, y) and path[-1] != [x, y]:
path.append([x, y])
pyxel实现可视化
类 Vision 的执行逻辑就是不断的调用 update 函数和 draw 函数,因此可以在 update 函数中更新物体的坐标,然后在 draw 函数中将图像画到屏幕即可。
如此我们就先把迷宫画出来,然后在渲染 dfs 遍历动画。
import pyxel
class Vision:
def __init__(self):
pyxel.init(160, 120)
self.x = 0
pyxel.run(self.update, self.draw)
def update(self):
self.x = (self.x + 1) % pyxel.width
def draw(self):
pyxel.cls(0)
pyxel.rect(self.x, 0, 8, 8, 9)
Vision()
生成迷宫的宽和高定了48,36
width, height = 48, 36
my_maze = Maze(width, height)
my_maze.generate()
class Vision:
def __init__(self):
pyxel.init(width * pixel, height * pixel)
pyxel.run(self.update, self.draw)
def update(self):
if pyxel.btn(pyxel.KEY_Q):
pyxel.quit()
if pyxel.btn(pyxel.KEY_S):
self.death = False
def draw(self):
# draw maze
for x in range(height):
for y in range(width):
color = road_color if my_maze.map[x][y] is 0 else wall_color
pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
pyxel.rect(0, pixel, pixel, pixel, start_point_color)
pyxel.rect((width - 1) * pixel, (height - 2) * pixel, pixel, pixel, end_point_color)
Vision()
修改 update 函数和 draw 函数来渲染路径
self.index = 0
self.route = [] # 用于记录待渲染的路径
self.step = 1 # 步长,数值越小速度越快,1:每次1格
self.color = start_point_color
self.bfs_route = my_maze.bfs_route()
其中 index 和 step 是用来控制渲染速度的,在 draw 函数中 index 每次自增 1,然后再对 step 求余得到当前的真实下标 real_index
函数draw()
def draw(self):
# draw maze
for x in range(height):
for y in range(width):
color = road_color if my_maze.map[x][y] is 0 else wall_color
pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
pyxel.rect(0, pixel, pixel, pixel, start_point_color)
pyxel.rect((width - 1) * pixel, (height - 2) * pixel, pixel, pixel, end_point_color)
if self.index > 0:
# draw route
offset = pixel / 2
for i in range(len(self.route) - 1):
curr = self.route[i]
next = self.route[i + 1]
self.color = backtrack_color if curr in self.route[:i] and next in self.route[:i] else route_color
pyxel.line(curr[0] + offset, (curr[1] + offset), next[0] + offset, next[1] + offset, self.color)
pyxel.circ(self.route[-1][0] + 2, self.route[-1][1] + 2, 1, head_color)
函数update()
def update(self):
if pyxel.btn(pyxel.KEY_ESCAPE):
pyxel.quit()
if pyxel.btn(pyxel.KEY_SPACE):
self.death = False
if not self.death:
self.check_death()
self.update_route()
def check_death(self):
if self.dfs_model and len(self.route) == len(self.dfs_route) - 1:
self.death = True
elif not self.dfs_model and len(self.route) == len(self.bfs_route) - 1:
self.death = True
def update_route(self):
index = int(self.index / self.step)
self.index += 1
if index == len(self.route): # move
if self.dfs_model:
self.route.append([pixel * self.dfs_route[index][0], pixel * self.dfs_route[index][1]])
else:
self.route.append([pixel * self.bfs_route[index][0], pixel * self.bfs_route[index][1]])
Vision()
最后ESC键可以退出,空格键可以执行迷宫自动寻路
总结
我们用深度优先算法实现了迷宫的遍历,相对于A算法,递归这思路可能还能接受一点,但是确实比A算法慢太多了,因为要遍历每个角落,所以有时间要用A*算法来改善迷宫。
附展示gif