package com.ir.search;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.PriorityQueue;
import com.ir.objects.Node;
import com.ir.objects.Path;
import com.ir.objects.Piece;
import com.ir.objects.Square;
/**
* The A* Search Algorithm - finds the shortest path between two Nodes using heuristics
* @author Mark Feaver
*
*/
public class AStarSearch {
// The shortest path, initially empty
private Path path = new Path();
// The g-score associated with each square
private HashMap<Square, Integer> gScore = new HashMap<Square, Integer>();
// The f-score associated with each square
private HashMap<Square, Integer> fScore = new HashMap<Square, Integer>();
// The set of tentative nodes to be evaluated
private PriorityQueue<Node> openSet = new PriorityQueue<Node>();
// The set of Nodes already evaluated
private List<Square> closedSet = new ArrayList<Square>();
// The map of navigated nodes.
private HashMap<Square, Square> cameFrom = new HashMap<Square, Square>();
private Piece chessPiece = null;
private Square start = null;
private Square end = null;
/**
* Constructor. Takes a start position, end position, and the chess piece
* @param start - the starting square
* @param end - the end square
* @param chessPiece - a chess piece on a board
*/
public AStarSearch(Square start, Square end, Piece chessPiece){
this.start = start;
this.end = end;
this.chessPiece = chessPiece;
}
/**
* The core functionality of the A* Search. Uses the start and end squares to
* search for the shortest path between the two
* @return the shortest Path
*/
public Path Search(){
gScore.put(start, 0);
// Estimated total cost from start to goal through y.
fScore.put(start, (gScore.get(start) + chessPiece.getHeuristicMoves(start, end)));
// The open set initially contains the start node
Node startNode = new Node(start, fScore.get(start));
openSet.add(startNode);
// While we still have nodes to explore
while (!openSet.isEmpty()){
// Get the node with the lowest f-score from the priority queue
Node current = openSet.poll();
Square currentSquare = current.getSquare();
// If we've found the goal, return the path we took to get there
if (currentSquare.equals(end))
return reconstructPath(currentSquare);
// Add current to closedset, and move the chess piece to that square
closedSet.add(currentSquare);
chessPiece.move(currentSquare);
// For each neighbour from the current chess piece position
for (Square neighbour : chessPiece.getValidMoves()){
if (closedSet.contains(neighbour))
continue;
// A tentative g score, used to determine whether we explore a node or not
int tentativeGScore = gScore.get(currentSquare) + 1;
// Here we clone the openSet and modify the clone, as we are
// unable to iterate through a PriorityQueue.
PriorityQueue<Node> clone = new PriorityQueue<Node>(openSet);
boolean isInSet = false;
while (!clone.isEmpty()){
Node n = clone.poll();
if (n.getSquare().equals(neighbour)){
isInSet = true;
break;
}
}
// If neighbor not in openset or tentative_g_score < g_score[neighbor]
if (!isInSet || tentativeGScore < gScore.get(neighbour)){
// Calculate a new f-score for the square, and update its "path" (cameFrom)
cameFrom.put(neighbour, currentSquare);
gScore.put(neighbour, tentativeGScore);
fScore.put(neighbour, tentativeGScore + chessPiece.getHeuristicMoves(neighbour, end));
// Add the neighbour to the open set
Node n = new Node(neighbour, fScore.get(neighbour));
openSet.add(n);
}
}
}
// If no path was found, return failure
return null;
}
/**
* Reconstruct the path from the current square to the starting square
* @param currentSquare - the square the chess piece is currently on
* @return the path to the current square
*/
public Path reconstructPath(Square currentSquare){
path.addSquare(currentSquare);
Square c = null;
while ((c = cameFrom.get(currentSquare)) != null){
path.addSquare(c);
currentSquare = c;
}
return path;
}
}