diff --git a/Maze.py b/Maze.py index 6a7b7ef..c6ca550 100644 --- a/Maze.py +++ b/Maze.py @@ -1,305 +1,497 @@ -import tkinter as tk -from tkinter.messagebox import showinfo -from mazeGenerator import Maze -import time -import copy -import math -import numpy as np - -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) - -def draw_maze(canvas, matrix, path, moves): - """ - 根据matrix中每个位置的值绘图: - -1: 墙壁 - 0: 空白 - 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') - for p in path: - matrix[p[0]][p[1]] = 1 - for move in moves: - matrix[move[0]][move[1]] = 2 - -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 level > 2: - colors = ['#232323', '#242424', '#2a2a32', '#424242', '#434368', '#b4b4b4', '#525288', '#F2F2F2'] - - 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() - -def check_reach(): - global next_maze_flag - if movement_list[-1] == maze.destination: - print("Congratulations! You reach the goal! The step used: {}".format(click_counter)) - x0, y0 = 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(width / 2, y0 + 20, text = "Congratulations! You reach the goal! Steps used: {}".format(click_counter), fill = "#525288") - next_maze_flag = True - - -def _eventHandler(event): - global movement_list - global click_counter, total_counter - global next_maze_flag - global level - - if not next_maze_flag and event.keysym in ['Left', 'Right', 'Up', 'Down']: - cur_pos = movement_list[-1] - ops = {'Left': [0, -1], 'Right': [0, 1], 'Up': [-1, 0], 'Down': [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 - movement_list.pop() - 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 - 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] - maze.path = [] - update_maze(canvas, maze.matrix, maze.path, movement_list) - check_reach() - elif next_maze_flag: - next_maze_flag = False - total_counter += click_counter - click_counter = 0 - generate_matrix() - maze.path = [] - level += 1 - - -def _paint(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(event): - maze.path = [] - update_maze(canvas, maze.matrix, maze.path, movement_list) - -def generate_matrix(): - global movement_list - movement_list = [maze.start] - if map_generate_mode == 0: - maze.generate_matrix_kruskal() - elif map_generate_mode == 1: - maze.generate_matrix_dfs() - elif map_generate_mode == 2: - maze.generate_matrix_prim() - elif map_generate_mode == 3: - maze.generate_matrix_split() - draw_maze(canvas, maze.matrix, maze.path, movement_list) - -def _set_size(): - showinfo(title='设置地图大小', message='对不起当前版本暂不支持此功能') - -def _set_algo_0(): - global map_generate_mode - map_generate_mode = 0 - -def _set_algo_1(): - global map_generate_mode - map_generate_mode = 1 - -def _set_algo_2(): - global map_generate_mode - map_generate_mode = 2 - -def _set_algo_3(): - global map_generate_mode - map_generate_mode = 3 - -def _open_map(): - pass - -def _save_map(): - pass - -def _developer(): - showinfo(title='开发者信息', message='当前版本:v 1.0.5\n开发时间:2020年2月\n开发者:Howard Wonanut') - -def _man(): - showinfo(title='操作说明', message='控制移动:方向键\n查看提示:鼠标单击地图中空白处即可查看从起点到点击处的路径(查看一次提示增加20步)\n进入下一关:到达终点后按任意键进入下一关') - -def set_label_text(): - message = " Mode: {} Algorithm: {} Level: {} Total steps: {} Time: {}s".format("Simple" if level <= 2 else 'Roguelike', \ - ['Kruskal', 'Random DFS', 'Prim', 'Recursive Split'][map_generate_mode], level, click_counter + total_counter, int(time.time() - t0)) - label["text"] = message - - -if __name__ == '__main__': - # 基础参数 - cell_width = 20 - rows = 37 - cols = 71 - height = cell_width * rows - width = cell_width * cols - level = 1 - click_counter, total_counter = 0, 0 - next_maze_flag = False - - # 地图生成算法:0-kruskal,1-dfs,2-prim,3-split - map_generate_mode = 0 - - windows = tk.Tk() - windows.title("Maze") - windows.resizable(0, 0) - t0 = time.time() - - # 创建菜单栏 - menubar = tk.Menu(windows) - - filemenu = tk.Menu(menubar, tearoff=0) - menubar.add_cascade(label='文件', menu=filemenu) - filemenu.add_command(label='打开地图', command=_open_map) - filemenu.add_command(label='保存地图', command=_save_map) - filemenu.add_separator() - filemenu.add_command(label='退出', command=windows.quit) - - editmenu = tk.Menu(menubar, tearoff=0) - menubar.add_cascade(label='设置', menu=editmenu) - editmenu.add_command(label='换个地图', command=generate_matrix) - editmenu.add_command(label='尺寸设置', command=_set_size) - - 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) - - helpmenu = tk.Menu(menubar, tearoff=0) - menubar.add_cascade(label='帮助', menu=helpmenu) - helpmenu.add_command(label='操作说明', command=_man) - helpmenu.add_command(label='开发者信息', command=_developer) - - 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("", _paint) - canvas.bind("", _reset) - canvas.bind_all("", _eventHandler) - windows.mainloop() +# @Author: Howard Wonanut +# @Version: v1.0.7 +# @Date: 2020-02-04 + +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 + +# import matplotlib.pyplot as plt +# import seaborn as sns + + +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) + + +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'] + + 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() + + +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() + elif event.keysym == "F7": + _developer() + + +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) + + +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) + + +def _set_size(): + showinfo(title='上帝', message='对不起当前版本暂不支持此功能') + + +def _set_algo_0(): + global map_generate_mode + map_generate_mode = 0 + generate_matrix() + + +def _set_algo_1(): + global map_generate_mode + map_generate_mode = 1 + generate_matrix() + + +def _set_algo_2(): + global map_generate_mode + map_generate_mode = 2 + generate_matrix() + + +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) + + +def _set_size_31x31(): + _set_size(31, 31, 0) + + +def _set_size_41x41(): + _set_size(41, 41, 1) + + +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 + + +def _developer(): + showinfo(title='开发者信息', message='当前版本:v 1.0.7\n开发时间:2020年2月\n开发者:Howard Wonanut') + + +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) + + helpmenu = tk.Menu(menubar, tearoff=0) + menubar.add_cascade(label='帮助', menu=helpmenu) + helpmenu.add_command(label='操作说明', command=_man, accelerator='F6') + helpmenu.add_command(label='开发者信息', command=_developer, accelerator='F7') + + 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("", _paint_answer_path) + canvas.bind("", _reset_answer_path) + canvas.bind_all("", _event_handler) + windows.mainloop() \ No newline at end of file diff --git a/README.md b/README.md index 8f5c213..ee25d81 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,109 @@ # Maze-Game -A funny maze game implemented with python3. -# 游戏介绍 -包含两个模块:使用四种算法生成迷宫以及迷宫的可视化以及可玩性设计。 +current-version: ***v1.0.7*** -语言:Python3 +简单模式截图: -界面: -### 普通模式 ![](./imgs/img1.png) -### 迷雾模式 + +迷雾模式截图: + ![](./imgs/img2.png) -# 版本更新 -### *v1.0.5*版本功能说明 -更新时间:2020年2月2日 - - [ ] 游戏背景音乐 - - [ ] 游戏难度递增 - - [x] 增加状态栏显示状态信息 - - [x] 作弊(查看提示)增加惩罚分数(当前作弊一次惩罚20分) - - [ ] ~~游戏界面截图~~ 经过深思熟虑决定取消此功能 - - [x] 菜单栏,可用于设置地图生成算法,地图尺寸等 - - [x] 增加迷雾模式 - - [ ] 增加生存模式,参考[Roguelike Vision Algorithms](http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html)丰富可玩性 - - [x] 显示等级以及当前移动步数 - - [x] 随机生成游戏地图 - - [x] 按方向键后自动前进倒退(到分岔路停止) - - [x] 起点到任意位置辅助路径显示(鼠标左键单击空白地方显示路线) 移动次数计数 - - [x] 到达终点后通关,按任意键进入下一关(目前没有难度设置,难度相同) + + +## Requirements + +- `Python3` +- `Pandas` +- `Numpy` +- ~~`Seaborn`~~ +- ~~`matplotlib`~~ + + + +## Download & Installation + +### 1 Download + +#### Git + +使用Git克隆当前项目到本地: + +``` +git clone https://github.com/wonanut/Maze-Game.git +``` + +#### Download + +直接下载当前项目到本地后解压 + + + +### 2 Installation + +进入根目录: + +``` +cd Maze-Game-Maze-game-v1.0.7 +``` + +在确保安装了上述python库之后,执行命令 + +```python +python Maze.py +``` + +如果执行上述命令不能打开且没有报错,多尝试几次即可。 + + + +## Log files + +程序将会在根目录自动生成日志文件 `./maze_game.log` + + + +## TODO lists + +- 游戏背景音乐以及交互音乐 +- 添加更人性化的显示界面 +- 完善历史数据展示功能 +- 进一步修改为2.5D或者3D,拟使用Unity开发 +- 添加闯关模式,将当前模式设置为休闲模式 +- 添加生存模式,参考[Roguelike Vision Algorithms](http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html)丰富可玩性 +- 完善当前迷雾模式,添加视野以及奖励 +- 开发Android版 + + + +## Update information + +- 2020-02-04 ***v1.0.7*** 添加功能: + 1. 添加保存、读取地图功能 + 2. 添加快捷键操作 + 3. 进一步完善编辑菜单功能,添加 回到起点,换个地图等功能 + 4. 添加日志功能,目前暂时不支持统计结果展示功能 + 5. 添加地图大小选择功能,目前支持三种尺寸地图:31x31, 41x41, 81x37 + 6. 添加自动前进到下一个路口功能开关,默认开 + 7. 添加简单模式和迷雾模式切换开关,默认简单模式 + 8. 完善底部状态栏 + + + +- 2020-02-02 ***v1.0.5*** 版本上传,基础功能有 + 1. 增加状态栏显示状态信息 + 2. 作弊(查看提示)增加惩罚分数(当前作弊一次惩罚20分) + 3. 菜单栏,可用于设置地图生成算法,地图尺寸等(待完善) + 4. 增加迷雾模式 + 5. 显示等级以及当前移动步数 + 6. 随机生成游戏地图 + 7. 按方向键后自动前进倒退(到分岔路停止) + 8. 起点到任意位置辅助路径显示(鼠标左键单击空白地方显示路线) 移动次数计数 + 9. 到达终点后通关,按任意键进入下一关(目前没有难度设置,难度相同) + + + +## Developer + +Howard Wonanut:wonanut@foxmail.com diff --git a/imgs/img1.png b/imgs/img1.png index 0d73973..6fc5eb3 100644 Binary files a/imgs/img1.png and b/imgs/img1.png differ diff --git a/imgs/img2.png b/imgs/img2.png index 9f6a96d..24a92aa 100644 Binary files a/imgs/img2.png and b/imgs/img2.png differ diff --git a/mazeGenerator.py b/mazeGenerator.py index 91de574..3799895 100644 --- a/mazeGenerator.py +++ b/mazeGenerator.py @@ -1,258 +1,285 @@ -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_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 - - # 虽然说是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 - - 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) - - # 最小生成树算法-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 - - # 迷宫寻路算法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() - - +# @Author: Howard Wonanut +# @Version: v1.0.7 +# @Date: 2020-02-04 + +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 + + # 虽然说是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 + + 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) + + # 最小生成树算法-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 + + # 迷宫寻路算法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() + +