在《Problem Solving with Algorithms and Data Structures》一书的141页,作者给出了一个迷宫寻路的程序。程序的核心是一段递归程序,通过不断尝试,找到从起点到终点的路。虽然不是最短路径,但程序可以动态显示寻路的过程,对于理解递归过程非常有帮助。
书中给出的程序见“http://download.csdn.net/detail/pospro/8952965”,运行之后就会发现,由于是基于turtle作图(注:turtle是python自带的一个绘图函数集),画迷宫本身就要花费太多时间,让人无法一下抓住核心。
在这里,PosPro引入pygame取代原有的turtle,大大降低了载入迷宫所花费的时间。同时利用pygame的函数产生了可以媲美turtle的动态效果。全部程序及相关资源见:http://download.csdn.net/detail/pospro/8947365
程序运行后的效果如下:
下面对程序进行逐段分析:
首先建立一个迷宫类(Maze),包含四个函数:构造函数__init__, 实现索引取数的__getitem,判断是否是迷宫出口的isExit,以及动态显示变化的updateScreen
构造函数的详细代码如下。这一段代码完成了以下工作:
1.按照pygame的要求,对屏幕进行初始化,
2.读入迷宫的点阵文件
3.根据读入的点阵文件,确定墙的的位置,出发点的位置。
def __init__(self, mazeDotFile): pygame.init() #为了简化,这里直接给定了迷宫的行列值,其实也可以在读入文件时动态判断 mazeColumn=22 mazeRow=11 self.mazeColumn=mazeColumn self.mazeRow=mazeRow brickImg = pygame.image.load("brick.png") #每个砖块是40*40 startflagImg = pygame.image.load("startflag.png") # 30*30 self.mazeScreen=pygame.display.set_mode((40*mazeColumn, 40*mazeRow)) self.mazeScreen.fill((255,255,255)) self.startRow=0 self.startCol=0 self.mazeList = [] #用于保存从文件中读入的迷宫点阵 maze_file = open(mazeDotFile,'r') for line in maze_file: row_list = [] for ch in line: #原文是line[: -1],但报错;估计是line读入时已自动去掉最后的换行符? row_list.append(ch) self.mazeList.append(row_list) maze_file.close() for y in range(mazeRow): for x in range(mazeColumn): #print self.mazeList[y][x], if self.mazeList[y][x]==OBSTACLE: self.mazeScreen.blit(brickImg,(x*40,y*40)) #注意这里有个容易让人混淆的地方!注意屏幕向右,即Column方向为x方向,向下为y方向!! elif self.mazeList[y][x]=='S': self.startRow=y self.startCol=x self.mazeScreen.blit(startflagImg,(x*40+5, y*40+5))
迷宫文件只是个简单的文本文件,22行11列,用+表示墙的位置,用空格表示通道,用S表示起点,示例如下:
++++++++++++++++++++++ + + + +++ ++ + +++ + + + ++++ ++++++++++++++ + + + ++ + +++ + ++++++++++++ +++ + S ++ + + ++++++++++++++++++++++
def __getitem__(self, idx): return self.mazeList[idx]
def isExit(self, row, col): return (row==0 or row==self.mazeRow-1 or col==0 or col==self.mazeColumn-1)
最后一个,也是最核心的函数,主要思路就是在被调用时,在当前位置画一个圆,其颜色由改点的属性决定(通路?死路?已经试过的路?),还有需要调用pygame的wait和flip函数,以便让这种变化动态的和及时反映出来。
def updateScreen(self, row, col, val=None): #如果通过val传递了值,则将值填入对应位置 if val: self.mazeList[row][col] = val #根据val值,确定颜色 if val == PART_OF_PATH: color = (0,255,0) elif val == OBSTACLE: color = (255,0,0) elif val == TRIED: color = (0,0,255) elif val == DEAD_END: color = (255,0,0) else: color = None #之所以val控制颜色,是为了保证执行Step01时,不会在墙上画个圈 if color: pygame.draw.circle(self.mazeScreen, color, (col*40+20, row*40+20), 5) #等待一段时间,以便动态显示(不然会很快完成,体现不出颜色变化) pygame.time.wait(50) pygame.display.flip()
下面就是程序的算法核心——递归法寻路,用一个单独的函数来实现(程序分析已在注释中体现):
#本函数用递归方法寻找迷宫出路,顺序是先向北,再向东,南,西 def findRoute(maze, currentRow, currentCol): #Step01首先显示已到此点,也即在此处画一个圈 maze.updateScreen(currentRow, currentCol) #以下设置递归(退出)的条件,肯能存在好几种可能 # 1.该点为墙 2.如果该点已检查过或是死胡同 3.改点是出口 4.非1~3的情况 if maze[currentRow][currentCol]==OBSTACLE: return False if maze[currentRow][currentCol]==TRIED or maze[currentRow][currentCol]==DEAD_END: return False #程序走到这里已经可以判定改点不是墙了,所以一旦该点的位置是边沿,则必是出口 # 故该点定为路径上的一点 # 若该点不是边沿,只能标记为已到过,并且继续尝试 if maze.isExit(currentRow,currentCol): maze.updateScreen(currentRow,currentCol,PART_OF_PATH) return True # 若该点不是边沿,只能标记为已到过,并且继续尝试 maze.updateScreen(currentRow,currentCol,TRIED) routeFound=findRoute(maze,currentRow-1,currentCol) or \ findRoute(maze,currentRow,currentCol+1) or \ findRoute(maze,currentRow+1,currentCol) or \ findRoute(maze,currentRow,currentCol-1) if routeFound: #如果从此点递归出去找到了出口,则此点必在路径上 maze.updateScreen(currentRow,currentCol,PART_OF_PATH) else: maze.updateScreen(currentRow,currentCol,DEAD_END) return routeFound
def main(): myMaze=Maze('maze2.txt') # 以下是个保持屏幕正常显示和退出的最小循环体 findRoute(myMaze, myMaze.startRow, myMaze.startCol) while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit(0) pygame.display.flip() # 执行程序 main()
PS:
书中原程序下载位置(用Turtle函数实现):http://download.csdn.net/detail/pospro/8952965
经修改完善后的例子(用pygame实现,也即上文所用程序代码):http://download.csdn.net/detail/pospro/8947365