游戏中的算法

游戏中的两个常用算法

  • A*算法的介绍
    • 代码实现
  • 排序算法的应用

A*算法的介绍

游戏中的寻路通常由深度优先搜索(DFS)、广度优先搜索(BFS)或者A算法实现。其中DFS与BFS属于状态式搜索,A算法属于启发式搜索。今天给大家介绍一下A*算法。

  1. G(n)、H(n)、F(n)
    G(n)代表从起点到达节点n的实际代价值
    H(n)代表从节点n到达终点的估计代价值
    F(n)代表从起点经由节点n到达终点的估计代价值

    下面通过一个例子来计算 G(n)、H(n)、F(n)
    绿色方块为起点,蓝色为障碍,红色代表终点
    游戏中的算法_第1张图片

  2. G(n)、H(n)、F(n)的计算(以起点右边F=40的方块为例)
    G=当前格的G值 + 父节点(上一格)的G值
    直接将起点的右边这个方块称为当前格了。
    当前格的G值=10+0。
    这个10是当前格与父节点之间的代价,如果当前格在上一格的正方向,则代价为10,如果在上一格的斜方向则代价为14,可以对比当前这一格和当前上方的一格。然后要用这个代价加上父节点的代价,由于父节点是起点,所以父节点的G=0。

    H=(当前格与终点之间x方向的单位差 + y方向的单位差)*10
    当前格的H=(3+0)*10
    当前格与终点在x方向上差了三个单位,y方向差了0个单位

    F=当前格的G值+H值
    当前格F=10+30

3.开启列表与关闭列表
两个用来存储节点的容器,因为有很多的增删操作,所以选择List是比较好的。开启列表中存储的是可以行走的节点,关闭列表存储走过的节点,我们会在开启列表中选择一个F最小的值去走,那就要把这个点从开启列表中移除并加入关闭列表。

4.流程
1.将起点放入开启列表中
2.while(开启列表不为空)
{
1.在开启列表中找到F值最低的点,称为当前格
2.将当前格加入到关闭列表中,从开启列表移除
3.遍历当前格的周围8个点
if(不能走 || 在关闭列表中)
continue;
if(在开启列表中)
当前的G值比较原来的G值,如果小于,更新GF值,更新父节点为当前格
else
将点放入到开启列表中,计算GHF值,设置父节点为当前格
判断终点是否在关闭列表中,如果在,推导路径
}

代码实现

#include 
#include 
 
const int kCost1 = 10; //直移一格消耗
const int kCost2 = 14; //斜移一格消耗
 
struct Point
{
    int x, y; //点坐标,x代表横排,y代表竖列
    int F, G, H; //F=G+H
    Point* parent; //当前点的父节点,用于回溯查找路径
    
    Point(int x, int y)
    :x(x),
     y(y),
     F(0),
     G(0),
     H(0),
     parent(NULL)  //带参构造
    {
    }
};
//A*算法
class Astar
{
public:
    //初始化A*算法使用的地图
    void InitAstar(std::vector<std::vector<int>> &_maze);
    //获取路径:1.开始点 2.结束点 3.斜边移动是否考虑四周有障碍物
    std::list<Point*> GetPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner);
private:
    //查找路径的方法:A*算法的核心逻辑
    Point *findPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner);
    //获取周围八个点,如果能走或者不在关闭列表中,则放入vector并返回
    std::vector<Point *> getSurroundPoints(const Point *point, bool isIgnoreCorner) const;
    //判断某个点是否可走
    bool isCanreach(const Point *point, const Point *target, bool isIgnoreCorner) const;
    //判断开启/关闭列表中是否包含某点
    Point *isInList(const std::list<Point *> &list, const Point *point) const;
    //从开启列表中查找F值最小的节点
    Point *getLeastFpoint();
    //计算FGH值
    int calcG(Point *temp_start, Point *point);
    int calcH(Point *point, Point *end);
    int calcF(Point *point);
private:
    std::vector<std::vector<int>> maze;//地图(maze:迷宫)
    std::list<Point *> openList;  //开启列表
    std::list<Point *> closeList; //关闭列表

};
#include 
#include "Astar.h"
 
void Astar::InitAstar(std::vector<std::vector<int>> &_maze)
{
    maze = _maze;
}
 
int Astar::calcG(Point *temp_start, Point *point)
{
    //如果是斜边,extraG为14,否则为10
    int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;
    //如果是初始节点,则其父节点是空
    int parentG = point->parent == NULL ? 0 : point->parent->G;
    //当前点的G值 = 父节点的G值 + 额外的G值
    return parentG + extraG;
}
 
int Astar::calcH(Point *point, Point *end)
{
    //欧几里距离:两点之间的直线距离
    //return sqrt((double)(end->x - point->x)*(double)(end->x - point->x) + (double)(end->y - point->y)*(double)(end->y - point->y))*kCost1;
    //曼哈顿距离:水平距离+垂直距离
    return (abs(end->x - point->x) + abs(end->y - point->y)) * kCost1;
}
 
int Astar::calcF(Point *point)
{
    //F = G + H
    return point->G + point->H;
}
 
Point* Astar::getLeastFpoint()
{
    //将第一个点作为F值最小的点,进行比较,最终拿到F值最小的点
    if (!openList.empty())
    {
        auto resPoint = openList.front();
        for (auto &point : openList)
        if (point->F<resPoint->F)
            resPoint = point;
        return resPoint;
    }
    return NULL;
}
 
Point* Astar::findPath(Point& startPoint, Point& endPoint, bool isIgnoreCorner)
{
    //把起始点添加到开启列表
    openList.push_back(new Point(startPoint.x, startPoint.y));
    do
    {
        auto curPoint = getLeastFpoint();//找到F值最小的点
        openList.remove(curPoint); //从开启列表中删除
        closeList.push_back(curPoint);//放到关闭列表
        //找到当前周围八个格中可以通过的格子(1.可走且没有在关闭列表)
        std::vector<Point*> surroundPoints = getSurroundPoints(curPoint, isIgnoreCorner);
        for (auto &target : surroundPoints)
        {
            //对某一个格子,如果它不在开启列表中,加入到开启列表,设置当前格为其父节点,计算F G H
            if (!isInList(openList, target))
            {
                target->parent = curPoint;
 
                target->G = calcG(curPoint, target);
                target->H = calcH(target, &endPoint);
                target->F = calcF(target);
 
                openList.push_back(target);//添加到开启列表中
            }
            //对某一个格子,它在开启列表中,计算G值, 如果比原来的大, 就什么都不做,否则设置它的父节点为当前点,并更新G和F
            else
            {
                int tempG = calcG(curPoint, target);
                if (tempG < target->G)
                {
                    target->parent = curPoint;
 
                    target->G = tempG;
                    target->F = calcF(target);
                }
            }
            //查找结束点是否已经在开启列表中,如果在,这时候路径被找到。
            Point *resPoint = isInList(openList, &endPoint);
            //如果返回不为空,表示找到终点,则通过终点的parent一直反推得出路径
            if (resPoint)
                return resPoint;
        }
    }while (!openList.empty());//当开启列表为空时,跳出循环
 
    return NULL;//找不到一条路径
}
 
std::list<Point*> Astar::GetPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner)
{
    Point *result = findPath(startPoint, endPoint, isIgnoreCorner);
    std::list<Point *> path;
    //返回路径,如果没找到路径,返回空链表
    while (result)
    {
        path.push_front(result);
        result = result->parent;//通过parent回溯
    }
 
    //清空临时开闭列表,防止重复执行GetPath导致结果异常
    openList.clear();
    closeList.clear();
 
    return path;
}
 
Point* Astar::isInList(const std::list<Point *> &list, const Point *point) const
{
    //判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
    for (auto p : list)
    if (p->x == point->x&&p->y == point->y)
        return p;
    return NULL;
}
 
bool Astar::isCanreach(const Point *point, const Point *target, bool isIgnoreCorner) const
{
    //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回false
    if (target->x<0 || target->x>maze.size() - 1
        || target->y<0 || target->y>maze[0].size() - 1
        || maze[target->x][target->y] == 1
        || (target->x == point->x&&target->y == point->y)
        || isInList(closeList, target))
    {
        return false;
    }
    else
    {
        if (abs(point->x - target->x) + abs(point->y - target->y) == 1)//非斜角可以
            return true;
        else
        {
            //斜对角要判断是否绊住
            if (maze[point->x][target->y] == 0 && maze[target->x][point->y] == 0)
                return true;
            else
                return isIgnoreCorner;
        }
    }
}
 
std::vector<Point *> Astar::getSurroundPoints(const Point *point, bool isIgnoreCorner) const
{
    std::vector<Point *> surroundPoints;
 
    for (int x = point->x - 1; x <= point->x + 1; x++)
    for (int y = point->y - 1; y <= point->y + 1; y++)
    if (isCanreach(point, new Point(x, y), isIgnoreCorner))//判断是否能走,能走则加入到vector中返回
        surroundPoints.push_back(new Point(x, y));
 
    return surroundPoints;
}

排序算法的应用

在游戏中,排序算法一般都是用来做排名的,像下面这张图
游戏中的算法_第2张图片
在这么多种排序算法中,一般是选择归并排序和快速排序,这两种是较优的,一般归并排序运用在对文件的排序,而快速排序适合大多数情况。

游戏中的算法_第3张图片

你可能感兴趣的:(游戏,游戏,算法)