摘要:本文主要使用QT5,实现了一个走迷宫的小游戏,本文采用:非递归深度优先搜索算法(DFS)解决迷宫寻路问题,深度优先搜索是一种用于遍历或搜索图形结构的算法,优先访问尽可能深入的节点。非递归实现通过显式使用栈来模拟递归调用栈,避免了递归的函数调用开销。
除此外,本小游戏还可以自定义游戏地图。
文中源码文件【获取方式】:关注公众号:利哥AI实例探险,
给公众号发送 “小老鼠闯迷宫”
获取下载方式,关注发送关键字即可获取下载方式,无套路。由于本人能力有限,难免有疏漏之处。
1. 小游戏演示
小老鼠闯迷宫
2. 逻辑实现
2.1 主界面
- 使用QT Design拖拽窗口及按钮,并通过CSS设置界面效果。
- 封装按钮类,增加动画效果,通过按钮按下及抬起事件设置音效及动画等。
- Design中涉及到的按钮,需要右键提升为 封装的按钮类
//初始化选关窗口
chooseWindow = new ChooseLevelWindow;
//初始化编辑迷宫地图窗口
editWindow = new MapEditWindow;
//监听编辑地图窗口的返回按钮点击事件
connect(editWindow, &MapEditWindow::backBtnClicked, [=](){
editWindow->hide();
setGeometry(editWindow->geometry());
this->show();
});
//监听选关窗口的返回按钮点击事件
connect(chooseWindow, &ChooseLevelWindow::backBtnClicked, [=](){
chooseWindow->hide();
setGeometry(chooseWindow->geometry());
this->show();
});
void MainWindow::on_btnStart_clicked()
{
// 按钮弹跳效果
ui->btnStart->jumpDown();
ui->btnStart->jumpUp();
// 0.3秒后打开选择关卡页面
QTimer::singleShot(300, this, [=](){
this->hide();
chooseWindow->setGeometry(this->geometry());
chooseWindow->show();
});
}
2.2 选关界面
- 生成16关的关卡,并绑定各个关卡的地图,链接每个关卡按钮的信号和槽函数,点击关卡时匹配对应的地图。
- 提升按钮为自定义按钮类,加载动画效果。
选关界面的实际效果如下:
for (int i = 0; i < 16; i++) {
QPushButton *tmpBtn = new QPushButton(this);
tmpBtn->setText(QString::number(i+1));
tmpBtn->setFixedSize(60, 60);
tmpBtn->setObjectName("img_button");
tmpBtn->setStyleSheet("#img_button{border-image:url(:/res/chooseBtn_1.png); color:rgb(20,20,20); font-size:20px;}"
"#img_button:pressed{border-image: url(:/res/chooseBtn_2.png);}");
tmpBtn->move(145+(i%4)*150, 90+(i/4)*100);
tmpBtn->show();
//绑定按钮点击事件
connect(tmpBtn, &ImgButton::clicked, [=](){
QTimer::singleShot(100, [=](){
this->hide();
playWindow = new PlayWindow(QString(":/mazedata/").append(QString::number(i+1)).append(".mazedata"));
playWindow->setGeometry(this->geometry());
playWindow->show();
//监听游戏窗口的返回按钮点击事件
connect(playWindow, &PlayWindow::backBtnClicked, [=](){
playWindow->hide();
setGeometry(playWindow->geometry());
delete playWindow;
playWindow = NULL;
this->show();
});
});
});
}
2.3. 闯关界面
- 拦截WASD为上下左右,并做出相应处理。
- 使用非递归深度优先搜索实现寻路逻辑。
- 提供游戏成功、失败的界面及音效等。
// 非递归深度优先搜索实现寻路
bool PlayWindow::findPossibleWay(bool drawPath)
{
//探索路径策略
int MOVE_STRATEGY[4][2] = {
{1, 0}, // down
{0, 1}, // right
{-1, 0}, // up
{0, -1} // left
};
//是否走过的标记
bool gone_mark[31][31];
for (int i = 0; i < 31; i++)
{
for (int j = 0; j < 31; j++)
{
gone_mark[i][j] = false;
}
}
QStack<QPair<int, int>> possible_path; // 储存路线的栈
QStack<QPair<int, int>> pos_stack; // 储存待遍历位置的栈,位置的最后一个总是最先遍历
pos_stack.push(QPair<int, int>(startX, startY)); // 加入起点
while(!pos_stack.empty())
{
int pos_x = pos_stack.top().first;
int pos_y = pos_stack.top().second;
possible_path.push(pos_stack.top());
// 终点出口
if(pos_x == endX && pos_y == endY)
{
if(drawPath)
{
for(QPair<int, int> p: possible_path)
{
int tx = p.first, ty = p.second;
if(tx!=posX || ty!=posY)
tiles[tx][ty]->changeStatus(Tile::ENDING);
}
}
return true;
//return possible_path
}
//如果不是出口则标记走过
gone_mark[pos_x][pos_y] = true;
int wall_count=0 , gone_count=0;
// 探索顺序与MOVE_STRATEGY相反,因为最后入栈的最先遍历
for(int i=0; i<4; i++)
{
int next_pos_x = pos_x + MOVE_STRATEGY[i][0];
int next_pos_y = pos_y + MOVE_STRATEGY[i][1];
// 是否撞墙或已走过
if(next_pos_x<0 || next_pos_x>=mazeData->mazeSize || next_pos_y<0 || next_pos_y>=mazeData->mazeSize || mazeData->mazeData[next_pos_x][next_pos_y] == Tile::WALL)
{
wall_count++;
continue;
}
if(gone_mark[next_pos_x][next_pos_y])
{
gone_count++;
continue;
}
pos_stack.push(QPair<int, int>(next_pos_x, next_pos_y));
}
if(wall_count + gone_count == 4)
{
pos_stack.pop();
possible_path.pop();
if(gone_count != 1)
possible_path.pop();
}
}
if(drawPath) {
QMessageBox::information(this,"提示","该迷宫没有通路");
}
return false;
}
- 提供初始界面,提供随机生成地图逻辑
- 提供保存地图功能,自定义或随机生成的地图可以在首界面进行加载。
往期文章回顾