现在的位置: 首页 > 综合 > 正文

A*算法实现寻找较优路径

2012年01月29日 ⁄ 综合 ⁄ 共 4755字 ⁄ 字号 评论关闭

    【qboy】原创 2013年2月2日

    好久没回到这里来写了,回家过年之前再写一篇吧。这是在2012年11月到12月之间做的一个游戏中所采用的算法。跟大家分享一下。

一、A*算法的简介

    在大学时,在一个人工智能的选修课,我第一次接触了A*算法,也采用这个算法实现课堂上一个作业8数码问题。

    简单的说A*算法的核心就是F=G+H;G为到第i步经过的步数,H为到达目的地预计还需要通过多少步,即是一个可能值(或者排除一切障碍的最优值,视问题情况而定),F即是两者相加,对当前情况下的所有F值进行排序,得到F的最优的一步进行第i+1步,直到H值为0。通过这个公式我们可以看出A*算法照顾了过去,也预测着未来

二、问题背景

      我们游戏的背景是在一个有限的海洋中(可以定义为一个二维数组),敌方船向我方船进行逼近,直到撞到我方船为止。在海洋中,有一些零散的岛屿或者其他是在海洋中的障碍物,有些敌方船会以直线的方式逼近(即不考虑障碍物,撞到障碍会死亡),而另一些船AI较高会绕开障碍物向我方逼近。

        如下图所示,五角形是我方船位置,即目标位置,菱形和五边形是敌方船,菱形是直线逼近,无智能,五边形是有AI,每艘船可以沿着八个方向进行行走,黄色方块是障碍物。那么菱形会右下撞向障碍物,五边形以实线方向逼近我方船,而不是走最近虚线。当然游戏是回合制,一次只能走特定的步数。

        

三、实现步骤

        为了解决以上问题,我们采用了A*算法(由于游戏是IOS上,所以很多都是ObjC,不过此方法主要还是采用C语言来实现)。

        1、我们设定了一个即二维数组(世界环境),描述各船和障碍物所在的位置。

        2、定义在寻路过程中的数据结构:

typedef struct ShipPointAStar

{

    ShipPoint point;//所在位置

    int GValue;//已经过的步数,G

    int HValue;//H值

    int FValue;//=G+H

    struct ShipPointAStar *prePoint;//前一步,这是为了逆着找到要走的点

}ShipPointAStar;

3、定义H函数和G函数:

//A star 算法中的H 

// 输入:curPoint为当前所在点,destPoint为目标点

//输出:H值

//原理是:假设没有障碍的情况下两点之间的最短距离数

CG_INLINE int H(ShipPoint curPoint,ShipPoint destPoint)

{

    ShipPoint prePoint = curPoint;

    int numStep = 0;//预测的步数

    while (!EqualShipPoint(prePoint, destPoint)) {//当前点是否与目标点重合

        prePoint = getNextShipPoint(prePoint, destPoint);//获取下一步

        numStep++;

    }

    return numStep;

}

 

G的求法:

定义:

G(i)=G(i-1)+1;

G(0)=0;

根据数学知识可以推算出G(i)=i;其中i为分解步数。

4、构造可选区域的,由于船最多往8个方向移动,则把当前步下的所有可能方向,并计算出相应的H值和G值,求出F值。

5、对所有的可能情况(未走过的点)进行F值排序,取出F值最小的点进行下一步展开,在这采用的是冒泡排序所以就不贴出来了,有机会再改成快速排序。

当出现点与目标点重合时查找结束,或者当所有的能展开的点都已排除,则查找失败(不可达)。

当找到路径时我们逆着把当前点下一步点返回,这已经不在我们讨论范围了。

以下是算法的程序段:

//通过A*算法 得到移动位置

//输入:srcPoint源点, destPoint目标点,arrmap地点二维数组,我们定义1是不可走区域,mapW是二维数组的行数,mapH是二维数组的列数 maxFValue是扩展功能,即最大F值,当超过时不再寻路,0时没有限制。

//输出:船的下个行动点

CG_INLINE ShipPoint getNextShipPointByAStarAndMaxFValue(ShipPoint srcPoint,ShipPoint destPoint,int **arrmap,int mapW,int mapH,int maxFValue)

