com.github.mathiewz.slick.util.pathfinding.AStarPathFinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modernized-slick Show documentation
Show all versions of modernized-slick Show documentation
The main purpose of this libraryis to modernize and maintain the slick2D library.
The newest version!
package com.github.mathiewz.slick.util.pathfinding;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import com.github.mathiewz.slick.util.pathfinding.heuristics.ClosestHeuristic;
/**
* A path finder implementation that uses the AStar heuristic based algorithm
* to determine a path.
*
* @author Kevin Glass
*/
public class AStarPathFinder implements PathFinder, PathFindingContext {
/** The set of nodes that have been searched through */
private final ArrayList closed = new ArrayList<>();
/** The set of nodes that we do not yet consider fully searched */
private final PriorityList open = new PriorityList();
/** The map being searched */
private final TileBasedMap map;
/** The maximum depth of search we're willing to accept before giving up */
private final int maxSearchDistance;
/** The complete set of nodes across the map */
private final Node[][] nodes;
/** True if we allow diaganol movement */
private final boolean allowDiagMovement;
/** The heuristic we're applying to determine which nodes to search first */
private final AStarHeuristic heuristic;
/** The node we're currently searching from */
private Node current;
/** The mover going through the path */
private Mover mover;
/** The x coordinate of the source tile we're moving from */
private int sourceX;
/** The y coordinate of the source tile we're moving from */
private int sourceY;
/** The distance searched so far */
private int distance;
/**
* Create a path finder with the default heuristic - closest to target.
*
* @param map
* The map to be searched
* @param maxSearchDistance
* The maximum depth we'll search before giving up
* @param allowDiagMovement
* True if the search should try diaganol movement
*/
public AStarPathFinder(TileBasedMap map, int maxSearchDistance, boolean allowDiagMovement) {
this(map, maxSearchDistance, allowDiagMovement, new ClosestHeuristic());
}
/**
* Create a path finder
*
* @param heuristic
* The heuristic used to determine the search order of the map
* @param map
* The map to be searched
* @param maxSearchDistance
* The maximum depth we'll search before giving up
* @param allowDiagMovement
* True if the search should try diaganol movement
*/
public AStarPathFinder(TileBasedMap map, int maxSearchDistance, boolean allowDiagMovement, AStarHeuristic heuristic) {
this.heuristic = heuristic;
this.map = map;
this.maxSearchDistance = maxSearchDistance;
this.allowDiagMovement = allowDiagMovement;
nodes = new Node[map.getWidthInTiles()][map.getHeightInTiles()];
for (int x = 0; x < map.getWidthInTiles(); x++) {
for (int y = 0; y < map.getHeightInTiles(); y++) {
nodes[x][y] = new Node(x, y);
}
}
}
/**
* @see PathFinder#findPath(Mover, int, int, int, int)
*/
@Override
public Path findPath(Mover mover, int sx, int sy, int tx, int ty) {
current = null;
// easy first check, if the destination is blocked, we can't get there
this.mover = mover;
sourceX = tx;
sourceY = ty;
distance = 0;
if (map.blocked(this, tx, ty)) {
return null;
}
for (int x = 0; x < map.getWidthInTiles(); x++) {
for (int y = 0; y < map.getHeightInTiles(); y++) {
nodes[x][y].reset();
}
}
// initial state for A*. The closed group is empty. Only the starting
// tile is in the open list and it's cost is zero, i.e. we're already there
nodes[sx][sy].cost = 0;
nodes[sx][sy].depth = 0;
closed.clear();
open.clear();
addToOpen(nodes[sx][sy]);
nodes[tx][ty].parent = null;
// while we haven't found the goal and haven't exceeded our max search depth
int maxDepth = 0;
while (maxDepth < maxSearchDistance && open.size() != 0) {
// pull out the first node in our open list, this is determined to
// be the most likely to be the next step based on our heuristic
int lx = sx;
int ly = sy;
if (current != null) {
lx = current.x;
ly = current.y;
}
current = getFirstInOpen();
distance = current.depth;
if (current == nodes[tx][ty] && isValidLocation(mover, lx, ly, tx, ty)) {
break;
}
removeFromOpen(current);
addToClosed(current);
// search through all the neighbours of the current node evaluating
// them as next steps
for (int x = -1; x < 2; x++) {
for (int y = -1; y < 2; y++) {
// not a neighbour, its the current tile
if (x == 0 && y == 0 || !allowDiagMovement && x != 0 && y != 0) {
continue;
}
// determine the location of the neighbour and evaluate it
int xp = x + current.x;
int yp = y + current.y;
if (isValidLocation(mover, current.x, current.y, xp, yp)) {
// the cost to get to this node is cost the current plus the movement
// cost to reach this node. Note that the heursitic value is only used
// in the sorted open list
float nextStepCost = current.cost + getMovementCost(mover, current.x, current.y, xp, yp);
Node neighbour = nodes[xp][yp];
map.pathFinderVisited(xp, yp);
// if the new cost we've determined for this node is lower than
// it has been previously makes sure the node hasn't been discarded. We've
// determined that there might have been a better path to get to
// this node so it needs to be re-evaluated
if (nextStepCost < neighbour.cost) {
if (inOpenList(neighbour)) {
removeFromOpen(neighbour);
}
if (inClosedList(neighbour)) {
removeFromClosed(neighbour);
}
}
// if the node hasn't already been processed and discarded then
// reset it's cost to our current cost and add it as a next possible
// step (i.e. to the open list)
if (!inOpenList(neighbour) && !inClosedList(neighbour)) {
neighbour.cost = nextStepCost;
neighbour.heuristic = getHeuristicCost(mover, xp, yp, tx, ty);
maxDepth = Math.max(maxDepth, neighbour.setParent(current));
addToOpen(neighbour);
}
}
}
}
}
// since we've got an empty open list or we've run out of search
// there was no path. Just return null
if (nodes[tx][ty].parent == null) {
return null;
}
// At this point we've definitely found a path so we can uses the parent
// references of the nodes to find out way from the target location back
// to the start recording the nodes on the way.
Path path = new Path();
Node target = nodes[tx][ty];
while (target != nodes[sx][sy]) {
path.prependStep(target.x, target.y);
target = target.parent;
}
path.prependStep(sx, sy);
// thats it, we have our path
return path;
}
/**
* Get the X coordinate of the node currently being evaluated
*
* @return The X coordinate of the node currently being evaluated
*/
public int getCurrentX() {
if (current == null) {
return -1;
}
return current.x;
}
/**
* Get the Y coordinate of the node currently being evaluated
*
* @return The Y coordinate of the node currently being evaluated
*/
public int getCurrentY() {
if (current == null) {
return -1;
}
return current.y;
}
/**
* Get the first element from the open list. This is the next
* one to be searched.
*
* @return The first element in the open list
*/
protected Node getFirstInOpen() {
return (Node) open.first();
}
/**
* Add a node to the open list
*
* @param node
* The node to be added to the open list
*/
protected void addToOpen(Node node) {
node.setOpen(true);
open.add(node);
}
/**
* Check if a node is in the open list
*
* @param node
* The node to check for
* @return True if the node given is in the open list
*/
protected boolean inOpenList(Node node) {
return node.isOpen();
}
/**
* Remove a node from the open list
*
* @param node
* The node to remove from the open list
*/
protected void removeFromOpen(Node node) {
node.setOpen(false);
open.remove(node);
}
/**
* Add a node to the closed list
*
* @param node
* The node to add to the closed list
*/
protected void addToClosed(Node node) {
node.setClosed(true);
closed.add(node);
}
/**
* Check if the node supplied is in the closed list
*
* @param node
* The node to search for
* @return True if the node specified is in the closed list
*/
protected boolean inClosedList(Node node) {
return node.isClosed();
}
/**
* Remove a node from the closed list
*
* @param node
* The node to remove from the closed list
*/
protected void removeFromClosed(Node node) {
node.setClosed(false);
closed.remove(node);
}
/**
* Check if a given location is valid for the supplied mover
*
* @param mover
* The mover that would hold a given location
* @param sx
* The starting x coordinate
* @param sy
* The starting y coordinate
* @param x
* The x coordinate of the location to check
* @param y
* The y coordinate of the location to check
* @return True if the location is valid for the given mover
*/
protected boolean isValidLocation(Mover mover, int sx, int sy, int x, int y) {
boolean invalid = x < 0 || y < 0 || x >= map.getWidthInTiles() || y >= map.getHeightInTiles();
if (!invalid && (sx != x || sy != y)) {
this.mover = mover;
sourceX = sx;
sourceY = sy;
invalid = map.blocked(this, x, y);
}
return !invalid;
}
/**
* Get the cost to move through a given location
*
* @param mover
* The entity that is being moved
* @param sx
* The x coordinate of the tile whose cost is being determined
* @param sy
* The y coordiante of the tile whose cost is being determined
* @param tx
* The x coordinate of the target location
* @param ty
* The y coordinate of the target location
* @return The cost of movement through the given tile
*/
public float getMovementCost(Mover mover, int sx, int sy, int tx, int ty) {
this.mover = mover;
sourceX = sx;
sourceY = sy;
return map.getCost(this, tx, ty);
}
/**
* Get the heuristic cost for the given location. This determines in which
* order the locations are processed.
*
* @param mover
* The entity that is being moved
* @param x
* The x coordinate of the tile whose cost is being determined
* @param y
* The y coordiante of the tile whose cost is being determined
* @param tx
* The x coordinate of the target location
* @param ty
* The y coordinate of the target location
* @return The heuristic cost assigned to the tile
*/
public float getHeuristicCost(Mover mover, int x, int y, int tx, int ty) {
return heuristic.getCost(map, mover, x, y, tx, ty);
}
/**
* A list that sorts any element provided into the list
*
* @author kevin
*/
private class PriorityList {
/** The list of elements */
private final List> list = new LinkedList<>();
/**
* Retrieve the first element from the list
*
* @return The first element from the list
*/
public Object first() {
return list.get(0);
}
/**
* Empty the list
*/
public void clear() {
list.clear();
}
/**
* Add an element to the list - causes sorting
*
* @param o
* The element to add
*/
public void add(Node o) {
// float the new entry
for (int i = 0; i < list.size(); i++) {
if (list.get(i).compareTo(o) > 0) {
list.add(i, o);
break;
}
}
if (!list.contains(o)) {
list.add(o);
}
}
/**
* Remove an element from the list
*
* @param o
* The element to remove
*/
public void remove(Object o) {
list.remove(o);
}
/**
* Get the number of elements in the list
*
* @return The number of element in the list
*/
public int size() {
return list.size();
}
@Override
public String toString() {
return "{" + list.stream().map(Object::toString).collect(Collectors.joining(",")) + "}";
}
}
/**
* A single node in the search graph
*/
private class Node implements Comparable {
/** The x coordinate of the node */
private final int x;
/** The y coordinate of the node */
private final int y;
/** The path cost for this node */
private float cost;
/** The parent of this node, how we reached it in the search */
private Node parent;
/** The heuristic cost of this node */
private float heuristic;
/** The search depth of this node */
private int depth;
/** In the open list */
private boolean open;
/** In the closed list */
private boolean closed;
/**
* Create a new node
*
* @param x
* The x coordinate of the node
* @param y
* The y coordinate of the node
*/
public Node(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Set the parent of this node
*
* @param parent
* The parent node which lead us to this node
* @return The depth we have no reached in searching
*/
public int setParent(Node parent) {
depth = parent.depth + 1;
this.parent = parent;
return depth;
}
/**
* @see Comparable#compareTo(Object)
*/
@Override
public int compareTo(Node o) {
float f = heuristic + cost;
float of = o.heuristic + o.cost;
if (f < of) {
return -1;
} else if (f > of) {
return 1;
} else {
return 0;
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node)) {
return false;
}
Node o = (Node) obj;
float f = heuristic + cost;
float of = o.heuristic + o.cost;
return f == of;
}
@Override
public int hashCode() {
int result = cost != +0.0f ? Float.floatToIntBits(cost) : 0;
result = 31 * result + (heuristic != +0.0f ? Float.floatToIntBits(heuristic) : 0);
return result;
}
/**
* Indicate whether the node is in the open list
*
* @param open
* True if the node is in the open list
*/
public void setOpen(boolean open) {
this.open = open;
}
/**
* Check if the node is in the open list
*
* @return True if the node is in the open list
*/
public boolean isOpen() {
return open;
}
/**
* Indicate whether the node is in the closed list
*
* @param closed
* True if the node is in the closed list
*/
public void setClosed(boolean closed) {
this.closed = closed;
}
/**
* Check if the node is in the closed list
*
* @return True if the node is in the closed list
*/
public boolean isClosed() {
return closed;
}
/**
* Reset the state of this node
*/
public void reset() {
closed = false;
open = false;
cost = 0;
depth = 0;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "[Node " + x + "," + y + "]";
}
}
/**
* @see com.github.mathiewz.slick.util.pathfinding.PathFindingContext#getMover()
*/
@Override
public Mover getMover() {
return mover;
}
/**
* @see com.github.mathiewz.slick.util.pathfinding.PathFindingContext#getSearchDistance()
*/
@Override
public int getSearchDistance() {
return distance;
}
/**
* @see com.github.mathiewz.slick.util.pathfinding.PathFindingContext#getSourceX()
*/
@Override
public int getSourceX() {
return sourceX;
}
/**
* @see com.github.mathiewz.slick.util.pathfinding.PathFindingContext#getSourceY()
*/
@Override
public int getSourceY() {
return sourceY;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy