基于JAVA的迷宫游戏设计与实现

Blackbox

发布日期: 2018-10-31 10:54:10 浏览量: 1525
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

一、问题描述

1.1 课程设计题目

程序开始运行时显示一个迷宫地图,迷宫中央有一只老鼠,迷宫的右下方有一个粮仓。游戏的任务是使用键盘上的方向键操纵老鼠在规定的时间内走到粮仓处。

1.2 课程设计要求

  • 老鼠形象可辨认,可用键盘操纵老鼠上下左右移动

  • 迷宫的墙足够结实,老鼠不能穿墙而过

  • 正确检测结果,若老鼠在规定时间内走到粮仓处,提示成功,否则提示失败

  • 添加编辑迷宫功能,可修改当前迷宫,修改内容:墙变路、路变墙

  • 找出走出迷宫的所有路径,以及最短路径

  • 利用序列化功能实现迷宫地图文件的存盘和读出等功能

二、需求分析

迷宫游戏,作为一种经典的游戏,与之相关的程序可谓琳琅满目,数不胜数。在这种情况下,自己设计的程序要想有点看头,单靠黑洞洞的控制台是绝对不行的,至少要有像样一点的图形界面和简单方便的操作。

上学期刚学过C++之后,我通过自学MFC,完成了拥有图形界面的课程设计。这学期学数据结构的同时,也学习了另外一门重要的高级语言——java,所以,也想牛刀小试,玩一下java的图形界面设计。

为了达到题目中的要求,用java实现的话,显然需要用到它的图形界面、事件驱动、多线程等知识。前两部分内容,教材上讲的已基本上能帮我完成相关设计了,多线程的知识,教材上没有,但查阅相关资料(包括图书馆的纸质书籍和网上的各种论坛信息),也很容易找到设置时间限制的方法。

Java工具的问题解决了,毕竟大神们把这些工具设计的很方便和漂亮,使用起来当然不会太难,所以,剩下的才是核心部分——真正实现程序设计要求的重要算法。

用户只能自己设计迷宫玩是不太方便的,当然也不能没有这个功能,但我想在保留此功能的同时,添加一个生成随机迷宫的功能。当然这种随机不能是简单的调用java提供的random方法,这样所得到的迷宫很难保证有通路。要想设计出随机的,而且至少有一条通路的迷宫,自然要用到特殊的、巧妙的算法。另外,要求出最短路径,也是需要好的算法设计的,算法时间复杂度太高的话,用户也会等的不耐烦的。

三、概要设计

3.1 程序界面设计

程序主要有两个界面,均继承自javax.swing.Jframe,它们分别是开始界面StartUI和游戏界面map。

StartUI界面提供4个操作按钮,让用户选择游戏的场地大小,其中有一个按钮允许用户自定义场地大小。其实,这种设计完全是在模仿linux系统gnome集成桌面环境中所提供的扫雷游戏。

map界面主要是迷宫地图和操作菜单栏,在此选择了使用动态的gif图片显示老鼠。另外,除了实现利用方向键控制老鼠外,也顺便为菜单选项设计了快捷键(反正用的是相同的事件驱动处理程序,多加几个按键响应而已)。

3.2 总体功能框架

程序总体功能大致可分为游戏模块、编辑模块、文件操作模块以及提示路径模块,另外在菜单栏中提供一些帮助信息,姑且把它看作是帮助模块吧。

结合程序界面,程序总体功能框架如下图所示:

程序最核心,最重要的算法主要用在游戏模块和路径提示模块中,其它模块只不过是一些简单的操作而已。

四、详细设计

4.1 数据结构设计

程序源码由四个源文件组成,每个源文件有一个属性为public的类,它们分别是StartUI、map 、Operations、wrmPane。其中,StartUI和map是程序界面类,wrmPane是用于显示老鼠、路、墙、粮仓等元素的面板,Operations类中几乎提供了对迷宫进行操作的所有方法。此外,在某些类的定义里又有匿名内部类的存在,不再细述。

虽然本程序的核心算法用到了栈和图论的思想,但我并没有定义相应的数据结构。其原因在于:对于栈来说,本来就可以通过对数组的简单操作很方便的实现压栈、出栈等功能;对于图来说,由于迷宫可以看作是一个相对来说很有特点的图,它点与点之间的关系不像一般的图那样随意,一个点只可能与它周围四个方向有确定关系的点之间具有直接联系。因此,迷宫本身自带的信息就已经构成了一种抽象一点的邻接链表或邻接矩阵(我本人感觉它更像是个邻接链表),所以也没必要再去实际构造这些数据类型来存储迷宫信息。

为了降低求取迷宫最短路径算法的时间和空间复杂度,也就是说尽可能地优化算法,程序设计中引入了路径深度和路径深度图的概念,从而很巧妙地设计出了相对于传统的迷宫最短路径算法更易理解且效率更高的算法(当然,这种绝妙的算法思想绝不是我的小脑袋瓜所能构想出来的,这是我从一篇论文上看到的)。明白了它的思想之后,算法是很容易写出来的。这种算法虽然离不开图的思想,但还是能通过对数组的简单操作来实现。

