一、实验目的
1.BFS生成迷宫
2.A*算法寻找最短路径
3.可视化界面和人机交互按钮
二、实验内容
1.设计Block类用于生成障碍物
2.设计Map类用于生成地图和刷新地图
3.设计DrawMap类用于生成最短路径的路线
4.使用tkinter开发组件设计全局布局界面
三、实验过程
3.1 BLOCK类功能函数
创建队列,通过对周围的点的遍历,利用队列先进先出的特点,将需要扩展出去的点加入队列,进行扩展,然后移出队列。直到队列为空就可以完全遍历。
这里我们为了更好的操作,首先先创建Block类用于填充功能函数:
初始化操作,主要是初始化Wall的四个方向为空,继承self中墙体的位置,代码如下:
Get_next_block_pos()函数获取周围的目标,通过输入的点和方向,返回首位四个点的坐标情况,方便BFS遍历的时候不需要使用长篇的for循环和if判断,代码如下:
Get_next_block()函数时通过对四个方向的判断,遍历每个方向,如果没有超出边界,并且没有墙体,就返回空。如果碰到墙体,则将walls设置为false,并且将此时的方向和坐标信息反馈给Block类,记录墙体位置。代码如下:
3.2 BFS 生成迷宫
这里设置block_stack列表存放路径,这里block_stack将起始点存放入内,接下来的待扩展的节点也存放入内。
设置空集solution存放到达的节点,只要block_stack不为空,就从block_stack中取出一个元素block进行扩展。对这个block元素所有的邻居next_block,只要next_block不在solution中,说明没有到达过,就把next_block放入到block_stack里面,然后放到solution中标记为已经到达。
直道solution中放入目的地节点,循环结束。代码如下:
3.3 A*算法寻路
代码原理:A*吸取了Dijkstra 算法中的cost_so_far,为每个边长设置权值,不停的计算每个顶点到起始顶点的距离(G),以获得最短路线,
同时也汲取贪婪最佳优先搜索算法中不断向目标前进优势,并持续计算每个顶点到目标顶点的距离(Heuristic distance),以引导搜索队列不断想目标逼近,从而搜索更少的顶点,保持寻路的高效。
这里我们设置一个open表存放路径信息,设置openList表存放估值,将估值最小的点存放区index=0中,通过对open表和close表的检测,判断是否需要添加入openList中,这里还需要检测是否到达边界。
通过不断更新估值表中的值,将最优路径加入open中直到目的节点存入表中,循环解释,代码如下:
3.4 地图组件
地图模块组件,开始按钮 ,主要是反馈初始条件,调用寻路函数,答应solution中的路径信息,实现一键寻路。
刷新地图的操作这里调用tkinter组件delete将mmap中路径信息删除,然后调用draw_map重新生成新地图。我编写draw_end函数,将reset_map功能集成到tkinter界面的按钮中,用户可以点击change按键就可以i实现地图的刷新。
绘图模块,主要是画墙和画路线,墙体的数据全部都存放在wall数组中,我们通过draw_cell函数遍历数组,调用tkinter中的creat_line函数,画线。
路径数据我存放在solution中,和画墙一样的操作,代码如下:
四、成果展示
4.1 地图展示
通过tkinter组件我们成功的生成了迷宫,如下如所示:
4.2 寻路路线展示
通过A*算法我们生成了一条最短路径,并通过绘图组件展示,如下图:
4.3 刷新地图展示
点击change刷新地图,获得新的地图和新的路径,如下图所示:
代码展示
# -*- coding: utf-8 -*-
# 单文件版本
from __future__ import unicode_literals
import random
try:
from tkinter import *
except ImportError:
from tkinter import *
import matplotlib.image as mpimg # 用于显示图片
import numpy
class Block(object):
def __init__(self, mmap, x, y, direction=None):
super(Block, self).__init__()
# 初始定义四个方向都没有堵塞
self.walls = [True, True, True, True] # top,right,bottom,left
self.mmap = mmap
if mmap:
mmap.mmap[x][y] = self
self.x, self.y = x, y
# 根据算法设置堵塞的墙
if direction is not None:
direction = (direction + 2) % 4
self.walls[direction] = False
def __unicode__(self):
return "%s" % [1 if e else 0 for e in self.walls]
def __str__(self):
return unicode(self).encode('utf-8')
# 根据已有点的位置坐标,获取周围四个方向的坐标
def get_next_block_pos(self, direction):
x = self.x
y = self.y
if direction == 0: # Top
y -= 1
elif direction == 1: # Right
x += 1
if direction == 2: # Bottom
y += 1
if direction == 3: # Left
x -= 1
return x, y
def get_next_block(self):
directions = list(range(4)) # 四个方向
random.shuffle(directions) # 随机找一个方向
for direction in directions: # 每个方向遍历查找
x, y = self.get_next_block_pos(direction) # 下一个方向的坐标
if x >= self.mmap.max_x or x < 0 or y >= self.mmap.max_y or y < 0:
continue # 可以往下走
if self.mmap.mmap[x][y]: # if walked
continue # 走了
self.walls[direction] = False # 此路不通
return Block(self.mmap, x, y, direction) # 记住堵塞的位置
return None
class Map(object):
def __init__(self):
super(Map, self).__init__()
# 重画地图
def reset_map(self):
self.gen_map(self.max_x, self.max_y)
def reset_map_changesize(self, chang, kuan):
self.max_x = chang
self.max_y = kuan
self.gen_map(self.max_x, self.max_y)
# 生成地图
def gen_map(self, max_x=10, max_y=10):
self.max_x, self.max_y = max_x, max_y
self.mmap = [[None for j in range(self.max_y)] for i in range(self.max_x)]
self.solution = [] # 定义路线的列表
block_stack = [Block(self, 0, 0)] # a unused block
while block_stack:
block = block_stack.pop()
next_block = block.get_next_block()
if next_block:
block_stack.append(block)
block_stack.append(next_block)
# 如果到了终点,开始循环加载路线图
if next_block.x == self.max_x - 1 and next_block.y == self.max_y - 1: # is end
for o in block_stack:
self.solution.append((o.x, o.y)) # 将正确的路线记录下来
# python2用的
def __unicode__(self):
out = ""
for y in range(self.max_y):
for x in range(self.max_x):
out += "%s" % self.mmap[x][y]
out += "\n"
return out
# python3用的
def __str__(self):
return unicode(self).encode('utf-8')
class DrawMap(object):
def __init__(self, mmap, cell_width=10):
super(DrawMap, self).__init__()
self.mmap = mmap
self.cell_width = cell_width
# 地图尺寸
def get_map_size(self):
# width, height
return (self.mmap.max_x + 2) * self.cell_width, (self.mmap.max_y + 2) * self.cell_width
# 画地图的线
def create_line(self, x1, y1, x2, y2, **kwarg):
raise NotImplemented()
# 行进路线
def create_solution_line(self, x1, y1, x2, y2):
self.create_line(x1, y1, x2, y2)
def draw_start(self):
raise NotImplemented()
def draw_end(self):
raise NotImplemented()
def get_cell_center(self, x, y):
w = self.cell_width
return (x + 1) * w + w // 2, (y + 1) * w + w // 2
def draw_solution(self):
pre = (0, 0) # 起点位置
for o in self.mmap.solution:
p1 = self.get_cell_center(*pre) # 起始点位置
p2 = self.get_cell_center(*o) # 下一个点的位置
self.create_solution_line(p1[0], p1[1], p2[0], p2[1])
pre = o # 起始点为上一个点
def draw_cell(self, block):
width = self.cell_width
x = block.x + 1
y = block.y + 1
walls = block.walls
if walls[0]:
self.create_line(x * width, y * width, (x + 1) * width + 1, y * width)
if walls[1]:
self.create_line((x + 1) * width, y * width, (x + 1) * width, (y + 1) * width + 1)
if walls[2]:
self.create_line(x * width, (y + 1) * width, (x + 1) * width + 1, (y + 1) * width)
if walls[3]:
self.create_line(x * width, y * width, x * width, (y + 1) * width + 1)
# 画图
def draw_map(self):
for y in range(self.mmap.max_y):
for x in range(self.mmap.max_x):
self.draw_cell(self.mmap.mmap[x][y])
self.draw_start()
self.draw_end()
class TKDrawMap(DrawMap):
def __init__(self, mmap):
super(TKDrawMap, self).__init__(mmap, cell_width=10)
master = Tk() # 调用tkinter
master.title('汤海彤的迷宫')
master.geometry('500x300') # 这里的乘号不是 * ,而是小写英文字母 x
btn_start = Button(master, text='start', command=self.draw_solution)
btn_start.place(x=10, y=10)
btn_change = Button(master, text='change', command=self.reset_map)
btn_change.place(x=10, y=60)
width, height = self.get_map_size() # 获取尺寸大小
self.w = Canvas(master, width=width, height=width) # 调用Canvas
self.w.pack() # 显示
self.draw_map()
mainloop() # 循环
def ok(self):
change_size = self.var.get()
change_size2 = self.var2.get()
change_size = int(change_size)
change_size2 = int(change_size2)
self.mmap.reset_map_changesize(change_size, change_size2)
self.w.delete('all')
self.draw_map()
# 按开始处红点按钮,开始画路线图
def draw_start(self):
r = self.cell_width // 3
x, y = self.get_cell_center(0, 0)
# start = self.w.create_oval(x - r, y - r, x + r, y + r, fill="red")
start = self.w.create_rectangle(x - r, y - r, x + r, y + r, dash=(4, 4), fill='pink')
self.w.tag_bind(start, '<ButtonPress-1>', lambda e: self.draw_solution())
# 按结尾处红点按钮,更换地图
def draw_end(self):
r = self.cell_width // 3
x, y = self.get_cell_center(self.mmap.max_x - 1, self.mmap.max_y - 1)
end = self.w.create_oval(x - r, y - r, x + r, y + r, fill="red")
self.w.tag_bind(end, '<ButtonPress-1>', lambda e: self.reset_map())
# 重画地图
def reset_map(self):
self.mmap.reset_map()
self.w.delete('all')
self.draw_map()
def create_line(self, x1, y1, x2, y2, **kwargs):
self.w.create_line(x1, y1, x2, y2, **kwargs)
def create_solution_line(self, x1, y1, x2, y2):
self.create_line(x1, y1, x2, y2, fill="green")
def main():
m = Map() # 创建对象
m.gen_map(20, 20) # 调用方法,生成地图
TKDrawMap(m) # 画出路线图
if __name__ == '__main__':
main()