迷宫游戏开发


程序设计分析

基于之前两个项目的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()

展示

在这里插入图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值