C++A星寻路的实现


前言

本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●)
本文主要讲的A星寻路的代码实现,对A星寻路有一定的了解后再来阅读本文是个不错的选择。
本文还用到了图形库,编译器为vs2019。图形库没有的可以去官方网站下载,步骤很简单下载直接安装即可。
A星寻路的流程
1.准备存储的路径和终点标志位。
2.将起点放入closeList
3.将起点周围的格子放入openlist
4.寻找最小和值F和终点
5.找到后将它存入closelist同时将它从openlist中剔除
6.从终点标志位开始往父亲节点遍历,存入路径中。
7.输出路径。
剩下的解释全在代码注释中。


main.cpp

#include <iostream>
#include<vector>
#include<easyx.h>
#include<list>
#include"AStart.h"
#include"Level.h"
#include"Draw.h"
using namespace std;
class LevelMap:public Level
{
public:
	vector<vector<int>> level;
	// 通过 Level 继承
	virtual bool isValid(Pos v) override
	{
		return !((v.x<0||v.y<0)||
			v.x> level[v.x].size()||v.y> level.size()||
			level[v.y][v.x]==1);
	}
};
void test()
{
	vector < vector<int>> vec = {//15*15
		{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},//▶x 
		{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
		{1,0,0,0,0,0,1,1,1,0,0,0,0,0,1},
		{1,0,1,1,1,1,0,1,0,1,0,0,0,0,1},
		{1,0,1,0,1,0,1,0,1,0,0,0,0,0,1},
		{1,0,1,1,1,0,1,1,1,0,0,0,0,0,1},
		{1,0,0,0,0,0,1,0,1,0,0,0,0,0,1},
		{1,0,0,0,0,0,1,1,1,0,0,0,0,0,1},
		{1,0,0,1,1,1,1,1,0,0,0,0,0,0,1},
		{1,0,0,0,1,1,0,1,0,0,0,0,0,0,1},
		{1,0,0,1,0,1,1,0,0,0,0,0,0,0,1},
		{1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
		{1,0,0,1,0,1,0,1,0,0,0,0,0,0,1},
		{1,0,1,0,1,1,0,1,0,0,0,0,0,0,1},
		{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
		//▼y
	};
	LevelMap lm;
	lm.level = vec;
	AStart a(lm);
	Draw draw(600, vec);
	draw.update();
	list<Pos> path;
	Pos start{ -1,-1 };
	Pos end{ -1,-1 };
	int x;//从鼠标的位置转换到
	int y;//贴图需要更改的位置
	bool flag=false;//起点和终点的标志位
	while (true)
	{
		draw.level = lm.level;
		draw.update();
		while (true)
		{
			MOUSEMSG msg = GetMouseMsg();
			x = msg.x / draw.getSize();
			y = msg.y / draw.getSize();
			if (msg.uMsg == WM_RBUTTONDOWN)//右键按下,路变墙,墙变路。
			{
				lm.level[y][x] = draw.level[y][x] =
					draw.level[y][x] == Draw::road ? Draw::wall : Draw::road;
				draw.update();
			}
			if (msg.uMsg == WM_LBUTTONDOWN)//左键按下设置起点终点准备寻路。
			{//这里不能将lm.level改变,需要还原用。
				if (!flag && draw.level[y][x] == Draw::road)
				{
					start.x = x;
					start.y = y;
					draw.level[y][x] = Draw::visit;
					flag = true;
					draw.update();
				}
				else if (draw.level[y][x] == Draw::road)
				{
					end.x = x;
					end.y = y;
					draw.level[y][x] = Draw::end;
					path = a.find(start, end);
					flag = false;
					draw.update();
					break;
				}
			}

		}
		for (const auto& e : path)
		{
			draw.level[e.y][e.x] = Draw::visit;
			draw.update();
		}
	}
}
int main()
{
	test();
	return 0;
}

Level.h

#pragma once
struct Pos
{
	int x;
	int y;
	Pos(int x = 0, int y = 0):x(x),y(y){}
};
class Level
{
public:
	//外部规则
	virtual bool isValid(Pos v) = 0;
};

Draw.h

#pragma once
#include <vector>

//取别名
using Vec2r = std::vector<std::vector<int>>;

//绘制地图的类
class Draw
{
public:
  /*使用图形库的接口实现地图绘制
   *length  屏幕长度
    level   地图关卡
   */
  Draw(int length, Vec2r& level);
  ~Draw();
  /*
  0 路
  1 墙
  2 走过的路径
  3 终点
  */
  enum {
	road=0,
	wall,
	visit,
	end,
  }; 
  Vec2r level;   //绘制的地图上

  //地图更新函数
  void update();

  //获取瓦片大小
  int getSize();

protected:

  int length;			    //宽度和高度
  int size;				    //每个瓦片的大小
  void draw();              //绘制地图
  void text();              //绘制文本
  void rect(int x,int y);   //绘制瓦片

};

Draw.cpp

#include "Draw.h"
#include <easyx.h>		//图形库文件
#include <string>
#include <iostream>
using namespace std;

Draw::Draw(int length, Vec2r& level)
  :length(length)
{
  initgraph(length, length, EW_SHOWCONSOLE);		//初始化图形库,EW_SHOWCONSOLE表示显示控制台
  size = length / level.size();					//动态计算小方块的宽度
  this->level=level;							//从外部传入地图
}

Draw::~Draw()
{
  closegraph();
}

void Draw::update()
{
  draw();
  text();
}

int Draw::getSize()
{
  return size;
}

void Draw::draw()
{
  BeginBatchDraw();//开始批量画图
  cleardevice();
  for (size_t i = 0; i < level.size(); i++)//level.size()等于7,
  {
	for (size_t j = 0; j < level[i].size(); j++)//level[i].size()等于7,这里不懂是因为二维vector没理解透。
	{
	  if (level[i][j] == road)//路
	  {
		setfillcolor(RGB(0xdd, 0xdd, 0xdd)); //设置路的颜色
	  }
	  else  if (level[i][j] == wall){  //墙
		setfillcolor(RGB(0x33, 0x33, 0xcc));
	  }
	  else  if (level[i][j] == visit) {	  //走过的路
		setfillcolor(RGB(0x33, 0xcc, 0x33));
	  }
	  else  if (level[i][j] == end) {	  //终点
		setfillcolor(RGB(0xff, 0x33, 0x33));
	  }
	  rect(j, i);	//绘制瓦片,j代表x  i代表y.
	}
  }
  EndBatchDraw();//结束批量画图
}
//绘制瓦片
void Draw::rect(int x, int y) 
{
	fillrectangle(x * size, y * size, (x + 1) * size, (y + 1) * size);
	/*void fillrectangle(
	int left,
	int top,
	int right,
	int bottom
);*/
}

void Draw::text()
{
  string m;
  for (size_t i = 0; i < level.size(); i++)
  {
	for (size_t j = 0; j < level[i].size(); j++)
	{
	  if (level[i][j] == road)//路
	  {
		m += "  "; //设置路的颜色
	  } 
	  else  if (level[i][j] == wall) 
	  {  //墙
		m += "+ ";
	  } 
	  else  if (level[i][j] == visit) 
	  {	  //走过的路
		m += ". ";
	  } 
	  else  if (level[i][j] == end) 
	  {	  //终点
		m += "= ";
	  }
	}
	m += "\n";
  }
  system("cls");	  //调用命令清屏
  cout << m << endl;
}

Astart.h

#pragma once
#include "Level.h"
#include<list>
class Node
{
public:
	Pos v;//节点坐标
	int G;//从起点到当前位置的代价;
	int H;//从当前位置到终点的预估代价;
	Node* pre;//树的父亲节点
	int getF() { return G + H; }//代价和值
	Node(Pos v, int G, int H, Node* pre = nullptr) :v(v), G(G), H(H), pre(pre) {}
};
class AStart 
{
public:
	AStart(Level& level) :level(level) {}//多态不能用基类实例化对象这里的引用不能掉
	std::list<Pos> find(Pos start, Pos end);
private:
	Level& level;//多态
	Pos end;//保存终点坐标
	std::list<Node*> openLsit;//准备要走的路但是没有走;
	std::list<Node*> closeList;//走过的路,正在走的路;
	void addRound(Node* now);//将节点周围的节点加入openlist
	int getH(Pos now);//获取节点位置与终点的预估距离
	bool check(Pos v);//检查该坐标能不能加入openlist
	bool findList(Pos v);//检查该坐标是否在openlist或者closelist中
	void destory();//释放内存。
};

Astart.cpp

#include "AStart.h"
#include<algorithm>
std::list<Pos> AStart::find(Pos start, Pos end)
{
    this->end = end;
    std::list<Pos> path;
    closeList.push_back(new Node(start, 0, getH(start)));
    Node* endNode = nullptr;//终点到达标志位,同时为遍历父亲节点做准备
    
    size_t length = 0;//做循环的判断条件用的
    while (length < closeList.size()&&!endNode)
    {
        length = closeList.size();
        addRound(closeList.back());
        
        //找到openList里面和值最小的node
        auto it = openLsit.begin();//默认最小的是openList的第一个元素,如果找到了就将其替换。
        for (auto i = openLsit.begin(); i != openLsit.end(); i++)
        {
            //判断是否到达终点
            if ((*i)->v.x == end.x && (*i)->v.y == end.y)
            {
                endNode=*i;
                break;
            }
            //找到最小的和值F
            if ((*it)->getF() > (*i)->getF())
            {
                it = i;
            }
        }
        //将找到的最小和值F的节点放入closeList中,同时需要将此节点从openlist中剔除;
        if (it != openLsit.end())
        {
            closeList.push_back((*it));
            openLsit.erase(it);
        }
        //break后将路径从终点往父亲节点遍历,逐个插入到path的头部,实现正序输出。
    }
    if (endNode)
    {
        while (endNode)
        {
            path.push_front(endNode->v);
            endNode = endNode->pre;
        }
    }
    //最后内存的清理
    destory();
    return path;
}

int AStart::getH(Pos now)
{
    return std::abs(now.x-end.x)+std::abs(now.y-end.y);
}

bool AStart::check(Pos v)
{
    if (level.isValid(v) && !findList(v))
        return true;
    return false;
}

bool AStart::findList(Pos v)
{
    for (const auto& e : openLsit)
    {
        if (e->v.x == v.x && e->v.y == v.y)
        {
            return true;
        }
    }
    for (const auto& e : closeList)
    {
        if (e->v.x == v.x && e->v.y == v.y)
        {
            return true;
        }
    }
    return false;
}

void AStart::destory()
{
    for (auto e : openLsit)
        delete e;
    for (auto e : closeList)
        delete e;
    openLsit.clear();
    closeList.clear();
}


void AStart::addRound(Node* now)
{
    static Pos dir[]//使用花括号直接初始化不能有强转否则会报错
    {
        {0,1},//下
        {1,0},//右
        {0,-1},//上
        {-1,0}//左
    };
    for (auto e : dir)
    {
        e = { now->v.x + e.x,now->v.y + e.y };
        if (check(e))
        {
            openLsit.push_back(new Node(e, now->G + 1, getH(e), now));
        }
    }
}

测试结果

在这里插入图片描述

最近有小伙伴需要这整个工程,来满足一下大家。

A星寻路工程,vs2019,0积分下载

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值