A*寻路算法

问题

由于游戏中寻路出了个小问题–玩家寻路到一个死角后在那边不停的来回跑,就是无法越过障碍物, 就研究了下A*寻路算法以解决这个问题;
研究了几天,自己写了个demo这里给出总结;

原理

paper: http://www.policyalmanac.org/games/aStarTutorial.htm

A*算法给出的是权值最优的路径而不是最短路径;
权值有F = G + H来表示;
启发式函数如下:
F(p) = G(p) + H(p)

  • F:从起点经过当前节点p到达目标节点预计总消耗;
  • G:从起点到达当前节点p时需要消耗的值;(可以有上一步G(p-1)+g_p(当前步的消耗)得到)
  • H:为从当前节点p到达目标节点需要消耗的值(预估权值);
H值估算方法

这里写图片描述

这里的估算方法类似距离*单位消耗, 计算过程优先使用整数, 浮点数的运算速度慢;

//曼哈顿估价法
private function manhattan(node:Node):Number
{
    return Math.abs(node.x - _endNode.x) * _straightCost + Math.abs(node.y + _endNode.y) * _straightCost;
}

//几何估价法
private function euclidian(node:Node):Number
{
    var dx:Number=node.x - _endNode.x;
    var dy:Number=node.y - _endNode.y;
    return Math.sqrt(dx * dx + dy * dy) * _straightCost;
}

//对角线估价法
private function diagonal(node:Node):Number
{
    var dx:Number=Math.abs(node.x - _endNode.x);
    var dy:Number=Math.abs(node.y - _endNode.y);
    var diag:Number=Math.min(dx, dy);
    var straight:Number=dx + dy;
    return _diagCost * diag + _straightCost * (straight - 2 * diag);
}
G值估算方法

从起点到当前节点的消耗, 可以用叠加过程来得到, 即G(p)=G(p-1)+g_p; g_p为从p-1走到p的消耗, 上下左右为1则对角线为1.4 (sqrt(2)得到). 实际计算用整数,所以会使用10, 14来计算.

openlist与closelist
  • 每次查找openlist中F值最小的节点n, 从openlist中删除, 将n放入closelist中.
  • 遍历n的8个单步移动方向, 无法到达的节点, 将那些不在closelist的节点放入到openlist中,且所有节点的parent节点指向n;(这里要注意n地址的固定不变的处理, 不然当发生变动时n的子节点可能会失去parent的指向)
  • 在将node放入openlist前, 找一下openlist里是否已经有该节点位置的node, 如果有,则比较openlist里的oldnode的G值, 如果oldG小于newG, 则不改变openlist里的数据, oldnode的parent仍然指向原先指向的parent, 而如果oldG大于newG,则说明新的路径(从n到oldnode)的F值将更小,所以将openlist里的oldnode的parent从新指向当前节点n;
设计

节点设计

class point
{
public:
    int x;
    int y;
    point(int X=-1,int Y=-1):x(X),y(Y){};
    bool operator==(const point& pos)
    {
        return (x == pos.x) && (y == pos.y);
    }
};
struct node
{
    point pos;
    node* parent;
    bool isvisited;
    short GVal;
    short HVal;
    node(int x=-1,int y=-1):pos(x,y),parent(NULL),isvisited(false),GVal(0),HVal(0){};
    bool operator==(const node& node)
    {
        return (pos == node.pos) && (pos.x >= 0) && (pos.y >= 0);
    }
};

所有的parent都是指向closelist里的成员,而closelist里的值是不会变的,closelist做成一维数组表示二维,利用(X,Y)定位,openlist也同样处理,这样当openlist里的isvisited为true则表明被访问过了,就可以对比G值并更新parent;list中的所有没有访问过的或删除的内存全部清零(isvisited=0表示没有访问过);

