package edu.hawaii.ics.yucheng;
import java.util.ArrayList;
import java.util.PriorityQueue;
/**
* A class that finds a path from a location to the base of a graph.
*/
public class PathFinder {
/**
* A node that keeps information for the A* algorithm matrix.
*/
private class Node implements Comparable<Node> {
/** The cost getting to this node. */
public final int cost;
/** The distance from this node to the base. */
public final int distance;
/** The node along the path. */
public final Node parent;
/** The sum of the cost and the distance. */
public final int sum;
/** The vertex representing this node location. */
public final Vertex vertex;
/**
* Initializes a new instance of the Node class.
*
* @param cost The cost getting to this node.
*
* @param vertex The coordinate of the node.
*
* @param graph The graph.
*
* @param parent The node visited before this one.
*/
public Node(final int cost, final Vertex vertex, final Graph graph,
final Node parent) {
assert null != vertex;
assert null != graph;
this.cost = cost;
this.vertex = vertex;
this.parent = parent;
distance = Math.abs(graph.base.x - vertex.x)
+ Math.abs(graph.base.y - vertex.y);
sum = this.cost + distance;
}
/**
* Compares this node and another one. The sums are compared.
*
* @param n The other node.
*
* @return The comparison value for the priority queue.
*/
public int compareTo(final Node n) {
assert null != n;
return sum - n.sum;
}
}
/**
* Verifies a pointer is not null.
*
* @param pointer The pointer object.
*
* @param name The name of the parameter.
*/
private static void verifyPointer(final Object pointer, final String name) {
if (pointer == null)
throw new NullPointerException(name);
}
/**
* Verifies a pointer is not null.
*
* @param value The value.
*
* @param max The maximum value (exclusive).
*
* @param name The name of the parameter.
*/
private static void verifyRange(final int value, final int max,
final String name) {
if (value < 0 || value >= max)
throw new IllegalArgumentException(name);
}
/** The cost of the path. */
private int myCost;
/** The graph. */
private final Graph myGraph;
/** The nodes used to compute the path. */
private final Node[] myNodes;
/** The path. */
private Vertex[] myPath;
/** The priority queue used to compute the path. */
private final PriorityQueue<Node> myQueue;
/**
* Initializes a new instance of the PathFinder class.
*
* @param g The graph.
*
* @param x The x location away from the base.
*
* @param y The y location away from the base.
*/
public PathFinder(final Graph g, final int x, final int y) {
verifyPointer(g, "g");
verifyRange(x, g.width, "x");
verifyRange(y, g.height, "y");
myGraph = g;
myNodes = new Node[myGraph.width * myGraph.height];
myQueue = new PriorityQueue<Node>();
myCost = Integer.MAX_VALUE;
search(x, y);
}
/**
* Returns the total cost of the path.
*
* @return The total cost of the path.
*/
public int cost() {
return myCost;
}
/**
* Returns the path computed by the class or null if there is no path.
*
* @return The path.
*/
public Vertex[] getPath() {
if (myPath == null)
return null;
final Vertex[] result = new Vertex[myPath.length];
for (int i = 0; i < myPath.length; i++)
result[i] = myPath[i];
return result;
}
/**
* Returns the A* matrix index.
*
* @param x The x coordinate.
*
* @param y The y coordinate.
*
* @return The array index.
*/
private int indexAt(final int x, final int y) {
return myGraph.width * y + x;
}
/**
* Returns true if a location is blocked in the graph.
*
* @param x The x coordinate.
*
* @param y The y coordinate.
*
* @return True indicates the path is blocked.
*/
private boolean isBlockedLocation(final int x, final int y) {
// Check if this is not on the graph.
if (x < 0 || x >= myGraph.width || y < 0 || y >= myGraph.height)
return true;
// Check if this is an obstacle.
return myGraph.isObstacle(x, y);
}
/**
* Returns true if a path was found.
*
* @return True indicates a path was found.
*/
public boolean isFound() {
return myPath != null;
}
/**
* Saves the result. The best path is stored to the class data.
*
* @param node The last node visited in the iterative loop from search.
*/
private void saveResult(final Node node) {
// Store the queue.
final ArrayList<Vertex> path = new ArrayList<Vertex>();
for (Node n = node; n != null; n = n.parent)
path.add(0, n.vertex);
myPath = new Vertex[path.size()];
path.toArray(myPath);
myCost = node.cost;
// Empty out the priority queue so that no more attempts are made to find
// solutions.
myQueue.clear();
}
/**
* Searches for a path based on the next item from the priority queue.
*/
private void search() {
// Get the most desirable starting point.
final Node node = myQueue.poll();
final int nx = node.vertex.x;
final int ny = node.vertex.y;
// Check if this is a blocked path.
if (isBlockedLocation(nx, ny))
return;
// Check if this is the base. Save the result if it is.
if (nx == myGraph.base.x && ny == myGraph.base.y) {
saveResult(node);
return;
}
// Check if this is a previously visited node.
if (myNodes[indexAt(nx, ny)] != null)
return;
// Save this node. It is now visited.
myNodes[indexAt(nx, ny)] = node;
// Compute a new cost from this location.
final int newCost = node.cost + 1;
// Add nodes in each possible direction.
myQueue.add(new Node(newCost, new Vertex(nx + 1, ny), myGraph, node));
myQueue.add(new Node(newCost, new Vertex(nx - 1, ny), myGraph, node));
myQueue.add(new Node(newCost, new Vertex(nx, ny + 1), myGraph, node));
myQueue.add(new Node(newCost, new Vertex(nx, ny - 1), myGraph, node));
}
/**
* Searches for a path.
*
* @param x The initial x location.
*
* @param y The initial y location.
*/
private void search(final int x, final int y) {
// Create a new node representing this initial location.
final Node initialNode = new Node(0, new Vertex(x, y), myGraph, null);
// Put this node into the priority queue as an initial starting point.
myQueue.add(initialNode);
// Loop until the queue is empty.
while (myQueue.size() > 0)
search();
}
/**
* Returns the string representation of the class data.
*
* @return The string representation of the class data.
*/
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Cost: " + cost() + "; Path: ");
if (myPath == null)
builder.append("null");
else
for (int i = 0; i < myPath.length; i++) {
builder.append(myPath[i]);
if (i + 1 < myPath.length)
builder.append(" --> ");
}
return builder.toString();
}
}