user3407039
user3407039

Reputation: 1335

Changing A* path finding into HPA* - Java

I have implemented an A* algorithm for a simple 2d grid based game. However the algorithm slows the game down a lot and I recently learned about HPA* which is meant to try and solve this issue to an extent.

I can't really find any coding examples of it in place that are useful to me since I am coding in Java and I'm a bit confused about where to begin with adapting my initial code.

My code is below if anyone wants to have a look. I really just want to somehow adapt the code so that the path will be split into grids so that i'll be calcuating a path of maybe 5 aquares length as opposed to one that would be 20 aquares long and slow down the game.

  if (shortestPath == null) {

     openLocations.add(playerLocation);

        // While the goal has not been found yet
        while (openLocations.size() != 0 || pathFound != true) {
            // get the first node from the open list
            Node current = openLocations.get(0);
            shortestPath = reconstructPath(current);

            // check if current node is the goal node
            if (current.getX() == goalLocation.getX()
                    && current.getY() == goalLocation.getY()
                    //|| shortestPath.getWayPointPath().size() > GameInfo.getPathLength() + GameInfo.getPathCounter()
                    //|| shortestPath.getWayPointPath().size() >= 5
                    ) {
                shortestPath = reconstructPath(current);
                pathFound = true;

                for(Node node: shortestPath.getWayPointPath())
                 totalClosedLocations.add(node);

                // path has been found
                break;
            }

            // move current node to the already searched (closed) list
            openLocations.remove(current);
            closedLocations.add(current);

            // set the current nodes neighbours
            current = setNeighbours(current);

            // Now it's time to go through all of the current nodes
            // neighbours and see if they should be the next step
            for (Node neighbor : current.getNeighborList()) {
                boolean neighborIsBetter;

                // if we have already searched this Node, don't bother and
                // continue to the next one
                if (closedLocations.contains(neighbor)) {
                    continue;
                }

                boolean found = false;
                for (Node neighbournode : closedLocations) {
                    if (neighbournode.getX() == neighbor.getX()
                            && neighbournode.getY() == neighbor.getY()) {
                        found = true;
                        continue;
                    }
                }

                if (found)
                    continue;

                Node movable = new Node(neighbor.getX(), neighbor.getY(), 
                        neighbor.getCategory(), neighbor.getItype(), neighbor.getId());

                if (grid[movable.getX()][movable.getY()].size() > 0) {
                    // check to make sure that the square is not of category
                    // 4(immovable object) or category 3(enemy)
                    if ((grid[movable.getX()][movable.getY()].get(0).category == 4 && grid[movable
                            .getX()][movable.getY()].get(0).itype == 0)
                            && grid[movable.getX()][movable.getY()].get(0).obsID != goalLocation.getId()
                            ) {
                        // You cannot move on this square
                        neighbor.setMoveable(false);
                    } else {
                        // You can move on this square. Set parent location
                        // as the players current position.
                        movable.setParent(playerLocation);
                    }
                }

                // also just continue if the neighbor is an obstacle
                if (neighbor.getMoveable()) {

                    // calculate how long the path is if we choose this
                    // neighbor
                    // as the next step in the path
                    float neighborDistanceFromStart = (current
                            .getDistanceFromStart() + getDistanceBetween(
                            current, neighbor));

                    // add neighbor to the open list if it is not there
                    if (!openLocations.contains(neighbor)) {
                        openLocations.add(neighbor);
                        neighborIsBetter = true;
                        // if neighbor is closer to start it could also be
                        // better
                    } else if (neighborDistanceFromStart < current
                            .getDistanceFromStart()) {
                        neighborIsBetter = true;
                    } else {
                        neighborIsBetter = false;
                    }
                    // set neighbors parameters if it is better
                    if (neighborIsBetter) {
                        neighbor.setParent(current);
                        neighbor.setDistanceFromStart(neighborDistanceFromStart);
                        neighbor.setHeuristicDistanceFromGoal(heuristicStar
                                .getEstimatedDistanceToGoal(
                                        neighbor.getX(), neighbor.getY(),
                                        goalLocation.getX(),
                                        goalLocation.getY()));
                    }
                }

            }
        }

        System.out.println("====================");
    }

Upvotes: 0

Views: 1698

Answers (1)

Martin Frank
Martin Frank

Reputation: 3454

Let's say: you implement a crawl-like game and have a maze, hero and a lot of monsters...

after the hero made it's move it' the monsters turn and they may move, move toward the hero (a*star). i guess you calculate that path for each monster at every turn?!? right? ok, then something is very wrong:

let each monster keep his path. if the monster is very far away, you don't have to search a shortest path, because even if you hero had moved, the shortest path would be mostly the same (only the last few steps are different). then, maybe if the monster has moved 10 turns you can recalculate the path. this way you speed up the calculation time by the factor 10 - considering that longer paths take far time than shorter paths you can do some more optimizing

if the distance is far away, you can recalculate the path every 5 steps, maybe and if it's really close it's required to recalculate the path every turn, but don't worry... if it's a short path it doesnt take much time to calculate!

sorry - no code added...

ok, let's continue: here is a simple maze/map:

simple maze

you start at upper/left and want to go to the end (not A, not B , you want to go to the end!)...

if you don't calculate the whole path, you will never find the way, because all other paths seem to be shorter, especially when you use heuristics in the search!!!

so - there is no way to not make full path search!

optimization: finding the neighbours is very often a bottle neck: when you create a field, set the neighbours for each field!

public class Test {

    public Field[][] field;
    public void doSomeTest(){

        field = new Field[25][25]
        MazeGenerator.createMaze(field); //this makes your map = this sets the fields passable or not
        for (int dy == 0; dy < 25; dy ++){
            for (int dx == 0; dx < 25; dx ++){
                createNeighbours(field[dx][dy]);                    
            }
        }

    }

    public void createNeigbours(Field f){

        //north
        if (isPassable(f.xpos, f.ypos-1) f.neighbours.add(field[xpos][ypos-1]); 

        //east
        if (isPassable(f.xpos+1, f.ypos) f.neighbours.add(field[xpos+1][ypos]);

        //south
        if (isPassable(f.xpos, f.ypos+1) f.neighbours.add(field[xpos][ypos+1]);

        //west
        if (isPassable(f.xpos-1, f.ypos) f.neighbours.add(field[xpos-1][ypos]);

    }

    public boolean isPassable(int xpos, int ypos){

        if (xpos <= 0) return false; //outside [maybe you even have a border, then xpos <= 1
        if (ypos <= 0) return false; //outside
        if (xpos >= Map.width) return false; //outside
        if (ypos >= Map.height) return false; //outside
        if (field[xpos][ypos].isBlocked) return false; //blocked

        return true;
    }
}

public class Field{

    public final int xpos;
    public final int ypos;
    public boolean isBlocked = false; // this makes your map

    public ArrayList<Field> neigbours = new ArrayList<Field>();

    public Field(int xpos, int ypos){
        this.xpos = xpos;
        this.ypos = ypos;
    }

    public List<Field> getNeighbours(){
        return neigbours ;
    }
}

the code above only explains how to create neighbours at the moment you create the field... but if you expand a node (a*) you can get the neighbours very fast and without calculation time (think how often you create objects in your code)

Upvotes: 1

Related Questions