递归的逻辑(5)——米诺斯的迷宫

  米诺斯迷宫的传说来源于克里特神话,在希腊神话中也有大量的描述,号称世界四大迷宫之一。

  米诺斯是宙斯和欧罗巴的儿子,因智慧和公正而闻名,死后成为了冥国的判官。由于米诺斯得罪了海神波塞冬,波塞冬便以神力使米诺斯的妻子帕西法厄爱上了一头公牛,生下了一个牛首人身的怪物米诺陶洛斯。这个半人半牛的怪物不吃其他食物,只吃人肉,因此米诺斯把他关进一座迷宫中,令它无法危害人间。

  后来雅典人杀死了米诺斯的一个儿子,为了复仇,米诺斯恳求宙斯的帮助。宙斯给雅典带来了瘟疫,为了阻止瘟疫的流行,雅典从必须每年选送七对童男童女去供奉怪物米诺陶洛斯。

  当雅典第三次纳贡时,王子忒修斯自愿充当祭品,以便伺机杀掉怪物,为民除害。当勇敢的王子离开王宫时,他对自己的父亲说,如果他胜利了,船返航时便会挂上白帆,反之则还是黑帆。忒修斯到了米诺斯王宫,公主艾丽阿德涅对他一见钟情,并送他一团线球和一柄魔剑,叫他将线头系在入口处,放线进入迷宫。忒修斯在迷宫深处找到了米诺陶洛斯,经过一场殊死搏斗,终于将其杀死。

  忒修斯带着深爱他的艾丽阿德涅公主返回雅典,却在途中把她抛在一座孤岛上。由于他这一背信弃义的行为,他遭到了惩罚——胜利的喜悦冲昏了他的头脑,他居然忘记更换船上的黑帆!结果,站在海边遥望他归来的父亲看到那黑帆之后,认为儿子死掉了,便悲痛地投海而死。

  似乎我很小的时候就听过这个故事,随着时间的流逝,故事的梗概早已忘却,但那个神奇的迷宫却至今都记忆犹新。虽然不清楚当时的迷宫是怎样设计的,但是我们可以通过递归的方法让米诺斯的迷宫重现人间。

 1 # 迷宫矩阵
 2 maze = [
 3     [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 4     [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
 5     [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1],
 6     [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1],
 7     [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1],
 8     [1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
 9     [1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1],
10     [1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1],
11     [1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1],
12     [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1],
13     [1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1],
14     [1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1],
15     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
16     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
17     [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
18     [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
19 ]
20 def paint(maze):
21     ''' 打印迷宫 '''
22     for a in maze:
23         for i in a:
24             print('%4d' % i, end='')
25         print()
26
27 if __name__ == '__main__':
28     paint(maze)

  在矩阵中,用0表示通道,1表示墙壁,忒修斯王子可以在0之间任意穿行,矩阵迷宫的打印结果:

迷宫的数据结构

  虽然可以用0和1绘制出一个迷宫,但仍然属于手动编辑,我们的目标是寄希望于计算机,自动生成并绘制一个大型的迷宫:

  迷宫中有很多墙壁,再用0和1组成的简单矩阵就不合适了。如果将迷宫矩阵的每一个位置看作一个方块,则方块的上、下、左、右都可能有墙壁存在,这就需要对每个位置记录四面墙壁的信息:

  实际上没那么复杂,只要记录上墙和右墙就可以了,至于下墙和左墙,完全可以由相邻方块的上墙和右墙代替:

  当然,最后还要在四周套上一层边框:

  在生成迷宫时,每一个方块都需要记录三种信息:是否已经被设置、是否有右墙、是否有上墙。一个较为“面向对象”的方法是将方块信息设计成一个类结构,用三个布尔型属性来记录信息,但是这样做性价比并不高,一种更简单且高效的方式是用一个3位的二进制数来表示:

  矩阵中所有元素的初始值都设置为011,也就是方块未设置、有右墙和上墙;如果已经设置了某个方块,那么第3位被置为1,如此一来,每个方块可能会有5种状态:

  使用下面的代码设置一个8×8迷宫矩阵的初始状态:

 1 # 迷宫矩阵
 2 class MinosMaze:
 3     maze = []            # 迷宫矩阵
 4     n = 0                # 矩阵维度
 5     init_status = 0b011  # 初始状态,有上墙和右墙
 6     def __init__(self, n: int):
 7         ''' 初始化一个 n 维的迷宫 '''
 8         self.n = n
 9         # 初始化迷宫矩阵,所有方块未设置、有右墙、有上墙
10         self.maze = [([self.init_status] * n) for i in  range(n)]
11
12     def patin_maze(self):
13         for a in self.maze:
14             for i in a:
15                 print('%4d' % i, end='')
16             print()
17
18 if __name__ == '__main__':
19     m = MinosMaze(8)
20     m.patin_maze()

  我们使用拆墙法自动生成迷宫,这需要遍历迷宫矩阵中的每一个方格,设置是否拆除右墙或上墙。用递归的方法随机遍历上下左右四个方向,直到所有方向全部遍历完为止:

  四个的拆墙过程如下:

  1. 向上遍历,需要拆除当前方格的上墙;

  2. 向下遍历,需要拆除下侧方格的上墙;

  3. 向左遍历,需要拆除左侧方格的右墙;

  4. 向右遍历,需要拆除当前单元格的右墙。

1 class MinosMaze:
2     ……
3     def remove_wall(self, i, j, side):
4         ''' 拆掉maze[i][j] 的上墙或右墙 '''
5         if side == 'U':
6             self.maze[i][j] &= 0b110  # 拆掉上墙
7         elif side == 'R':
8             self.maze[i][j] &= 0b101  # 拆掉右墙

自动生成迷宫

  通过递归的方式遍历方格,迷宫矩阵的方格会逐一被设置:

 1 import random
 2
 3 class MinosMaze:
 4     ...
 5     def create(self):
 6         ''' 自动创建迷宫 '''
 7         def auto_create(i, j):
 8             self.maze[i][j] |= 0b100    # maze[i][j] 已经被设置过
 9             # 当self.maze[i][j]的上下左右四个方向都是初始状态时,开始拆墙操作
10             while (i - 1 >= 0 and self.maze[i - 1][j] == self.init_status) \
11                     or (i + 1 < self.n and self.maze[i + 1][j] == self.init_status) \
12                     or (j - 1 >= 0 and self.maze[i][j - 1] == self.init_status) \
13                     or (j + 1 < self.n and self.maze[i][j + 1] == self.init_status):
14                 side = random.choice(['U', 'D', 'L', 'R'])   # 随机方向
15                 # 能够向↑走
16                 if side == 'U' and i - 1 >= 0 and self.maze[i - 1][j] == self.init_status:
17                     self.remove_wall(i, j, 'U')     # 拆除当前方格的上墙
18                     auto_create(i - 1, j)           # 向↑走
19                 # 能够向↓走
20                 elif side == 'D' and i + 1 < self.n and self.maze[i + 1][j] == self.init_status:
21                     self.remove_wall(i + 1, j, 'U') # 拆除下侧方格的上墙
22                     auto_create(i + 1, j)           # 向↓走
23                 # 能够向←走
24                 elif side == 'L' and j - 1 >= 0 and self.maze[i][j - 1] == self.init_status:
25                     self.remove_wall(i, j - 1, 'R') # 拆除左侧方格的右墙
26                     auto_create(i, j - 1)           # 向←走
27                 # 能够向→走
28                 elif side == 'R' and j + 1 < self.n and self.maze[i][j + 1] == self.init_status:
29                     self.remove_wall(i, j, 'R')     # 拆除当前单元格的右墙
30                     auto_create(i, j + 1)           # 向→走
31         auto_create(0, 0)   # 从入口位置开始遍历
32
33     def patin_maze(self):
34         ''' 打印迷宫数组 '''
35         for a in self.maze:
36             for i in a:
37                 print('%4d' % i, end='')
38             print()
39
40 if __name__ == '__main__':
41     m = MinosMaze(8)
42     m.create()
43     m.patin_maze()

  程序构造了一个8×8的迷宫,一种可能的结果是:

  矩阵元素的打印的结果是十进制整数,它和二进制的对应关系:

画出迷宫

  绘制迷宫的方法很简单,只需在坐标轴中画出每个方格的墙壁就好了:

 1 import random
 2 import matplotlib.pyplot as plt
 3
 4 class MinosMaze:
 5     ……
 6     def paint(self):
 7         # 绘制迷宫内部
 8         for i in range(self.n):
 9             for j in range(self.n):
10                 # 有右墙
11                 if self.maze[i][j] & 0b010 == 0b010:
12                     # 右墙的坐标
13                     r_x, r_y = [j + 1, j + 1], [self.n - i, self.n - i - 1]
14                     plt.plot(r_x, r_y, color='black')
15                 # 有上墙
16                 if self.maze[i][j] & 0b001 == 0b001:
17                     # 上墙的坐标
18                     u_x, u_y = [j, j + 1], [self.n - i, self.n - i]
19                     plt.plot(u_x, u_y, color='black')
20
21         plt.axis('equal')
22         ax = plt.gca()
23         ax.spines['top'].set_visible(False)
24         ax.spines['right'].set_visible(False)
25         plt.show()

  看起来不那么像迷宫,这是由于没有添加边框,因此还需要在paint()方法中加上最后的完善工作:

 1 def paint(self):
 2     ……
 3     plt.plot([0, self.n], [self.n, self.n], color='black')   # 上边框
 4     plt.plot([0, self.n], [0, 0], color='black')             # 下边框
 5     plt.plot([0, 0], [0, self.n], color='black')             # 左边框
 6     plt.plot([self.n, self.n], [0, self.n], color='black')  # 右边框
 7
 8     # 设置入口和出口
 9     entrance, exit = ([0, 0], [self.n, self.n - 1]), ([self.n, self.n], [0, 1])
10     plt.plot(entrance[0], entrance[1], color='white')
11     plt.plot(exit[0], exit[1], color='white')
12
13     plt.axis('equal')
14     ax = plt.gca()
15     ax.spines['top'].set_visible(False)
16     ax.spines['right'].set_visible(False)
17     plt.show()

  出口的位置在迷宫的右下角,由于创建迷宫时遍历了所有方格,因此出口方格一定是从它上侧或左侧的方格遍历而来的,这意味着它一定没有上墙或左墙,拆掉它的右边框一定能够成为出口。现在可以终于可以绘制出一个完整的迷宫了:

  米诺斯的迷宫复杂的多,也许一个32×32的设计图可以困住怪兽:

  以下是完整的代码:

 

  1 import random
  2 import matplotlib.pyplot as plt
  3 
  4 # 迷宫矩阵
  5 class MinosMaze:
  6     ''' 米诺斯迷宫
  7         Attributes:
  8             maze:           迷宫矩阵
  9             n:              矩阵维度
 10             init_status:    方格的初始状态
 11         '''
 12     maze = []
 13     n = 0
 14     init_status = 0b011  # 初始状态,有上墙和右墙
 15 
 16     def __init__(self, n: int):
 17         ''' 初始化一个 n 维的迷宫 '''
 18         self.n = n
 19         # 初始化迷宫矩阵,所有方块未设置、有右墙、有上墙
 20         self.maze = [([self.init_status] * n) for i in  range(n)]
 21 
 22     def remove_wall(self, i, j, side):
 23         ''' 拆掉maze[i][j] 的上墙或右墙 '''
 24         if side == 'U':
 25             # 拆掉上墙
 26             self.maze[i][j] &= 0b110
 27         elif side == 'R':
 28             # 拆掉右墙
 29             self.maze[i][j] &= 0b101
 30 
 31     def create(self):
 32         ''' 自动创建迷宫 '''
 33         def auto_create(i, j):
 34             # maze[i][j] 已经被设置过
 35             self.maze[i][j] |= 0b100
 36 
 37             # 当self.maze[i][j]的上下左右四个方向都是初始状态时,开始拆墙操作
 38             while (i - 1 >= 0 and self.maze[i - 1][j] == self.init_status) \
 39                     or  (i + 1 < self.n and self.maze[i + 1][j] == self.init_status) \
 40                     or (j - 1 >= 0 and self.maze[i][j - 1] == self.init_status) \
 41                     or (j + 1 < self.n and self.maze[i][j + 1] == self.init_status):
 42                 # 随机方向
 43                 side = random.choice(['U', 'D', 'L', 'R'])
 44                 # 能够向↑走
 45                 if side == 'U' and i - 1 >= 0 and self.maze[i - 1][j] == self.init_status:
 46                     # 拆除当前方格的上墙
 47                     self.remove_wall(i , j, 'U')
 48                     # 向↑走
 49                     auto_create(i - 1, j)
 50                 # 能够向↓走
 51                 elif side == 'D' and i + 1 < self.n and self.maze[i + 1][j] == self.init_status:
 52                     # 拆除下侧方格的上墙
 53                     self.remove_wall(i + 1 , j, 'U')
 54                     # 向↓走
 55                     auto_create(i + 1, j)
 56                 # 能够向←走
 57                 elif side == 'L' and j - 1 >= 0 and self.maze[i][j - 1] == self.init_status:
 58                     # 拆除左侧方格的右墙
 59                     self.remove_wall(i, j - 1, 'R')
 60                     # 向←走
 61                     auto_create(i, j - 1)
 62                 # 能够向→走
 63                 elif side == 'R' and j + 1 < self.n and self.maze[i][j + 1] == self.init_status:
 64                     # 拆除当前单元格的右墙
 65                     self.remove_wall(i, j, 'R')
 66                     # 向→走
 67                     auto_create(i, j + 1)
 68         # 从入口位置开始遍历
 69         auto_create(0, 0)
 70 
 71     def patin_maze(self):
 72         for a in self.maze:
 73             for i in a:
 74                 print('%4d' % i, end='')
 75             print()
 76 
 77     def paint(self):
 78         # 绘制迷宫内部
 79         for i in range(self.n):
 80             for j in range(self.n):
 81                 # 有右墙
 82                 if self.maze[i][j] & 0b010 == 0b010:
 83                     # 右墙的坐标
 84                     r_x, r_y = [j + 1, j + 1], [self.n - i, self.n - i - 1]
 85                     plt.plot(r_x, r_y, color='black')
 86                 # 有上墙
 87                 if self.maze[i][j] & 0b001 == 0b001:
 88                     # 上墙的坐标
 89                     u_x, u_y = [j, j + 1], [self.n - i, self.n - i]
 90                     plt.plot(u_x, u_y, color='black')
 91 
 92         # 上边框
 93         plt.plot([0, self.n], [self.n, self.n], color='black')
 94         # 下边框
 95         plt.plot([0, self.n], [0, 0], color='black')
 96         # 左边框
 97         plt.plot([0, 0], [0, self.n], color='black')
 98         # 右边框
 99         plt.plot([self.n, self.n], [0, self.n], color='black')
100 
101         # 设置入口和出口
102         entrance, exit = ([0, 0], [self.n, self. n - 1]), ([self.n, self.n], [0, 1])
103         plt.plot(entrance[0], entrance[1], color='white')
104         plt.plot(exit[0], exit[1], color='white')
105 
106         plt.axis('equal')
107         ax = plt.gca()
108         ax.spines['top'].set_visible(False)
109         ax.spines['right'].set_visible(False)
110         plt.show()
111 
112 if __name__ == '__main__':
113     # m = MinosMaze(8)
114     # m = MinosMaze(16)
115     m = MinosMaze(32)
116     m.create()
117     m.patin_maze()
118     m.paint()

 

 

 


   作者:我是8位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途! 

  扫描二维码关注公众号“我是8位的”

posted on 2019-02-19 23:42  我是8位的  阅读(3338)  评论(0编辑  收藏  举报

导航