头文件.h
#ifndef STRATEGYFACTORY_H
#define STRATEGYFACTORY_H
/***************
A* searching road
***************/
#include <stdio.h>
#include <string.h>
using namespace std;
class point
{
public:
    int x;
    int y;
    point(int X=-1,int Y=-1):x(X),y(Y){};
    bool operator==(const point& pos)
    {
        return (x == pos.x) && (y == pos.y);
    }
};
struct node
{
    point pos;
    node* parent;
    bool isvisited;
    short GVal;
    short HVal;
    node(int x=-1,int y=-1):pos(x,y),parent(NULL),isvisited(false),GVal(0),HVal(0){};
    bool operator==(const node& node)
    {
        return (pos == node.pos) && (pos.x >= 0) && (pos.y >= 0);
    }
};
// 全局变量的定义
static const int MAX_SEARCH_NUM = 1024;
static const point UP(0,0);
static const int MAX_GRID_NUM = 15;
static const point DOWN(MAX_GRID_NUM,MAX_GRID_NUM);
static const char MAP_BLOCK = 'X';
static const char MAP_NONBLOCK = '.';
static const char MAP_LOAD = 'O';
char Map[MAX_GRID_NUM][MAX_GRID_NUM];
enum SearchDir  // 8个搜索方向
{
    Dir_LEFT = 0,
    Dir_LEFT_DOWN,
    Dir_DOWN,
    Dir_RIGHT_DOWN,
    Dir_RIGHT,
    Dir_RIGHT_UP,
    Dir_UP,
    Dir_UP_LEFT,
};
// A*搜索类
class AStar
{
private:
    node openlist[MAX_SEARCH_NUM]; //the next step grid (使用优先队列会有更好的查找性能)
    node closelist[MAX_SEARCH_NUM];//the grid that already visited
    int OpenCount; // 标识openlist使用节点情况
    point mStart;
    point mEnd;
    inline int GetIndex(const point pos) const {return pos.x*(DOWN.y - UP.y)+pos.y;}
    void GetMinFNode(node& cur);
    bool GetNextStepNode(node& cur,node& nextnode, int dir);
    bool IsInCloselist(node& cur);
    bool IsInOpenlist(node& cur);
    void CalHVal(node& nextnode);
    void CalNodeVal(node& nextnode, int dir);
    void UpdateGH(node& cur);
public:
    AStar():OpenCount(0)
    {
        memset(closelist,0,MAX_SEARCH_NUM*sizeof(node));
        memset(openlist,0,MAX_SEARCH_NUM*sizeof(node));
    }
    void SetStartPos(point spos) {mStart = spos;}
    void SetEndPos(point epos) {mEnd = epos;}
    bool ASSearch(point SPt, point Ept); // 寻路核心函数
    bool CalLoad(); // 寻路完后通过父节点从终点查找到起点
};

#endif // STRATEGYFACTORY_H
源文件.cpp
#include <iostream>
#include "Test.h"
#include "cmath"
#include <cstdlib>
#include <assert.h>
bool AStar::ASSearch(point SPt, point Ept)
{
    SetStartPos(SPt);
    SetEndPos(Ept);
    node S(mStart.x,mStart.y);
    S.isvisited = true;
    openlist[GetIndex(S.pos)] = S;
    OpenCount++;
    while(OpenCount)
    {
        node cur;
        GetMinFNode(cur);// 最小F值节点
        assert(cur.isvisited);
        closelist[GetIndex(cur.pos)] = cur;// 压入当前节点到closelist
        memset(&openlist[GetIndex(cur.pos)],0,sizeof(node)); // 从openlist中移除该节点
        --OpenCount;
        if(cur.pos == mEnd)
        {
            closelist[GetIndex(cur.pos)] = cur;
            return true;
        }
        // 将相邻节点计算GH后压入
        for(int i = Dir_LEFT; i <= Dir_UP_LEFT; ++i)
        {
            node nextnode ;
            // 已经在closelist中则跳过
            if(!GetNextStepNode(cur,nextnode,i) || IsInCloselist(nextnode))
                continue;
            // 计算H值
            CalHVal(nextnode);
            CalNodeVal(nextnode,i); // 计算Node的相关属性值
            // 访问过了则更新最优GH(在closelist中)
            if(IsInOpenlist(nextnode))
                UpdateGH(nextnode);
            else
            {
                openlist[GetIndex(nextnode.pos)] = nextnode;
                ++OpenCount;
            }
        }
    }
    return false;
}
void AStar::CalHVal(node& nextnode)
{
    nextnode.HVal = abs(nextnode.pos.x - mEnd.x) + abs(nextnode.pos.y - mEnd.y);
    nextnode.HVal *= 10;
}
bool AStar::GetNextStepNode(node& cur,node& nextnode, int dir)
{
    point nxt;
    switch(dir)
    {
    case Dir_LEFT:
        nxt.x = cur.pos.x-1;
        nxt.y = cur.pos.y;
        nextnode.GVal = cur.GVal +10;
        break;
    case Dir_LEFT_DOWN:
        nxt.x = cur.pos.x-1;
        nxt.y = cur.pos.y-1;
        nextnode.GVal = cur.GVal +14;
        break;
    case Dir_DOWN:
        nxt.x = cur.pos.x;
        nxt.y = cur.pos.y-1;
        nextnode.GVal = cur.GVal +10;
        break;
    case Dir_RIGHT_DOWN:
        nxt.x = cur.pos.x+1;
        nxt.y = cur.pos.y-1;
        nextnode.GVal = cur.GVal +14;
        break;
    case Dir_RIGHT:
        nxt.x = cur.pos.x+1;
        nxt.y = cur.pos.y;
        nextnode.GVal = cur.GVal +10;
        break;
    case Dir_RIGHT_UP:
        nxt.x = cur.pos.x+1;
        nxt.y = cur.pos.y+1;
        nextnode.GVal = cur.GVal +14;
        break;
    case Dir_UP:
        nxt.x = cur.pos.x;
        nxt.y = cur.pos.y+1;
        nextnode.GVal = cur.GVal +10;
        break;
    case Dir_UP_LEFT:
        nxt.x = cur.pos.x-1;
        nxt.y = cur.pos.y+1;
        nextnode.GVal = cur.GVal +14;
        break;
    }
    if(nxt.x<UP.x || nxt.y<UP.y||nxt.x>=DOWN.x||nxt.y>=DOWN.y)
        return false;
    if(Map[nxt.x][nxt.y] == MAP_BLOCK)
        return false;
    nextnode.pos = nxt;
    nextnode.parent= &closelist[GetIndex(cur.pos)]; //指向closelist中的对应位置
    return true;
}
bool AStar::IsInCloselist(node& cur)
{
    return closelist[GetIndex(cur.pos)].isvisited? true : false;
}
bool AStar::IsInOpenlist(node& cur)
{
    return openlist[GetIndex(cur.pos)].isvisited? true : false;
}
void AStar::GetMinFNode(node& cur)
{
    if(!OpenCount)
        return;
    int F = 0x7FFF;
    node* re = NULL;
    for(int i =0; i<MAX_SEARCH_NUM;++i)
    {
        re = &openlist[i];
        if(re->isvisited && F>re->GVal+re->HVal)
        {
            cur = *re;
            F = re->GVal + re->HVal;
        }
    }
    return;
}
void AStar::UpdateGH(node& cur)
{
    node& node = openlist[GetIndex(cur.pos)];
    if(node.isvisited && node.GVal > cur.GVal)
        node = cur;
}
void AStar::CalNodeVal(node& nextnode, int dir)
{
    nextnode.isvisited = true;
}

