Java - A *算法进入不可行走的区块

时间:2015-08-11 14:16:37

标签: java algorithm a-star

我正在为流行的游戏编写自动化脚本,其中一部分需要一个探路者才能走远距离。

我写了这个A *探路者来实现这个目标,但是它有一些问题:

  • 有时候,探路者会进入不可行走的瓷砖,这最终会导致它进入地图的顶角然后越界
  • 探路者也会迷路并远离所需的位置然后返回,留下10000多个节点的路径,即使有时只有大约100个地块的距离

这是我的AStar.java:

    package Webwalker;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.osbot.rs07.api.map.Position;
import org.osbot.rs07.script.Script;

import Pickpocketer.PickpocketScript;

public class AStar {
    List<Position> open, closed;
    List<PathTile> holdingList;

    Position s, e, current;

    int horizontalCost, diagonalCost, index;

    Script scr;

    public AStar(Script script) {
        open = new ArrayList<Position>();
        closed = new ArrayList<Position>();
        holdingList = new ArrayList<PathTile>();
        horizontalCost = 10;
        index = 0;
        diagonalCost = 14;
        scr = script;
    }

    public List<Position> findPath(Position start, Position end) {
        s = current = start;
        closed.add(s);
        holdingList.add(new PathTile(start, 0, 0, null));
        e = end;
        while (!nextToEnd()) {
            //scr.log("Finding next node");
            PathTile[] nodes = getAdjacent(current);
            PathTile node = getLeastCost(nodes);
            current = node.p;
            if (!node.walkable) {
                scr.log("Node " + node.p + " - f " + node.f);
            }
            closed.add(current);
            open.remove(current);
            Position mapP = PickpocketScript.map.getMapPos(current);
            int x = mapP.getX();
            int y = mapP.getY();
            PickpocketScript.map.drawPixel(x, y, node.walkable);
            //holdingList.add(node);
            //scr.log(current);

            index++;
        }
        /*scr.log("Cleaning nodes!");
        List<Position> actualPath = new ArrayList<Position>();
        for (PathTile pt : holdingList) {
            if (pt == null || pt.parent == null) continue;
            if (pt.parent.p.equals(start)) {
                actualPath.add(pt.parent.p);
                break;
            }
            if (pt.p.equals(start)) break;
            actualPath.add(pt.parent.p);
            Position mapP = PickpocketScript.map.getMapPos(pt.parent.p);
            int x = mapP.getX();
            int y = mapP.getY();
            PickpocketScript.map.drawPixel(x, y, pt.parent.walkable);
        }*/
        scr.log("Finished finding " + closed.size() + " nodes");
        //Collections.reverse(actualPath);
        return closed;
    }

    public boolean nextToEnd() {
        int mh = ManhattenValue(current);
        return (mh < 21);
        //return false;
    }

    public int[] distance(Position a, Position b) {
        int x = Math.abs(a.getX() - b.getX());
        int y = Math.abs(a.getY() - b.getY());
        return new int[] { x, y };
    }

    public PathTile getLeastCost(PathTile[] nodes) {
        int lowestIndex = 0;
        int lowestF = Integer.MAX_VALUE;
        for (int i = 0; i < nodes.length; i++) {
            //if (!nodes[i].walkable) scr.log("Node " + nodes[i].p + " - f " + nodes[i].f +" getleastcost");
            if (nodes[i] != null && nodes[i].walkable && nodes[i].f < lowestF && !closed.contains(nodes[i].p)) {

                lowestF = nodes[i].f;
                lowestIndex = i;
            }
        }
        //scr.log(lowestIndex);
        return nodes[lowestIndex];
    }

    public PathTile[] getAdjacent(Position p) {
        PathTile[] nodes = new PathTile[8];
        int x = p.getX();
        int y = p.getY();
        PathTile old = null;
        if (holdingList.size() > 0) old = holdingList.get(holdingList.size() - 1);

        Position newNode = new Position(x - 1, y - 1, p.getZ()); //top left
        if (!open.contains(newNode)) open.add(newNode);
        int h = OctileValue(newNode);
        int g = diagonalCost;
        nodes[0] = new PathTile(newNode, g, h, old);
        //nodes[0] = nodes[2] = nodes[4] = nodes[5] = nodes[7] = null;

        newNode = new Position(x, y - 1, p.getZ()); //top centre
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = horizontalCost;
        nodes[1] = new PathTile(newNode, g, h, old);

        newNode = new Position(x + 1, y - 1, p.getZ()); //top right
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = diagonalCost;
        nodes[2] = new PathTile(newNode, g, h, old);

        newNode = new Position(x - 1, y, p.getZ()); //centre left
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = horizontalCost;
        nodes[3] = new PathTile(newNode, g, h, old);

        /* Centre centre would be "p", so we skip it (we are not moving back to p) */

        newNode = new Position(x + 1, y, p.getZ()); //centre right
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = horizontalCost;
        nodes[4] = new PathTile(newNode, g, h, old);

        newNode = new Position(x - 1, y + 1, p.getZ()); //bottom left
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = diagonalCost;
        nodes[5] = new PathTile(newNode, g, h, old);

        newNode = new Position(x, y + 1, p.getZ()); //bottom centre
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = horizontalCost;
        nodes[6] = new PathTile(newNode, g, h, old);

        newNode = new Position(x + 1, y + 1, p.getZ()); //bottom right
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = diagonalCost;
        nodes[7] = new PathTile(newNode, g, h, old);

        /*PathTile[] temp = nodes.clone();
        for (int i = 0; i < nodes.length; i++) {
            if (!temp[i].walkable) {
                nodes[i] = null;
            }
        }*/

        return nodes;
    }

