I'm quite new to pathfinding and recently got A-Star working for the first time in Java with Libgdx, but it has some flaws, it doesnt always find the fastest path , or the program simply kills itself(because it's too slow?) :/
(Input/Output here: Imgur album: White = untouched Node, green = start, red = target, blue = path, yellow = node is on closed list but unrelevant)
The rest of the code can be found on Github.
This is the code for the Algorithm itself:
Node lastNode;
Node[] neighborNodes;
int lowestF = 2000;
Node bestNode;
public void findPath() {
for(int x = 0; x < map.worldWidth; x++) {
for(int y = 0; y < map.worldHeight; y++) {
nodes[x][y].calculateHeuristic(targetNode);
}
}
lastNode = startNode;
while(lastNode != targetNode || !openList.isEmpty()) {
neighborNodes = map.getNeighbors(lastNode);
for(Node node:neighborNodes) {
if(node != null)
if(node.state != State.BLOCKED && !closedList.contains(node)) {
openList.add(node);
node.parentNode = lastNode;
}
}
lowestF = 1000;
for(Node node:openList) {
if(node.f <= lowestF) {
lowestF = node.f;
bestNode = node;
}
}
if(openList.isEmpty() && bestNode != targetNode) {
System.out.println("No Path possible");
return;
}
openList.remove(bestNode);
closedList.add(bestNode);
lastNode = bestNode;
lastNode.setState(State.SEARCHED);
}
reconstructPath();
}
public void reconstructPath() {
Node lastNode = targetNode;
while(lastNode != startNode) {
lastNode = lastNode.parentNode;
lastNode.setState(State.PATH);
}
setStartAndEnd();
}
And the Node Class:
public class Node {
public enum State {
NORMAL, BLOCKED, START, END, SEARCHED, PATH
}
public State state;
int xPos, yPos;
Color color;
Node parentNode;
int f;
int movementCost = 10;
int heuristic;
public Node(int x, int y) {
xPos = x;
yPos = y;
setState(State.NORMAL);
}
public void setState(State newState) {
state = newState;
}
public boolean isNodeClicked() {
int inputX = Gdx.input.getX();
int inputY = Gdx.graphics.getHeight() - Gdx.input.getY();
if(inputX > xPos*32 && inputX < xPos*32+32 &&
inputY > yPos*32 && inputY < yPos*32+32) {
return true;
}
return false;
}
public void calculateHeuristic(Node targetNode) {
heuristic = (Math.abs((xPos-targetNode.xPos)) + Math.abs((yPos-targetNode.yPos))) * movementCost;
f = movementCost+heuristic;
}
public int calculateHeuristic(Node finishNode, int useless) {
return (Math.abs((xPos-finishNode.xPos)) + Math.abs((yPos-finishNode.yPos))) * movementCost;
}
}
At the moment I'm using a 2-dimensional array for the map or nodes and Arraylist for open and closed list.
It'd be much appreciated if somebody could help me get my A-star to behave and explain to me what I did wrong, I would also be very grateful for any other criticism, since I want to improve my programming :)
Thanks for your help in Advance :)
Your problem is here:
public void calculateHeuristic(Node targetNode) {
heuristic = (Math.abs((xPos-targetNode.xPos)) + Math.abs((yPos- targetNode.yPos))) * movementCost;
f = movementCost+heuristic;
}
Your calculation of your heuristic is wrong, because your calculation of your movementCost is wrong. The cost of a node is not a fixed value. It's the summation of all of the costs to move between nodes along the path to that node so far. So your node should actually have a function,
public int calculateCost(){
if(parentNode != null){
return movementCost + parentNode.calculateCost();
} else{
return movementCost;
}
}
And your heuristic thus becomes:
public void calculateHeuristic(Node targetNode) {
heuristic = (Math.abs((xPos-targetNode.xPos)) + Math.abs((yPos- targetNode.yPos))) * movementCost;
f = calculateCost()+heuristic;
}
Your other problems look like they probably all come from the various typos/logical errors I mentioned in the comments (while(...||openSet.isEmpty()) instead of while(...|| !openSet.isEmpty()), etc)
Related
I have an A* pathfinding algorithm that I've used for a Spigot plugin which worked fine. I then added a requirements system so it won't try and pathfind through places it shouldn't. Now it seems REALLY slow, and it looks like it has nothing to do with the requirements code itself, and more to do with the algorithm having many more incorrect paths when calculating. I seem to be getting 1500ms+ on this, which is definitely not good xD
Here is the pathfinder code:
public Path calculate(PathfinderGoal goal, PathScorer scorer, List<PathRequirement> requirements, int maxNodes) {
PathNode start = toNode(npc.getLocation());
PathNode end = toNode(goal.getLocation());
List<PathNode> open = new ArrayList<>() {{ add(start); }};
List<PathNode> navigated = new ArrayList<>();
start.setF(scorer.computeCost(start, end));
Timer timer = new Timer().start();
while (!open.isEmpty()) {
PathNode current = null;
for (PathNode node : open) {
if (current == null || node.getH() < current.getH()) {
current = node;
}
}
if (scorer.computeCost(current, end) < 1 || (navigated.size() >= maxNodes && maxNodes != -1)) {
navigated.add(navigated.size() < maxNodes ? end : current);
return reconstruct(navigated, navigated.size() - 1);
}
open.remove(current);
current.close();
for (PathNode node : current.getNeighbors()) {
if (node.isClosed()) {
continue;
}
double tentG = current.getG() + scorer.computeCost(current, node);
if (!open.contains(node) || tentG < node.getG()) {
boolean requirementsMet = true;
for (PathRequirement requirement : requirements) {
requirement.setNavigated(navigated);
if (!navigated.isEmpty() && !requirement.canMoveToNewNode(navigated.get(navigated.size() - 1), node)) {
requirementsMet = false;
break;
}
}
if (!navigated.contains(current)) {
navigated.add(current);
}
node.setG(tentG);
node.setH(scorer.computeCost(node, end));
node.setF(tentG + node.getH());
if (!open.contains(node) && requirementsMet) {
open.add(node);
}
}
}
Bukkit.broadcastMessage("Open Set Size: " + open.size());
Bukkit.broadcastMessage(timer.stop() + "ms");
}
return null;
}
private Path reconstruct(List<PathNode> navigated, int index) {
final PathNode current = navigated.get(index);
Path withCurrent = new Path(new ArrayList<>() {{ add(current); }});
if (index > 0 && navigated.contains(current)) {
return reconstruct(navigated, index - 1).append(withCurrent);
}
return withCurrent;
}
And here is the PathNode class:
public PathNode(Pathfinder pathfinder, int x, int y, int z) {
this.pathfinder = pathfinder;
this.x = x;
this.y = y;
this.z = z;
}
#Override
public boolean equals(Object other) {
if (!(other instanceof PathNode otherNode)) {
return false;
}
return otherNode.x == x && otherNode.y == y && otherNode.z == z;
}
public List<PathNode> getNeighbors() {
return new ArrayList<>() {
{
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
add(new PathNode(pathfinder, PathNode.this.x + x, PathNode.this.y + y, PathNode.this.z + z));
}
}
}
}
};
}
public Location getLocation() {
return new Location(pathfinder.getNPC().getLocation().getWorld(), x, y, z);
}
public double getF() {
return F;
}
public void setF(double f) {
this.F = f;
}
public double getG() {
return G;
}
public void setG(double g) {
this.G = g;
}
public double getH() {
return H;
}
public void setH(double h) {
this.H = h;
}
public boolean isClosed() {
return closed;
}
public void close() {
this.closed = true;
}
Valid Requirements Class:
public class ValidPathRequirement extends PathRequirement {
#Override
public boolean canMoveToNewNode(PathNode from, PathNode to) {
Block fromBlock = from.getLocation().getBlock();
Block toBlock = to.getLocation().getBlock();
boolean validHeight = toBlock.getType().isAir() && toBlock.getRelative(BlockFace.UP).getType().isAir(); // checks if is player height
boolean validGround = toBlock.getRelative(BlockFace.DOWN).getType().isSolid(); // is there a block underneath that they can stand on?
boolean validFromPrev = toBlock.getLocation().subtract(fromBlock.getLocation()).getY() <= 1; // is it max one block higher than the last one?
// is this one causing issues?
Location fromLocDist = from.getLocation().clone();
Location toLocDist = to.getLocation().clone();
toLocDist.setY(fromLocDist.getY());
boolean validDistance = fromLocDist.distance(toLocDist) <= 1;
return validHeight && validGround && validFromPrev;
}
}
Without looking at the rest of the algorithm, the first thing that stands out is that your data structures are incorrect. The "open" list needs to be a Priority Queue, and "closed" (or "navigated") should be a set.
The evaluation function of A* Search is, according to AI: A Modern Approach, f(x) = h(x) + g(x), where f(x) is the evaluation function, h(x) the heuristic function (Euclidian straight line in my case), and g(x) the path-cost. f(x) for greedy search is simply f(x) = h(x).
To test these I made a visualization program.
With obstacle:
As you can see, the greedy search was far more effective than A*. This surprised me, so I wonder if this is expected and whether there is something wrong with my A*-code; either the heuristic or the priority queue.
int heuristic(Node n) {
int i=distanceFrom(n.state, grid.goalState) + n.state.pCost;
return i;
}
PriorityQueue<Node> front = new PriorityQueue<Node>(30, new Comparator<Node>() {
// override compare method
public int compare(Node i, Node j) {
if (heuristic(i) > heuristic(j)) {
return 1;
}
else if (heuristic(i) < heuristic(j)) {
return -1;
}
else {
return 0;
}
}
});
If I try making pCost less significant and more similar to Greedy by dividing it by an integer, it will perform better. Is A* supposed to perform like this?
The entire A* execute-function looks like this:
public void execute() {
SwingWorker worker = new SwingWorker() {
#Override
protected Void doInBackground() throws Exception {
boolean retGoal = false;
PriorityQueue<Node> front = new PriorityQueue<Node>(30, new Comparator<Node>() {
// override compare method
public int compare(Node i, Node j) {
if (heuristic(i) > heuristic(j)) {
return 1;
}
else if (heuristic(i) < heuristic(j)) {
return -1;
}
else {
return 0;
}
}
});
Hashtable<State,Node> reached = new Hashtable<State,Node>();
State s;
Node goal = null;
n = new Node(grid.startState, null);
int pCost = 0;
n.pathCost = pCost;
n.state.pCost = pCost;
front.add(n);
reached.put(n.state, n);
n.state.front=true;
panel.repaint();
while(!front.isEmpty()) {
Thread.sleep(Main.delay);
n.state.front=false;
n=front.poll();
if(grid.isGoal(n.state)) {
goal = n;
break;
}
pCost=n.state.pCost+1;
panel.repaint();
for (Node child : expand(n, grid)) {
s = child.state;
if(!reached.containsKey(s) && !s.isObstacle) {
s.pCost = pCost; //steps from goal
}
if((!reached.containsKey(s) || heuristic(child) < heuristic(reached.get(s))) && !s.isObstacle) {
s.reached = true;
s.front = true;
reached.put(s, child);
front.add(child);
}
panel.repaint();
}
}
if(goal != null) {
Node n = goal.parent;
while(n.parent != null) {
n.state.isPath=true;
n = n.parent;
}
}
panel.repaint();
return null;
}
};
worker.execute();
}
So far I have the algorithm figured out to add to my binary search tree, but I'm having a bit of difficulty translating it into code. The algorithm is as follows:
public void add(int v) {
Create a new node n to hold value v.
If tree is empty
Set root to n.
Else
Create temporary node reference m, initialized to root.
Loop looking for a gap (a null left or right pointer) that is on the
correct side of m for v
If v < m.value, look at the left pointer
If v >= m.value, look at the right pointer
If pointer on correct side is not null, set m to that child node and
continue looking
m = m.left or m = m.right
The search for insertion position stops when node m has a null pointer on
the correct side.
Insert the new node n at that position
m.left = n or m.right = n
}
So far I have:
public void add(int v) {
Node n = new Node(v);
if(root==null)
root = n;
else {
Node m = root;
while(...) {
if(...)
m = m.left;
else
m = m.right;
}
if(...)
m.left = m;
else
m.right = n;
}
}
I believe most of that is correct, but I don't know what needs to be done at places marked as "..."
first of all a binary search tree should not have any duplicate values, an important requirement you have not implemented in your code. I have implemented the binary search tree recently while learning datastructures in java. Here is the code i wrote:
public class OrderedBinaryTree
{
private int _elementsPresent = 0;
private Node _root = null;
private int [] _values = null;
private class Node
{
Node _left = null;
Node _right = null;
Node _parent = null;
int _value = 0;
public Node(int value,Node parent)
{
_value = value;
_parent = parent;
}
}
public void put(int value)
{
boolean valueInserted = false;
Node temp = _root;
while(!valueInserted)
{
if(_root == null)
{
_root = new Node(value,null);
break;
}
else if(value == temp._value)
{
System.out.println("the entered value is already present");
return;
}
else if(value<=temp._value)
{
if(temp._left == null)
{
temp._left = new Node(value,temp);
break;
}
else
{
temp = temp._left;
}
}
else
{
if(temp._right == null)
{
temp._right = new Node(value,temp);
break;
}
else
{
temp = temp._right;
}
}
}
_elementsPresent++;
}
I am currently writing a program that takes a graph of nodes and returns the characteristic path length (path length from one node to each of the other nodes averaged then repeated for each node and averaged again). I have just about everything else done but I am unsure on how to implement a depth first search of the nodes to find the shortest path from one node to another. This is what I have so far.
int dist = 0;
List<Node> path = new ArrayList<Node>();
List<Node> shrtPath = new ArrayList<Node>();
Node curNode = rtNode;
if (rtNode == goalNode) {
return dist;
}
path.add(curNode);
for (int l = 0; l < rtNode.connections.size(); l++) {
Node tempNode = new Node();
curNode = rtNode.connections.get(l);
for (int i = 0; i < curNode.connections.size(); i++) {
tempNode = curNode.connections.get(i);
path.add(tempNode);
if (curNode.connections.get(i) == goalNode) {
if (path.size() < shrtPath.size()) {
shrtPath.clear();
shrtPath = path;
}
}
}
}
dist = shrtPath.size();
return dist;
I know it is incomplete but I am not sure where to go from here or if I am even headed in the right direction. I know I need to be able to search through the root node and its connections but I'm not sure how to loop through the connections of connections and so forth. I also realize I have to mark the nodes visited as I go I have a boolean in my Node class for that I just haven't implemented this yet. Here is my Node class as well.
import java.util.ArrayList;
import java.util.List;
public class Node {
List <Node> connections = new ArrayList<Node>();
int cons = 0;
boolean hub;
boolean visited = false;
public Node() {
hub = false;
}
public void setNbs(Node nb1, Node nb2) {
connections.add(nb1);
connections.add(nb2);
}
public boolean addExtraCon(Node con) {
for (int i = 0; i < connections.size(); i++) {
if(connections.get(i) == con) {
return false;
}
}
connections.add(con);
return true;
}
public boolean makeHub() {
hub = true;
return hub;
}
public int numberOfConnections() {
cons = connections.size();
return cons;
}
public boolean isHub() {
if (hub == true) {
return true;
}
return false;
}
}
I hope this helps any and all help is greatly appreciated. Thanks in advance.
I am using A* for pathfinding in a Java project that I am working on. My attempt at implementing it though, has some issues. Not only is it slightly slow, but it sometimes runs into infinite loop issues where it never finds a path. I believe this to be the result of a bug somewhere in my code. The pathfinder operates on 3d space, and uses a basic heuristic (I can provide the code if necessary, however, it does little more than just calculate the distance squared between the two points).
The code is here:
public class BinaryPathFinder extends PathFinder {
private final PriorityBuffer<PathNode> paths;
private final HashMap<Point, Integer> mindists = new HashMap<Point, Integer>();
private final ComparableComparator<PathNode> comparator = new ComparableComparator<PathNode>();
private Path lastPath;
private Point start, end;
private final byte SIZE_INCREMENT = 20;
public BinaryPathFinder(PathHeuristic heuristic,
Pathplayer player, PathWorld pathWorld) {
super(heuristic, player, pathWorld);
this.world = pathWorld.getWorld();
paths = new PriorityBuffer<PathNode>(8000, comparator);
}
#Override
public boolean find() {
try {
PathNode root = new PathNode();
root.point = start;
calculateTotalCost(root, start, start, false);
expand(root);
while (true) {
PathNode p = paths.remove();
if (p == null)
return false;
Point last = p.point;
if (isGoal(last)) {
calculatePath(p); // Iterate back.
this.mindists.clear();
this.paths.clear();
return true;
}
expand(p);
}
} catch (Exception e) {
e.printStackTrace();
}
this.mindists.clear();
this.paths.clear();
return false;
}
#Override
public Path path() {
return this.lastPath;
}
#Override
public void recalculate(Point start, Point end) {
this.start = start;
this.end = end;
this.lastPath = null;
}
private void expand(PathNode path) {
Point p = path.point;
Integer min = mindists.get(p);
if (min == null || min > path.totalCost)
mindists.put(p, path.totalCost);
else
return;
Point[] successors = generateSuccessors(p);
for (Point t : successors) {
if (t == null)
continue;
PathNode newPath = new PathNode(path);
newPath.point = t;
calculateTotalCost(newPath, p, t, false);
paths.add(newPath);
}
}
private void calculatePath(PathNode p) {
Point[] retPath = new Point[20];
Point[] copy = null;
short added = 0;
for (PathNode i = p; i != null; i = i.parent) {
if (added >= retPath.length) {
copy = new Point[retPath.length + SIZE_INCREMENT];
System.arraycopy(retPath, 0, copy, 0, retPath.length);
retPath = copy;
}
retPath[added++] = i.point;
}
this.lastPath = new Path(retPath);
}
private int calculateHeuristic(Point start, Point end, boolean endPoint) {
return this.heuristic.calculate(start, end, this.pathWorld,
this.player, endPoint);
}
private int calculateTotalCost(PathNode p, Point from, Point to,
boolean endPoint) {
int g = (calculateHeuristic(from, to, endPoint) + ((p.parent != null) ? p.parent.cost
: 0));
int h = calculateHeuristic(from, to, endPoint);
p.cost = g;
p.totalCost = (g + h);
return p.totalCost;
}
private Point[] generateSuccessors(Point point) {
Point[] points = new Point[27];
Point temp = null;
byte counter = -1;
for (int x = point.x - 1; x <= point.x + 1; ++x) {
for (int y = point.y + 1; y >= point.y - 1; --y) {
for (int z = point.z - 1; z <= point.z + 1; ++z) {
++counter;
if (x == 0 && y == 0 && z == 0)
continue;
temp = new Point(x, y, z);
if (valid(temp))
points[counter] = temp;
}
}
}
return points;
}
private boolean isGoal(Point last) {
return last.equals(this.end);
}
}
PathNode is here:
public class PathNode implements Comparable<PathNode> {
public Point point;
public final PathNode parent;
public int cost;
public int totalCost;
public PathNode(Point point, PathNode parent, short cost, short totalCost) {
this.point = point;
this.parent = parent;
this.cost = cost;
this.totalCost = totalCost;
}
public PathNode() {
this.point = null;
this.parent = null;
this.cost = this.totalCost = 0;
}
public PathNode(PathNode path) {
this.parent = path;
this.cost = path.cost;
this.totalCost = path.totalCost;
}
#Override
public int compareTo(PathNode node) {
int result = node.totalCost - node.cost;
if (result > node.totalCost)
return 1;
else if (result == 0)
return 0;
else
return -1;
}
}
Point is just a wrapper class that defines integer x, y, z values.
I am using the Apache Commons PriorityBuffer for storing path nodes.
Help would be appreciated - thanks in advance!
just from a programming point of view - are you sure that
for(PathNode i = p; i != null; i = i.parent) always terminates? this looks like the only place it could really hang.