package com.jenjinstudios.world.server.ai;
import com.jenjinstudios.world.Location;
import com.jenjinstudios.world.Zone;
import com.jenjinstudios.world.util.ZoneUtils;
import java.util.LinkedList;
import java.util.Stack;
/**
* This class contains helper methods used to determine a list of Locations to follow to get from point to point around
* obstacles.
* @author Caleb Brinkman
*/
public class Pathfinder
{
/** The maximum number of nodes to check before giving up and assuming the path cannot be found. */
private static final int NODE_LIMIT = 1000;
private final Zone zone;
private final Location start;
private final Location end;
public Pathfinder(Zone zone, Location start, Location end) {
this.zone = zone;
this.start = start;
this.end = end;
}
/**
* Find a path between the two locations.
* @return The Locations necessary to traverse in order to travel from A to B.
*/
public LinkedList<Location> findPath() {
LinkedList<PathNode> openList = new LinkedList<>();
LinkedList<PathNode> closedList = new LinkedList<>();
PathNode selectedPathNode = new PathNode(start, end);
openList.add(selectedPathNode);
while (selectedPathNode.location != end && !openList.isEmpty() && openList.size() < NODE_LIMIT)
{
selectedPathNode = getLowestFNode(openList);
moveNodeFromOpenToClosed(openList, closedList, selectedPathNode);
addAdjacentToCorrectList(openList, closedList, selectedPathNode);
}
LinkedList<Location> path = new LinkedList<>();
if (selectedPathNode.location == end)
{
path = getReverseNodeTrace(selectedPathNode);
}
return path;
}
private void moveNodeFromOpenToClosed(LinkedList<PathNode> open, LinkedList<PathNode> closed, PathNode node) {
open.remove(node);
closed.add(node);
}
private PathNode getLowestFNode(LinkedList<PathNode> openList) {
PathNode selectedPathNode;
int lowestF = Integer.MAX_VALUE;
selectedPathNode = openList.peek();
for (PathNode pathNode : openList)
{
if (pathNode.F < lowestF)
{
lowestF = pathNode.F;
selectedPathNode = pathNode;
}
}
return selectedPathNode;
}
private void addAdjacentToCorrectList(LinkedList<PathNode> open, LinkedList<PathNode> closed, PathNode selected) {
for (Location adjacentLocation : ZoneUtils.getAdjacentWalkableLocations(zone, selected.location))
{
PathNode adjacentPathNode = new PathNode(selected, adjacentLocation, end);
if (closed.contains(adjacentPathNode))
{
continue;
}
if (open.contains(adjacentPathNode))
{
int indexOfOldNode = open.indexOf(adjacentPathNode);
PathNode oldPathNode = open.get(indexOfOldNode);
if (adjacentPathNode.G < oldPathNode.G)
{
oldPathNode.parent = selected;
}
} else
{
open.add(adjacentPathNode);
}
}
}
private LinkedList<Location> getReverseNodeTrace(PathNode selectedPathNode) {
Stack<Location> reversePath = getNodeStack(selectedPathNode);
return reverseNodeStack(reversePath);
}
private LinkedList<Location> reverseNodeStack(Stack<Location> reversePath) {
LinkedList<Location> path;
path = new LinkedList<>();
while (!reversePath.isEmpty())
{
path.add(reversePath.pop());
}
return path;
}
private Stack<Location> getNodeStack(PathNode selectedPathNode) {
Stack<Location> reversePath = new Stack<>();
while (selectedPathNode.location != start)
{
reversePath.push(selectedPathNode.location);
selectedPathNode = selectedPathNode.parent;
}
reversePath.push(selectedPathNode.location);
return reversePath;
}
}