General cleanups to path calculation

This commit is contained in:
Brady 2018-08-02 20:48:21 -07:00
parent 5537954180
commit 77f6e1c6c4
No known key found for this signature in database
GPG Key ID: 73A788379A197567
3 changed files with 49 additions and 34 deletions

View File

@ -12,6 +12,7 @@ import java.util.List;
* @author leijurv * @author leijurv
*/ */
public interface IPath { public interface IPath {
/** /**
* Ordered list of movements to carry out. * Ordered list of movements to carry out.
* movements.get(i).getSrc() should equal positions.get(i) * movements.get(i).getSrc() should equal positions.get(i)
@ -44,11 +45,13 @@ public interface IPath {
} }
/** /**
* @param currentPosition * Determines whether or not a position is within this path.
* @return *
* @param pos The position to check
* @return Whether or not the specified position is in this class
*/ */
default boolean isInPath(BlockPos currentPosition) { default boolean isInPath(BlockPos pos) {
return positions().contains(currentPosition); return positions().contains(pos);
} }
default Tuple<Double, BlockPos> closestPathPos(double x, double y, double z) { default Tuple<Double, BlockPos> closestPathPos(double x, double y, double z) {

View File

@ -14,65 +14,73 @@ import java.util.stream.Collectors;
* @author leijurv * @author leijurv
*/ */
class Path implements IPath { class Path implements IPath {
public final BlockPos start; public final BlockPos start;
public final BlockPos end; public final BlockPos end;
public final Goal goal; public final Goal goal;
/** /**
* The blocks on the path. Guaranteed that path.get(0) equals start and * The blocks on the path. Guaranteed that path.get(0) equals start and
* path.get(path.size()-1) equals end * path.get(path.size()-1) equals end
*/ */
public final ArrayList<BlockPos> path; public final List<BlockPos> path;
final ArrayList<Movement> movements;
final List<Movement> movements;
Path(PathNode start, PathNode end, Goal goal) { Path(PathNode start, PathNode end, Goal goal) {
this.start = start.pos; this.start = start.pos;
this.end = end.pos; this.end = end.pos;
this.goal = goal; this.goal = goal;
this.path = new ArrayList<>(); this.path = new LinkedList<>();
this.movements = new ArrayList<>(); this.movements = new LinkedList<>();
assemblePath(start, end); assemblePath(start, end);
sanityCheck(); sanityCheck();
} }
private final void assemblePath(PathNode start, PathNode end) { private void assemblePath(PathNode start, PathNode end) {
if (!path.isEmpty() || !movements.isEmpty()) { if (!path.isEmpty() || !movements.isEmpty()) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
PathNode current = end; PathNode current = end;
LinkedList<BlockPos> tempPath = new LinkedList<>();//repeatedly inserting to the beginning of an arraylist is O(n^2) LinkedList<BlockPos> tempPath = new LinkedList<>(); // Repeatedly inserting to the beginning of an arraylist is O(n^2)
LinkedList<Movement> tempMovements = new LinkedList<>();//instead, do it into a linked list, then convert at the end LinkedList<Movement> tempMovements = new LinkedList<>(); // Instead, do it into a linked list, then convert at the end
while (!current.equals(start)) { while (!current.equals(start)) {
tempPath.addFirst(current.pos); tempPath.addFirst(current.pos);
tempMovements.addFirst(current.previousMovement); tempMovements.addFirst(current.previousMovement);
current = current.previous; current = current.previous;
} }
tempPath.addFirst(start.pos); tempPath.addFirst(start.pos);
//can't directly convert from the PathNode pseudo linked list to an array because we don't know how long it is // Can't directly convert from the PathNode pseudo linked list to an array because we don't know how long it is
//inserting into a LinkedList<E> keeps track of length, then when we addall (which calls .toArray) it's able // inserting into a LinkedList<E> keeps track of length, then when we addall (which calls .toArray) it's able
//to performantly do that conversion since it knows the length. // to performantly do that conversion since it knows the length.
path.addAll(tempPath); path.addAll(tempPath);
movements.addAll(tempMovements); movements.addAll(tempMovements);
} }
public void sanityCheck() { /**
* Performs a series of checks to ensure that the assembly of the path went as expected.
*/
private void sanityCheck() {
if (!start.equals(path.get(0))) { if (!start.equals(path.get(0))) {
throw new IllegalStateException(); throw new IllegalStateException("Start node does not equal first path element");
} }
if (!end.equals(path.get(path.size() - 1))) { if (!end.equals(path.get(path.size() - 1))) {
throw new IllegalStateException(); throw new IllegalStateException("End node does not equal last path element");
} }
if (path.size() != movements.size() + 1) { if (path.size() != movements.size() + 1) {
throw new IllegalStateException(); throw new IllegalStateException("Size of path array is unexpected");
} }
for (int i = 0; i < path.size(); i++) { for (int i = 0; i < path.size(); i++) {
BlockPos src = path.get(i); BlockPos src = path.get(i);
BlockPos dest = path.get(i + 1); BlockPos dest = path.get(i + 1);
Movement movement = movements.get(i); Movement movement = movements.get(i);
if (!src.equals(movement.getSrc())) { if (!src.equals(movement.getSrc())) {
throw new IllegalStateException(); throw new IllegalStateException("Path source is not equal to the movement source");
} }
if (!dest.equals(movement.getDest())) { if (!dest.equals(movement.getDest())) {
throw new IllegalStateException(); throw new IllegalStateException("Path destination is not equal to the movement destination");
} }
} }
} }

View File

@ -12,19 +12,25 @@ import java.util.Objects;
* @author leijurv * @author leijurv
*/ */
class PathNode { class PathNode {
final BlockPos pos; final BlockPos pos;
final Goal goal; final Goal goal;
final double estimatedCostToGoal; final double estimatedCostToGoal;
// These three fields are mutable and are changed by PathFinder // These three fields are mutable and are changed by PathFinder
double cost; double cost;
PathNode previous; PathNode previous;
Movement previousMovement; Movement previousMovement;
/** /**
* Is this a member of the open set in A*? (only used during pathfinding) * Is this a member of the open set in A*? (only used during pathfinding)
*/ */
boolean isOpen; boolean isOpen;
/** /**
* In the linked list of open nodes, which one is next? (only used during pathfinding) * In the linked list of open nodes, which one is next? (only used during pathfinding)
*/ */
@ -40,30 +46,28 @@ class PathNode {
this.isOpen = false; this.isOpen = false;
} }
/**
// TODO possibly reimplement hashCode and equals. They are necessary for this class to function but they could be done better * TODO: Possibly reimplement hashCode and equals. They are necessary for this class to function but they could be done better
*
* @return The hash code value for this {@link PathNode}
*/
@Override @Override
public int hashCode() {//this is some OG code right here public int hashCode() {
int hash = 3241; int hash = 3241;
hash = 3457689 * hash + this.pos.getX(); hash = 3457689 * hash + this.pos.getX();
hash = 8734625 * hash + this.pos.getY(); hash = 8734625 * hash + this.pos.getY();
hash = 2873465 * hash + this.pos.getZ(); hash = 2873465 * hash + this.pos.getZ();
hash = 3241543 * hash + Objects.hashCode(this.goal);//don't call goal.hashcode. this calls objects hashcode to verify that the actual goal objects are == identical, which is important for node caching // Don't call goal.hashCode(). this calls objects hashcode to verify that the actual goal objects are == identical, which is important for node caching
hash = 3241543 * hash + Objects.hashCode(this.goal);
return hash; return hash;
} }
@Override @Override
public boolean equals(Object obj) {//autogenerated by netbeans. that's why it looks disgusting. public boolean equals(Object obj) {
if (obj == null) { if (obj == null || !(obj instanceof PathNode))
return false; return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PathNode other = (PathNode) obj; final PathNode other = (PathNode) obj;
if (!Objects.equals(this.pos, other.pos)) { return Objects.equals(this.pos, other.pos) && Objects.equals(this.goal, other.goal);
return false;
}
return Objects.equals(this.goal, other.goal);
} }
} }