AI与游戏——吃豆人(3)基本路径规划算法(下)

来源:互联网 发布:java可以开发vr游戏吗 编辑:程序博客网 时间:2024/05/22 15:17

在上一部分讲的是大致的路径规划算法,在这一部分来讲一下准确的路径寻找方法。先来说一下之前算法存在的问题。

这里写图片描述

如上图所示,左边是Approximate的算法,右边是不带Approximate的,在这种情况下,O为起始点,X为目标点,这两种算法都会出现在原地打转的问题,因为前者只记录前一步的走法,而不记录之前走过的路径,后者则是连前一步路径都不考虑。所以每次都想离目标近一点,结果却是原地打转。

在代码中有个路径计算函数是避免了这个问题,在计算路径时考虑了之前走过的路径,函数名为getPathFromA2B,代码如下所示,我在代码中加了注释,后面还有详细解释:

public int[] getPathFromA2B(int a, int b, MOVE lastMoveMade)    {        //起始点与目标点一致,不做任何移动        if(a==b)            return new int[]{};        //找到最近的岔路,最近的只有一个,因为这里考虑到了lastMoveMade意味着不能掉头        JunctionData fromJunction = nodes[a].getNearestJunction(lastMoveMade);        //如果目标在通向岔路的路上,那么寻路结束。这里的fromJunction.path是一个整型数组,里面存放的是fromNode到最近岔路所经过的点的Index。        for (int i = 0; i < fromJunction.path.length; i++)            if (fromJunction.path[i] == b)                return Arrays.copyOf(fromJunction.path, i + 1);        //到达岔路,记录该岔路的ID        int junctionFrom = fromJunction.nodeID;        int junctionFromId = junctionIndexConverter.get(junctionFrom);        MOVE moveEnteredJunction = fromJunction.lastMove.equals(MOVE.NEUTRAL) ? lastMoveMade : fromJunction.lastMove; //记录到达岔路之前所采用的行走方式        //得到目标点周围的岔路,junctionsTo是一个hashmap结构,将nodeID与junctionID对应起来        ArrayList<JunctionData> junctionsTo=nodes[b].closestJunctions;        int minDist = Integer.MAX_VALUE;        int[] shortestPath = null;        int closestJunction = -1;               boolean onTheWay=false;        for (int q = 0; q < junctionsTo.size(); q++)         {            int junctionToId = junctionIndexConverter.get(junctionsTo.get(q).nodeID);            //此时a处于junctionFrom,若此时a也处于b周围的岔路口上,则意味着a与b之间没有岔路            if(junctionFromId==junctionToId)            {                if(!game.getMoveToMakeToReachDirectNeighbour(junctionFrom, junctionsTo.get(q).reversePath[0]).equals(moveEnteredJunction.opposite()))                //这里是如果a到达b的方向不与a进入junction的方向相反的话。意味着a到b只用顺着b到closetjunction的反方向走就可以找到b点。(junctionsTo里面存放的就是b到各个closetjunction的路径)                {                    int[] reversepath=junctionsTo.get(q).reversePath;                    int cutoff=-1;                    for(int w=0;w<reversepath.length;w++)                        if(reversepath[w]==b)                            cutoff=w;                    shortestPath = Arrays.copyOf(reversepath, cutoff+1);                    minDist = shortestPath.length;                    closestJunction = q;                    onTheWay=true;                }                //这里再说一下,如果方向相反的话,说明之前a直接找最近junction的路是背道而驰的,所以这里也就暂不做处理了。            }            else //a不在b周围的junctions上            {                               EnumMap<MOVE, int[]> paths = junctions[junctionFromId].paths[junctionToId];                 //这里先挖个坑,现在先知道paths得到的是两个junctions的最短路径                         Set<MOVE> set=paths.keySet();                for (MOVE move : set)                 {                                   if (!move.opposite().equals(moveEnteredJunction) && !move.equals(MOVE.NEUTRAL)) //同理先判断一开始是否背道而驰,而且set里面没有不动这种操作                    {                        int[] path = paths.get(move);                        if (path.length+junctionsTo.get(q).path.length < minDist)//计算最小距离时时除了计算junctions之间的距离还要计算junctionsTo到b的距离                        {                                                       minDist = path.length+junctionsTo.get(q).path.length;                            shortestPath = path;                            closestJunction = q;                            onTheWay=false;                        }                    }                }            }        }    //最后根据上述两种情况计算出最终路径             if(!onTheWay)            return concat(fromJunction.path, shortestPath, junctionsTo.get(closestJunction).reversePath);        else            return concat(fromJunction.path, shortestPath);//          return concat(fromJunction.path, junctionsTo.get(closestJunction).reversePath);    }

这只是简单的计算路径,其中有个大坑,两个junctions之间的path应该怎么计算呢,这里就用到A*路径规划算法了。A*算法的代码如下(下面代码涉及comparable与PriorityQueue这两种数据结构,不知道的话可以先去查下资料):

public synchronized int[] computePathsAStar(int s, int t, MOVE lastMoveMade, Game game)    {       // 这里的N是一个comparable接口实现的类,作用是为了实现后面类似小顶堆的PriorityQueue结构。这里用于比较的是N.h+N.g,后面有这两个代表什么         N start=graph[s];        N target=graph[t];    // open可以看做由N组成的小顶堆。closed是链表           PriorityQueue<N> open = new PriorityQueue<N>();        ArrayList<N> closed = new ArrayList<N>();//这里的h是得到start与target的最短距离,这个距离时提前计算好的写入文本中的,至于怎么计算的这里没提,值域为什么不直接存储路径,可能是为了节约空间吧。因为路径是一个数组,而距离只是一个数。        start.g = 0;        start.h = game.getShortestPathDistance(start.index, target.index);        start.reached=lastMoveMade; //将start加入小顶堆                open.add(start);        while(!open.isEmpty())        {            N currentNode = open.poll();            closed.add(currentNode);            if (currentNode.isEqual(target))                break;//E是遍历currentNode的的邻居            for(E next : currentNode.adj)            {            //(暂时)不考虑上一步走过的点,因为move里面有个neutral                if(next.move!=currentNode.reached.opposite())                {                //cost初始化都是1                    double currentDistance = next.cost;                //next这个点之前没有考虑过                    if (!open.contains(next.node) && !closed.contains(next.node))                    {                    //这里可以看出g是指当前所行走的路程,h是当前这一点到目标的距离,所以g+h也就是总的最小路程                        next.node.g = currentDistance + currentNode.g;                        next.node.h = game.getShortestPathDistance(next.node.index, target.index);                        next.node.parent = currentNode;                        next.node.reached=next.move;                        //next这点已经考虑过了,加入小顶堆,之后从所有的邻居点中选最小的那个。                        open.add(next.node);                    }                    //若果某个邻居点之前计算过,则计算currentNode+cost是否比之前计算的next.g小,小的话说明之前到达该next不是最优的方式,则进行更新                              else if (currentDistance + currentNode.g < next.node.g)                    {                        next.node.g = currentDistance + currentNode.g;                        next.node.parent = currentNode;                        next.node.reached=next.move;                        if (open.contains(next.node))                            open.remove(next.node);                        if (closed.contains(next.node))                            closed.remove(next.node);                        open.add(next.node);                    }                }            }        }        //还记得之前添加的parent与reached的作用吗。这里就是从子节点网上遍历从而找出从start到target的所有move方法        return extractPath(target);    }

这里的代码除了没告诉你亮点之间最小的距离时怎么计算出的之外用了很巧妙地数据结构来实现路径寻找方法,收获也颇多。

讲了这么多,都还只是在说怎么走路的问题,还没提到为什么要走这条路的问题,学会了走路,接下来便可以考虑走什么路了。

原创粉丝点击