[学习笔记]A星寻路算法实例

时间:2022-12-28 12:25:21

A星寻路算法的实现,单击左键编辑障碍物,单击右键清除障碍物,双击右键开始自动寻路。

AStar.h:

#pragma once
#include <windows.h>
#include <vector>
#define F_H_WHITE  0x0004 | 0x0002 | 0x0001 | 0x0008
#define B_H_WHITE  0x0010|0x0020|0x0040|0x0080
#define F_H_YELLOW 0x0002|0x0004|0x0008
#define F_H_GREEN  0x0002|0x0008
#define F_RED      0x0004       
using std::vector;
class CAStar
{
public:
	CAStar();
	~CAStar();
public:
	//表的节点结构体
	typedef struct _NODE
	{
		COORD stcPos;		//当前点的坐标
		COORD stcPrtPos;	//上一个点的坐标
		int F;				//移动损耗
		int G;				//估算距离
		int H;				//G+H
	}NODE,*PNOED;
public:
	//寻路函数,获取最短路径
	bool GetPath(_Out_ vector<COORD>& vecPath);
	//设置控制台信息
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	void SetConsoleInfo();
	//描点画图函数
	void WriteChar(int x, int y, wchar_t* pszChar, WORD wArr);
	//画地图
	void DrawMap();
	//编辑地图
	void EditMap();
private:
	COORD m_Begin;			//起始点
	COORD m_End;			//终止点
	int m_Map[40][40];		//地图
	vector<NODE> m_vecOpen;	//Open表
	vector<NODE> m_vecClose;//Close表
};


AStar.cpp:

#include "stdafx.h"
#include "AStar.h"

