一、实验环境
QT
C++
二、实验内容
1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
3) 要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
三、代码实现
迷宫游戏的设计分两个重点,一个是迷宫的生成,还有一个是自动寻路操作的实现。
迷宫生成
迷宫的生成有很多方法:PRIM算法、深度优先算法······这里我主要使用了递推的算法。
/**************************************************
* -1:边界 0:墙壁
* 1:空单元(初始化后未判定的单元)
* 2:待定墙壁(新建已定单元时建立)
* 3:已定单元(确定迷宫树已延伸到的单元)
* 4:打通墙壁(延伸操作)
* 5.起点
* 6.终点
* 7.已经过路径
* **************************************************/
void maze:: op(int i, int j) {
if ((map[i - 1][j] == 3 || map[i - 1][j] == 5) && map[i + 1][j] == 1) {
map[i][j] = 4;
map[i + 1][j] = 3;
_2(i + 1, j);
start_x = i + 1;
start_y = j;
} else if ((map[i][j - 1] == 3 || map[i][j - 1] == 5) &&
map[i][j + 1] == 1) {
map[i][j] = 4;
map[i][j + 1] = 3;
_2(i, j + 1);
start_x = i;
start_y = j + 1;
} else if ((map[i + 1][j] == 3 || map[i + 1][j] == 5) &&
map[i - 1][j] == 1) {
map[i][j] = 4;
map[i - 1][j] = 3;
_2(i - 1, j);
start_x = i - 1;
start_y = j;
} else if ((map[i][j + 1] == 3 || map[i][j + 1] == 5) &&
map[i][j - 1] == 1) {
map[i][j] = 4;
map[i][j - 1] = 3;
_2(i, j - 1);
start_x = i;
start_y = j - 1;
} else {
map[i][j] = 0;
}
}
初始化迷宫地图后可以用该段代码实现打通墙壁生成路径。全部完成之后还要判断地图是否还有待定的方格(即数值为2的方格)。
A*算法
定义一个结构体Node,其中有五个整型参数和两个函数。
struct Node {
int x1,y1,f,h,g;
Node(){
x1=y1=f=h=g=0;
}
bool operator < (const Node& tmp)const{
return g<tmp.g;
}
Node(int x,int y,int ft,int ht,int gt){
x1=x,y1=y,f=ft,h=ht,g=gt;
}
};
其中x1和y1是当前位置四周方格的坐标,f是中间节点到终点的代价,h是中间节点到终点的预估代价,g为二者之和。比较运算符 'operator <' 重载了'<'运算符,可以比较两个Node对象的'g'成员。如果'g'成员小于另一个Node对象的'g'成员,那么该Node对象就被视为小于另一个Node对象。
void mazeWidget::on_pushButton_clicked()
{
std::priority_queue<Node> q;
this->setFocus();
memset(pre,0,sizeof(pre));
map->map[map->p_x][map->p_y]=5;
memcpy(mpback,map->map,sizeof(map->map));
q.push(Node(map->p_x,map->p_y,0,0,0));
while(q.size()){
Node tmpdata=q.top();
q.pop();
for(int i=0;i<4;i++){
int dx=tmpdata.x1+dir[i][0],dy=tmpdata.y1+dir[i][1];
if(mpback[dx][dy]==0||mpback[dx][dy]==-1||mpback[dx][dy]==9)continue;
pre[dx][dy]=qMakePair(tmpdata.x1,tmpdata.y1);
if(mpback[dx][dy]==6){
break;
}
mpback[dx][dy]=9;
Node topush;
topush.x1=dx,topush.y1=dy;
topush.f=tmpdata.f+1;
topush.h=heuristic(dx,dy);
topush.g=topush.h+topush.f;
q.push(topush);
}
}
int tmpx=map->start_x,tmpy=map->start_y;
while(map->map[tmpx][tmpy]!=5){
map->map[tmpx][tmpy]=5;
int a=pre[tmpx][tmpy].first,b=pre[tmpx][tmpy].second;
tmpx=a,tmpy=b;
}
map->map[map->start_x][map->start_y]=6;
repaint();
return;
}
这个是点击寻路按钮的槽函数。其中使用了A*算法:
-
创建一个优先队列
q
,其中元素类型为Node
。Node
类型应该包含代表迷宫中的点的信息,以及可能还有一些额外的信息,例如该节点的父节点等。 -
将当前焦点设置为该控件。
-
清空一个名为
pre
的二维数组,这个数组可能用来保存迷宫的生成过程或者路径。 -
在一个二维数组
map->map
中,将当前位置(map->p_x
,map->p_y
)标记为5,这可能代表该位置为障碍物或者特殊标记。 -
将
map->map
的内容复制到mpback
中,可能是为了保存一份迷宫的备份。 -
将起点位置(
map->p_x
,map->p_y
)以及一些其他信息(例如代价等)封装为一个Node
对象并加入到优先队列q
中。 -
通过while循环和for循环,不断地从优先队列
q
中取出代价最小的节点(根据g值来比较,g值为[0, 1, 2, 3]),并对该节点的四个方向进行遍历。- )如果该方向是可行的(即不是障碍物或者已经访问过),就更新该位置的代价(pre值),并将其加入到优先队列中。
- )如果该方向是终点(即代价为6),就跳出循环。
-
当所有的节点都被访问过之后,算法结束。
-
将起点位置标记为已访问(即将其代价设为5)。
-
重新绘制界面。