计算机软件实习之迷宫

基于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 。可以根据输入不同的数值,来改变迷宫的难度值。

最终结果展示

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值