基于A*算法自动寻路迷
项目准备
实现迷宫游戏的开发,需要分别实现迷宫的自动生成和自动寻找最短路径,对于每次一个的游戏开始都需要生成不同的迷宫地图,并且针对同意一难度下的迷宫,每次生成的地图都必须是不一样的
迷宫生成算法
首先,迷宫生成算法主要有以下三种
1.Recursive backtracker ( 递归回溯,也是深度优先算法)
2.Randomized Prim’s algorithm(随机Prim算法,让我想起了最小生成树的Prim算法)
3.Recursive division (递归分割算法)
而我们在这里主要使用的迷宫生成算法是深度优先算法,即深度优先遍历,直到撞到四周判断走不了时,在返回沿着下一跳路径继续寻找。初始地图如下图所示:
通过深度优先遍历,找到几条通路。如下图所示:
在可走的路径之上,将白色方块设置成和黄色方块一样的属性即表示可走。
深度优先遍历算法的具体实现,如下所示:
#include <bits/stdc++.h>
using namespace std;
#define ROUTE 1
#define WALL 0
int L;
int a[5000][5000];
int b[50][50]; //1表示可以走,2表示不可以走 0表示边界
int rank = 0;
void create_a(int x,int y)
{
a[x][y] = ROUTE;
int dxdy[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
//作用就是把原有的固定的方向进行拉乱来实现随机的状态
for (int i = 0;i<4;i++)
{
int r = rand()%4;
int t = dxdy[0][0];
dxdy[0][0] = dxdy[r][0];
dxdy[r][0] = t;
t = dxdy[0][1];
dxdy[0][1] = dxdy[r][1];
dxdy[r][1] = t;
}
//任意方向
for (int i = 0;i<4;i++)
{
int tx = x;
int ty = y;
int range = 1 + (rank == 0 ? 0 : rand() % rank);
while (range > 0)
{
tx += dxdy[i][0];
ty += dxdy[i][1];
//将之前走过的路排除,不能再继续走
if (a[tx][ty] == ROUTE)
{
break;
}
//现在面临三个方向可走
//相当于九宫格现在tx,ty处于最终的格子上
int cnt = 0;
//横坐标的可走的范围
for (int j = tx - 1;j<=tx+1;j++)
{
//纵坐标的可走范围
for (int k = ty - 1;k<=ty+1;k++)
{
//只判断上、下、左、右四个位置
if (a[j][k] == ROUTE && abs(j-tx)+abs(k-ty)==2)
{
cnt++;
}
}
}
//表示已经向前走了一步,退出循环进行下一个位置的选取
if (cnt > 1)
{
break;
}
--range;
a[tx][ty] = ROUTE;
}
if (range <= 0)
{
create_a(tx,ty);
}
}
}
int main ()
{
cout<<"输入所要生成的地图的长度:";
cin>>L;
//把长度的四周进行标记为可走,然后排除被包括的区域
for (int i = 0;i<L;i++)
{
a[i][0] = ROUTE;
a[0][i] = ROUTE;
a[i][L-1] = ROUTE;
a[L-1][i] = ROUTE;
}
//开始生成迷宫
create_a(2,2);
a[2][1] = ROUTE;
for (int i = L - 3;i>=0;i--)
{
if (a[i][L-3] == ROUTE)
{
a[i][L-2] = ROUTE;
break;
}
}
for (int i = 1;i<L-1;i++)
{
for (int j = 1;j<L-1;j++)
{
if (a[i][j] == ROUTE)
{
//a[i][j] = 1;
cout<<"1";
// cout<<" ";
}
else cout<<"2";//cout<<"帅";//a[i][j] = 2; //墙是2
}
cout<<endl;
}
for (int i = 1;i<L-1;i++)
{
for (int j = 1;j<L-1;j++)
{
b[i][j] = a[i][j];
}
//cout<<endl;
}
/*for (int i = 1;i<L-1;i++)
{
for (int j = 1;j<L-1;j++)
{
cout<<b[i][j];
}
cout<<endl;
}*/
system("pause");
return 0;
}
注: 对于相同游戏难度下,每一次迷宫的生成不同的原因在于,如下代码:
int dxdy[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
//作用就是把原有的固定的方向进行拉乱来实现随机的状态
for (int i = 0;i<4;i++)
{
int r = rand()%4;
int t = dxdy[0][0];
dxdy[0][0] = dxdy[r][0];
dxdy[r][0] = t;
t = dxdy[0][1];
dxdy[0][1] = dxdy[r][1];
dxdy[r][1] = t;
}
每一次运行程序,都是重新设置DFS搜索的方向进行随机变化,以此来实现地图的随机生成。
自动寻路算法(A*)算法
本人觉得最详细的的A*算法介绍同时也是最容易理解的算法介绍。基础的知识介绍就不再赘述了。A*算法的步骤主要如下
计算每个方块的和值(我们将它称为F,等于G+H), 我们来看下A星算法的原理。
将方块添加到open列表中,该列表有最小的和值。且将这个方块称为S吧。
将S从open列表移除,然后添加S到closed列表中。
对于与S相邻的每一块可通行的方块T:
· 如果T在closed列表中:不管它。
· 如果T不在open列表中:添加它然后计算出它的和值。
· 如果T已经在open列表中:当我们使用当前生成的路径到达那里时,检查F和值是否更小。如果是,更新它的和值和它的前继。
· 然后将每一步计算出的最小F值的父节点记录下来,以便最后的自动寻路的演示。
具体代码如下:
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#define N 500 // 的阶数
using namespace std;
int L;
class Node
{
public:
int x, y; // 节点所在位置
int F, G, H; // G:从起点开始,沿着产的路径,移动到网格上指定方格的移动耗费。
// H:从网格上那个方格移动到终点B的预估移动耗费,使用曼哈顿距离。
// F = G + H
Node(int a, int b):x(a), y(b){}
// 重载操作符,使优先队列以F值大小为标准维持堆
bool operator < (const Node &a) const
{
return F > a.F;
}
};
// 定义八个方向
int dir[4][2] = { {-1, 0}, {0, -1},
{0, 1}, {1, 0}};
// 优先队列,就相当于open表
//queue<Node> que;
priority_queue<Node>que;
// 棋盘
int qp[N][N];
bool visit[N][N]; // 访问情况记录,close表
int valF[N][N]; // 记录每个节点对应的F值
int path[N][N][2]; // 存储每个节点的父节点
int Manhuattan(int x, int y, int x1, int y1); // 计算曼哈顿距离
bool NodeIsLegal(int x, int y, int xx, int yy); // 判断位置合法性
void A_start(int x0, int y0, int x1, int y1); // A*算法
void PrintPath(int x1, int y1); // 打印路径
int main()
{
/*
测试数据:
22
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1
1 0 1 0 0 1 0 0 1 1 0 1 1 1 0 0 0 1 0 1
1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 1 0 1 0 1
1 0 0 0 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1
1 1 0 1 0 1 0 1 0 1 1 1 0 0 0 0 0 0 0 1
1 0 0 1 0 1 0 0 0 0 1 1 0 1 1 1 0 0 0 1
1 0 0 1 0 1 0 0 1 0 1 0 0 1 0 1 0 1 0 1
1 0 0 1 0 1 0 0 1 0 1 0 1 1 0 1 1 1 0 1
1 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 0 0 0 1
1 0 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 0 1 1
1 0 1 1 1 1 0 0 1 0 0 0 0 1 1 1 1 0 1 1
1 0 1 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 1
1 0 1 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 0 1
1 0 1 1 0 0 0 0 1 0 0 1 0 0 1 1 0 1 0 1
1 0 1 1 0 1 1 0 0 0 0 1 0 0 0 0 0 1 0 1
1 0 1 1 0 0 0 0 0 1 0 1 1 1 1 0 1 1 0 1
1 0 1 0 0 1 1 1 1 1 1 1 1 0 0 0 1 1 0 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
*/
cin>>L;
for (int i = 0;i<L-2;i++)
{
for (int j = 0;j<L-2;j++)
{
cin>>qp[i][j];
}
}
fill(visit[0], visit[0]+L*L, false); // 将visit数组赋初值false
fill(valF[0], valF[0]+L*L, 0); // 初始化F全为0
fill(path[0][0], path[0][0]+L*L*2, -1); // 路径同样赋初值-1
// // 起点 // 终点
int x0, y0, x1, y1;
//cout<<"输入起点:";
//cin>>x0>>y0;
//cout<<"输入终点:";
//cin>>x1>>y1;
x0 = 2,y0 = 1;
x1 = 3,y1 = 16;
x0--; y0--; x1--; y1--;
if(!NodeIsLegal(x0, y0, x0, y0)) //判断位置的合法性
{
cout<<"非法起点!"<<endl;
return 0;
}
A_start(x0, y0, x1, y1); // A*算法
PrintPath(x1, y1); // 打印路径
}
void A_start(int x0, int y0, int x1, int y1) //起点和终点的坐标
{
// 初始化起点
Node node(x0, y0);
node.G = 0;
node.H = Manhuattan(x0, y0, x1, y1); //距离终点的距离
node.F = node.G + node.H; //F = 离原点的距离加离终点的距离
valF[x0][y0] = node.F; //记录此节点的F值
// 起点加入open表
que.push(node);
while(!que.empty())
{
Node node_top = que.top(); que.pop();
visit[node_top.x][node_top.y] = true; // 访问该点,加入closed表
if(node_top.x == x1 && node_top.y == y1) // 到达终点
break;
// 遍历node_top周围的8个位置
for(int i=0; i<4; i++)
{
Node node_next(node_top.x + dir[i][0], node_top.y + dir[i][1]); // 创建一个node_top周围的节点
// 该节点坐标合法 且 未加入close表 即没有出现越界,碰到障碍物,已经访问过的情况
if(NodeIsLegal(node_next.x, node_next.y, node_top.x, node_top.y) && !visit[node_next.x][node_next.y])
{
// 计算从起点并经过node_top节点到达该节点所花费的代价
node_next.G = node_top.G + int(sqrt(pow(dir[i][0],2)+pow(dir[i][1],2))*10);
// 计算该节点到终点的曼哈顿距离
node_next.H = Manhuattan(node_next.x, node_next.y, x1, y1);
// 从起点经过node_top和该节点到达终点的估计代价
node_next.F = node_next.G + node_next.H;
// node_next.F < valF[node_next.x][node_next.y] 说明找到了更优的路径,则进行更新
// valF[node_next.x][node_next.y] == 0 说明该节点还未加入open表中,则加入
if(node_next.F < valF[node_next.x][node_next.y] || valF[node_next.x][node_next.y] == 0)
{
// 保存该节点的父节点
path[node_next.x][node_next.y][0] = node_top.x;
path[node_next.x][node_next.y][1] = node_top.y;
valF[node_next.x][node_next.y] = node_next.F; // 修改该节点对应的valF值
que.push(node_next); // 加入open表
}
}
}
}
}
void PrintPath(int x1, int y1)
{
if(path[x1][y1][0] == -1 || path[x1][y1][1] == -1)
{
cout<<"没有可行路径!"<<endl;
return;
}
int x = x1, y = y1;
int a, b;
while(x != -1 || y != -1)
{
qp[x][y] = 2; // 将可行路径上的节点赋值为2
a = path[x][y][0];
b = path[x][y][1];
x = a;
y = b;
}
//cout<<qp[0][0]<<endl;
//cout<<qp[1][0]<<endl;
for(int i=0; i<L-2; i++)
{
for(int j=0; j<L-2; j++)
cout<<qp[i][j];
cout<<endl;
}
}
int Manhuattan(int x, int y, int x1, int y1)
{
return (abs(x - x1) + abs(y - y1)) * 10;
}
bool NodeIsLegal(int x, int y, int xx, int yy)
{
if(x < 0 || x >= N || y < 0 || y >= N) return false; // 判断边界
if(qp[x][y] == 1) return false; // 判断障碍物
// 两节点成对角型且它们的公共相邻节点存在障碍物
//if(x != xx && y != yy && (qp[x][yy] == 1 || qp[xx][y] == 1)) return false;
return true;
}
此时迷宫游戏的难度是:22 。可以根据输入不同的数值,来改变迷宫的难度值。