文章目录
程序设计分析
基于之前两个项目的Python基础,试图尝试更加复杂的游戏设计
迷宫难度:
- 迷宫长宽:尺寸越大,生成的地图越难
- 迷宫生成算法:地图难度:kruskai算法>随机深度优先算法>prim算法>递归分割算法
功能
-
增加状态栏显示状态信息
-
作弊(查看提示)增加惩罚分数(当前作弊一次惩罚20分)
-
保存读取地图
-
菜单栏,可用于设置地图生成算法,地图尺寸等
-
增加迷雾模式
-
显示等级以及当前移动步数
-
随机生成游戏地图
-
按方向键后自动前进倒退(到分岔路停止)
-
起点到任意位置辅助路径显示(鼠标左键单击空白地方显示路线) 移动次数计数
-
到达终点后通关,按任意键进入下一关(目前没有难度设置,难度相同)
一、主要函数说明
迷宫类Maze:
rint_matrix
generate_matrix_dfs
generate_matrix_prim
generate_matrix_kruskal
generate_matrix_split
find_path_dfs
find_path_bfs (TODO)
并查集类 UnionSet:
find
union
可视化:
draw_cell
draw_path
draw_maze
check_reach
eventHandler
paint
reset
update_maze
二、Maze类说明
1.利用tkinter库设计界面
Python 的 GUI 库非常多,之所以选择 Tkinter,一是最为简单,二是自带库,不需下载
安装,随时使用,跨平台兼容性非常好,三则是从需求出发的,Python 在实际应用中极
少用于开发复杂的桌面应用。以下列出一些所需Tkinte控件。
#引入所需要的的库
import tkinter as tk
#弹窗
from tkinter.messagebox import showinfo
#选择文件对话框的格式打开图片
from tkinter import filedialog
#生成任意尺寸的迷宫
from mazeGenerator import Maze
import pandas as pd
import numpy as np
#图片处理
from PIL import Image
import time
import copy
import math
import os
# 绘制背景(灰色)
def draw_cell(canvas, row, col, color="#F2F2F2"):
x0, y0 = col * cell_width, row * cell_width
x1, y1 = x0 + cell_width, y0 + cell_width
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline =color, width = 0)
# 绘制路径
def draw_path(canvas, matrix, row, col, color, line_color):
# 列
if row + 1 < rows and matrix[row - 1][col] >= 1 and matrix[row + 1][col] >= 1:
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width
x1, y1 = x0 + cell_width / 5, y0 + cell_width
# 行
elif col + 1 < cols and matrix[row][col - 1] >= 1 and matrix[row][col + 1] >= 1:
x0, y0 = col * cell_width, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + cell_width, y0 + cell_width / 5
# 左上角
elif col + 1 < cols and row + 1 < rows and matrix[row][col + 1] >= 1 and matrix[row + 1][col] >= 1:
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + 3 * cell_width / 5, y0 + cell_width / 5
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline = line_color, width = 0)
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + cell_width / 5, y0 + 3 * cell_width / 5
# 右上角
elif row + 1 < rows and matrix[row][col - 1] >= 1 and matrix[row + 1][col] >= 1:
x0, y0 = col * cell_width, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + 3 * cell_width / 5, y0 + cell_width / 5
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline = line_color, width = 0)
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + cell_width / 5, y0 + 3 * cell_width / 5
# 左下角
elif col + 1 < cols and matrix[row - 1][col] >= 1 and matrix[row][col + 1] >= 1:
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width
x1, y1 = x0 + cell_width / 5, y0 + 3 * cell_width / 5
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline = line_color, width = 0)
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + 3 * cell_width / 5, y0 + cell_width / 5
# 右下角
elif matrix[row - 1][col] >= 1 and matrix[row][col - 1] >= 1:
x0, y0 = col * cell_width, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + 3 * cell_width / 5, y0 + cell_width / 5
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline = line_color, width = 0)
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width
x1, y1 = x0 + cell_width / 5, y0 + 3 * cell_width / 5
else:
x0, y0 = col * cell_width + 2 * cell_width / 5, row * cell_width + 2 * cell_width / 5
x1, y1 = x0 + cell_width / 5, y0 + cell_width / 5
canvas.create_rectangle(x0, y0, x1, y1, fill = color, outline = line_color, width = 0)
2.绘制迷宫
def draw_maze(canvas, matrix, path, moves):
"""
根据matrix中每个位置的值绘图:
-1: 墙壁
0: 空白
1: 参考路径
2: 移动过的位置
"""
canvas.delete("all")
matrix = copy.copy(matrix)
# 参考路径
for p in path:
matrix[p[0]][p[1]] = 1
# 移动过的路径
for move in moves:
matrix[move[0]][move[1]] = 2
# 通过地图大小判断空白和墙壁
for r in range(rows):
for c in range(cols):
if matrix[r][c] == 0:
draw_cell(canvas, r, c)
elif matrix[r][c] == -1:
# 将墙壁设置为蓝色
draw_cell(canvas, r, c, '#525288')
elif matrix[r][c] == 1:
draw_cell(canvas, r, c)
# 将参考路径设置为紫色
draw_path(canvas, matrix, r, c, '#bc84a8', '#bc84a8')
elif matrix[r][c] == 2:
draw_cell(canvas, r, c)
# 将移动过的位置设置为红色
draw_path(canvas, matrix, r, c, '#ee3f4d', '#ee3f4d')
# 窗口标题
windows.title("Maze Level-{} Steps-{}".format(level, click_counter))
set_label_text()
# 更新迷宫
def update_maze(canvas, matrix, path, moves):
windows.title("Maze Level-{} Steps-{}".format(level, click_counter))
canvas.delete("all")
matrix = copy.copy(matrix)
# 重置参考
for p in path:
matrix[p[0]][p[1]] = 1
# 重置移动路径
for move in moves:
matrix[move[0]][move[1]] = 2
# 初始化颜色
row, col = movement_list[-1]
colors = ['#525288', '#F2F2F2', '#525288', '#F2F2F2', '#525288', '#F2F2F2', '#525288', '#F2F2F2']
if map_mode > 0:
colors = ['#232323', '#242424', '#2a2a32', '#424242', '#434368', '#b4b4b4', '#525288', '#F2F2F2']
# 根据距离调用colors函数
for r in range(rows):
for c in range(cols):
distance = (row - r) * (row - r) + (col - c) * (col - c)
if distance >= 100:
color = colors[0:2]
elif distance >= 60:
color = colors[2:4]
elif distance >= 30:
color = colors[4:6]
else:
color = colors[6:8]
if matrix[r][c] == 0:
draw_cell(canvas, r, c, color[1])
elif matrix[r][c] == -1:
draw_cell(canvas, r, c, color[0])
elif matrix[r][c] == 1:
draw_cell(canvas, r, c, color[1])
draw_path(canvas, matrix, r, c, '#bc84a8', '#bc84a8')
elif matrix[r][c] == 2:
draw_cell(canvas, r, c, color[1])
draw_path(canvas, matrix, r, c, '#ee3f4d', '#ee3f4d')
set_label_text()
3.绘制结果
在函数内部声明global函数,以便想在函数内部对函数外的变量进行操作。
# 检查是否通关
def check_reach():
global next_maze_flag
# 通关条件
if movement_list[-1] == maze.destination:
print("Congratulations! You reach the goal! Steps used: {}".format(click_counter))
save_logs(logs_path, set_log_data())
x0, y0 = cols * cell_width / 2 - 200, 30
x1, y1 = x0 + 400, y0 + 40
canvas.create_rectangle(x0, y0, x1, y1, fill = '#F2F2F2', outline ='#525288', width = 3)
canvas.create_text(cols * cell_width / 2, y0 + 20, text = "Congratulations! You reach the goal! Back steps: {}".format(back_counter), fill = "#525288")
next_maze_flag = True
# 绘制结果
def draw_result():
if len(history_data) == 0:
showinfo(title='Oppose', message='当前没有任何历史数据')
return
sns.barplot(x='level', y='value', hue='name', data=history_data)
plt.title("History score")
plt.show()
# 保存日志
def save_logs(path, text):
with open(path, 'a+') as file:
file.write(text)
# 移动更新处理
def movement_update_handler(event):
global movement_list
global click_counter, back_counter
cur_pos = movement_list[-1]
ops = {'Left': [0, -1], 'Right': [0, 1], 'Up': [-1, 0], 'Down': [1, 0], 'a': [0, -1], 'd': [0, 1], 'w': [-1, 0], 's': [1, 0]}
r_, c_ = cur_pos[0] + ops[event.keysym][0], cur_pos[1] + ops[event.keysym][1]
if len(movement_list) > 1 and [r_, c_] == movement_list[-2]:
# 统计步骤数
click_counter += 1
# 撤退步骤数
back_counter += 1
movement_list.pop()
# 提示模式
if auto_mode:
while True:
cur_pos = movement_list[-1]
counter = 0
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
r_, c_ = cur_pos[0] + d[0], cur_pos[1] + d[1]
if c_ >= 0 and maze.matrix[r_][c_] == 0:
counter += 1
if counter != 2:
break
movement_list.pop()
elif r_ < maze.height and c_ < maze.width and maze.matrix[r_][c_] == 0:
click_counter += 1
if auto_mode:
while True:
movement_list.append([r_, c_])
temp_list = []
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
r__, c__ = r_ + d[0], c_ + d[1]
if c__ < maze.width and maze.matrix[r__][c__] == 0 and [r__, c__] != cur_pos:
temp_list.append([r__, c__])
if len(temp_list) != 1:
break
cur_pos = [r_, c_]
r_, c_ = temp_list[0]
else:
movement_list.append([r_, c_])
maze.path = []
update_maze(canvas, maze.matrix, maze.path, movement_list)
check_reach()
# 通关后升级难度
def next_level():
global click_counter, total_counter, back_counter
global next_maze_flag
global level
global t1
next_maze_flag = False
t1 = int(time.time())
level, total_counter, click_counter, back_counter = level + 1, total_counter + click_counter, 0, 0
generate_matrix()
# 功能按键
def _event_handler(event):
# 难度升级
if next_maze_flag:
next_level()
elif event.keysym in ['Left', 'Right', 'Up', 'Down', 'w', 'a', 's', 'd']:
movement_update_handler(event)
# 打开地图
elif event.keysym == "F1":
_open_map()
# 保存地图
elif event.keysym == 'F2':
_save_map()
# 退出窗口
elif event.keysym == 'F3':
windows.quit()
# 回到起点
elif event.keysym == "F4":
_back_to_start_point()
# 换个地图
elif event.keysym == "F5":
generate_matrix()
# 操作说明
elif event.keysym == "F6":
_man()
# 绘制答案路径
def _paint_answer_path(event):
global click_counter
x, y = math.floor((event.y - 1) / cell_width), math.floor((event.x - 1) / cell_width)
if maze.matrix[x][y] == 0:
maze.find_path_dfs([x, y])
click_counter += 20
update_maze(canvas, maze.matrix, maze.path, movement_list)
# 重置应答路径
def _reset_answer_path(event):
maze.path = []
update_maze(canvas, maze.matrix, maze.path, movement_list)
4.功能函数
# 生成地图
def generate_matrix():
global movement_list
global map_generate_mode
global click_counter, back_counter
# 如果地图大小不存在则不生成地图
if map_size_mode == -1:
map_generate_mode = 0
click_counter, back_counter = 0, 0
movement_list = [maze.start]
maze.generate_matrix(map_generate_mode, None)
draw_maze(canvas, maze.matrix, maze.path, movement_list)
# 选择地图生成算法的难度
# 选择kruskal算法生成地图
def _set_algo_0():
global map_generate_mode
map_generate_mode = 0
generate_matrix()
# 选择dfs算法生成地图
def _set_algo_1():
global map_generate_mode
map_generate_mode = 1
generate_matrix()
# 选择prim算法生成地图
def _set_algo_2():
global map_generate_mode
map_generate_mode = 2
generate_matrix()
# 选择split算法生成地图
def _set_algo_3():
global map_generate_mode
map_generate_mode = 3
generate_matrix()
# 选择游戏模式
# 选择简单模式
def _set_mode_0():
global map_mode
map_mode = 0
# 选择迷雾模式
def _set_mode_1():
global map_mode
map_mode = 1
# 打开地图
def _open_map():
global map_generate_mode
# 打开文件里的地图文件
img_path = filedialog.askopenfilename(title='打开地图文件', filetypes=[('png', '*.png'), ('All Files', '*')])
if img_path:
image = Image.open(img_path)
matrix = np.asarray(image) / -255
assert len(matrix) <= 41 and len(matrix[0]) < 91
map_generate_mode = -1
_set_size(len(matrix[0]), len(matrix), -1, matrix)
# 保存地图
def _save_map():
path = "{}{}".format(os.getcwd(),image_save_path).replace('\\','/')
if not os.path.exists(path):
os.makedirs(path)
imgs_len = len(os.listdir(path))
image = Image.fromarray(-255 * maze.matrix).convert('L')
image.save("{}map_{}.png".format(path,str(imgs_len), 'PNG'))
showinfo(title='上帝', message='当前地图已经保存在{}'.format(path))
image.show()
# 设置地图尺寸
def _set_size(width, height, mode, matrix = None):
global map_size_mode
global rows, cols
global movement_list
global click_counter, back_counter
click_counter, back_counter = 0, 0
map_size_mode = mode
movement_list = [maze.start]
rows, cols = height, width
canvas['width'] = width * cell_width
canvas['height'] = height * cell_width
if mode == -1:
maze.resize_matrix(width, height, -1, matrix)
else:
maze.resize_matrix(width, height, map_generate_mode, matrix)
draw_maze(canvas, maze.matrix, maze.path, movement_list)
# 31x31
def _set_size_31x31():
_set_size(31, 31, 0)
# 41x41
def _set_size_41x41():
_set_size(41, 41, 1)
# 37x81
def _set_size_37x81():
_set_size(81, 37, 2)
# 返回起点
def _back_to_start_point():
global movement_list
movement_list = [maze.start]
draw_maze(canvas, maze.matrix, maze.path, movement_list)
# 打开自动模式
def _set_auto_on():
global auto_mode
auto_mode = True
# 关闭自动模式
def _set_auto_off():
global auto_mode
auto_mode = False
5.按键回调函数绑定
此操作与计算机设计思想一致。
# 操作说明
def _man():
showinfo(title='操作说明', message='控制移动:方向键\n查看提示:鼠标单击地图中空白处即可查看从起点到点击处的路径(查看一次提示增加20步)\n进入下一关:到达终点后按任意键进入下一关')
# 底部状态栏说明
def set_label_text():
message = " Mode: {} Map size: {} Algorithm: {} Total steps: {} Back steps: {} Time: {}s".format( \
"Simple" if map_mode == 0 else 'Roguelike', ['31x31', '41x41', '81x37'][map_size_mode] if map_size_mode >= 0 else "{}x{}".format(cols, rows), \
['Kruskal', 'Random DFS', 'Prim', 'Recursive Split'][map_generate_mode] if map_generate_mode >= 0 else 'Unknown', \
click_counter + total_counter, back_counter, int(time.time() - t0))
label["text"] = message
return message
# 设置数据
def set_log_data():
return "[{}]Mode:{},Map-size:{},Algorithm:{},Level:{},Steps:{},Back-steps:{},Time-cost:{}\n".format(
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))), "Simple" if map_mode == 0 else 'Roguelike', \
['31x31', '41x41', '81x37'][map_size_mode] if map_size_mode >= 0 else "{}x{}".format(cols, rows), \
['Kruskal', 'Random DFS', 'Prim', 'Recursive Split'][map_generate_mode] if map_generate_mode >= 0 else 'Unknown', \
level, click_counter, back_counter, int(time.time() - t1))
if __name__ == '__main__':
# 基础参数
logs_path = './maze_game.log'
image_save_path = '/maze_map/'
cell_width = 20
rows = 37
cols = 81
height = cell_width * rows
width = cell_width * cols
level = 1
click_counter, total_counter, back_counter = 0, 0, 0
next_maze_flag = False
history_data = pd.DataFrame(columns=['level', 'name', 'value'])
# 地图生成算法:0-kruskal,1-dfs,2-prim,3-split
map_generate_mode = 0
# 游戏模式:0-简单模式,1-迷雾模式
map_mode = 0
# 地图大小:0-31x31, 1-41x41, 2-81x37
map_size_mode = 2
# 自动前进模式,默认开
auto_mode = True
windows = tk.Tk()
windows.title("Maze")
windows.resizable(0, 0)
t0 = int(time.time())
t1 = t0
# 创建菜单栏
menubar = tk.Menu(windows)
filemenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='文件', menu=filemenu)
filemenu.add_command(label='打开地图', command=_open_map, accelerator='F1')
filemenu.add_command(label='保存地图', command=_save_map, accelerator='F2')
filemenu.add_separator()
filemenu.add_command(label='退出', command=windows.quit, accelerator='F3')
editmenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='设置', menu=editmenu)
editmenu.add_command(label='回到起点', command=_back_to_start_point, accelerator='F4')
editmenu.add_command(label='换个地图', command=generate_matrix, accelerator='F5')
sizemenu = tk.Menu(editmenu, tearoff=0)
editmenu.add_cascade(label='尺寸设置', menu=sizemenu)
sizemenu.add_command(label='31x31', command=_set_size_31x31)
sizemenu.add_command(label='41x41', command=_set_size_41x41)
sizemenu.add_command(label='37x81', command=_set_size_37x81)
automenu = tk.Menu(editmenu, tearoff=0)
editmenu.add_cascade(label='自动前进', menu=automenu)
automenu.add_command(label='开', command=_set_auto_on)
automenu.add_command(label='关', command=_set_auto_off)
modemenu = tk.Menu(editmenu, tearoff=0)
editmenu.add_cascade(label='游戏模式', menu=modemenu)
modemenu.add_command(label='普通模式', command=_set_mode_0)
modemenu.add_command(label='迷雾模式', command=_set_mode_1)
algomenu = tk.Menu(editmenu, tearoff=0)
editmenu.add_cascade(label='生成算法', menu=algomenu)
algomenu.add_command(label='Kruskal最小生成树算法', command=_set_algo_0)
algomenu.add_command(label='随机深度优先算法', command=_set_algo_1)
algomenu.add_command(label='prim最小生成树算法', command=_set_algo_2)
algomenu.add_command(label='递归分割算法', command=_set_algo_3)
scoremenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='统计', menu=scoremenu)
scoremenu.add_command(label='历史成绩', command=draw_result)
windows.config(menu=menubar)
# end 创建菜单栏
# 创建状态栏
label = tk.Label(windows, text="Maze Game", bd=1, anchor='w') # anchor left align W -- WEST
label.pack(side="bottom", fill='x')
set_label_text()
canvas = tk.Canvas(windows, background="#F2F2F2", width = width, height = height)
canvas.pack()
maze = Maze(cols, rows)
movement_list = [maze.start]
generate_matrix()
canvas.bind("<Button-1>", _paint_answer_path)
canvas.bind("<Button-3>", _reset_answer_path)
canvas.bind_all("<KeyPress>", _event_handler)
windows.mainloop()
三、mazeGenerator类说明
1.输出迷宫
assert 表达式 [, 参数]
当表达式为真时,程序继续往下执行;
当表达式为假时,抛出AssertionError错误,并将参数输出
# 引入需要的库
import tkinter as tk
import numpy as np
import time
import random
import copy
class UnionSet(object):
"""
并查集实现,构造函数中的matrix是一个numpy类型
"""
def __init__(self, arr):
self.parent = {pos: pos for pos in arr}
self.count = len(arr)
def find(self, root):
if root == self.parent[root]:
return root
return self.find(self.parent[root])
def union(self, root1, root2):
self.parent[self.find(root1)] = self.find(root2)
class Maze(object):
"""
迷宫生成类
"""
def __init__(self, width = 11, height = 11):
assert width >= 5 and height >= 5, "Length of width or height must be larger than 5."
self.width = (width // 2) * 2 + 1
self.height = (height // 2) * 2 + 1
self.start = [1, 0]
self.destination = [self.height - 2, self.width - 1]
self.matrix = None
self.path = []
# 输出迷宫
def print_matrix(self):
matrix = copy.deepcopy(self.matrix)
for p in self.path:
matrix[p[0]][p[1]] = 1
for i in range(self.height):
for j in range(self.width):
if matrix[i][j] == -1:
print('□', end = '')
elif matrix[i][j] == 0:
print(' ', end = '')
elif matrix[i][j] == 1:
print('■', end = '')
elif matrix[i][j] == 2:
print('▲', end = '')
print('')
# 生成迷宫
def generate_matrix(self, mode, new_matrix):
assert mode in [-1, 0, 1, 2, 3], "Mode {} does not exist.".format(mode)
if mode == -1:
self.matrix = new_matrix
elif mode == 0:
self.generate_matrix_kruskal()
elif mode == 1:
self.generate_matrix_dfs()
elif mode == 2:
self.generate_matrix_prim()
elif mode == 3:
self.generate_matrix_split()
# 调整迷宫大小
def resize_matrix(self, width, height, mode, new_matrix):
self.path = []
self.width = (width // 2) * 2 + 1
self.height = (height // 2) * 2 + 1
self.start = [1, 0]
self.destination = [self.height - 2, self.width - 1]
self.generate_matrix(mode, new_matrix)
# 迷宫寻路函数
def generate_matrix_dfs(self):
# 地图初始化,并将出口和入口处的值设置为0
self.matrix = -np.ones((self.height, self.width))
self.matrix[self.start[0], self.start[1]] = 0
self.matrix[self.destination[0], self.destination[1]] = 0
visit_flag = [[0 for i in range(self.width)] for j in range(self.height)]
def check(row, col, row_, col_):
temp_sum = 0
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
temp_sum += self.matrix[row_ + d[0]][col_ + d[1]]
return temp_sum <= -3
def dfs(row, col):
visit_flag[row][col] = 1
self.matrix[row][col] = 0
if row == self.start[0] and col == self.start[1] + 1:
return
directions = [[0, 2], [0, -2], [2, 0], [-2, 0]]
random.shuffle(directions)
for d in directions:
row_, col_ = row + d[0], col + d[1]
if row_ > 0 and row_ < self.height - 1 and col_ > 0 and col_ < self.width - 1 and visit_flag[row_][col_] == 0 and check(row, col, row_, col_):
if row == row_:
visit_flag[row][min(col, col_) + 1] = 1
self.matrix[row][min(col, col_) + 1] = 0
else:
visit_flag[min(row, row_) + 1][col] = 1
self.matrix[min(row, row_) + 1][col] = 0
dfs(row_, col_)
dfs(self.destination[0], self.destination[1] - 1)
self.matrix[self.start[0], self.start[1] + 1] = 0
2.递归分割算法生成迷宫
算法调用参数是一个矩阵的位置(x, y, width, height)
判断矩阵是否不能继续分割(长度或宽度<=1)
如果能继续分割
- 随机选择十字交点的位置(WALL_X, WALL_Y),
- 将这条十字所在的单元都设为墙,分割成4个更小的矩阵
- 十字所在的四条边中随机选择三条边来打通(每条边上去掉一个单元的墙) 对于4个小矩阵递归调用算法
- 否则,直接返回
# 递归分割算法生成迷宫
def generate_matrix_split(self):
# 地图初始化,并将出口和入口处的值设置为0
self.matrix = -np.zeros((self.height, self.width))
self.matrix[0, :] = -1
self.matrix[self.height - 1, :] = -1
self.matrix[:, 0] = -1
self.matrix[:, self.width - 1] = -1
# 随机生成位于(start, end)之间的偶数
def get_random(start, end):
rand = np.random.randint(start, end)
if rand & 0x1 == 0:
return rand
return get_random(start, end)
# split函数的四个参数分别是左上角的行数、列数,右下角的行数、列数,墙壁只能在偶数行,偶数列
def split(lr, lc, rr, rc):
if rr - lr < 2 or rc - lc < 2:
return
# 生成墙壁,墙壁只能是偶数点
cur_row, cur_col = get_random(lr, rr), get_random(lc, rc)
for i in range(lc, rc + 1):
self.matrix[cur_row][i] = -1
for i in range(lr, rr + 1):
self.matrix[i][cur_col] = -1
# 挖穿三面墙得到连通图,挖孔的点只能是偶数点
wall_list = [
("left", cur_row, [lc + 1, cur_col - 1]),
("right", cur_row, [cur_col + 1, rc - 1]),
("top", cur_col, [lr + 1, cur_row - 1]),
("down", cur_col, [cur_row + 1, rr - 1])
]
random.shuffle(wall_list)
for wall in wall_list[:-1]:
if wall[2][1] - wall[2][0] < 1:
continue
if wall[0] in ["left", "right"]:
self.matrix[wall[1], get_random(wall[2][0], wall[2][1] + 1) + 1] = 0
else:
self.matrix[get_random(wall[2][0], wall[2][1] + 1), wall[1] + 1] = 0
# self.print_matrix()
# time.sleep(1)
# 递归
split(lr + 2, lc + 2, cur_row - 2, cur_col - 2)
split(lr + 2, cur_col + 2, cur_row - 2, rc - 2)
split(cur_row + 2, lc + 2, rr - 2, cur_col - 2)
split(cur_row + 2, cur_col + 2, rr - 2, rc - 2)
self.matrix[self.start[0], self.start[1]] = 0
self.matrix[self.destination[0], self.destination[1]] = 0
split(0, 0, self.height - 1, self.width - 1)
3.prim算法生成迷宫
Prim算法:假设N=(V,E) 是具有n个顶点的连通图,设U是最小生成树中顶点的集合,设TE是最小生成树中边的集合;
- 初始,U = { u1 } ,TE = { } ,
- 重复执行: 在所有 u∈U,v∈V-U 的边 ( u , v ) 中寻找代价最小的边( u’ , v’ ) ,并纳入集合 TE 中;
- 同时将 v’ 纳入集合 U 中;直至 U = V 为止。
Prim算法也是典型的贪心算法。最小生成树的主要有两个重复过程:寻找一个满足条件的未插入到生成树集合的结点;利用该结点更新其余顶点到生成树集合的最小权值信息。因此,时间复杂度为O(n^{2})。
# prim算法(似乎更像随机广度优先算法)
def generate_matrix_prim(self):
# 地图初始化,并将出口和入口处的值设置为0
self.matrix = -np.ones((self.height, self.width))
def check(row, col):
temp_sum = 0
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
temp_sum += self.matrix[row + d[0]][col + d[1]]
return temp_sum < -3
queue = []
row, col = (np.random.randint(1, self.height - 1) // 2) * 2 + 1, (np.random.randint(1, self.width - 1) // 2) * 2 + 1
queue.append((row, col, -1, -1))
while len(queue) != 0:
row, col, r_, c_ = queue.pop(np.random.randint(0, len(queue)))
if check(row, col):
self.matrix[row, col] = 0
if r_ != -1 and row == r_:
self.matrix[row][min(col, c_) + 1] = 0
elif r_ != -1 and col == c_:
self.matrix[min(row, r_) + 1][col] = 0
for d in [[0, 2], [0, -2], [2, 0], [-2, 0]]:
row_, col_ = row + d[0], col + d[1]
if row_ > 0 and row_ < self.height - 1 and col_ > 0 and col_ < self.width - 1 and self.matrix[row_][col_] == -1:
queue.append((row_, col_, row, col))
self.matrix[self.start[0], self.start[1]] = 0
self.matrix[self.destination[0], self.destination[1]] = 0
4.最小生成树算法-kruskal(选边法)思想生成迷宫
-
将边按权重从小到大进行排序;
-
将每个顶点独立视为根节点,产生n个树;
-
依次选取每条边,如果边的两个顶点不属于同一个树,则将其合并
-
如果属于同一个树(意味着会形成回路),则将其舍弃,考虑下一条边,最后形成(n-1)条边
# 最小生成树算法-kruskal(选边法)思想生成迷宫地图,这种实现方法最复杂。
def generate_matrix_kruskal(self):
# 地图初始化,并将出口和入口处的值设置为0
self.matrix = -np.ones((self.height, self.width))
def check(row, col):
ans, counter = [], 0
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
row_, col_ = row + d[0], col + d[1]
if row_ > 0 and row_ < self.height - 1 and col_ > 0 and col_ < self.width - 1 and self.matrix[row_, col_] == -1:
ans.append([d[0] * 2, d[1] * 2])
counter += 1
if counter <= 1:
return []
return ans
nodes = set()
row = 1
while row < self.height:
col = 1
while col < self.width:
self.matrix[row, col] = 0
nodes.add((row, col))
col += 2
row += 2
unionset = UnionSet(nodes)
while unionset.count > 1:
row, col = nodes.pop()
directions = check(row, col)
if len(directions):
random.shuffle(directions)
for d in directions:
row_, col_ = row + d[0], col + d[1]
if unionset.find((row, col)) == unionset.find((row_, col_)):
continue
nodes.add((row, col))
unionset.count -= 1
unionset.union((row, col), (row_, col_))
if row == row_:
self.matrix[row][min(col, col_) + 1] = 0
else:
self.matrix[min(row, row_) + 1][col] = 0
break
self.matrix[self.start[0], self.start[1]] = 0
self.matrix[self.destination[0], self.destination[1]] = 0
5.迷宫寻路算法dfs
采用DFS算法生成随机迷宫,输入参数很简单,就是棋盘的大小,因为棋盘是正方形,所有只有一个输入参数。返回的事一个储存全局地图信息的二维数组,0为无墙,1为有墙。
值得注意的是,起点和终点的坐标并不影响,由于DFS算法+完美迷宫的特性,某些特定单元格一定是路,而不是墙,那么只需要任意在路单元格里选两个点,令其为起点或是终点也就可以了。
# 迷宫寻路算法dfs
def find_path_dfs(self, destination):
visited = [[0 for i in range(self.width)] for j in range(self.height)]
def dfs(path):
visited[path[-1][0]][path[-1][1]] = 1
if path[-1][0] == destination[0] and path[-1][1] == destination[1]:
self.path = path[:]
return
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
row_, col_ = path[-1][0] + d[0], path[-1][1] + d[1]
if row_ > 0 and row_ < self.height - 1 and col_ > 0 and col_ < self.width and visited[row_][col_] == 0 and self.matrix[row_][col_] == 0:
dfs(path + [[row_, col_]])
dfs([[self.start[0], self.start[1]]])
# 迷宫寻路算法bfs
# def find_path_bfs(self, destination):
# visited = [[0 for i in range(self.width)] for j in range(self.height)]
# queue = [(self.start[0], self.start[1])]
# visited[self.start[0]][self.start[1]] = 1
# while len(queue) != 0:
# row, col = queue.pop(0)
# if
if __name__ == '__main__':
maze = Maze(51, 51)
maze.generate_matrix_prim()
maze.print_matrix()
maze.find_path_dfs(maze.destination)
print("answer", maze.path)
maze.print_matrix()