package vooga.rts.ai; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import vooga.rts.map.GameMap; import vooga.rts.map.Node; import vooga.rts.map.NodeMap; /** * An implementation of the pathfinder interface that uses the A-star algorithm * to create a path for units based on the Nodes in our map. * * @author Challen Herzberg-Brovold, Jonno Schmidt * */ public class AstarFinder implements PathFinder { @Override public Path calculatePath (Node start, Node finish, NodeMap map) { if (start == null || finish == null) { return null; } Path result = null; List<Node> close = new ArrayList<Node>(); List<Node> open = new ArrayList<Node>(); open.add(start); Map<Node, Node> comesFrom = new HashMap<Node, Node>(); Map<Node, Double> gScore = new HashMap<Node, Double>(); Map<Node, Double> fScore = new HashMap<Node, Double>(); gScore.put(start, 0.0); fScore.put(start, calculateHeuristic(start, finish)); double fMax = 0; double gMax = 0; while (open.size() > 0) { Node current = getLowest(fScore, open); if (current.equals(finish)) { result = new Path(constructPath(comesFrom, finish)); break; } open.remove(current); close.add(current); for (Node neighbor : map.getNeighbors(current)) { if (neighbor == null || !neighbor.connectsTo(current)) { continue; } double newGscore = gScore.get(current) + Node.NODE_SIZE; if (close.contains(neighbor) && newGscore >= gScore.get(neighbor)) { continue; } if (!open.contains(neighbor) || newGscore < gScore.get(neighbor)) { comesFrom.put(neighbor, current); gScore.put(neighbor, newGscore); fScore.put(neighbor, gScore.get(neighbor) + calculateHeuristic(neighbor, finish)); if (!open.contains(neighbor)) { if (neighbor.getTier() < 1) { open.add(neighbor); } } } } } return result; // At some point, there will be a catch so it just returns to the closest // node to the desired one. } /** * Calculates the heuristic that is used by the A* finder. * Currently, the heuristic we use is the Manhattan difference, which is * the sum of the change in x and the change in y * * @param start the starting node from which to calculate the heuristic * @param finish the ending node from which to calculate the heuristic * @return the heuristic value. */ private double calculateHeuristic (Node start, Node finish) { int dx = Math.abs(finish.getX() - start.getX()); int dy = Math.abs(finish.getY() - start.getY()); return dx + dy; } /** * Constructs the path the finish node to the beginning node, using the * comesFrom map. * * @param comesFrom maps each node to the node connected to it which has the lowest heuristic * value * @param finish the destination node, i.e. where the unit wants to go. * @return a queue of nodes from finish to the starting point */ private Queue<Node> constructPath (Map<Node, Node> comesFrom, Node finish) { Queue<Node> path = new LinkedList<Node>(); if (comesFrom.containsKey(finish)) { path.addAll(constructPath(comesFrom, comesFrom.get(finish))); path.add(finish); } else { path.add(finish); } return path; } /** * Returns the node with the lowest f-value (node's heuristic value plus its * G-score). * * @param fScore map of nodes and their f-scores. * @param from list of open nodes still to be explored * @return the node with the lowest value. */ private Node getLowest (Map<Node, Double> fScore, List<Node> from) { Node minNode = from.get(0); double minF = fScore.get(minNode); for (int i = 1; i < from.size(); i++) { Node current = from.get(i); double f = fScore.get(current); if (f < minF) { minNode = current; minF = f; } } return minNode; } }