迷宫机器人最短路径使用tkinter绘制
起因
我想要写一个玩家和机器对战的迷宫游戏。这个项目我没有写完,我实现了最短机器人路径并绘制在tkinter上,以及玩家移动的功能。更多的关于GUI的设计太花时间了我没有写完。
算法介绍
我在写机器人路径时想到了学习强化学习时的方法,创建一个迷宫大小(height×width)的列表,对迷宫每一个可移动的位置设置一个价值,价值的具体值是这样得到的:机器人从终点开始(从起点也可以逻辑反着来)每移动一步就得到-1的奖励值,也就是说机器人应该以尽可能少的步数走完迷宫,因为这个奖励值在逻辑上其实惩罚,没走一步就得到-1的惩罚。终点的价值为0,这样和终点相邻的位置因为只需要移动一步所以价值就是-1(0+(-1)),移动两步就是-2(上一个位置的价值+奖励值)。这样使用广度优先算法遍历整个列表就可以得到每一个可移动的位置的价值。然后只需要让机器人从起点开始每一步都走价值最高的格子一直到终点就是最短路径了。
代码实现
我在上一篇博客已经实现了迷宫生成,之后我又改了一下把plt绘图单独放到了一个方法具体我更新在了github,下面的maze.generate_maze()
调用后会得到一个墙的列表,可以使用maze.edges
访问
maze = Maze(height=height, width=width)
maze.generate_maze()
使用tkinter绘制迷宫
这里老师的要求是使用tkinter,所以我对绘制迷宫做了如下更改,目的是使绘的图变的大一点,比如一个50*50的迷宫大小绘制到屏幕上会非常小,ratio就是就是按比例放大的倍数。can_line
先不要管,在图上画线时也要等比例放大edge[0] * ratio
后面又加了ratio变成了这样edge[0] * ratio + ratio
是因为使用tkinter绘图时y为0的坐标画不出来所以加上ratio
window = tk.Tk()
window.title("简单绘画")
height_can = 0;
width_can = 0;
ratio = 0
while height_can < 500 or width_can < 600:
ratio += 1
height_can = maze.HEIGHT * ratio
width_can = maze.WIDTH * ratio
maze_can = tk.Canvas(window, width=width_can + ratio, height=height_can + ratio)
maze_can.pack()
can_line = [(ratio, ratio, ratio, 1 * ratio + ratio),
(maze.WIDTH * ratio + ratio, (maze.HEIGHT - 1) * ratio + ratio, maze.WIDTH * ratio + ratio,
maze.HEIGHT * ratio + ratio)]
for edge in maze.edges:
q = (edge[0] * ratio + ratio, edge[1] * ratio + ratio, edge[2] * ratio + ratio, edge[3] * ratio + ratio)
can_line.append(q)
maze_can.create_line((q[0], q[1]), (q[2], q[3]), width=2)
绘制玩家起始位置
maze_can.x_coordinate
是起始开始的x坐标,也就是玩家的起始坐标,迷宫的入口左上角的坐标,使用maze_can.create_oval
绘圆只需要提供圆的左上角和右下角坐标就可以了,这是tkinter自带的方法不是我自己写的。maze_can.x_coordinate, maze_can.y_coordinate
是左上角坐标,maze_can.x_coordinate + ratio, maze_can.y_coordinate + ratio
是右下角坐标。bind
方法的用处看下一段
maze_can.x_coordinate = ratio
maze_can.y_coordinate = ratio
maze_can.player = maze_can.create_oval(maze_can.x_coordinate, maze_can.y_coordinate,
maze_can.x_coordinate + ratio, maze_can.y_coordinate + ratio,
width=1, fill='red')
window.bind('<Left>', left_arrow)
window.bind('<Right>', right_arrow)
window.bind('<Up>', up_arrow)
window.bind('<Down>', down_arrow)
下面代码的目的是让玩家可以移动
上面的都是绘图,下面则要开始实现一些逻辑,玩家想要移动首先需要判断往哪个方向可以移动,这个问题实现在left_move(x_coordinate, y_coordinate)
。这个问题简化下来比如我们想要向左移动,只需要知道当前位置的左上角坐标和左下角坐标是否在墙的列表can_line
中,如果在里面则不能向左移动,can_line
是maze.edges
的坐标等比例放大,目的也是绘图...其实逻辑上反而是花时间比较少的,绘图才花时间。判断方向应该有4个函数,这里写出来的是其中一个能否向左移动。left_arrow
是把事件绑定到左箭头上,如果可以移动则删除当前位置的圆在移动的位置画一个圆,目的是像游戏一样有交互感
def left_move(x_coordinate, y_coordinate):
x = (x_coordinate, y_coordinate,
x_coordinate, y_coordinate + ratio)
if x not in can_line:
return True
return False
def left_arrow(event):
if left_move(maze_can.x_coordinate, maze_can.y_coordinate):
maze_can.x_coordinate -= ratio
maze_can.delete(maze_can.player)
maze_can.player = maze_can.create_oval(maze_can.x_coordinate, maze_can.y_coordinate,
maze_can.x_coordinate + ratio, maze_can.y_coordinate + ratio,
width=1, fill='red')
初始化价值列表
maze_can.agent
一开始我是打算让机器人一步一步走到出口的,但太花时间了,也就是这段代码可以删掉没有一丝影响,在后面我直接画出了路径没有用到这个属性。好了,要开始写最短路径的机器人了!首先我们创建一个和迷宫相同大小的列表agent_list
然后把每一个值赋值-100000这个值可以是任意的只要别太大就行,比如-100就不行,因为迷宫很大时迷宫入口的价值很可能小于-100。然后在出口位置agent_list[height - 1][width - 1]
赋值为0代表出口位置的价值为0。valid_queue
是用来广度优先搜索的列表,其实在逻辑上是一个队列,先加入列表的先遍历,后加入列表的一个一个往后排
maze_can.agent = maze_can.create_oval(maze_can.x_coordinate, maze_can.y_coordinate,
maze_can.x_coordinate + ratio, maze_can.y_coordinate + ratio,
width=1, fill='blue')
agent_list = np.zeros((height, width))
agent_list -= 100000
agent_list[height - 1][width - 1] = 0
valid_queue = []
valid_queue.append((height - 1, width - 1))
下面的目的是得到迷宫每个位置的价值
get_queue(height_index, width_index)
函数的目的是获得与当前位置相邻且可移动(没有墙的阻碍)的位置坐标(准确的说是索引,在后面的其他函数会转换为坐标)。reward_value
是得到所有的与当前位置相邻且可移动位置的价值(最大是4个,代表上下左右),结果是一个字典比如{-13:'left'}
,之所以是字典因为后面还有一个逻辑要知道这个价值对应的方向。后面的for循环里的逻辑是把当前位置的价值设置为上面列表里最大值-1的值,即最大值+奖励值。然后遍历队列里的下一个位置。这段代码执行结束后就会得到迷宫每个位置的价值并存储在agent_list
中
def get_queue(height_index, width_index):
if left_move((width_index + 1) * ratio, (height_index + 1) * ratio) and agent_list[height_index][
width_index - 1] == -100000:
valid_queue.append((height_index, width_index - 1))
if right_move((width_index + 1) * ratio, (height_index + 1) * ratio) and agent_list[height_index][
width_index + 1] == -100000:
valid_queue.append((height_index, width_index + 1))
if up_move((width_index + 1) * ratio, (height_index + 1) * ratio) and agent_list[height_index - 1][
width_index] == -100000:
valid_queue.append((height_index - 1, width_index))
if down_move((width_index + 1) * ratio, (height_index + 1) * ratio) and agent_list[height_index + 1][
width_index] == -100000:
valid_queue.append((height_index + 1, width_index))
def reward_value(height_index, width_index):
i = {}
if left_move((width_index + 1) * ratio, (height_index + 1) * ratio):
i[agent_list[height_index][width_index - 1]] = 'left'
if right_move((width_index + 1) * ratio, (height_index + 1) * ratio):
i[agent_list[height_index][width_index + 1]] = 'right'
if up_move((width_index + 1) * ratio, (height_index + 1) * ratio):
i[agent_list[height_index - 1][width_index]] = 'up'
if down_move((width_index + 1) * ratio, (height_index + 1) * ratio):
i[agent_list[height_index + 1][width_index]] = 'down'
return i
for index in valid_queue:
get_queue(index[0], index[1])
if agent_list[index[0]][index[1]] == -100000:
nearby_dic = reward_value(index[0], index[1])
agent_list[index[0]][index[1]] = max(nearby_dic.keys()) - 1
这时如果打印agent_list
,假如迷宫是5*5大小则可以得到下图
下面是在图上画出最短路径
使机器人在入口位置然后每一步都走价值最高的格子,get_path(height_index, width_index)
是得到这样的格子。x = index[1] * ratio + 1.3 * ratio
以及后面的绘图代码都是为了矩形在图里小一点,起到美观的作用,不然太丑了。
def get_path(height_index, width_index):
nearby_dic = reward_value(height_index, width_index)
direction = nearby_dic[max(nearby_dic.keys())]
if direction == 'left':
path_queue.append((height_index, width_index - 1))
if direction == 'right':
path_queue.append((height_index, width_index + 1))
if direction == 'up':
path_queue.append((height_index - 1, width_index))
if direction == 'down':
path_queue.append((height_index + 1, width_index))
path_queue = [(0, 0)]
for index in path_queue:
x = index[1] * ratio + 1.3 * ratio
y = index[0] * ratio + 1.3 * ratio
maze_can.create_rectangle(x, y, x + 0.4 * ratio, y + 0.4 * ratio, fill='green', outline='green')
if index == (height - 1, width - 1):
break
get_path(index[0], index[1])
window.mainloop()
假如一个50*50的迷宫最终结果是这样的
完整的代码我放在了这里agent_maze.py
迷宫机器人最短路径使用tkinter绘制的更多相关文章
- 编程算法 - 迷宫的最短路径 代码(C++)
迷宫的最短路径 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定一个大小为N*M的迷宫. 迷宫由通道和墙壁组成, 每一步能够向邻接的上下 ...
- 数据结构实习 Problem H 迷宫的最短路径
数据结构实习 Problem H 迷宫的最短路径 题目描述 设计一个算法找一条从迷宫入口到出口的最短路径. 输入 迷宫的行和列m n 迷宫的布局 输出 最短路径 样例输入 6 8 0 1 1 1 0 ...
- 用Q-learning算法实现自动走迷宫机器人
项目描述: 在该项目中,你将使用强化学习算法,实现一个自动走迷宫机器人. 如上图所示,智能机器人显示在右上角.在我们的迷宫中,有陷阱(红色炸弹)及终点(蓝色的目标点)两种情景.机器人要尽量避开陷阱.尽 ...
- hdu1728逃离迷宫 (利用最短路径思想+优先队列(BFS))
Problem Description 给定一个m × n (m行, n列)的迷宫,迷宫中有两个位置,gloria想从迷宫的一个位置走到另外一个位置,当然迷宫中有些地方是空地,gloria可以穿越,有 ...
- 迷宫的最短路径 (BFS)
N*M的迷宫,从起点到终点,求最短距离 宽度优先搜索按照距开始状态由近及远的顺序进行搜索,因此可以很容易的用来求最短路径,最少操作之类问题的答案. (可以构造成pair或者编码成int来表达状态) ...
- 广度优先搜索(BFS)——迷宫的最短路径
宽度优先搜索按照距开始状态由近到远的顺序进行搜索,因此可以很容易的用来求最短路径,最少操作之类问题的答案. 宽度优先搜索介绍(一篇不错的文章). 题目描述: 给定一个大小为N*M的迷宫.迷宫有通道和墙 ...
- BFS求解迷宫的最短路径问题
题目:给定一个大小为N*M的迷宫,迷宫由通道('.')和墙壁('#')组成,其中通道S表示起点,通道G表示终点,每一步移动可以达到上下左右中不是墙壁的位置.试求出起点到终点的最小步数.(本题假定迷宫是 ...
- 【原】使用Tkinter绘制GUI并结合Matplotlib实现交互式绘图
在数据分析的过程中,往往需要对所建立的模型进行可视化,并调整其中的某些参数. 通常情况下,在Python中可以通过Matplotlib来进行绘制图像.然而该绘制过程是静态的,也就是每次调整完参数需要重 ...
- 挑战程序设计——迷宫的最短路径(BFS)
题目详情 Description 给定一个大小为 N * M 的迷宫.迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动.请求出从起点到终点所需的最小步数 限制条件: N,M <= ...
- 51nod 1459 迷宫游戏 (最短路径—Dijkstra算法)
题目链接 中文题,迪杰斯特拉最短路径算法模板题. #include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f ],v ...
随机推荐
- 一次 Redis 事务使用不当引发的生产事故
这是悟空的第 170 篇原创文章 官网:http://www.passjava.cn 你好,我是悟空. 本文主要内容如下: 一.前言 最近项目的生产环境遇到一个奇怪的问题: 现象:每天早上客服人员在后 ...
- 【JavaWeb】学习笔记——Tomcat集成
简介 Tomcat是基于Java的一个开放源代码.运行servlet和JSP Web应用的Web应用软件容器,又称servlet容器 安装 官方网站:https://tomcat.apache.org ...
- Landau-Vishkin
基础算法 假设我们有两个字符串:,每个字符串由A C G T四个字母组成. 在两个字符串上,都有三种可能的编辑操作(突变): 删除某个字符 在某个位置插入字符 改变某个字符 每一个编辑操作都有惩罚值. ...
- 题解 AT2361 [AGC012A] AtCoder Group Contest
\(\sf{Solution}\) 显然要用到贪心的思想. 既然最终的结果只与每组强度第二大选手有关,那就考虑如何让他的值尽可能大. 其实,从小到大排个序就能解决,越靠后的值越大,使得每组强度第二大选 ...
- 在FreeSQL中实现「触发器」和软删除功能
前言 最近做新项目,技术栈 AspNetCore + FreeSQL 这个ORM真的好用,文档也很完善,这里记录一下两个有关「触发器」的功能实现 修改实体时记录更新时间 模型代码 我的模型都是基于这个 ...
- Debian安装WPS的方法
1.防止安装失败,请尽量重启电脑,关闭系统的软件商店,因为商店的权限可能会锁住pkg的配置文件,导致无法安装wps. 2.将原机残废的WPS卸载干净,卸载方法:手动或命令行操作. sudo apt r ...
- day11-Servlet01
Servlet01 官方api文档:https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html Servlet和Tomcat的关系:一 ...
- 对比entrypoint以及cmd
如何查看 现有 images 的 入口信息 docker image inspect image_id { "entrypoint": "xxx" " ...
- C++实现真值表
这一片文章主要是关于真值表,在完成之前我也遇到了许多问题.比如怎么去求解表达式的值,怎么去将每个变量进行赋值,也就是如何 将n个字符进行01全排列. 01全排列真的神奇,01全排列其实就是2^n.他可 ...
- 【云原生 · Kubernetes】Kubernetes运维
(1)Node的隔离与恢复 在硬件升级.硬件维护等情况下,需要将某些Node隔离.使用kubectl cordon <node_name>命令可禁止Pod调度到该节点上,在其上运行的Pod ...