顺便说一下,迷宫地图的每一块最小单元,其实就是二维平面上的一个点,所以,它本来是应该以二维的坐标来标识的,不过,我设计代码总喜欢尽可能地把维数降到最低,最后在算法设计中还是把它们转化为与之一一对应的点了。其实,这次的降维对提高算法效率而言,并没有什么实质性的效果,只是能让我写代码的时候看着更舒服而已(可能我确实有点强迫症吧)。

4.2 具体模块设计

下面介绍一下各功能模块的设计。由于各功能模块的实现难易程度不同,那些简单的模块就不再详细说明了。

4.2.1 游戏模块

该模块用到了一个重要的算法,同时需要对设计关于时间控制的线程以及按键事件驱动的处理。其中使用到的最主要的方法,原型如下:

  1. public static void DFS(int s);
  2. public static void creatMaze();

creatMaze方法通过调用DFS方法实现至少有一条通道的随机迷宫的生成。至于控制时间的线程,不过是继承Runnable接口后重写一下run方法进行倒计时,按键事件处理不过是根据按键不同调用相应方法,限于篇幅,它们的具体实现不再赘述。下面主要讲述一下生成随机迷宫的算法思想,并以流程图的方式大致描述一下它的代码实现。

该算法利用了图的深度优先遍历,算法的最初设计仅适用于行数和列数均为奇数的随机迷宫生成,我做了一点修改以使它能避开行数和列数的限制,同时将老鼠的位置设为随机。算法思想不难理解,我将修改后的算法思想大致描述如下:

首先,将所有的点初始化为墙,然后随机生成老鼠的位置,再将相应点修改成老鼠,粮仓的位置修改好;然后,从老鼠位置出发,利用深度优先遍历算法,通过随机遍历方向,将所有与老鼠位置X、Y坐标差值均为偶数的点遍历出来,并修改成路,最终连接成一条能联通所有这些点的通道。在此过程中,当我们需要由一个点连接到下一个点时,仅需要打通隔开它们的那堵墙(将它修改成路)即可。

遍历结束后,仅能保证老鼠可以到达迷宫上与之位置坐标差值均为偶数的任意点,而对于行数和列数均为奇数,且入口和出口分别位于左上角和右下角的迷宫来说,其入口和出口恰好符合这种关系,但条件不全就不能确保迷宫有通路了,这正是原算法的缺陷所在。弥补这个缺陷的方法很简单,我借助下/图说明一下:

假设粮仓在图2中的位置3上,很显然,田字格中必然有一个点与老鼠位置满足上述关系,也就是说,老鼠能到达该点。我们可以看到,若老鼠能到达2或4点的话,它也就能到达3了,所以唯一的问题就是位置1,我们不难发现,联通位置1和3也很简单,只需打通2或4就行了。于是,每次遍历后随机将位置2或4修改为路,就能保证迷宫在任何情况下都有通路了。

其实,还有一个不太重要的小问题,对于不满足行列数均为奇数的迷宫,这种算法产生的随机迷宫会在某一个或两个全为墙的边,这样的迷宫看起来是有点不“协调”的,所以,我稍微修改了一下遍历前的初始化工作,让这些边上的点随机初始化为墙或路。

通过以上步骤,就使得生成的迷宫满足随机性的同时,也至少拥有一条通道。

下面是算法大致流程图。

creatMaze方法流程图

DFS方法流程图

4.2.2 文件操作模块

该模块利用文件选择器java.awt. JFileChooser.。以二进制文档方式保存或导入迷宫结构。内容比较简单,故不再详述。

4.2.3 编辑模块

编辑模块利用ActionListener接口,通过对鼠标点击事件的处理,提供了墙路互变的功能,其中对生成的随机迷宫的编辑同样调用了creatMaze方法。

4.2.4 路径提示模块

该模块在程序非常重要,包括有程序两个核心算法,而且还引入了关于图论的新概念。模块中用到的两个方法,原型如下:

  1. public static void sortestPath();
  2. public static void findPath();

在讲述sortestPath方法之前先引入以下两个概念:

路径深度——从某位置走到出口的最短路径的长度,设每一方块为单位路径长度。假定出口处路径深度为0 , 障碍处路径深度为 - 1 。

路径深度图——与迷宫对应的图, 每一个节点值为与该节点对应的迷宫单元格的路径深度。

基于上述概念,不难证明,迷宫最短路径必然是一条从入口处开始、到出口处结束的路径深度逐次递减1的序列。因此,如果能求出整个迷宫每个单元格的路径深度,那么求解迷宫最短路径就简单了。于是,问题的重点就在于如何求解路径深度图。

