代码如下:
#coding=utf-8
import random, sys, time
from Tkinter import *
def timing(out_prefix, func, *args, **kwargs):
print("%s:timing start..."%(out_prefix))
t1 = time.time()
r = func(*args, **kwargs)
t2 = time.time()
print("%s:%ss"%(out_prefix, t2 - t1))
return r
class UI:
def __init__(self, maze):
self.maze = maze
self.w, self.b = 1, 2
self.root, self.canvas = None, None
def output_cell(self, clac_func):
self.root = Tk()
self.root.title('MAZE')
self.canvas = Canvas(self.root, width=self.maze.width*self.w*4+self.w+self.b*2-4, height=self.maze.heigh*self.w*4+self.w+self.b*2-4)
self.canvas.create_rectangle(self.b, self.b, self.maze.width*self.w*4+self.w+self.b, self.b+self.w, fill='dimgray', width = 0)
self.canvas.create_rectangle(self.b, self.b, self.b+self.w, self.maze.heigh*self.w*4+self.w+self.b, fill='dimgray', width = 0)
for y in range(self.maze.heigh):
for x in range(self.maze.width):
if not self.maze.cells[x][y].is_open(Cell.EDGE_RIGHT):
self.canvas.create_rectangle(self.b+(x+1)*self.w*4, self.b+y*self.w*4, self.b+(x+1)*self.w*4+self.w, self.b+y*self.w*4+self.w*5, fill='dimgray', width = 0)
if not self.maze.cells[x][y].is_open(Cell.EDGE_BOTTOM):
self.canvas.create_rectangle(self.b+x*self.w*4, self.b+(y+1)*self.w*4, self.b+x*self.w*4+self.w*5, self.b+(y+1)*self.w*4+self.w, fill='dimgray', width = 0)
self.canvas.pack()
self.root.bind('<Button-1>', clac_func)
self.root.mainloop()
def output_path(self, paths, entrance, exit):
self.canvas.create_rectangle(self.b+entrance[0]*self.w*4+self.w, self.b+entrance[1]*self.w*4+self.w, self.b+entrance[0]*self.w*4+self.w+3*self.w, self.b+entrance[1]*self.w*4+self.w+3*self.w, fill='lime', width = 0)
self.canvas.create_rectangle(self.b+exit[0]*self.w*4+self.w, self.b+exit[1]*self.w*4+self.w, self.b+exit[0]*self.w*4+self.w+3*self.w, self.b+exit[1]*self.w*4+self.w+3*self.w, fill='fuchsia', width = 0)
line_colors = ['LightSkyBlue4', 'LightYellow2', 'pale goldenrod', 'SlateGray1', 'PaleVioletRed4', 'DarkOliveGreen2', 'SlateGray2', 'khaki3', 'PaleVioletRed3', 'DeepPink3', 'gold3', 'honeydew4', 'snow2', 'OliveDrab1', 'medium slate blue', 'DeepPink2', 'DeepSkyBlue3', 'coral3', 'plum3', 'goldenrod2']
line_color = line_colors[random.randint(0, len(line_colors) - 1)]
for p in paths:
for i in range(len(p)):
x0, y0 = p[i]
if len(p) <= i + 1:
break
x1, y1 = p[i+1]
if x0 > x1:
x0, x1 = x1, x0
if y0 > y1:
y0, y1 = y1, y0
p1x, p1y, p2x, p2y = self.b+x0*self.w*4+self.w*2, self.b+y0*self.w*4+self.w*2, self.b+x1*self.w*4+self.w*3, self.b+y1*self.w*4+self.w*3
self.canvas.create_rectangle(p1x, p1y, p2x, p2y, fill=line_color, width = 0)
self.root.update()
time.sleep(0.01)
class Cell:
EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM = 1, 2, 4, 8
EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM
EDGES = [EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM]
__slots__ = ('edges', 'branch_no')
def __init__(self):
self.edges = self.EDGE_ALL
self.branch_no = 0
def is_open(self, edge):
return self.edges & edge != edge
def open_edge(self, edge, branch_no):
if self.is_open(edge):
return False
self.edges = self.edges & (~edge)
if self.branch_no == 0:
self.branch_no = branch_no
return True
def get_branch_no(self):
return self.branch_no
class Maze:
def __init__(self, width, heigh):
self.width, self.heigh = int(width), int(heigh)
self.cells = [[Cell() for y in range(heigh)] for x in range(width)]
self.valid = self.width > 0 and self.heigh > 0 and self.width + self.heigh > 2
if not self.valid:
print('args for constructor wrong, the new Maze objest is invalid!')
return
timing('generate', self._generate)
self.ui = UI(self)
self.ui.output_cell(lambda e: self.calc((random.randint(0, self.width - 1), random.randint(0, self.width - 1)), (random.randint(0, self.heigh - 1), random.randint(0, self.heigh - 1))))
timing('generate', self._generate)
def calc(self, entrance, exit):
entrance = int(max(0, min(self.width-1, entrance[0]))), int(max(0, min(self.heigh-1, entrance[1])))
exit = int(max(0, min(self.width-1, exit[0]))), int(max(0, min(self.heigh-1, exit[1])))
paths = self._calc(entrance, exit)
self.ui.output_path(paths, entrance, exit)
def _generate(self):
left = [(x, y) for x in range(self.width) for y in range(self.heigh)]
edges = Cell.EDGES
branch_no = 0
while len(left):
x, y = left[random.randint(0, len(left) - 1)]
branch_no, branchs = branch_no + 1, []
while True:
if (x, y) not in branchs:
branchs.append((x, y))
left.remove((x, y))
random.shuffle(edges)
for e in edges:
if self._open_edge(x, y, e, branch_no):
x, y, e = self._get_neighbor(x, y, e)
break
else:
if branch_no == 1:
break
x, y = branchs[random.randint(0, len(branchs) - 1)]
continue
if self.cells[x][y].is_open(~e & Cell.EDGE_ALL):
break
def _calc(self, entrance, exit):
return timing('calc_bfs',self._calc_bfs, entrance, exit)
return timing('calc_dfs',self._calc_dfs, entrance, exit)
def _open_edge(self, x, y, e, branch_no):
if not self._check_open_edge(x, y, e, branch_no):
return False
if not self.cells[x][y].open_edge(e, branch_no):
return False
self._open_neighbors(x, y, e, branch_no)
return True
def _check_open_edge(self, x, y, e, branch_no):
if x < 0 or x >= self.width or y < 0 or y >= self.heigh:
return False
for edge in Cell.EDGES:
if e & edge:
x1, y1, e1 = self._get_neighbor(x, y, edge)
if x1 is None or y1 is None or e1 is None or branch_no == self.cells[x1][y1].get_branch_no():
return False
return True
def _open_neighbors(self, x, y, e, branch_no):
for edge in Cell.EDGES:
if e & edge:
self._open_neighbor(x, y, edge, branch_no)
def _open_neighbor(self, x, y, e, branch_no):
x, y, e = self._get_neighbor(x, y, e)
if x is None or y is None or e is None:
return False
return self.cells[x][y].open_edge(e, branch_no)
def _get_neighbor(self, x, y, e):
if e == Cell.EDGE_LEFT:
x, y, e = x - 1, y, Cell.EDGE_RIGHT
elif e == Cell.EDGE_TOP:
x, y, e = x, y - 1, Cell.EDGE_BOTTOM
elif e == Cell.EDGE_RIGHT:
x, y, e = x + 1, y, Cell.EDGE_LEFT
elif e == Cell.EDGE_BOTTOM:
x, y, e = x, y + 1, Cell.EDGE_TOP
else:
return None, None, None
if x < 0 or x >= self.width or y < 0 or y >= self.heigh:
return None, None, None
return x, y, e
def _calc_bfs(self, entrance, exit): #广度优先
paths = []
idx, todos, searchs = 0, [(entrance[0], entrance[1], 0)], [[None for y in range(self.heigh)] for x in range(self.width)]
while idx < len(todos):
x, y, edge_from = todos[idx]
idx += 1
for e in Cell.EDGES:
if e == edge_from:
continue
if self.cells[x][y].is_open(e):
x1, y1, e1 = self._get_neighbor(x, y, e)
searchs[x1][y1] = (x, y)
if (x1, y1) == exit:
path = [(x1, y1)]
prev =searchs[x1][y1]
while prev:
path = [prev] + path
if prev == entrance:
break
prev = searchs[prev[0]][prev[1]]
paths.append(path)
else:
todos.append((x1, y1, e1))
return paths
def _calc_dfs(self, entrance, exit): #深度优先
sys.setrecursionlimit(1000000) #设置递归深度
def __calc(paths, path, x, y, edge_from = 0):
def __add_path(path, i):
path.append((i[0], i[1]))
if i[0] == exit[0] and i[1] == exit[1]:
paths.append(path)
else:
__calc(paths, path, i[0], i[1], i[2])
next = []
for e in Cell.EDGES:
if e == edge_from:
continue
if self.cells[x][y].is_open(e):
x1, y1, e1 = self._get_neighbor(x, y, e)
next.append((x1, y1, e1))
if len(next):
if len(next) > 1:
for i in next:
path1 = [p for p in path]
__add_path(path1, i)
else:
__add_path(path, next[0])
paths = []
__calc(paths, [entrance], entrance[0], entrance[1])
return paths
def main():
m = Maze(80, 50)
if __name__ == '__main__':
main()
运行结果:
1. 求解的过程有两种方式深度优先(DFS)和广度优先(BFS),DFS方式使用了递归,一旦迷宫的行列数过大的时候递归层数太深就会触发python的异常(默认递归深度是1000),所以需要设置递归深度,相比之下BFS性能更好。
2. 生成迷宫的算法效率并不是很高,测试了下,生成一个400x300的大迷宫,需要耗时大概1min的样子。
3. 此随机生成的迷宫所有的节点都是连通的,并且是唯一路径连通,所以生成完成之后,以任何点为起终点都可以。