学习目标:
学习内容:
迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控 制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置 到迷宫出口的最优路径。设计交互友好的游戏图形界面。
学习成果:
1.迷宫生成和求解的程序。
使用深度优先搜索算法来生成迷宫,并根据给定的起点和终点求解出迷宫的路径。
代码中的Point
类表示迷宫中的一个点,具有行和列两个属性。copy()
方法用于复制一个点。
代码最开始定义了一些变量和初始化工作,包括窗口大小、背景颜色等。然后定义了三个函数:lu_q()
、qi_l()
和xiangling()
。
lu_q()
函数根据给定的点返回该点周围墙的位置。它遍历墙的列表,判断周围是否有墙与给定点相邻,如果有则将其加入到一个列表中并返回。
qi_l()
函数根据给定的点返回该点周围路的位置。它遍历路的列表,判断周围是否有路与给定点相邻,如果有则将其加入到一个列表中并返回。
xiangling()
函数根据给定的点返回该点周围路中距离最远的点。它遍历路的列表,判断周围是否有路与给定点相隔一个墙,如果有则将其加入到一个列表中。然后根据当前点在路列表中的位置,将该点从路列表中删除,并返回找到的点列表。
接下来是创建迷宫的过程。首先创建了墙的列表Qiang
,该列表包含了迷宫内所有的墙。然后创建了路的列表lu
,该列表包含了迷宫内所有的路。两个循环分别遍历每个位置,将符合条件的墙和路加入到对应的列表中。
然后根据一定的规则,将一些墙从墙列表中删除,以确保迷宫的连通性。处理完之后,定义了起点和路径列表,并将起点加入到路径列表和临时列表中。
2.实现迷宫的路径求解和绘制功能。
首先定义了目标点zhongdian和一个空列表lujin用于存储找到的路径。然后定义了变量x,并进入一个循环,循环条件是len(liebiao)不为0。
在每次循环中,使用random.randint(1, len(liebiao))随机选取一个索引值,将对应位置的点加入到路径列表lujing中。然后使用另一个循环遍历lujing列表中的点,并判断其相邻的点是否在路径列表lujing中。根据不同情况,将相邻点的位置添加到路径列表lujin中。最后调用xiangling()函数,获取与选取点距离最远的点,并将获取到的点列表拼接到liebiao列表末尾。
完成上述循环后,将路径列表lujin和之前生成的路径列表lujing拼接到迷宫路的列表lu中。
接下来,定义了一个函数rect()用于绘制迷宫地图的小方格。函数通过计算方格的左上角坐标和大小,调用pygame.draw.rect()方法绘制方格。
然后定义了函数rect2()用于绘制人物所在的位置。函数通过计算人物所在方格的左上角坐标,调用window.blit()方法绘制人物。
接下来定义了变量xx和yy用于表示人物的起始位置,以及空列表jilu_ren用于记录人物的移动轨迹。在迷宫中,人物的起始位置通常是(1, 1)。
最后,定义了函数Index()用于判断某个点是否在迷宫路径列表lu中。函数通过遍历路径列表,匹配行和列均相等的点,并返回布尔值。
这部分代码实现了求解迷宫路径和绘制迷宫地图、人物位置的功能。可能还有其他相关代码未包含在此处。
3.寻找最短路径的算法。
首先,定义了一个二维数组num,通过列表生成式初始化为全1的矩阵。然后,使用两个嵌套的循环遍历num中的每个元素,并通过Index函数判断是否为墙,如果是墙则将对应位置的元素设为0。接下来,定义了一个名为xunlu的函数,该函数实现了最短路径的寻找。在这个函数中,通过numpy库的mat函数将传入的列表转化为矩阵,然后定义了一系列辅助函数。在寻找最短路径的过程中,使用了A*算法,通过扩展节点、生成节点和回退节点的操作来逐步搜索到最短路径。最后,根据寻找结果将路径以及地图的状态进行打印输出。
在主循环中,使用了pygame库来可视化地展示寻找最短路径的过程。根据不同的按键事件,可以控制角色的移动以及选择不同的最短路径长度比例进行显示。
学习产出:
import pygame
import random
import numpy as np
import time
class Point:
def __init__(self, row=0, col=0):
self.row = row
self.col = col
def copy(self):
return Point(self.row, self.col)
w = 707
h = 707
Row = 101
Col = 101
size = (w, h)
pygame.init()
window = pygame.display.set_mode(size)
pygame.display.set_caption("迷宫")
bak_color = (0, 0, 0)
showWindow = True
showjiesu = True
Shengli = False
clock = pygame.time.Clock() # 时钟控制
#深度优先搜索
#整体思路是通过不断地将路径上的点加入到liebiao列表中,
#并利用lu_q()、qi_l()和xiangling()函数来扩展路径。
#路 内部循环墙列表
def lu_q(point):
a = []
for i in range(0, len(Qiang)):
if point.col - 1 == Qiang[i].col and point.row == Qiang[i].row:
a.append(Qiang[i])
if point.col + 1 == Qiang[i].col and point.row == Qiang[i].row:
a.append(Qiang[i])
if point.col == Qiang[i].col and point.row - 1 == Qiang[i].row:
a.append(Qiang[i])
if point.col == Qiang[i].col and point.row + 1 == Qiang[i].row:
a.append(Qiang[i])
return a
#根据给定的点,返回其周围的路(lu)的位置。
def qi_l(point):
a = []
for i in range(0, len(lu)):
if point.col - 1 == lu[i].col and point.row == lu[i].row:
a.append(lu[i])
if point.col + 1 == lu[i].col and point.row == lu[i].row:
a.append(lu[i])
if point.col == lu[i].col and point.row - 1 == lu[i].row:
a.append(lu[i])
if point.col == lu[i].col and point.row + 1 == lu[i].row:
a.append(lu[i])
return a
#根据给定的点,返回其周围的路(lu)中距离最远的点
def xiangling(point):
global s
a = []
for i in range(0, len(lu)):
if point.col - 2 == lu[i].col and point.row == lu[i].row:
a.append(lu[i])
if point.col + 2 == lu[i].col and point.row == lu[i].row:
a.append(lu[i])
if point.col == lu[i].col and point.row - 2 == lu[i].row:
a.append(lu[i])
if point.col == lu[i].col and point.row + 2 == lu[i].row:
a.append(lu[i])
for j in range(0, len(lu)):
if point.col == lu[j].col and point.row == lu[j].row:
s = j
genghuan = []
for i in range(0, len(a)):
for j in liebiao:
if a[i].col == j.col:
if a[i].row == j.row:
genghuan.append(i)
genghuan.sort(reverse=True)
for i in genghuan:
a.pop(i)
if a != None:
lu.pop(s)
return a
Qiang = []
for i in range(1, Row - 1):
for j in range(1, Col - 1):
Qiang.append(Point(i, j))
lu = []
for i in range(1, Row, 2):
for j in range(1, Col, 2):
lu.append(Point(i, j))
lu2 = lu
genghuan = []
for i in range(0, len(Qiang)):
for j in lu:
if Qiang[i].col == j.col:
if Qiang[i].row == j.row:
genghuan.append(i)
genghuan.sort(reverse=True)
for i in genghuan:
Qiang.pop(i)
qidian = Point(1, 1)
lujing = []
liebiao = []
liebiao.append(qidian)
lujing.append(qidian)
zhongdian = Point(Row - 2, Col - 2)
lujin = []
x = 0
while len(liebiao):
#for i in range(0,10):
ran = random.randint(1, len(liebiao))
lujing.append(liebiao[ran - 1])
for i in range(0, len(lujing)):
if lujing[i].row - 2 == lujing[x + 1].row and lujing[i].col == lujing[x + 1].col:
lujin.append(Point(lujing[i].row - 1, lujing[i].col))
break
if lujing[i].row + 2 == lujing[x + 1].row and lujing[i].col == lujing[x + 1].col:
lujin.append(Point(lujing[i].row + 1, lujing[i].col))
break
if lujing[i].row == lujing[x + 1].row and lujing[i].col - 2 == lujing[x + 1].col:
lujin.append(Point(lujing[i].row, lujing[i].col - 1))
break
if lujing[i].row == lujing[x + 1].row and lujing[i].col + 2 == lujing[x + 1].col:
lujin.append(Point(lujing[i].row, lujing[i].col + 1))
break
# lujin.append(Point(int((lujing[x].row+lujing[x+1].row)/2),int((lujing[x].col+lujing[x+1].col)/2)))
guo = xiangling(liebiao[ran - 1])
liebiao.pop(ran - 1)
liebiao.extend(guo)
x = x + 1
lu.extend(lujin)
lu.extend(lujing)
def rect(point, color):
cell_width = w / Col
cell_height = h / Row
left = (point.col) * cell_width
top = (point.row) * cell_height
pygame.draw.rect(
window, color,
(left, top, cell_width, cell_height)
)
# 传入一个Point类型的参数,用于绘制地图中的一个小方格(即一个单元格)。
# 变量xx和yy分别表示起始位置的行和列。
# renwu用于表示当前人物所在的位置,初始化为(1, 1),即地图左上角。
# jilu_ren用于记录人物的移动轨迹。
def rect2(point):
cell_width = w / Col
cell_height = h / Row
left = point.col * cell_width
top = point.row * cell_height
# py.draw.rect(
# window, color,
# (left, top, cell_width, cell_height)
# )
window.blit(
(left, top)
)
xx = 1
yy = 1
renwu = Point(xx, yy)
jilu_ren = []
jilu_ren.append(renwu)
def Index(point):
for i in range(0, len(lu)):
if point.col == lu[i].col and point.row == lu[i].row:
return True
return False
# A*算法自动寻路
#寻找最短路径的算法实现。首先定义了一个二维列表num,表示地图中的每个位置的状态,1表示墙,0表示路。
#然后定义了一个函数xunlu用于进行路径搜索。在函数内部,通过广度优先搜索的方式逐步扩展节点,直到找到终点或无法再扩展节点为止
#搜索过程中使用了启发式函数来评估节点的优先级,并根据优先级选择下一个节点进行扩展。最终返回找到的最短路径。
num = [[1 for i in range(0, Row - 2)] for j in range(0, Col - 2)]
for i in range(0, Row - 2):
for j in range(0, Col - 2):
if Index(Point(i + 1, j + 1)):
num[i][j] = 0
def xunlu(liebiao, qi, zhong):
a = np.mat(liebiao)
def gs(i, j):
return abs(i - startx) + abs(j - starty)
def h1(i, j):
return 10 * (abs(i - endx) + abs(j - endy))
def h2(i, j):
return pow(i - endx, 2) + pow(j - endy, 2)
print("地图为:(1表示墙,0表示路)")
for l in range(len(a)):
for m in range(a[0].size):
print(a[l, m], end=' ')
print('')
print('')
startx, starty = qi.row, qi.col
endx, endy = zhong.row, zhong.col
if a[startx - 1, starty - 1] == 1:
print("起点%s位置为墙,最短路径寻找失败!" % ([startx, starty]))
else:
# 存储已扩展结点,即所有寻找的节点
Close = [[startx, starty]]
# 存储已生成节点,即最终走过的节点
Opens = [[startx, starty]]
# 存储未走的分叉节点
crossings = []
# 设置在原地图行走的路径值
road = 100
start = time.time()
rows, cols = a.shape
while True:
# 判断最后走过的节点是否为终点
if Close[-1] != [endx, endy]:
Open = []
# 减1得到数组下标值
i, j = Close[-1][0] - 1, Close[-1][1] - 1
# 对当前节点上下左右四个节点进行判断
for ni, nj in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
if [ni + 1, nj + 1] not in Opens and 0 <= ni < rows and 0 <= nj < cols and a[ni, nj] == 0:
Open.append([ni + 1, nj + 1])
# 将已走过的节点值修改为路径值,并将路径值加1
a[i, j] = road
road = road + 1
if Open:
# 对所有扩展到的节点进行排序,reverse=True 结果从大到小,即尾部存储代价最小的节点
Open = sorted(Open, key=lambda x: gs(x[0], x[1]) + h2(x[0], x[1]), reverse=True)
Opens.extend(Open)
# 将代价最小的节点存储到 Close 表
Close.append(Open.pop())
# 如果 pop 后 Open 表不为空,说明存在岔路,将岔路存储到 crossings 中
if Open:
crossings.extend(Open)
# 判断是否存在未走过的分叉节点
elif crossings:
next_way = crossings.pop()
# 实现路径回退,循环条件为回退节点与分叉节点不相邻
while sum((np.array(Close[-1]) - np.array(next_way)) ** 2) != 1:
# 当一条路径寻找失败后,是否将该路径岔路后的部分恢复为原地图
# i, j = Close[-1]
# a[i-1, j-1] = 0
# 每次 while 循环路径值 road 减1,并弹出 Close 表中的节点
road -= 1
Close.pop()
# 将 crossings 中最后一个节点添加到 Close 表
Close.append(next_way)
else:
print("最短路径寻找失败,失败位置为:%s,路径为:" % Close[-1])
break
else:
a[endx - 1, endy - 1] = road
print("最短路径寻找成功,路径为:")
break
for r in range(rows):
for c in range(cols):
# 0表示format中第一个元素,>表示右对齐输出,4表示占四个字符
print("{0: >4}".format(a[r, c]), end='')
print('')
end = time.time()
print("\n扩展节点数为:%d, 生成节点数为:%d, 用时为 %.8f" % (len(Opens), len(Close), float(end - start)))
print('Close表为:%s' % Close)
print("点的移动轨迹为:")
for k in range(len(Close)):
print(Close[k], end='')
if k < len(Close) - 1:
print("-->", end='')
luxian = []
for i in range(0, len(Close)):
luxian.append(Point(Close[i][0], Close[i][1]))
return luxian
print(num)
xianlu = []
while showWindow:
for event in pygame.event.get():
if event.type == pygame.QUIT:
showWindow = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and Index(Point(renwu.row - 1, renwu.col)):
renwu.row -= 1
elif event.key == pygame.K_DOWN and Index(Point(renwu.row + 1, renwu.col)):
renwu.row += 1
elif event.key == pygame.K_LEFT and Index(Point(renwu.row, renwu.col - 1)):
renwu.col -= 1
elif event.key == pygame.K_RIGHT and Index(Point(renwu.row, renwu.col + 1)):
renwu.col += 1
elif event.key == pygame.K_q:
print(renwu.row)
print(renwu.col)
print(zhongdian.row)
print(zhongdian.col)
elif event.key == pygame.K_1:
xianlu = []
xianlu_1 = xunlu(num, renwu, zhongdian)
xianlu = xianlu_1[0:int(len(xianlu_1) / 3)]
elif event.key == pygame.K_2:
xianlu = []
xianlu_1 = xunlu(num, renwu, zhongdian)
xianlu = xianlu_1[0:int(len(xianlu_1) / 3 * 2)]
elif event.key == pygame.K_3:
xianlu = []
xianlu_1 = xunlu(num, renwu, zhongdian)
xianlu = xianlu_1
# jilu_ren.append(renwu)
jilu_ren.insert(0, renwu.copy())
if renwu.col == zhongdian.col and renwu.row == zhongdian.row:
showWindow = False
while showjiesu:
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
showjiesu = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_s :
showjiesu=False
pygame.draw.rect(window, (200, 200, 200), (0, 0, w, h))
for i in Qiang:
rect(i, (200, 200, 200))
for i in lujing:
rect(i, (255, 255, 255,0))
for i in lujin:
rect(i, (255, 255, 255,0))
for i in jilu_ren:
rect(i,(110, 255, 110))
for i in xianlu:
rect(i, (200, 255, 200))
rect(Point(1, 1), (0, 255, 255))
rect(Point(Row - 2, Col - 2), (0, 0, 255))
rect(renwu,(0,0,0))
pygame.display.update()
clock.tick(10000)