项目目标和主要内容:
(1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
(2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
(3)要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
项目的主要功能:
1、迷宫随机生成
2、玩家走迷宫,留下足迹;
3、系统用A*算法寻路,输出路径
迷宫设计内容:
1. 设计数据结构表示迷宫。内存中的迷宫表示数据结构可以用二维矩阵。矩阵
元素的值代表对应位置不同状态:入口,出口,过道,墙。
2. 初始化迷宫。生成迷宫,然后将玩家放置在某个位置(入口之一,或者就过
道某处)。对于迷宫 M,玩家初始位置为 S,指定出口为 E。如果存在从 S 到 E 的
一条连通的道路,则称为 S、E 之间的通路。
(1)存储迷宫。
(2)读入迷宫。
3. 寻找通路。
(1)手工交互方式。如果存在通路,玩家在规定时间交互到达指定出口,成功。
如果不存在通路,玩家判断没有通路,成功。否则,挑战失败。
(2)自动模式。算法判定是否存在通路。如果存在,显示所有通路。
在设计这个迷宫游戏大作业时,我首先进行了整体框架和实现过程的详细分析。我决定使用C++作为基础语言,并利用流行的集成开发环境Qt Creator来编写游戏。由于之前对Qt知识了解不多,所以在编写程序的过程中遇到了一些困难,但同时也让我获得了很多经验。
Qt编写是这次迷宫游戏的一个难点,另一个难题是实现迷宫算法。虽然我之前接触过一些相关知识,比如深度优先搜索,但在迷宫生成的prim算法和迷宫自动寻路的几种算法上,我还有一些不熟悉的地方。为了解决这些问题,我上网查阅了各种资料,包括电子教材资源、CSDN博客、GitHub等。虽然大部分问题都得到了解决,但仍有一些细节问题没有完全明白,于是我向认识的其他程序员请教,得到了他们的帮助,同时也解决了其他问题。
最后,我使用Qt官方提供的windeployqt工具对整个游戏进行了打包,这样用户可以方便地下载和使用。只需点击"solve_maze"文件即可运行游戏。
我认为这次软件实习主要增加了我在算法方面的知识,包括迷宫的生成和寻路的几种算法。同时,我还学会了一些新东西,比如Qt这种图形化编程语言,对我今后的学习和编程都有很大帮助。通过这个设计思路,我更加明确了整个项目的实现过程,并且能够清晰地表达我的工作思路和收获。
![](https://img-blog.csdnimg.cn/72876d32e6894ada8fcc8798345f19e4.png)
代码实现过程:
main.cpp文件:基于MainWindow的GUI应用程序的开始。
#include "mainwindow.h"
#include <QApplication>
#include <QIcon>
#include <QDesktopWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString atrPATH=QApplication::applicationDirPath();
atrPATH+="/img/df.png";
a.setWindowIcon(QIcon(atrPATH));
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp文件:设置见面窗口大小不能更改,设置见面窗口的图片。
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint); // 禁止最大化按钮
setFixedSize(this->width(),this->height()); // 禁止拖动窗口大小
mymainwindow.load("C:/Users/14700/Desktop/plant/img/mainwindow.jpg");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
this->hide();
//mymaze.resize(800,700);
mymaze.show();
}
void MainWindow::paintEvent(QPaintEvent *){
QPainter painter(this);
painter.drawPixmap(0,0,500,325,mymainwindow);
}
mainwindow.h文件:构造见面窗口的类,放入QT中的槽、信号等。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "play_maze.h"
#include "qpainter.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT//
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots://
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
play_maze mymaze;
QPixmap mymainwindow;
void paintEvent(QPaintEvent *);
};
#endif // MAINWINDOW_H
mainwindow.ui文件:通过qt creator进行编辑见面窗口的样式,包括见面窗口的大小、进入游戏按钮大小及字体等,编码实现的过程类似于web前端开发课程的的代码结构,所以很节约时间。
playmaze.h文件:封装了游戏的类。
playmaze.cpp文件:※游戏的核心代码※
play_maze(QWidget *parent)函数:设置游戏界面大小及游戏图片背景信息。
play_maze::play_maze(QWidget *parent) :
QWidget(parent),
ui(new Ui::play_maze)
{
ui->setupUi(this);
control_direction=2;//往右
setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint); // 禁止最大化按钮
setFixedSize(this->width(),this->height()); // 禁止拖动窗口大小
//this->move((a.desktop()->width() - this->width()) / 2, (app.desktop()->height() - win.height()) / 2);
QDesktopWidget *deskdop = QApplication::desktop();
move((deskdop->width() - this->width())/2, (deskdop->height() - this->height())/2);
control_LEFT.load("C:/Users/14700/Desktop/plant/img/cool_boy.jpg");
control_RIGHT.load("C:/Users/14700/Desktop/plant/img/cool_boy.jpg");
target.load("C:/Users/14700/Desktop/plant/img/girl.png");
road.load("C:/Users/14700/Desktop/plant/img/road.jpg");
wall.load("C:/Users/14700/Desktop/plant/img/nangua.png");
}
on_creat_maze_clicked函数:生成迷宫,迷宫采用二维数组方式,生成迷宫采用prim算法,常见迷宫(即任意两点间都能够找到路径,且仅有一条成功路径)可看做一组连通图,默认所有点间都为墙,我需要做的就是遍历整个图中所有的点,并且不重复访问,在遍历图的过程中将相邻(指的是上下左右相邻)的两个点间的墙随机打通;满足条件的常见算法有:Prim,Kruskal,DFS,BFS等,这里我采用了prim算法。
while(true){//利用prim算法生成迷宫
temp.i=i;
temp.j=j;
int randnum=rand()%4;
switch (randnum) {
case 0: if(!up&&i>2&&maze[i-2][j].state==0){
build_maze_stack.push(temp);
maze[i-2][j].state=1;
maze[i-1][j].state=1;
i=i-2;
if(maze[i-1][j].state==0)
up=false;
else
up=true;
if(maze[i+1][j].state==0)
down=false;
else
down=true;
if(maze[i][j-1].state==0)
left=false;
else
left=true;
if(maze[i][j+1].state==0)
right=false;
else
right=true;
}
else{
up=true;
}
break;
case 1: if(!down&&i<maze_row-3&&maze[i+2][j].state==0)
{
build_maze_stack.push(temp);
maze[i+2][j].state=1;
maze[i+1][j].state=1;
i=i+2;
if(maze[i-1][j].state==0)
up=false;
else
up=true;
if(maze[i+1][j].state==0)
down=false;
else
down=true;
if(maze[i][j-1].state==0)
left=false;
else
left=true;
if(maze[i][j+1].state==0)
right=false;
else
right=true;
}
else{
down=true;
}
break;
case 2: if(!left&&j>2&&maze[i][j-2].state==0)
{
build_maze_stack.push(temp);
maze[i][j-2].state = 1;
maze[i][j-1].state = 1;
j = j-2;
if(maze[i-1][j].state == 0)
up = false;
else
up = true;
if(maze[i+1][j].state == 0)
down = false;
else
down = true;
if(maze[i][j-1].state == 0)
left = false;
else
left = true;
if(maze[i][j+1].state==0)
right = false;
else
right = true;
}
else{
left = true;
}
break;
case 3: if(!right&&j<maze_col-3&&maze[i][j+2].state == 0)
{
build_maze_stack.push(temp);
maze[i][j+2].state = 1;
maze[i][j+1].state = 1;
j = j+2;
if(maze[i-1][j].state == 0)
up = false;
else
up = true;
if(maze[i+1][j].state == 0)
down = false;
else
down = true;
if(maze[i][j-1].state == 0)
left = false;
else
left = true;
if(maze[i][j+1].state == 0)
right = false;
else
right = true;
}
else{
right=true;
}
break;
}
if(up&&down&&left&&right){
if(!build_maze_stack.isEmpty()){
i = build_maze_stack.top().i;
j = build_maze_stack.top().j;
build_maze_stack.pop();
if(maze[i-1][j].state==0)
up=false;
else
up=true;
if(maze[i+1][j].state == 0)
down = false;
else
down = true;
if(maze[i][j-1].state == 0)
left = false;
else
left = true;
if(maze[i][j+1].state == 0)
right = false;
else
right = true;
}
else{
maze[1][1].state = 2;
maze[maze_row-3][maze_col-3].state=3;
creat_maze = true;
for(int i=0; i < maze_row; i++)//防止生成迷宫后依旧显示路线
for(int j = 0; j < maze_col; j++){
path[i][j].state = 0;//在这里的状态表示父节点,1,2,3,4分别表示从上下左右发展过来
path[i][j].i = i;
path[i][j].j = j;
}
//qDebug()<<"creat maze successfully!"<<endl;
return;
}
}
}
}
on_A_search_clicked函数:A*算法,从起点开始行动,首先找到起点周围可以行走的节点,然后在这个节点中,评估出距离终点最优(最短)的节点。那么这个最优节点,将作为下一步行动的点,以此类推,直至找到终点。
void play_maze::on_A_search_clicked()//A*算法
{
if(flag_click||flag_success){
return;
}
if(!creat_maze){
return;
}
A_search.clear();
//int md;//曼哈顿距离
qDebug()<<"begin find path";
ftime.start();
for(int i=0; i<maze_row; i++)
for(int j=0; j<maze_col; j++){
path[i][j].state=0;//在这里的状态表示父节点,1,2,3,4分别表示从上下左右发展过来
path[i][j].i=i;
path[i][j].j=j;
}
//用于记录该点是否搜索的矩阵
for(int i=0; i<maze_row; i++)
for(int j=0; j<maze_col; j++){
if(maze[i][j].state==0)
graph[i][j]=1;
else
graph[i][j]=0;//初始化未被搜索
}
QString message;
int searchnum=0;//搜索节点次数
point top;
top.i=control_X;
top.j=control_Y;
top.state=abs(control_X-target_X)+abs(control_Y-target_Y);
A_search.push_back(top);//这里的状态什么也不表示
graph[top.i][top.j]=0;
while(!A_search.isEmpty()){
qSort(A_search.begin(),A_search.end(),cmp);
top=A_search.front();
//qDebug()<<top.i<<" "<<top.j<<" "<<top.state<<endl;
A_search.pop_front();
if(graph[top.i][top.j]==0){
searchnum+=1;
if(maze[top.i][top.j].state==3){
QString s=QString::number(ftime.elapsed(),10);
// message="花费时间:"+s+"ms";
// QMessageBox::information(NULL, " ", message, QMessageBox::Yes, QMessageBox::Yes);
break;
}
//将此点标记为访问过
//并将真实路径代价计算进去,用graph存储
switch (path[top.i][top.j].state) {
case 1:
graph[top.i][top.j]=graph[top.i-1][top.j]+1;
break;
case 2:
graph[top.i][top.j]=graph[top.i+1][top.j]+1;
break;
case 3:
graph[top.i][top.j]=graph[top.i][top.j-1]+1;
break;
case 4:
graph[top.i][top.j]=graph[top.i][top.j+1]+1;
break;
default:
graph[top.i][top.j]=1;
break;
}
//将未访问的子节点放入开节点表
if((graph[top.i+1][top.j]==0)&&(maze[top.i+1][top.j].state!=0)){
A_search.push_back(point(top.i+1,top.j,(graph[top.i][top.j]+1+abs(top.i+1-target_X)+abs(top.j-target_Y))));
path[top.i+1][top.j].state=1;
//graph[top.i+1][top.j]=graph[top.i][top.j]+1;
}
if((graph[top.i-1][top.j]==0)&&(maze[top.i-1][top.j].state!=0)){
A_search.push_back(point(top.i-1,top.j,(graph[top.i][top.j]+1+abs(top.i-1-target_X)+abs(top.j-target_Y))));
path[top.i-1][top.j].state=2;
//graph[top.i-1][top.j]=graph[top.i][top.j]+1;
}
if((graph[top.i][top.j+1]==0)&&(maze[top.i][top.j+1].state!=0)){
A_search.push_back(point(top.i,top.j+1,(graph[top.i][top.j]+1+abs(top.i-target_X)+abs(top.j+1-target_Y))));
path[top.i][top.j+1].state=3;
//searchnum+=1;
//graph[top.i][top.j+1]=graph[top.i][top.j]+1;
}
if((graph[top.i][top.j-1]==0)&&(maze[top.i][top.j-1].state!=0)){
A_search.push_back(point(top.i,top.j-1,(graph[top.i][top.j]+1+abs(top.i-target_X)+abs(top.j-1-target_Y))));
path[top.i][top.j-1].state=4;
//searchnum+=1;
//graph[top.i][top.j-1]=graph[top.i][top.j]+1;
}
}
}
qDebug()<<"find the path!"<<endl;
}
mousePressEvent(QMouseEvent *mouse)函数:自动寻路
void play_maze::mousePressEvent(QMouseEvent *mouse){//自动寻路
if(!creat_maze){
return;
}
int x=mouse->x()-5;
int y=mouse->y()-5;
target_X2=x/maze_cell_size;
target_Y2=y/maze_cell_size;
//qDebug()<<maze[target_X2][target_Y2].state<<" aaaa";
if(maze[target_X2][target_Y2].state==0||maze[target_X2][target_Y2].state==3){
flag_click=false;
return;
}
else{
flag_click=true;
}
//下面开始找路
if(creat_maze==false){
return;
}
auto_path2.clear();
auto_path.clear();
//int md;//曼哈顿距离
qDebug()<<"begin find path";
for(int i=0; i<maze_row; i++)
for(int j=0; j<maze_col; j++){
path[i][j].state=0;//在这里的状态表示父节点,1,2,3,4分别表示从上下左右发展过来
path[i][j].i=i;
path[i][j].j=j;
}
//用于记录该点是否搜索的矩阵
for(int i=0; i<maze_row; i++)
for(int j=0; j<maze_col; j++){
if(maze[i][j].state==0)
graph[i][j]=1;
else
graph[i][j]=0;//初始化未被搜索
}
point top;
top.i = control_X;
top.j = control_Y;
top.state = abs(control_X-target_X2)+abs(control_Y-target_Y2);
auto_path.push_back(top);//这里的状态什么也不表示
while(!auto_path.isEmpty()){
qSort(auto_path.begin(),auto_path.end(),cmp);
top=auto_path.front();
//qDebug()<<top.i<<" "<<top.j<<" "<<top.state<<endl;
auto_path.pop_front();
if(graph[top.i][top.j]==0){
if(top.i == target_X2&&top.j == target_Y2){
//QMessageBox::information(NULL, " ", "搜索成功", QMessageBox::Yes, QMessageBox::Yes);
break;
}
//将未访问的子节点放入开节点表
if((graph[top.i+1][top.j] == 0)&&(maze[top.i+1][top.j].state != 0)){
auto_path.push_back(point(top.i+1,top.j,abs(top.i+1-target_X2)+abs(top.j-target_Y2)));
path[top.i+1][top.j].state = 1;
}
if((graph[top.i-1][top.j] == 0)&&(maze[top.i-1][top.j].state != 0)){
auto_path.push_back(point(top.i-1,top.j,abs(top.i-1-target_X2)+abs(top.j-target_Y2)));
path[top.i-1][top.j].state = 2;
}
if((graph[top.i][top.j+1] == 0)&&(maze[top.i][top.j+1].state != 0)){
auto_path.push_back(point(top.i,top.j+1,abs(top.i-target_X2)+abs(top.j+1-target_Y2)));
path[top.i][top.j+1].state = 3;
}
if((graph[top.i][top.j-1] == 0)&&(maze[top.i][top.j-1].state != 0)){
auto_path.push_back(point(top.i,top.j-1,abs(top.i-target_X)+abs(top.j-1-target_Y2)));
path[top.i][top.j-1].state = 4;
}
//将此点标记为访问过
graph[top.i][top.j]=1;
}
}
qDebug()<<"find the path!"<<endl;
//下面进行反序
point mypoint;
point pre_mypoint;
mypoint=path[target_X2][target_Y2];
while(mypoint.state!=0){
if(mypoint.i==control_X&&mypoint.j==control_Y)
break;
if(mypoint.state==1)//上
pre_mypoint=path[mypoint.i-1][mypoint.j];
if(mypoint.state==2)//下
pre_mypoint=path[mypoint.i+1][mypoint.j];
if(mypoint.state==3)//左
pre_mypoint=path[mypoint.i][mypoint.j-1];
if(mypoint.state==4)//右
pre_mypoint=path[mypoint.i][mypoint.j+1];
auto_path2.push(mypoint);
mypoint=pre_mypoint;
}
for(int i=0; i<maze_row; i++)
for(int j=0; j<maze_col; j++){
path[i][j].state=0;//在这里的状态表示父节点,1,2,3,4分别表示从上下左右发展过来
path[i][j].i=i;
path[i][j].j=j;
}
}
![](https://img-blog.csdnimg.cn/4ff5629fa0a1488499df23b24d74b1ad.png)
运行结果:
依托植物大战僵尸的游戏平台为支撑建立游戏背景,以人物走过迷宫找到人物为游戏的成功标志,玩家可设置迷宫的行列数来调整游戏难度。若玩家无法找到路线可点击准备好的自动寻路的算法获取提示。
![](https://img-blog.csdnimg.cn/57bfbb8cb87d44e580c4d69a11fb3551.png)
![](https://img-blog.csdnimg.cn/ddbbf1d9ef6440a9bff5a43b41036be7.png)
![](https://img-blog.csdnimg.cn/301d72f3114b4793ac39d37f69d39b08.png)
![](https://img-blog.csdnimg.cn/1f2ee5e18dc243e1b4e5a247a1b95f05.png)
![](https://img-blog.csdnimg.cn/df957221475c4927885dafd95497e1f2.png)
算法实现自动寻路并显示路线标题
心得体会:
在这个qt基于A*算法迷宫游戏开发的过程中,我收获了很多知识和经验。首先,我学习了A*搜索算法,这是一种常见的最短路径搜索算法,能够在迷宫中找到从起点到终点的最短路径。通过学习这个算法,我更好地理解了搜索算法的原理和应用,同时也提高了我的编程能力。
其次,我学习了Qt框架和C++语言,这是一门非常实用的编程语言,也是很多应用程序的开发基础。在此次项目中,我掌握了Qt基本控件的使用方法,并且学会了使用信号与槽机制等高级技术,提高了我的GUI编程能力。
最后,我要感谢我的老师王艳,她在整个项目期间给予了我很多的帮助和指导。她不仅耐心地解答我的问题,还为我提供了很多有用的建议和提示,这对我的学习和实践都非常有帮助。