From ca80e9f092a94ade51e4e69948d0734ae65aea22 Mon Sep 17 00:00:00 2001 From: Doran <1343121616@qq.com> Date: Tue, 4 Feb 2020 12:00:33 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=B8=8A=E4=BC=A0v1.0.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加如下功能: 1. 添加保存、读取地图功能 2. 添加快捷键操作 3. 进一步完善编辑菜单功能,添加 回到起点,换个地图等功能 4. 添加日志功能,目前暂时不支持统计结果展示功能 5. 添加地图大小选择功能,目前支持三种尺寸地图:31x31, 41x41, 81x37 6. 添加自动前进到下一个路口功能开关,默认开 7. 添加简单模式和迷雾模式切换开关,默认简单模式 8. 添加底部状态栏显示状态信息 --- Maze.py | 802 +++++++++++++++++++++++++++++------------------ mazeGenerator.py | 543 +++++++++++++++++--------------- 2 files changed, 782 insertions(+), 563 deletions(-) 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/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() + + From 33ed0646c0e2de5a092c02c11fe818e0a5fd7607 Mon Sep 17 00:00:00 2001 From: Doran <1343121616@qq.com> Date: Tue, 4 Feb 2020 12:32:41 +0800 Subject: [PATCH 2/4] Update README.md --- README.md | 125 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8f5c213..a904814 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 From 4a61b6901186a72f6ca505166b9451b591feab51 Mon Sep 17 00:00:00 2001 From: Doran <1343121616@qq.com> Date: Tue, 4 Feb 2020 12:41:13 +0800 Subject: [PATCH 3/4] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a904814..ee25d81 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ python Maze.py -### TODO lists +## TODO lists - 游戏背景音乐以及交互音乐 - 添加更人性化的显示界面 @@ -77,7 +77,7 @@ python Maze.py -### Update information +## Update information - 2020-02-04 ***v1.0.7*** 添加功能: 1. 添加保存、读取地图功能 From 6cb4097a930b79b427dfd7e5065971b92de74951 Mon Sep 17 00:00:00 2001 From: Doran <1343121616@qq.com> Date: Tue, 4 Feb 2020 12:55:37 +0800 Subject: [PATCH 4/4] Add files via upload --- imgs/img1.png | Bin 23454 -> 15367 bytes imgs/img2.png | Bin 23310 -> 27153 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/imgs/img1.png b/imgs/img1.png index 0d73973285911aa1e1a150bf00dad9bbd4f68703..6fc5eb309bddac18bc357efd24c5335cd4b2e839 100644 GIT binary patch literal 15367 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 literal 23454 zcmdUX2|Uz$+joni4U>wpg`!i|lzlmc)Dez0J0Uq5OUQ2Mq%0AYgJf%^veVecz9uo1 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

C%5D*!D9GB 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 DT?pA|