目录
项目需求
- 迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
- 要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘 方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于A*算法实现,输出走迷宫的最优路径并显示;
- 设计交互友好的游戏图形界面。
项目准备
- 仔细思考分析项目需求,生成项目的总体框架;
- 解决如何生成迷宫、起点终点的设置、自动寻址等问题,学习相关算法;
- 构造合适的界面UI;
项目框架
关键算法分析
prim算法生成迷宫
通过如下方法将迷宫生成问题和求最小生成树建立联系:
把迷宫看做这个样子,左边是用类似数组表示的迷宫,把墙简化后比较容易看
- 这是迷宫初始化的样子,所有墙壁都是封死的,路径格处于独立状态。如果把迷宫看作带权图G,则点集V即4为简化图中显示的9个路径格,边集E则是任意相邻两路径格间连线的集合,可以看作所有路径权值都相等。
- 设迷宫入口为左上角的路径格,出口为右下角的路径格,则生成一个迷宫的实质就是找G的一个最小生成树T,T包含所有路径格,不成环,且其中任意两个路径格连通。(事实上当不必连通所有格,只要连通了起点和终点,也可以算迷宫生成完毕,只是这样的迷宫不完整)
- 把起点加入空集A,则森林Ga中包含9棵根节点状态的树,接下来要不断在E中找安全边,使Ga中的根节点状态的树元素都加入到集合A中,直到Ga中只剩A一个元素(或者Ga中某树包含起点和终点)为止。
算法思想
设N=(V,E)是连通网,TE是N上最小生成树中边的集合。初始令U={U0},(U0∈V),TE={ }。在所有u∈U,v∈V-U的边(u,v)∈E中,找一条代价最小的边(u0,v0)。将(u0,v0)并入集合TE,同时v0并入U0。重复上述操作直至U=V为止,则T=(V.TE)为N的最小生成树。
A*算法自动寻址
1. 把起点加入 open list 。
2. 重复如下过程:
1)遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
2)把这个节点移到 close list 。
3) 对当前方格的 8 个相邻方格的每一个方格:
◆如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。
◆如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。
◆如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。
4)停止,当你
◆把终点加入到了 open list 中,此时路径已经找到了;
◆查找终点失败,并且 open list 是空的,此时没有路径。
3.保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。
详细设计
开始界面设计
加入了弹跳动画和延时进入下一场景的功能
//开始按钮
MyPushButton * startBtn = new MyPushButton(":img/img/playbutton.png");
startBtn->setParent(this);
startBtn->setStyleSheet("border:0px");
startBtn->move(this->width() * 0.5 - startBtn->width() * 0.5 , this->height() * 0.35);
connect(startBtn,&MyPushButton::clicked,[=](){
qDebug() << "点击了开始";
//弹起的特效
startBtn->zoom1();
startBtn->zoom2();
//延时进入到选择场景
QTimer::singleShot(200,this,[=](){
//自身隐藏
this->hide();
//显示选择场景
mymaze.show();
});
});
void MyPushButton:: zoom1(){
//创建动态对象
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");
//设置动画时间间隔
animation->setDuration(200);
//起始位置
animation -> setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));
//结束位置
animation -> setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
//设置弹跳曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start();
}
void MyPushButton:: zoom2(){
//创建动态对象
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");
//设置动画时间间隔
animation->setDuration(200);
//起始位置
animation -> setStartValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
//结束位置
animation -> setEndValue(QRect(this->x(),this->y(),this->width(),this->height()));
//设置弹跳曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start();
}
迷宫的生成
void play_maze::on_creat_maze_clicked()//创建迷宫
{
if(flag_click){
return;
}
if(flag_get_row){
maze_row=pre_maze_row;
}
else{
maze_row=10;
//qDebug()<<"hhhh";
}
if(flag_get_col){
maze_col=pre_maze_col;
}
else{
maze_col=10;
}
//qDebug()<<maze_row<<" "<<maze_col<<endl;
flag_success=false;
//获取行列值
control_X=1;
control_Y=1;
target_X=maze_row-3;
target_Y=maze_col-3;
//qDebug()<<target_X<<" "<<target_Y<<endl;
// maze=new point *[maze_row];
// for(int i=0;i<maze_row;i++){
// maze[i]=new point[maze_col];
// }
for(int i=0;i<maze_row;i++){
for(int j=0;j<maze_col;j++){
maze[i][j].i=i;
maze[i][j].j=j;
maze[i][j].state=0;
}
}
int max;
if(maze_row>=maze_col)
max=maze_row;
else
max=maze_col;
maze_cell_size=1080/max;
startTimer(5*650/max);
//qDebug()<<"get row and col"<<endl;
build_maze_stack.clear();
int i=3,j=3;
maze[i][j].state=1;
point temp;
temp.i=i;
temp.j=j;
temp.state=1;
bool up=false,down=false,left=false,right=false;
srand((unsigned)time(NULL));
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;
}
}
}
}
A*算法寻址
void play_maze::on_A_search_clicked()//A*算法
{
if(flag_click||flag_success){
return;
}
if(!creat_maze){
return;
}
A_search.clear();
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;
}
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;
}
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;
}
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;
}
}
}
qDebug()<<"find the path!"<<endl;
}
项目总结
在本实验项目中,关键在于迷宫的生成和迷宫的自动寻址,两者均可以有多种算法实现,但都具有一定难度,老师讲解后对prim算法和A*算法有了理解,再加之上网查阅算法的实现,勉强得到了应用,我也知道自己水平仍需提高,此迷宫也可以有很多更有趣的元素,但都因为自己编程不会,无法应用出来;本此项目的开始界面我应用了一定的动画,也发现自己可能更喜欢前端开发,以后会向这方面靠拢。