稍作思考,我们不难理解,对于迷宫上任意一个非障碍物的点,它的路径深度值必然不大于其周围四个方向上的非障碍物点中最小路径深度值+1。sortestPath方法就是在这个结论的基础下设计的算法。算法每一个循环动态地更新所有点的路径深度直至整张图达到稳态。再通过得到的路径深度图求得最短路径。该方法的流程图如下:

findPath方法使用传统的回溯法查找迷宫中的一条接近最短路径的通道,我感觉它有点贪心法的影子,因为总是先挑眼前看来是最容易接近出口的方向,但其实质还是深度优先搜索,只是在搜索过程中设置了四个方向的优先级。

回溯法算法思想很简单:从当前点出发时,访问的下一个点要选择未访问过的可行方向中优先级最高的。若当前点周围所有方向上的点都不能访问,便往后退一步,继续以相同方式探路。如此循环往复,直至找到迷宫出口或完全无路可走。显然这里需要用到栈存储路径,并通过压栈和出栈操作实现探路过程中的前进和后退。

由于回溯法是一个经典算法,有关它的资料很多,在这里就不再画它的流程图了,但这并不代表我认为它不重要,它仍然是本程序设计中的三大核心算法之一。

4.2.5 帮助模块

该模块利用消息框显示游戏使用说明和作者信息,没什么可细述的。

五、调试运行分析

5.1 经典迷宫最短路径算法与本程序新算法在时间和空间效率上的对比

本程序求解最短路径的算法借助了路径深度图的思想,算法完成的工作可以分为两部分,即求解路径深度图和通过路径深度图求解最短路径。

显然算法的主要时间开销在第一部分上,这一部分主要过程是逐步判断每个单元格是否与周围四个单元格达到稳定平衡状态,设迷宫规模为 M*N,则时间复杂度为O(4*M*N),空间方面仅需一个长度为 M*N 的数组来存储各单元格的路径深度。

第二部分中当最短路径长度为K时,时间方面最多需要4*K次,空间方面存储路径只需长度为K队列。而K=O(M+N),故这一部分时间空间复杂度均为O(M+N)。

综上所述,本算法的时间和空间复杂度均为O(M*N)。

而经典迷宫最短路径算法中广度优先搜索,空间复杂度方面除了要存储迷宫单元格访问信息外,还需使用堆栈来保存大量搜索记录。时间复杂度方面和具体迷宫布局有关, 主要时间开销在探索上, 由于算法采用了递归反复探索, 通常情况下访问一个单元格后要依次访问 4 个后继方向上的单元格, 第一次找到迷宫出口的路径即为最短路径。假设最短路径长度要 N+M, 当不设置迷宫单元格访问矩阵时, 则几乎要探索 O(4^(N+M) ) 次, 时间复杂度远大于本算法。

深度优先算法, 空间复杂度与深度优先搜索相差不大, 但时间复杂度方面, 算法要实现找出最短路径, 必须遍历出所有到达出口的路径, 比较各条路径的总长度, 从而得出最短路径,因此时间复杂度大于广度优先搜索, 自然而然远大于本算法。

由此,可得出结论,本程序使用的新算法明显优于传统算法。

5.2 程序运行效果演示

在Windows控制台或linux终端输入指令java StartUI,即进入程序开始界面:

可选择程序提供的迷宫场地大小,也可以自定义迷宫行数和列数:

当输入数据有误或点击取消时,会有错误提示:

以后程序使用过程中,用户的误操作都会有相应提示,不再提供相关截图。

选择好正确的迷宫场地大小后,即可进入游戏界面:

刚开始,游戏是没有时间限制的,当在游戏菜单中选择设置时间限制选项时,弹出输入对话框:

真确设置好时间后,弹出如下消息:

选择之后,发现上方开始倒计时:

现在可以开始玩游戏,通过方向键控制老鼠的移动

当剩余时间不多时,倒计时颜色改变:

在时间限制内是否完成任务,都会有相应提示:

在提示菜单中有显示最短路径和随意显示一个路径的子菜单,点击后会有相应路径的显示:

文件菜单的子菜单中可选择保存迷宫结构或导入迷宫结构:

编辑模式中,可点击某块区域使墙路互变,不再截屏。

上传的附件 cloud_download 基于JAVA的迷宫游戏设计与实现.7z ( 444.70kb, 198次下载 )
error_outline 下载需要2点积分

keyboard_arrow_left上一篇 : 基于JAVA和MYSQL数据库实现的图书资料管理信息系统 linux下实现的ping程序 : 下一篇keyboard_arrow_right



Blackbox
2018-10-31 10:56:36
基于图的DFS深度优先遍历算法走迷宫,使用JAVA实现界面
半度七月
2019-05-14 09:01:31
很棒
cainiao
2019-06-25 20:51:01
为什么生成不了迷宫呀,求大佬指点

发送私信

一个人的喜悦,无非是岁月静好,现世安稳

9
文章数
10
评论数
最近文章
eject