{

    ShipPoint nextPoint = ShipPointCopy(srcPoint);

    ShipPointAStar star;

    star.GValue = 0;

    star.HValue = H(nextPoint, destPoint);

    star.FValue = star.GValue+star.HValue;

    star.point=nextPoint;

    star.prePoint = NULL;

    

    ShipPointAStar* stararr =malloc(sizeof(ShipPointAStar)*mapW*mapH);//我个人觉得最多只有地图中的一半个节点会进入数组

    int num = 0;

    stararr[num] = star;

    num ++;

    int pi =0;

    while (!EqualShipPoint(stararr[pi].point, destPoint)) //已到达目标

    {

        nextPoint = stararr[pi].point;

        

        if (maxFValue > 0 && stararr[pi].FValue>maxFValue) {

            break;

        }

        

        int minX = nextPoint.ShipX > 0 ? nextPoint.ShipX - 1 : 0;

        int maxX = nextPoint.ShipX < mapW - 1 ? nextPoint.ShipX + 1 : mapW-1;

        

        int minY = nextPoint.ShipY > 0 ? nextPoint.ShipY - 1 : 0;

        int maxY = nextPoint.ShipY < mapH - 1 ? nextPoint.ShipY + 1 : mapH-1;

        

        for (int i = minY ; i <= maxY; i++) {

            for (int j = minX; j <= maxX; j++) {

                if (arrmap[i][j] == 1) //不可走

                {

                    continue;

                }

                else

                {

                    ShipPoint p =ShipPointMake(j, i);

                    bool blExist = NO;

                    for (int k = 0; k<num; k++) {

                        if (EqualShipPoint(p, stararr[k].point)) {

                            blExist = YES;

                            break;

                        }

                    }

                    if (!blExist) {

                        ShipPointAStar s;

                        s.GValue = stararr[pi].GValue+1;

                        s.HValue = H(p, destPoint);

                        s.FValue = s.GValue+s.HValue;

                        s.point=p;

                        s.prePoint = &stararr[pi];

                        stararr[num++] = s;

                    }

                }

            }

        }

        SortShipPointAStar(stararr, num);

        pi++;

        if (pi>=num) {

            break;

        }

    }

    

    for (int i = 0; i<num; i++) {

        ShipPoint p = stararr[i].point;

        FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,stararr[i].GValue,stararr[i].FValue);

    }

    

    FCLOG(@"Arr Num = %i",num);

    

    ShipPoint rstPoint= ShipPointNull();

    

    if (EqualShipPoint(stararr[pi].point, destPoint)) {

        FCLOG(@"循路成功");

        //往回走路径

        if (stararr[pi].prePoint) {

            ShipPoint p = stararr[pi].point;

            FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,stararr[pi].GValue,stararr[pi].FValue);

            ShipPointAStar *preP = stararr[pi].prePoint;

            if (preP->prePoint!=NULL) {

                while (preP->prePoint->prePoint!=NULL) {

                    p = preP->point;

                    CCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,preP->GValue,preP->FValue);

                    preP = preP ->prePoint;

                }

                p = preP->point;

                FCLOG(@"X = %i Y = %i,GValue = %i,FValue = %i",p.ShipX,p.ShipY,preP->GValue,preP->FValue);

                rstPoint = preP->point;

            }

            else

            {

                rstPoint = p;

            }

            

        }

    }

    else

    {

        rstPoint = ShipPointNull();

        FCLOG(@"循路失败");

    }

    free(stararr);

    return rstPoint;

}

四、缺陷问题

通过以上分析,我们把船的寻路给找到了。但是我们在实际发现了以下两个问题:

1、由于可走路径比较多(8个方向),我们寻找出的路径只是好几条较优路径中的一条。如下图中两个艘船就有三条路径。

 

2、由于在搜索时采用的是先上后下,先左后右的搜索顺序,所以在上图中敌方船的行走就不是中间那一条,而是上面那条,这样会引出船的方向可能并不是用户(玩家)所希望的,船的方向和位置会决定后续回会中的走法。

解决以上问题的办法:

一开始我想到的办法是将船的方向不要设定为8个方向,而是只有4个方向,上下左右。但是如果两艘船的位置是在斜线方向,则两者也有可能会出现多条路径,况且这也符合实际情况。。

当写到这儿的时候,我突然之间想到一个办法,不知道可行不,先写出来再说,再设定每个方向的权重,比如,通过两个点之间距离来决定权重,而不是步数。例如上图中,设定每个方块宽度为1,最上一条和最下面一条的距离就应该是,而中间的一条的路径是2。这样我们就可以用距离来计算G,H,F值,而不是之前的步数了。

 

抱歉!评论已关闭.