计算机软件实习实验三基于A*算法迷宫游戏开发

项目目标和主要内容:

(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这种图形化编程语言,对我今后的学习和编程都有很大帮助。通过这个设计思路,我更加明确了整个项目的实现过程,并且能够清晰地表达我的工作思路和收获。

图1  完成时间轴

代码实现过程:

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;
        }



}
图2  游戏程序结构

运行结果:

依托植物大战僵尸的游戏平台为支撑建立游戏背景,以人物走过迷宫找到人物为游戏的成功标志,玩家可设置迷宫的行列数来调整游戏难度。若玩家无法找到路线可点击准备好的自动寻路的算法获取提示。

图3  迷宫游戏功能架构图
​​​​​​

图4 主界面

 

 

 

图5  游戏界面标题

图6  成功找到出口提示标题
图8  A**
算法实现自动寻路并显示路线标题

 

 心得体会:

在这个qt基于A*算法迷宫游戏开发的过程中,我收获了很多知识和经验。首先,我学习了A*搜索算法,这是一种常见的最短路径搜索算法,能够在迷宫中找到从起点到终点的最短路径。通过学习这个算法,我更好地理解了搜索算法的原理和应用,同时也提高了我的编程能力。

其次,我学习了Qt框架和C++语言,这是一门非常实用的编程语言,也是很多应用程序的开发基础。在此次项目中,我掌握了Qt基本控件的使用方法,并且学会了使用信号与槽机制等高级技术,提高了我的GUI编程能力。

最后,我要感谢我的老师王艳,她在整个项目期间给予了我很多的帮助和指导。她不仅耐心地解答我的问题,还为我提供了很多有用的建议和提示,这对我的学习和实践都非常有帮助。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于A*算法的迷宫小游戏开发,可以让玩家面对迷宫的挑战,通过智慧和策略找到迷宫的出口。 首先,我们需要设计一个迷宫地图。可以采用多种方式生成迷宫地图,如随机生成、手动设计或者使用迷宫生成算法。迷宫地图由起点、终点以及迷宫墙壁组成。 接下来,我们使用A*算法来寻找最佳路径。A*算法是一种启发式搜索算法,通过估计每个节点到目标点的距离来决定搜索方向。在实现A*算法时,需要定义一个启发函数来评估节点的价值,以便选择最优的路径。在该游戏中,可以使用曼哈顿距离或欧几里得距离作为启发函数。 当玩家开始游戏后,可以使用方向键或鼠标来控制角色移动。同时,在游戏界面上显示迷宫地图和玩家的当前位置。 在实现A*算法时,需要考虑一些特殊情况。比如,如何处理墙壁、如何处理无法到达的位置等。可以采用合适的数据结构,如优先队列或堆栈,来实现算法的搜索和路径的存储。 最后,为了增加游戏的趣味性和挑战性,可以在迷宫中添加一些道具或陷阱,用来干扰玩家的寻路过程。比如,道具可以提供额外的移动能力,而陷阱则会减慢玩家的速度。 通过以上方法,基于A*算法的迷宫小游戏可以提供给玩家一个有趣而挑战的寻路体验。同时,这个游戏也可以帮助玩家锻炼逻辑思维和空间认知能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值