一、概述:
项目目标和主要内容
1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫。
2)要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
3)要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
项目的主要功能
1)玩家模式:程序应该允许玩家通过键盘方向键在迷宫中移动。同时,玩家的行走路径应该在图形界面中实时显示出来。
2)系统模式:程序应该能够基于A*算法或其他寻路算法自动找出从入口到出口的最优路径。最优路径应该是步数最少的路径,如果有多个步数相同的路径,应该都能够输出。
3)交互友好的图形界面:程序应该具有一个能够清晰地显示出迷宫的图形界面,以便于玩家进行游戏
二、实现:
1.开始界面:
用户可以自定义行列,因为有奇数道墙、偶数条路,所以行列数必须为奇数
2.游戏界面:
上下左右键控制移动,空格显示提示路径,R隐藏提示
3.PRIM算法生成迷宫:
现根据行列创建出二位数组matrix[][],每个元素都赋值为1,代表墙。将[1][1]赋值为0,作为第一个路点。加入新路点列表
1)得到一个路点,隔一面墙寻找它周围点,依次加入备选路点列表,从表中随机选出一个墙点赋值为0代表新路点,加入新路点列表,移出备选点列表,同时打通此点与相邻的随机路点之间的墙,即将他们之间的点赋值为0。
2)随机从新路点列表中选出一点,选出的新路点重复1)操作。
3)一直重复到备选点列表为空,最后得到一个迷宫。
4.astar算法寻找路径
重复以下步骤,直到遍历到终点 End:
1)选取当前 open 列表中评价值 F 最小的节点,将这个节点称为 S;
2)将 S 从 open列表移除,然后添加 S 到 closed 列表中;
3)对于与 S 相邻的每一块可移动的相邻节点 T:如果 T 在 closed列表中,忽略;如果 T 不在 open 列表中,添加它然后计算它的 F值;如果 T 已经在 open 列表中,当我们从 S 到达 T时:检查是否能得到更小的 F 值,如果是,更新它的 F 值和它的前继(parent = S)。
5.DFS寻找路径
重复以下步骤,直到遍历到终点 End:
1)从起点开始,依次从上下左右四个方向走。
2)如果可以走通即是有路,就递归 1),如过走不通,就返回上一个分叉口,递归 1)且走其他方向。
3)当可以走通时,在迷宫列表中设下标记。如果走不通就撤回标记。
4)利用迷宫的标记渲染出路径。
5)到终点end即退出此程序。
三、设计
总体框架:
详细设计:
import heapq
import sys
import tkinter as tk
from tkinter.font import Font
import pygame
import tkinter.messagebox as tk1
import random
import time
root = tk.Tk()
root.title('迷宫')
root.geometry('250x340')
R = 0#行数
C = 0#列数
var = tk.IntVar()
label1=tk.Label(root,text='-迷宫-',width=10,font=('STzhongsong',30))
label1.pack()
label2=tk.Label(root,text='-选择难度-',width=10,font=('STzhongsong',20))
label2.pack()
#迷宫行列数
label3=tk.Label(root,text='行数',width=10,font=('STzhongsongl',20))
label3.place(x=85,y=96)
label4=tk.Label(root,text='列数',width=10,font=('STzhongsongl',20))
label4.place(x=85,y=136)
e1=tk.Entry(root,show='',width=5,bd=2,font=('Arial',15))
e1.place(x=65,y=100)
e2=tk.Entry(root,show='',width=5,bd=2,font=('Arial',15))
e2.place(x=65,y=140)
#开始游戏
def cul():
try:
R=int(eval(e1.get()))#得到行数
C=int(eval(e2.get()))#得到列数
ROWS = R
COLUMNS = C
except:
ROWS = 31#默认行列,下同
COLUMNS = 31
root.destroy() # 关闭起始框
time_start = time.time()#记录开始时间
to_be_selected_point = []#备选点列表
roadwidth = 20#路宽墙宽
is_visit = [[0 for i in range(COLUMNS)] for j in range(ROWS)] # 访问过赋值1,反之赋值0。目前全都未访问,赋值为0
random_select_point = []#备选点周围路点组成的随机点
path_point_list = []#自动寻路时路点集合
x = [0, 2, 0, -2]#寻点用到的列表
y = [2, 0, -2, 0]
px = [0, 1, 0, -1]
py = [1, 0, -1, 0]
image1 = pygame.image.load('D:/Python/迷宫/mlo1.jpg')
image2 = pygame.image.load('D:/Python/迷宫/qiang.png')
image3 = pygame.image.load('D:/Python/迷宫/qi1.jpg')
#迷宫初始化
def matrix_init(r, c):
matrix = [[1 for i in range(c)] for j in range(r)] # 墙是1,路是0
matrix[1][1] = 0#坐标(1,1)定义为路
return matrix
#把点放入备选点
def put_node_in_to_be_selected_point(node):
for i in range(4):#上下左右隔一道墙寻点
xx = node[0] + x[i]
yy = node[1] + y[i]
#不超出界限,不在备选点列表且是墙点
if xx > 0 and xx < ROWS and yy > 0 and yy < COLUMNS and ([xx, yy] not in to_be_selected_point) and \
matrix[xx][
yy] == 1:
to_be_selected_point.append([xx, yy])
def be_selected_near_roadpoint(node): # node点周围的路点
random_select_point.clear()
for i in range(4):#上下左右不隔墙寻点
xx = node[0] + x[i]
yy = node[1] + y[i]
#不超出界限且是路点
if xx > 0 and xx < ROWS and yy > 0 and yy < COLUMNS and matrix[xx][yy] == 0:
random_select_point.append([xx, yy])
random_point = random.randint(0, len(random_select_point) - 1)
return random_select_point[random_point]#返回随机路点
#输入当前坐标自动寻路
def path_searching_dfs(pathx, pathy):
path_point_list.append([pathx, pathy]) # 将初始点加入路点
while True:
if len(path_point_list) > 0: # 如果有路点
# print(11111111111111111111111111111111111111111111111111111111111111111111111)
l = len(path_point_list) - 1
n = path_point_list[l] # 最后一个路点
if n[0] == ROWS - 2 and n[1] == COLUMNS - 2: # 如果第一个路点和最后一个路点重合
return
for i in range(4):
xx = n[0] + px[i]
yy = n[1] + py[i]
if xx > 0 and xx < ROWS - 1 and yy > 0 and yy < COLUMNS - 1 and (
matrix[xx][yy] == 0 or matrix[xx][yy] == 3 or matrix[xx][yy] == 4) and is_visit[xx][
yy] == 0: # 是路点或者终点并且没有被访问过
is_visit[xx][yy] = 1 # 成为访问过的点
matrix[n[0]][n[1]] = 2 # 记录访问过的路径
tmp = [xx, yy] # 加入路点
path_point_list.append(tmp)
break
elif i == 3: # 如果回退了
matrix[n[0]][n[1]] = 0 # 将路径撤回
path_point_list.pop() # 撤回路点
else:
return
# 计算曼哈顿距离
def heuristic(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
# A*算法寻路
def path_searching_astar(start):
end=(ROWS - 2, COLUMNS - 2)
frontier = []
heapq.heappush(frontier, (0, start))
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while len(frontier) > 0:
_,current = heapq.heappop(frontier)
if current == end:
break
for next in [(0, -1), (0, 1), (-1, 0), (1, 0)]: #上, 下,左, 右
next = (current[0] + next[0], current[1] + next[1])
if next[0] > 0 and next[0] <=COLUMNS and next[1] > 0 and next[1] <=ROWS and \
matrix[next[1]][next[0]] != 1:
new_cost = cost_so_far[current] + 1
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + heuristic(end, next)
heapq.heappush(frontier, (priority, next))
came_from[next] = current
return came_from, cost_so_far
# 记录A*搜索得到的最短路径
def reconstruct_path(came_from, start):
end=(ROWS - 2, COLUMNS - 2)
current = end
path = []
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
#渲染矩形
def draw_rect(x, y, color):
pygame.draw.rect(screen, color, (y * roadwidth, x * roadwidth, roadwidth, roadwidth))
#画迷宫
def draw_maze():
for i in range(ROWS):
for j in range(COLUMNS):
if matrix[i][j] == 1:
#draw_rect(i, j, (112, 128, 105))
screen.blit(image2, (j * roadwidth, i * roadwidth))
if matrix[i][j] == 2:
draw_rect(i, j, "yellow")
if matrix[i][j] == 3:
#draw_rect(i, j, (0,0,255))
screen.blit(image3, (i * roadwidth, j * roadwidth))
if matrix[i][j] == 4:
draw_rect(i, j, (0, 199, 140))
if matrix[i][j] == 5:
draw_rect(i, j, "white")
matrix = matrix_init(ROWS, COLUMNS) # 初始化迷宫
put_node_in_to_be_selected_point([1, 1]) # 初始点旁边的备选点
pygame.init()
snakex = roadwidth
snakey = roadwidth
snakemove = roadwidth
screen_plus=50
screen = pygame.display.set_mode((COLUMNS * roadwidth, ROWS * roadwidth+screen_plus))
icon = pygame.image.load('D:/Python/迷宫/780.jfif')
pygame.display.set_icon(icon)
pygame.display.set_caption("迷宫")
# 生成迷宫
for i in range(1000000):
if len(to_be_selected_point) > 0: # 如果还有备选点
rand_s = random.randint(0, len(to_be_selected_point) - 1) # 随机选点
select_nodeA = to_be_selected_point[rand_s] # 选择出的备选点
select_nodeB = be_selected_near_roadpoint(select_nodeA) # 和备选点相邻的路点
matrix[select_nodeA[0]][select_nodeA[1]] = 0 # 把备选点成为路点
mid_x = int((select_nodeA[0] + select_nodeB[0]) / 2) # 备选点和路点之间的墙点
mid_y = int((select_nodeA[1] + select_nodeB[1]) / 2) # 同上
matrix[mid_x][mid_y] = 0 # 把墙点成为路点
put_node_in_to_be_selected_point(select_nodeA) # 把以成为路点的备选点的周围的备选点加入备选列表
to_be_selected_point.remove(select_nodeA) # 从备选列表中移除刚刚成为路点的备选点
else:
break
matrix[ROWS - 2][COLUMNS - 2] = 3 # 终点
tip = 0
while True:
snakemovedirect = 0
screen.fill("white")
pygame.draw.polygon(screen, "gray", (
(0, COLUMNS * roadwidth), (ROWS * roadwidth, COLUMNS * roadwidth), (ROWS * roadwidth, COLUMNS * roadwidth + screen_plus),
(0, COLUMNS * roadwidth + screen_plus), (0, COLUMNS * roadwidth)))
draw_maze()
font: Font = pygame.font.SysFont('方正粗黑宋简体',30)
time_end = time.time()
time_c = round(time_end - time_start,1)
text = f"用时:{time_c} s -空格键提示- -C键隐藏提示-"
scorerender = font.render(text, True, (34,139,34))
screen.blit(scorerender, (10, COLUMNS * roadwidth+screen_plus-40))
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#键盘点击事件
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT and matrix[int(snakey / roadwidth)][int(snakex / roadwidth) + 1] != 1:
snakemovedirect = 1
elif event.key == pygame.K_LEFT and matrix[int(snakey / roadwidth)][int(snakex / roadwidth) - 1] != 1:
snakemovedirect = 2
elif event.key == pygame.K_UP and matrix[int(snakey / roadwidth) - 1][int(snakex / roadwidth)] != 1:
snakemovedirect = 3
elif event.key == pygame.K_DOWN and matrix[int(snakey / roadwidth) + 1][int(snakex / roadwidth)] != 1:
snakemovedirect = 4
elif event.key == pygame.K_SPACE:
if tip == 0:
# DFS
# path_searching_dfs(int(snakey / roadwidth), int(snakex / roadwidth))
# ASTAR
came_from, _ = path_searching_astar((int(snakex / roadwidth),int(snakey / roadwidth)))
path = reconstruct_path(came_from, (int(snakex / roadwidth),int(snakey / roadwidth)))
for position in path:
matrix[position[1]][position[0]] = 2
matrix[ROWS - 2][COLUMNS - 2] = 3
tip = 1
for i in range(ROWS):
for j in range(COLUMNS):
if matrix[i][j] == 5:
matrix[i][j] = 2
elif event.key == pygame.K_c:
for i in range(ROWS):
for j in range(COLUMNS):
if matrix[i][j] == 2:
matrix[i][j] = 5
# for i in matrix:
# if i!=1:
# print(i)
#玩家移动
if snakemovedirect == 2:
snakex -= snakemove
if snakemovedirect == 1:
snakex += snakemove
if snakemovedirect == 4:
snakey += snakemove
if snakemovedirect == 3:
snakey -= snakemove
if matrix[int(snakey / roadwidth)][int(snakex / roadwidth)] == 3:
tk1.showinfo("游戏成功", f"游戏成功,用时:{time_c}s")
path = matrix # 走过的路径
path[int(snakey / roadwidth)][int(snakex / roadwidth)] = 4
pygame.draw.rect(screen, "black", (snakex, snakey, roadwidth, roadwidth))
screen.blit(image1, (snakex, snakey))
pygame.display.flip()
pygame.time.Clock().tick(10)
b=tk.Button(root,text='开始游戏',width=8,height=1,command=cul,font=('STzhongsong',20))
b.place(x=55,y=190)
def close():
root.destroy()
bc=tk.Button(root,text='退出游戏',width=8,height=1,command=close,font=('STzhongsong',20))
bc.place(x=55,y=250)
root.mainloop()