bool AStar::CalLoad()
{
    node* endnode = &closelist[GetIndex(mEnd)];
    if(!endnode->isvisited)
    {
        cout<<"not found the end in CloseList!"<<endl;
        return false;
    }
    while(endnode->parent!=NULL)
    {
        Map[endnode->pos.x][endnode->pos.y] = MAP_LOAD;
        endnode = endnode->parent;
    }
    Map[mStart.x][mStart.y] = MAP_LOAD;
    Map[mEnd.x][mEnd.y] = MAP_LOAD;
    return true;
}

void InitMap()
{
    srand(1002); //可以改变随机数种子来改变地图情况
    for(int i = UP.x; i < DOWN.x; ++i)
    {
        for(int j = UP.y; j < DOWN.y; ++j)
            Map[i][j] = rand()&1?MAP_NONBLOCK:MAP_BLOCK; //将地图格子随机设为阻碍或非阻碍
    }
}
// 打印地图情况
void ShowMap()
{
    const char SPACE[8] = "    ";
    cout<<SPACE;
    for(int i = UP.x; i < DOWN.x; ++i)
        cout<<i<<SPACE;
    cout<<endl;
    for(int i = UP.x; i < DOWN.x; ++i)
    {
        cout<<i<<SPACE;
        for(int j = UP.y; j < DOWN.y; ++j)
            cout<<Map[i][j]<<SPACE;
        cout<<endl<<endl;
    }
    cout<<endl<<"======================================================="
        <<endl;
}

void GetStartPos(point& S)
{
    for(int i = 2; i >= 0; --i)
        for(int j = 2; j >= 0; --j)
            if(Map[i][j] != MAP_BLOCK)
            {
                S.x = i;
                S.y = j;
                return;
            }
}
void GetEndPos(point& E)
{
    for(int i = MAX_GRID_NUM-1; i >= MAX_GRID_NUM-3; --i)
        for(int j = MAX_GRID_NUM-1; j >= MAX_GRID_NUM-3; --j)
            if(Map[i][j] != MAP_BLOCK)
            {
                E.x = i;
                E.y = j;
                return;
            }
}

int main (void)
{
    InitMap();
    ShowMap();
    AStar astr;
    point s,e;
    GetStartPos(s);
    GetEndPos(e);
    if(astr.ASSearch(s,e) && astr.CalLoad())
        ShowMap();
    else
        cout<<"not found load"<<endl;
}
演示
  • 地图
    其中的.都是可以走的格子,X都是障碍格子;
    这里写图片描述

  • 寻路
    0表示寻得的路径, 这里也是唯最优路径.
    这里写图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值