    public int OctileValue(Position start) { //This is our "H" value
        int x = start.getX();
        int y = start.getY();

        int horizontal = Math.abs(x - e.getX());
        int vertical = Math.abs(y - e.getY());
        //return Math.max(horizontal, vertical) * horizontalCost; //bad algorithm lol
        //return (horizontal + vertical) * horizontalCost; //Manhatten

        //double tieBreaker = (1.0 + (diagonalCost / ManhattenValue(start)));
        if (horizontal == 0 && vertical == 0) return 0;

        //int h = horizontalCost * (horizontal + vertical) + (diagonalCost - 2 * horizontalCost) * Math.min(horizontal, vertical);

        int h = Math.max(horizontal, vertical) + (diagonalCost - horizontalCost) * Math.min(horizontal, vertical);

        //h *= (tieBreaker);
        return h; //Octile distance
    }

    public int ManhattenValue(Position start) {
        int x = start.getX();
        int y = start.getY();

        int horizontal = Math.abs(x - e.getX());
        int vertical = Math.abs(y - e.getY());
        return (horizontal + vertical) * horizontalCost;
    }

}

class PathTile {
    public Position p;
    public int g, h, f;
    public boolean walkable;
    public PathTile parent;

    public PathTile(Position pos, int G, int H, PathTile par) {
        p = pos;
        g = G;
        h = H;
        if (par != null) {
            g += par.g;
            parent = par;
        }
        if (h == 0) f = 0;
        else f = g + h;
        System.out.println(p + " - g " + g + " - h " + h + " - f " + f);
        walkable = (PickpocketScript.map.isWalkable(pos));

        if (!walkable) {
            f = Integer.MAX_VALUE;
        }
    }
}

我知道这些图块是不可走的,因为它们的F值为int.maxvalue(在PathTile类中定义)

此场景中的位置只有一个用于x,y,z的int。

我不太确定如何解决这些问题,因为我对编写这样的算法很陌生:)

1 个答案:

答案 0 :(得分:0)

您的findPath()方法看起来正在执行贪婪的最佳优先搜索。检查行PathTile node = getLeastCost(nodes); ...您正在从当前PathTile的邻居中选择成本最低的节点,而不是从开放集中的条目中选择。相反,使用按 f -value排序的PriorityQueue作为打开列表,并从打开列表的头部获取下一个节点。

其他资源:我建议您关注维基百科的A* Search Algorithm Pseudocode。另外,您可能需要查看{* 3}}上的A *简介。

最终注意事项:在网格图中使用路径查找时,使用枚举来定义路线会很有帮助。这使您的代码更具可读性和可维护性。这是一个例子:

enum Direction{
    TOP_LEFT(-1,-1,diagonalCost),
    TOP_CENTER(0,-1,horizontalCost),
    TOP_RIGHT(1,-1,diagonalCost),
    MIDDLE_LEFT(-1,0,horizontalCost),
    MIDDLE_RIGHT(1,0,horizontalCost),
    BOTTOM_LEFT(-1,1,diagonalCost),
    BOTTOM_CENTER(0,1,horizontalCost),
    BOTTOM_RIGHT(1,1,diagonalCost);

    public final int x;
    public final int y;
    public final int cost;

    Direction(int x, int y, int cost){
            this.x = x;
            this.y = y;
            this.cost = cost;
    }
}

您的(未经修正的)getAdjacent方法可简化为以下内容:

public PathTile[] getAdjacent(Position p) {
    PathTile[] nodes = new PathTile[8];
    int x = p.getX();
    int y = p.getY();
    PathTile old = null;
    if (holdingList.size() > 0) old = holdingList.get(holdingList.size() - 1);

    for(Direction d: Direction.values()){
        newNode = new Position(x+ d.x, y + d.y, p.getZ()); //top centre
        if (!open.contains(newNode)) open.add(newNode);
        h = OctileValue(newNode);
        g = d.cost;
        nodes[d.ordinal()] = new PathTile(newNode, g, h, old);
    }

    return nodes;
}