|
| 1 | +import random |
| 2 | +import pyxel |
| 3 | + |
| 4 | + |
| 5 | +# Python 3.6.9+ |
| 6 | +# |
| 7 | +# Win: |
| 8 | +# pip install -U pyxel |
| 9 | +# ************************ |
| 10 | +# Mac |
| 11 | +# 1、brew install python3 sdl2 sdl2_image |
| 12 | +# 2、restart the terminal |
| 13 | +# 3、pip3 install -U pyxel |
| 14 | +# ************************ |
| 15 | +# 「S」键开启游戏 |
| 16 | +# https://github.com/kitao/pyxel/blob/master/README.cn.md |
| 17 | + |
| 18 | +class Maze: |
| 19 | + def __init__(self, width, height): |
| 20 | + self.width = width |
| 21 | + self.height = height |
| 22 | + self.map = [[0 if x % 2 == 1 and y % 2 == 1 else 1 for x in range(width)] for y in range(height)] |
| 23 | + self.map[1][0] = 0 # 入口 |
| 24 | + self.map[height - 2][width - 1] = 0 # 出口 |
| 25 | + self.visited = [] |
| 26 | + # right up left down |
| 27 | + self.dx = [1, 0, -1, 0] |
| 28 | + self.dy = [0, -1, 0, 1] |
| 29 | + |
| 30 | + def set_value(self, point, value): |
| 31 | + self.map[point[1]][point[0]] = value |
| 32 | + |
| 33 | + def get_value(self, point): |
| 34 | + return self.map[point[1]][point[0]] |
| 35 | + |
| 36 | + # 获取坐标(x,y) 的邻居 返回数据结构为:二维数组 |
| 37 | + def get_neighbor(self, x, y, value): |
| 38 | + res = [] |
| 39 | + for i in range(4): |
| 40 | + if 0 < x + self.dx[i] < self.width - 1 and 0 < y + self.dy[i] < self.height - 1 and \ |
| 41 | + self.get_value([x + self.dx[i], y + self.dy[i]]) == value: |
| 42 | + res.append([x + self.dx[i], y + self.dy[i]]) |
| 43 | + return res |
| 44 | + |
| 45 | + # 获取坐标(x,y) 的邻墙 |
| 46 | + def get_neighbor_wall(self, point): |
| 47 | + return self.get_neighbor(point[0], point[1], 1) |
| 48 | + |
| 49 | + # 获取坐标(x,y) 的邻路 |
| 50 | + def get_neighbor_road(self, point): |
| 51 | + return self.get_neighbor(point[0], point[1], 0) |
| 52 | + |
| 53 | + def deal_with_not_visited(self, point, wall_position, wall_list): |
| 54 | + if not [point[0], point[1]] in self.visited: |
| 55 | + self.set_value(wall_position, 0) |
| 56 | + self.visited.append(point) |
| 57 | + wall_list += self.get_neighbor_wall(point) |
| 58 | + |
| 59 | + # generate maze |
| 60 | + # https://en.wikipedia.org/wiki/Maze_generation_algorithm |
| 61 | + # |
| 62 | + # 1、迷宫行和列必须为奇数。 |
| 63 | + # 2、奇数行和奇数列的交叉点为路,其余点为墙。迷宫四周全是墙。 |
| 64 | + # 3、选定一个为路的单元格(本例选 [1,1]),然后把它的邻墙放入列表 wall。 |
| 65 | + # 4、当列表 wall 里还有墙时: |
| 66 | + # 4.1、从列表里随机选一面墙,如果这面墙分隔的两个单元格只有一个单元格被访问过 |
| 67 | + # 3.1.1、那就从列表里移除这面墙,同时把墙打通 |
| 68 | + # 3.1.2、将单元格标记为已访问 |
| 69 | + # 3.1.3、将未访问的单元格的的邻墙加入列表 wall |
| 70 | + # 4.2、如果这面墙两面的单元格都已经被访问过,那就从列表里移除这面墙 |
| 71 | + def generate(self): |
| 72 | + start = [1, 1] |
| 73 | + self.visited.append(start) |
| 74 | + wall_list = self.get_neighbor_wall(start) |
| 75 | + while wall_list: |
| 76 | + wall_position = random.choice(wall_list) |
| 77 | + neighbor_road = self.get_neighbor_road(wall_position) |
| 78 | + wall_list.remove(wall_position) |
| 79 | + self.deal_with_not_visited(neighbor_road[0], wall_position, wall_list) |
| 80 | + self.deal_with_not_visited(neighbor_road[1], wall_position, wall_list) |
| 81 | + |
| 82 | + def is_out_of_index(self, x, y): |
| 83 | + return x == 0 or x == self.width - 1 or y == 0 or y == self.height - 1 |
| 84 | + |
| 85 | + # dfs |
| 86 | + def dfs(self, x, y, path, visited=[]): |
| 87 | + # 越界 |
| 88 | + if self.is_out_of_index(x, y): |
| 89 | + return False |
| 90 | + |
| 91 | + # 访问过 or 撞墙 |
| 92 | + if [x, y] in visited or self.get_value([x, y]) == 1: |
| 93 | + return False |
| 94 | + |
| 95 | + visited.append([x, y]) |
| 96 | + path.append([x, y]) |
| 97 | + |
| 98 | + # over |
| 99 | + if x == self.width - 2 and y == self.height - 2: |
| 100 | + return True |
| 101 | + |
| 102 | + # recursive |
| 103 | + for i in range(4): |
| 104 | + if 0 < x + self.dx[i] < self.width - 1 and 0 < y + self.dy[i] < self.height - 1 and \ |
| 105 | + self.get_value([x + self.dx[i], y + self.dy[i]]) == 0: |
| 106 | + if self.dfs(x + self.dx[i], y + self.dy[i], path, visited): |
| 107 | + return True |
| 108 | + elif not self.is_out_of_index(x, y) and path[-1] != [x, y]: |
| 109 | + path.append([x, y]) |
| 110 | + |
| 111 | + # dfs |
| 112 | + def dfs_route(self): |
| 113 | + path = [] |
| 114 | + self.dfs(1, 1, path) |
| 115 | + |
| 116 | + ans = [[0, 1]] |
| 117 | + for i in range(len(path)): |
| 118 | + ans.append(path[i]) |
| 119 | + if 0 < i < len(path) - 1 and path[i - 1] == path[i + 1]: |
| 120 | + ans.append(path[i]) |
| 121 | + ans.append([width - 1, height - 2]) |
| 122 | + return ans |
| 123 | + |
| 124 | + # bfs |
| 125 | + def bfs_route(self): |
| 126 | + start = {'x': 0, 'y': 1, 'prev': None} |
| 127 | + now = start |
| 128 | + q = [start] |
| 129 | + visited = [[start['x'], start['y']]] |
| 130 | + # 1、从起点出发,获取起点周围所有连通的路 |
| 131 | + # 2、如果该路没有走过,则加入队列 Q,否则跳过 同时记录其前驱节点 |
| 132 | + while q: |
| 133 | + now = q.pop(0) |
| 134 | + # 结束 |
| 135 | + if now['x'] == self.width - 2 and now['y'] == self.height - 2: |
| 136 | + break |
| 137 | + roads = my_maze.get_neighbor_road([now['x'], now['y']]) |
| 138 | + for road in roads: |
| 139 | + if not road in visited: |
| 140 | + visited.append(road) |
| 141 | + q.append({'x': road[0], 'y': road[1], 'prev': now}) |
| 142 | + |
| 143 | + ans = [] |
| 144 | + while now: |
| 145 | + ans.insert(0, [now['x'], now['y']]) |
| 146 | + now = now['prev'] |
| 147 | + ans.append([width - 1, height - 2]) |
| 148 | + return ans |
| 149 | + |
| 150 | + |
| 151 | +pixel = 5 |
| 152 | +width, height = 37, 21 |
| 153 | +road_color, wall_color = 7, 13 |
| 154 | +start_point_color, end_point_color, = 11, 11 |
| 155 | +head_color, route_color, backtrack_color = 9, 11, 8 |
| 156 | + |
| 157 | +my_maze = Maze(width, height) |
| 158 | +my_maze.generate() |
| 159 | + |
| 160 | + |
| 161 | +class App: |
| 162 | + def __init__(self): |
| 163 | + #pyxel.init(width * pixel, height * pixel, caption='maze', border_width=10, border_color=0xFFFFFF) |
| 164 | + pyxel.init(width * pixel, height * pixel) |
| 165 | + self.death = True |
| 166 | + self.index = 0 |
| 167 | + self.route = [] |
| 168 | + self.step = 1 # 步长,数值越小速度越快,1:每次一格; 10:每次 1/10 格 |
| 169 | + self.color = start_point_color |
| 170 | + self.bfs_route = my_maze.bfs_route() |
| 171 | + self.dfs_route = my_maze.dfs_route() |
| 172 | + self.dfs_model = True |
| 173 | + pyxel.run(self.update, self.draw) |
| 174 | + |
| 175 | + def update(self): |
| 176 | + if pyxel.btn(pyxel.KEY_Q): |
| 177 | + pyxel.quit() |
| 178 | + |
| 179 | + if pyxel.btn(pyxel.KEY_S): |
| 180 | + self.death = False |
| 181 | + |
| 182 | + if not self.death: |
| 183 | + self.check_death() |
| 184 | + self.update_route() |
| 185 | + |
| 186 | + def draw(self): |
| 187 | + # draw maze |
| 188 | + for x in range(height): |
| 189 | + for y in range(width): |
| 190 | + color = road_color if my_maze.map[x][y] is 0 else wall_color |
| 191 | + pyxel.rect(y * pixel, x * pixel, pixel, pixel, color) |
| 192 | + pyxel.rect(0, pixel, pixel, pixel, start_point_color) |
| 193 | + pyxel.rect((width - 1) * pixel, (height - 2) * pixel, pixel, pixel, end_point_color) |
| 194 | + |
| 195 | + if self.index > 0: |
| 196 | + # draw route |
| 197 | + offset = pixel / 2 |
| 198 | + for i in range(len(self.route) - 1): |
| 199 | + curr = self.route[i] |
| 200 | + next = self.route[i + 1] |
| 201 | + self.color = backtrack_color if curr in self.route[:i] and next in self.route[:i] else route_color |
| 202 | + pyxel.line(curr[0] + offset, (curr[1] + offset), next[0] + offset, next[1] + offset, self.color) |
| 203 | + pyxel.circ(self.route[-1][0] + 2, self.route[-1][1] + 2, 1, head_color) |
| 204 | + |
| 205 | + def check_death(self): |
| 206 | + if self.dfs_model and len(self.route) == len(self.dfs_route) - 1: |
| 207 | + self.death = True |
| 208 | + elif not self.dfs_model and len(self.route) == len(self.bfs_route) - 1: |
| 209 | + self.death = True |
| 210 | + |
| 211 | + def update_route(self): |
| 212 | + index = int(self.index / self.step) |
| 213 | + self.index += 1 |
| 214 | + if index == len(self.route): # move |
| 215 | + if self.dfs_model: |
| 216 | + self.route.append([pixel * self.dfs_route[index][0], pixel * self.dfs_route[index][1]]) |
| 217 | + else: |
| 218 | + self.route.append([pixel * self.bfs_route[index][0], pixel * self.bfs_route[index][1]]) |
| 219 | + |
| 220 | + |
| 221 | +App() |
0 commit comments