/*
* Copyright (c) 2012. HappyDroids LLC, All rights reserved.
*/
package com.happydroids.droidtowers.pathfinding;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectIntMap;
import java.util.PriorityQueue;
/**
* A* algorithm implementation using the method design pattern.
*
* @author Giuseppe Scrivano
*/
public abstract class AStar<T> {
protected Array<T> discoveredPath;
protected boolean working;
private Runnable completeCallback;
protected T start;
protected T goal;
public boolean isWorking() {
return working;
}
public void runCompleteCallback() {
if (completeCallback != null) {
completeCallback.run();
}
}
public void cancel() {
working = false;
lastCost = Integer.MAX_VALUE;
paths.clear();
}
public void setStart(T start) {
this.start = start;
}
public void setGoal(T goal) {
this.goal = goal;
}
public boolean isFinished() {
return !isWorking() && lastCost != Integer.MAX_VALUE;
}
public Runnable getCompleteCallback() {
return completeCallback;
}
private class Path implements Comparable {
public T point;
public int f;
public int g;
public Path parent;
public Path() {
parent = null;
point = null;
g = f = 0;
}
public Path(Path p) {
this();
parent = p;
g = p.g;
f = p.f;
}
/**
* Compare to another object using the total cost f.
*/
@SuppressWarnings("unchecked")
public int compareTo(Object o) {
Path p = (Path) o;
return f - p.f;
}
/**
* Get the last point on the path.
*/
public T getPoint() {
return point;
}
/**
* Set the point
*
* @param p The node
*/
public void setPoint(T p) {
point = p;
}
public void setParent(Path parent) {
this.parent = parent;
}
}
/**
* Check if the current node is a goal for the problem.
*/
protected abstract boolean isGoal(T node);
/**
* Cost for the operation to go to <code>to</code> from
* <code>from</from>.
*/
protected abstract int g(T from, T to);
/**
* Estimated cost to reach a goal node.
* An admissible heuristic never gives a cost bigger than the real
* one.
* <code>from</from>.
*/
protected abstract int h(T from, T to);
/**
* Generate the successors for a given node.
*/
protected abstract Array<T> generateSuccessors(T node);
protected PriorityQueue<Path> paths;
private ObjectIntMap<T> minDistances;
protected int lastCost;
private int expandedCounter;
public int getExpandedCounter() {
return expandedCounter;
}
public AStar() {
paths = new PriorityQueue<Path>();
minDistances = new ObjectIntMap<T>();
expandedCounter = 0;
lastCost = 0;
}
protected int f(Path p, T from, T to) {
int g = g(from, to) + ((p.parent != null) ? p.parent.g : 0);
int h = h(from, to);
p.g = g;
p.f = g + h;
return p.f;
}
private void expand(Path path) {
T p = path.getPoint();
int min = minDistances.get(path.getPoint(), Integer.MAX_VALUE);
/*
* If a better path passing for this point already exists then
* don't expand it.
*/
if (min == Integer.MAX_VALUE || min > path.f) {
minDistances.put(path.getPoint(), path.f);
} else {
return;
}
Array<T> successors = generateSuccessors(p);
for (T t : successors) {
Path newPath = new Path(path);
newPath.setPoint(t);
f(newPath, path.getPoint(), t);
paths.offer(newPath);
}
expandedCounter++;
}
public int getCost() {
return lastCost;
}
public void start() {
try {
working = true;
paths.clear();
minDistances.clear();
expandedCounter = 0;
lastCost = 0;
Path root = new Path();
root.setPoint(start);
/* Needed if the initial point has a cost. */
f(root, start, start);
expand(root);
step();
} catch (Exception e) {
e.printStackTrace();
}
}
public void step() {
if (!working) {
return;
}
Path p = paths.poll();
if (p == null) {
lastCost = Integer.MAX_VALUE;
working = false;
return;
}
T last = p.getPoint();
lastCost = p.g;
if (isGoal(last)) {
discoveredPath = new Array<T>(true, 20);
for (Path i = p; i != null; i = i.parent) {
T point = i.getPoint();
discoveredPath.insert(0, point);
}
working = false;
return;
}
expand(p);
}
public void setCompleteCallback(Runnable completeCallback) {
this.completeCallback = completeCallback;
}
public boolean wasSuccessful() {
return lastCost != Integer.MAX_VALUE;
}
public Array<T> getDiscoveredPath() {
return discoveredPath;
}
}