相信大家都玩过迷宫的游戏,对于简单的迷宫,我们可以一眼就看出通路,但是对于复杂的迷宫,可能要仔细寻找好久,甚至耗费数天,然后可能还要分别从入口和出口两头寻找才能找的到通路,甚至也可能找不到通路。

虽然走迷宫问题对于我们人类来讲比较复杂,但对于计算机来说却是很简单的问题。为什么这样说呢,因为看似复杂实则是有规可循的。

我们可以这么做,携带一根很长的绳子,从入口出发一直走,如果有岔路口就走最左边的岔口,直到走到死胡同或者找到出路。如果是死胡同则退回上一个岔路口,我们称之为岔口 A,

这时进入左边第二个岔口,进入第二个岔口后重复第一个岔口的步骤,直到找到出路或者死胡同退回来。当把该岔路口所有的岔口都走了一遍,还未找到出路就沿着绳子往回走,走到岔口 A 的前一个路口 B,重复上面的步骤。

不知道你有没有发现,这其实就是一个不断递归的过程,而这正是计算机所擅长的。

上面这种走迷宫的算法就是我们常说的深度优先遍历算法,与之相对的是广度优先遍历算法。有了理论基础,下面我们就来试着用 程序来实现一个走迷宫的小程序。

生成迷宫

生成迷宫有很多种算法,常用的有递归回溯法、递归分割法和随机 Prim 算法,我们今天是用的最后一种算法。

该算法的主要步骤如下:

1、迷宫行和列必须为奇数

2、奇数行和奇数列的交叉点为路,其余点为墙,迷宫四周全是墙

3、选定一个为路的单元格(本例选 [1,1]),然后把它的邻墙放入列表 wall

4、当列表 wall 里还有墙时:

4.1、从列表里随机选一面墙,如果这面墙分隔的两个单元格只有一个单元格被访问过

4.1.1、那就从列表里移除这面墙,同时把墙打通

4.1.2、将单元格标记为已访问

4.1.3、将未访问的单元格的邻墙加入列表 wall

4.2、如果这面墙两面的单元格都已经被访问过,那就从列表里移除这面墙

我们定义一个 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 = []

# right up left down

self.dx = [1, 0, -1, 0]

self.dy = [0, -1, 0, 1]

接下来就是生成迷宫的主函数了。

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)

该函数里面有两个主要函数 get_neighbor_road(point) 和 deal_with_not_visited(),前者会获得传入坐标点 point 的邻路节点,返回值是一个二维数组,后者 deal_with_not_visited() 函数处理步骤 4.1 的逻辑。

由于 Prim 随机算法是随机的从列表中的所有的单元格进行随机选择,新加入的单元格和旧加入的单元格被选中的概率是一样的,因此其分支较多,生成的迷宫较复杂,难度较大,当然看起来也更自然些。生成的迷宫。

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

[1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

[1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1]

[1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1]

[1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1]

[1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]

[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1]

[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0]

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

走出迷宫

得到了迷宫的地图,接下来就按照我们文首的思路来走迷宫即可。主要函数逻辑如下:

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])

很明显,这就是一个典型的递归程序。当该节点坐标越界、该节点被访问过或者该节点是墙壁的时候,直接返回,因为该节点肯定不是我们要找的路径的一部分,否则就将该节点加入被访问过的节点和路径的集合中。

然后如果该节点是出口则表示程序执行结束,找到了通路。不然就遍历四个方向向量,将节点的邻路传入函数 dfs 继续以上步骤,直到找到出路或者程序所有节点都遍历完成。

来看看我们 dfs 得出的路径结果:

[[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1], [9, 1], [8, 1], [7, 1], [6, 1], [5, 1], [5, 2], [5, 3], [6, 3], [7, 3], [8, 3], [9, 3], [9, 4], [9, 5], [9, 5], [9, 4], [9, 3], [8, 3], [7, 3], [7, 4], [7, 5], [7, 5], [7, 4], [7, 3], [6, 3], [5, 3], [4, 3], [3, 3], [2, 3], [1, 3], [1, 3], [2, 3], [3, 3], [3, 4], [3, 5], [2, 5], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [1, 9], [1, 8], [1, 7], [1, 6], [1, 5], [2, 5], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 9], [3, 8], [3, 7], [3, 6], [3, 5], [3, 4], [3, 3], [4, 3], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [6, 7], [7, 7], [8, 7], [9, 7], [9, 8], [9, 9], [10, 9]]

可视化

有了迷宫地图和通路路径,剩下的工作就是将这些坐标点渲染出来。今天我们用的可视化库是 pyxel,这是一个用来写像素级游戏的 Python 库,

当然使用前需要先安装下这个库。

Win 用户直接用 pip install -U pyxel命令安装即可。

Mac 用户使用以下命令安装:

brew install python3 gcc sdl2 sdl2_image gifsicle

pip3 install -U pyxel

先来看个简单的 Demo。

import pyxel

class App:

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)

App()

类 App 的执行逻辑就是不断的调用 update 函数和 draw 函数,因此可以在 update 函数中更新物体的坐标,然后在 draw 函数中将图像画到屏幕即可。

如此我们就先把迷宫画出来,然后在渲染 dfs 遍历动画。

width, height = 37, 21

my_maze = Maze(width, height)

my_maze.generate()

class App:

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)

App()

看起来还可以,这里的宽和高我分别用了 37 和 21 个像素格来生成,所以生成的迷宫不是很复杂,如果像素点很多的话就会错综复杂了。

接下里来我们就需要修改 update 函数和 draw 函数来渲染路径了。为了方便操作,我们在 init 函数中新增几个属性。

self.index = 0

self.route = [] # 用于记录待渲染的路径

self.step = 1 # 步长,数值越小速度越快,1:每次一格;10:每次 1/10 格

self.color = start_point_color

self.bfs_route = my_maze.bfs_route()

其中 index 和 step 是用来控制渲染速度的,在 draw 函数中 index 每次自增 1,然后再对 step 求余数得到当前的真实下标 real_index,简言之就是 index 每增加 step,real_index 才会加一,渲染路径向前走一步。

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)

def update(self):

if pyxel.btn(pyxel.KEY_Q):

pyxel.quit()

if pyxel.btn(pyxel.KEY_S):

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]])

App()

至此,我们完整的从迷宫生成,到寻找路径,再到路径可视化已全部实现。直接调用主函数 App() 然后按 S 键盘开启游戏

总结

今天我们用深度优先算法实现了迷宫的遍历,对于新手来说,递归这思路可能比较难理解,但这才是符合计算机思维的,随着经验的加深会理解越来越深刻的。

其次我们用 pyxel 库来实现路径可视化,难点在于坐标的计算更新,细节比较多且繁琐,当然读者也可以用其他库或者直接用网页来实现也可以。

python用一行代码画个迷宫_用 Python 制作一个迷宫游戏相关推荐

  1. Python 探究“一行代码画爱心”的秘密,去向心爱的人表白吧

    今天7月8号,恰逢儿子生日,我来画个爱心向他表达一下爱意吧: >>> print('\n'.join([''.join([('ILoveYangCheng'[(x-y)%len('I ...

  2. python用一行代码画个迷宫_[代码全屏查看]-用turtle不断的画回字迷宫

    [2].[文件] 画个回字(最原始方法).py ~ 3KB    下载(4) #coding=UTF8 from turtle import * import turtle, time # 绘制正方形 ...

  3. python代码画小猪佩奇_用 Python 画一个小猪佩奇和哆啦 A 梦

    0 前言 最近发现了很有意思的东西,有人用 turtle 这个库画了一个小猪佩奇,跑去 GitHub 看了一下代码,发现写这个的人真的很有耐心,居然能写下来. 再找了找,发现有人还画了哆啦 A 梦, ...

  4. python生成的字符画怎么查看_通过python将图片生成字符画

    原博文 2016-10-18 20:26 − 基础知识: 1.python基础知识 快速学习链接:https://www.shiyanlou.com/courses/214 2.linux命令行操作 ...

  5. java利用事件监听制作迷宫_用Java制作3D迷宫

    有许多迷宫生成算法在这里工作得很好,其中大部分是基于在3D网格图中创建某种 spanning tree. 举个例子,让我们假设我们有一个2D网格的单元格(我可以使用ASCII艺术实际渲染!),如下所示 ...

  6. 用python一行代码画爱心

    用python一行代码画出爱心 print('\n'.join([''.join([('LoveSongxiaolong'[(x-y)%8]if((x*0.05)**2+(y*0.1)**2-1)** ...

  7. python一行代码是什么意思_一行Python代码能做什么?

    很典型的文章,基本可以展示Python代码到底有多黑魔法.虽然文中有些案例连我都觉得走火入魔,但真的很值得一看... 自从08年接触Python,就有爱不释手的感觉,逐渐地,有些不忍地疏远了Perl ...

  8. Python:一行代码将以e为结尾的科学计算法类型的数值转为小数点类型数值

    Python:一行代码将以e为结尾的科学计算法类型的数值转为小数点类型数值 目录 一行代码将以e为结尾的科学计算法类型的数值转为小数点类型数值 一行代码将以e为结尾的科学计算法类型的数值转为小数点类型 ...

  9. python可视化迷宫求解_如何用 Python 制作一个迷宫游戏

    相信大家都玩过迷宫的游戏,对于简单的迷宫,我们可以一眼就看出通路,但是对于复杂的迷宫,可能要仔细寻找好久,甚至耗费数天,然后可能还要分别从入口和出口两头寻找才能找的到通路,甚至也可能找不到通路. 虽然 ...

最新文章

  1. java获取date的时分秒_Java 之 Date 获取 年月日时分秒
  2. 中文repo“霸榜”GitHub Trending,国外开发者不开心了
  3. telegraf output input 配置用法
  4. 部署环境_Hyperledger Fabric Composer环境部署(一)
  5. 【今日CS 视觉论文速览】 25 Jan 2019
  6. libuv 和 libev的对比
  7. Golang map 三板斧第一式:快速上手
  8. 关于Shiro的标签应用
  9. dubbo安装和使用
  10. kaggle实战之流浪猫狗归处预测
  11. 玩转Excel快捷键和常用函数和图表,赶紧学起来
  12. 苹果公司开发者账号注册流程详解
  13. u盘中毒如何删除system volume information文件
  14. 如何优雅地使用 Sublime Text
  15. Films have seen
  16. H5二维码海报生成保存相关问题记录
  17. 嵌入式系统之新建工程模版
  18. Azure 开发者新闻快讯丨开发者6月大事记一览
  19. 8个最佳的JavaScript移动应用开发框架
  20. LOGOAPLUS-为什么B2C都爱用动物做公司LOGO

热门文章

  1. release优化和不优化结果不一致_您了解网站优化吗?优化效果不明显?排名不稳定是为什么?...
  2. 如何使用分析模型 — 1. 关联图,从乱麻中里出头绪
  3. 作者:邢俊峰(1985-),男,烟台中科网络技术研究所研发工程师。
  4. 移动应用开发——作业3
  5. 【离散数学】幂集的求解
  6. 对unicode数据进行部分replace
  7. 使用RunWith注解改变JUnit的默认执行类,并实现自已的Listener
  8. 微信支付开发(7) 刷卡支付
  9. Python通过LDAP验证、查找用户(class,logging)
  10. HDUOJ---The number of divisors(约数) about Humble Numbers