CAStar::CAStar()
{
	m_Begin.X = 10;
	m_Begin.Y = 10;
	m_End.X = 30;
	m_End.Y = 30;
}
CAStar::~CAStar()
{
}
//************************************************************
// 函数名称:	GetPath
// 函数说明:	寻路函数,获取最短路径
// 作	者:	Dylan
// 时	间:	2015/08/28
// 参	数:	_Out_ vector<COORD> & vecPath
// 返 回	值:	bool
//************************************************************
bool CAStar::GetPath(_Out_ vector<COORD>& vecPath)
{
	//0.起点就是终点,直接返回
	if (m_Begin.X == m_End.X&&m_Begin.Y == m_End.Y)
	{
		return true;
	}
	//1.将起始点加入到Open表中
	NODE FirstNode = { 0 };
	FirstNode.stcPos = m_Begin;
	FirstNode.G = 0;
	FirstNode.H =
		abs(FirstNode.stcPos.X - m_End.X) +
		abs(FirstNode.stcPos.Y - m_End.Y);
	FirstNode.F = FirstNode.G + FirstNode.H;
	m_vecOpen.push_back(FirstNode);		//Open表中有了第一个点
	while (true)
	{
		//2.开始循环,从Open表中,选取F值最小的点
		int nMin = m_vecOpen[0].F;
		int nPos = 0;
		int nSize = m_vecOpen.size();
		for (int i = 0; i < nSize; i++)
		{
			if (nMin>m_vecOpen[i].F)
			{
				nMin = m_vecOpen[i].F;
				nPos = i;
			}
		}
		//3.从此点派生出周围的四个点,并为其赋值
		NODE stcTemp[4] = { 0 };
		//上
		stcTemp[0].stcPos.X = m_vecOpen[nPos].stcPos.X;
		stcTemp[0].stcPos.Y = m_vecOpen[nPos].stcPos.Y - 1;
		//下
		stcTemp[1].stcPos.X = m_vecOpen[nPos].stcPos.X;
		stcTemp[1].stcPos.Y = m_vecOpen[nPos].stcPos.Y + 1;
		//左
		stcTemp[2].stcPos.X = m_vecOpen[nPos].stcPos.X - 1;
		stcTemp[2].stcPos.Y = m_vecOpen[nPos].stcPos.Y ;
		//右
		stcTemp[3].stcPos.X = m_vecOpen[nPos].stcPos.X + 1;
		stcTemp[3].stcPos.Y = m_vecOpen[nPos].stcPos.Y;
		for (int i = 0; i < 4;i++)
		{
			stcTemp[i].stcPrtPos = m_vecOpen[nPos].stcPos;
			stcTemp[i].G = m_vecOpen[nPos].G + 1;
			stcTemp[i].H =
				abs(stcTemp[i].stcPos.X - m_End.X) +
				abs(stcTemp[i].stcPos.Y - m_End.Y);
			stcTemp[i].F = stcTemp[i].G + stcTemp[i].H;
		}
		//3.1将此最小点,将其加入到Close表中,从Open表中删除
		m_vecClose.push_back(m_vecOpen[nPos]);
		m_vecOpen.erase(m_vecOpen.begin() + nPos);		
		//4.检测一下这四个点是否可用
		for (int i = 0; i < 4; i++)
		{
			int nX = stcTemp[i].stcPos.X;
			int nY = stcTemp[i].stcPos.Y;
			//4.0找到了终点			
			if (nX == m_End.X&&nY == m_End.Y)
			{
				//4.0.1已经找到终点,从Close表中进行回溯,得到最短的路径,退出
				//回溯
				//-------------------------------------------------
				vecPath.push_back(stcTemp[i].stcPos);
				NODE PathNode = stcTemp[i];				
				for (int i = m_vecClose.size()-1; i >= 0; i--)
				{
					if (PathNode.stcPrtPos.X == m_vecClose[i].stcPos.X&&
						PathNode.stcPrtPos.Y == m_vecClose[i].stcPos.Y
						)
					{
						vecPath.push_back(m_vecClose[i].stcPos);
						PathNode = m_vecClose[i];
					}
				}
				//-------------------------------------------------
				m_vecOpen.clear();
				m_vecClose.clear();
				return true;
			}
			//4.1是否越界
			if (nX<0||nY<0||nX>39||nX>39)
			{
				continue;
			}
			//4.2是否在障碍物上
			if (m_Map[nY][nX]!=0)
			{
				continue;
			}
			//4.3是否在Open表中
			int j = 0;
			for (; j < m_vecOpen.size();j++)
			{
				if (m_vecOpen[j].stcPos.X == nX&&m_vecOpen[j].stcPos.Y == nY)
				{
					break;
				}
			}
			if (j != m_vecOpen.size())
			{
				continue;
			}
			//4.4是否在Close表中
			for (j = 0; j < m_vecClose.size();j++)
			{
				if (m_vecClose[j].stcPos.X == nX&&m_vecClose[j].stcPos.Y == nY)
				{
					break;
				}
			}
			if (j != m_vecClose.size())
			{
				continue;
			}
		    //4.5将可用的点加入到Open表中
			m_vecOpen.push_back(stcTemp[i]);
		}	
		//5.Open表中没有点了,说明没有找到路径,退出函数
		if (m_vecOpen.size()==0)
		{
			m_vecClose.clear();
			return false;
		}
	}
}
//************************************************************
// 函数名称:	SetConsoleInfo
// 函数说明:	设置控制台信息
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::SetConsoleInfo()
{
	//设置控制台标题
	SetConsoleTitle(L"A星寻路算法");
	//设置控制台缓冲区大小
	COORD BufferSize = { 99, 42 };
	SetConsoleScreenBufferSize(hStdOut, BufferSize);
	//设置控制台窗口大小
	SMALL_RECT strctWindow = { 0, 0, BufferSize.X - 1, BufferSize.Y - 1 };
	SetConsoleWindowInfo(hStdOut, true, &strctWindow);
	//设置光标位置
	COORD pos = { 0, 40 };
	SetConsoleCursorPosition(hStdOut, pos);
}
//************************************************************
// 函数名称:	WriteChar
// 函数说明:	描点画图函数
// 作	者:	Dylan
// 时	间:	2015/08/28
// 参	数:	int x
// 参	数:	int y
// 参	数:	wchar_t * pszChar
// 参	数:	WORD wArr
// 返 回	值:	void
//************************************************************
void CAStar::WriteChar(int x, int y, wchar_t* pszChar, WORD wArr)
{
	CONSOLE_CURSOR_INFO cci;
	cci.dwSize = 1;
	cci.bVisible = FALSE;//光标是否可见
	SetConsoleCursorInfo(hStdOut, &cci);//将光标属性应用到控制台
	COORD loc = { x * 2, y };
	DWORD dwRes1, dwRes2;
	WORD wClr[10];
	wmemset((wchar_t*)wClr, wArr, 10);
	WriteConsoleOutputAttribute(hStdOut, wClr, 2, loc, &dwRes1);//打印字符串颜色
	WriteConsoleOutputCharacter(hStdOut, pszChar, 1, loc, &dwRes2); //打印字符串到指定位置
}
//************************************************************
// 函数名称:	DrawMap
// 函数说明:	画地图
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::DrawMap()
{
	SetConsoleInfo();
	WriteChar(m_Begin.X, m_Begin.Y, L"☆", F_H_YELLOW);
	WriteChar(m_End.X, m_End.Y, L"★", F_H_YELLOW);
	for (int i = 0; i < 40; i++)
	{
		for (int j = 0; j < 40; j++)
		{
			m_Map[i][j] = 0;
			if (i == 0 || j == 0 || i == 39 || j == 39)
			{
				m_Map[i][j] = 1;
			}
		}
	}
	for (int i = 0; i < 40; i++)
	{
		for (int j = 0; j < 40; j++)
		{
			if (m_Map[j][i] == 1)
			{
				WriteChar(i, j, L"▇", B_H_WHITE | F_RED);
			}
		}
	}
}
//************************************************************
// 函数名称:	EditMap
// 函数说明:	编辑地图
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::EditMap()
{
	//画张空地图
	DrawMap();
	//获取标地图准输入输出句柄 
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
	CONSOLE_SCREEN_BUFFER_INFO bInfo;
	INPUT_RECORD mouseRec;
	DWORD res;
	COORD crPos = { 0, 0 }, crHome = { 82, 2 };
	SetConsoleMode(hIn, ENABLE_MOUSE_INPUT);//设置鼠标可用,这样cls以后鼠标就不会不好使
	while (true)
	{
		ReadConsoleInput(hIn, &mouseRec, 1, &res);
		if (mouseRec.EventType == MOUSE_EVENT)
		{
			if (mouseRec.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
			{
				if (mouseRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)
				{
					//双击右键退出循环,画路径
					break;					
				}
			}
			//设置坐标显示信息
			crPos = mouseRec.Event.MouseEvent.dwMousePosition;
			GetConsoleScreenBufferInfo(hOut, &bInfo);
			SetConsoleTextAttribute(hOut, F_H_WHITE);
			SetConsoleCursorPosition(hOut, crHome);
			printf("坐标X: %2lu Y: %2lu", crPos.X, crPos.Y);
			SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition);
			//鼠标点击事件
			switch (mouseRec.Event.MouseEvent.dwButtonState)
			{
				//单击左键
			case FROM_LEFT_1ST_BUTTON_PRESSED:
				if (crPos.X > 1 && crPos.X<77 && crPos.Y>0 && crPos.Y < 39)
				{
					crPos.X = crPos.X / 2 * 2;
					if (!((crPos.X / 2 == m_Begin.X&&crPos.Y == m_Begin.Y) ||
						(crPos.X / 2 == m_End.X&&crPos.Y == m_End.Y)))
					{
						WriteChar(crPos.X / 2, crPos.Y, L"▇", B_H_WHITE | F_RED);
						m_Map[crPos.Y][crPos.X / 2] = 1;
					}
				}
				break;
				//单击右键
			case RIGHTMOST_BUTTON_PRESSED:
				if (crPos.X > 1 && crPos.X<77 && crPos.Y>0 && crPos.Y < 39)
				{
					crPos.X = crPos.X / 2 * 2;
					if (!((crPos.X / 2 == m_Begin.X&&crPos.Y == m_Begin.Y) ||
						(crPos.X / 2 == m_End.X&&crPos.Y == m_End.Y)))
					{
						WriteChar(crPos.X / 2, crPos.Y, L"  ",NULL);
						m_Map[crPos.Y][crPos.X / 2] = 0;
					}
				}
				break;
			}
		}
	}
}


A_Star.cpp:

// A_Star.cpp : 定义控制台应用程序的入口点。
// A星算法

#include "stdafx.h"
#include "AStar.h"

int _tmain(int argc, _TCHAR* argv[])
{
	vector<COORD> Path;
	CAStar obj;
	obj.EditMap();
	obj.GetPath(Path);
	//画路线
	for (int i = Path.size() - 2;i>0;i--)
	{
		obj.WriteChar(Path[i].X, Path[i].Y, L"☉", F_H_GREEN);
	}
	system("pause");
	return 0;
}

演示效果:

[学习笔记]A星寻路算法实例