本文为我个人原创,首发于我的个人博客:http://migod.top/176.html,转载请注明出处!
项目介绍
迷宫大师是本人的C++程序设计的大作业,是一个可视化的迷宫小游戏。可视化界面基于Qt5,使用Qt Creator开发。
项目主要有如下特点:
- 对Qt自带的控件进行了二次封装,以实现更加美观的游戏效果;
- 为了更方便的设计关卡,配套开发了可视化的迷宫地图编辑器,并实现了复杂迷宫地图的深度优先生成;
- 迷宫游戏实现了文件的读写,实现了导出和读取玩家自制地图,并加入了通过DFS绘制迷宫出路的功能;
项目的设计树形图如下:
演示截图
- 主界面
- 选择关卡界面
- 游玩界面
- 游玩界面 – 自动寻路
(注明:左下角的终点无法到达,寻找到了右上角的终点)
- 游玩界面 – 胜利/失败
- 编辑地图界面(可以用鼠标点击Tile来更改地图图块的状态)
- 编辑地图界面 – 随机生成
开发思路
使用Qt GUI框架
Qt是一个成熟的跨平台的C++图形用户界面应用程序框架。选择Qt的作为GUI框架相对于选择OpenGL可以节省很多不必要的开发时间,提升开发效率的同时保证稳定性。Qt提供了一些工具类,比如QString,QDebug等。笔者选择使用Qt自带的这些类而不是C++本身提供的string,cin,cout这些工具类,以保证程序内部信息流交互更加方便。
封装图片按钮类 ImgButton
我们手动封装一个自己的图片按钮类:ImgButton。这个类继承QPushButton。
利用这个类和笔者自己用Photoshop制作的按钮图片素材,我们可以实现比Qt自带的QPushButton更美观的按钮。笔者还为按钮添加了音效。
笔者利用Photoshop自己制作的部分按钮素材如下(具体见源代码res文件夹):
主窗口(标题界面)MainWindow
主窗口将选关窗口和地图编辑窗口作为数据成员,便于进行与这些界面间的切换。
封装地图图块类 Tile
我们继承QPushButton封装一个地图图块类Tile,之后利用这个类和图片素材绘制迷宫地图。利用网上的图像素材,笔者制作的地图图块如下(按顺序为墙、路、起点、终点):
迷宫数据工具类 MazeData
设计迷宫数据文件:Unicode编码的文本文件,第一行n为迷宫大小(迷宫宽高相等),之后n行是迷宫地图信息。
如:
11
03000000000
01111111110
00000000010
01111111010
01000001010
01011111010
01010001010
01010211010
01010000010
01011111110
00000000000
为了方便我们在编辑窗口和游玩窗口快速读写迷宫文件,笔者设计了一个迷宫数据工具类MazeData。我们利用二维数组保存迷宫数据。
地图编辑窗口 MapEditWindow
加入地图编辑窗口,既方便笔者自己制作迷宫关卡,也为玩家提供了扩展游戏玩法和难度的可能性。通过笔者封装的Tile类,可以实现鼠标点击绘制。
地图编辑窗口类包含了本程序的核心难点之一:迷宫地图深度优先生成算法。这个算法使用在此类的createMaze成员函数里。算法的伪代码如下:
设置 迷宫内所有点 为 墙壁
设置 (1,1)点 为 路
把 (1,1)点 入栈
设置 迷宫连通标志 为 未连通
如果 迷宫连通标志 为 未连通,执行循环:
设置 当前点 为 栈内最后一个点 #因为最后一个储存的是最深的
寻找 当前点 潜在的邻居 #潜在邻居上下左右的墙都是未打通的,且和当前点只隔了一个墙
如果 存在潜在的邻居:
设置 任意一个 潜在的邻居 为 真邻居
打通 当前点 与 真邻居 之间的墙
把 真邻居 入栈
否则:#该路径没有合适的邻居,去除最后一个不满足要求的,往上一个点寻找
出栈
判断地图是否连通,把结果储存到 迷宫连通标志
算法流程图如下:
选关窗口 ChooseLevelWindow
选关窗口将游玩窗口为该类的数据成员,便于窗口间切换。除了内置关卡,笔者在选关窗口增加了读取地图的按钮,以期拓展游戏的可玩性。
游玩窗口 PlayWindow
游玩窗口是游戏的核心窗口。游玩窗口实现玩家移动功能、判定是否存在通路功能、绘制通路功能、游戏倒计时功能,还增加了成功或者失败时的简单动画。
地图编辑窗口类包含了本程序的另一个难点:深度优先搜索迷宫通路(笔者采用了利用栈的非递归的实现)。这个算法使用在此类的findPosssibleWay成员函数里。算法的思路如下:
- 首先起点入栈
pos_stack
,只要pos_stack
中有值,说明还有待遍历的位置,继续遍历 - 进入循环体,说明该点被遍历,该点加入
possible_path
栈 - 判断该点是否为出口,如果是,已经发现一条可行路线,返回true`,函数结束
- 如果循环向下执行,说明不是终点,将该点标记为已经走过
- 从该点探索与该点连接的,其他可走的位置,入栈
- 如果没有任何点入栈,说明是死路或者正在回退中,进行一步回溯,即出栈
- 如果离开循环体,说明没有通路,返回false
以上为各模块功能介绍及设计思路,具体实现见源代码。
总结
1、大作业完成过程中存在的问题、解决方法以及我的心得体会
在设计迷宫大师时,我计划要添加一个GUI的迷宫编辑的功能,但一开始一直没有想清楚要如何实现非文本输入的方式编辑迷宫。后来我在对Qt有一定了解之后,我发现我可以对QPushButton做一次二次封装,做成我自己的地图图块。这个图块可以开启或者关闭按钮功能,如果开启按钮功能,点击就能更换为其他状态(比如从墙变成路),于是问题就迎刃而解了。
在程序的地图绘制方面,我利用采取循环动态生成这个自己封装的地图图块Tile,并把图块保存在了31*31的二维数组里。实际上这个部分可以改用二维Vector或者QVector保存,可以在节省内存空间的同时解除地图大小的限制。
迷宫寻路采用常规的DFS,并且尝试使用了少见的非递归DFS解决了问题。
迷宫生成算法一开始没有思路,但是在网上查阅之后,我发现深度优先生成迷宫算法的效果比较好,于是在理解了算法之后将其添加到了程序里,实现了在编辑窗口自动生成迷宫的功能。
2、查阅文献的过程
我是从零开始学习Qt GUI框架的,所以一开始并没有急着开始着手迷宫大师的制作,而是先进行对Qt基本语法的学习。我购买了一本《Qt5开发及实例》,在快速看完了前五章之后才开始着手制作自己的迷宫程序。
另外,我在开发时也遇到了C++上的一些问题,这些问题我除了直接搜索,还经常在C++ Reference这个英文网站(http://www.cplusplus.com/reference/)上进行参考查阅。C++ Reference对C++各个类和功能的完整说明给了我很大帮助。在学习Qt时,我还发现我对Qt经常使用的C++ lambda表达式不太熟练,遇上我查阅了《C++ Primer》10.3.2.节 Lambda Expressions的内容——这对我理解C++ 11的lambda表达式起到了一定帮助。
除此之外,Qt官方的Qt5说明文档也是我在完成大作业时参考频率非常高的文献。
3、通过本门课程实验和大作业,自己对软件维护的认识
(1) 面向对象的程序设计思想是很重要的。设计程序时,根据现实情况将程序封装成一个个对象,能够有效地帮助我梳理程序结构;并且由于面向对象后的class对功能的封装,提高了我的代码复用率和可扩充性,而且让我在添加新功能的时候能够减少一些不必要的考虑。
(2) 代码注释是很有必要的。对代码进行规范的注释,可以让程序员可以更好的理解代码的思路和用途。在本次大作业中,我进行了大量标注,尤其是在涉及到核心算法的部分更是有详细标注,这对我进行功能扩展和后期Debug起到了很大帮助。
(3) 良好的缩进与代码风格可以减少我写代码时的阅读疲劳。
源代码及可执行程序下载/Clone
github:
https://github.com/migodz/Qt-MazeMaster
如果这个项目有帮助到你,还请点个Star,拜托啦!