Implement A* path finding algorithm

This commit is contained in:
2021-03-08 12:21:12 +01:00
parent e1252f1638
commit 8b09f16827
4 changed files with 76 additions and 53 deletions

View File

@@ -0,0 +1,34 @@
package com.bartlomiejpluta.base.api.game.ai;
import com.bartlomiejpluta.base.api.game.entity.Direction;
import com.bartlomiejpluta.base.api.game.entity.Entity;
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.util.pathfinding.AstarPathFinder;
import com.bartlomiejpluta.base.api.util.pathfinding.PathFinder;
import org.joml.Vector2i;
public class FollowEntityAI implements AI {
private final NPC npc;
private final Entity target;
private final PathFinder pathFinder = new AstarPathFinder();
public FollowEntityAI(NPC npc, Entity target) {
this.npc = npc;
this.target = target;
}
@Override
public void nextActivity(ObjectLayer layer, float dt) {
if (!npc.isMoving() && npc.manhattanDistance(target) > 1) {
var path = pathFinder.findPath(layer, npc.getCoordinates(), target.getCoordinates(), 30);
if (!path.isEmpty()) {
var node = new Vector2i(path.getLast()).sub(npc.getCoordinates());
var direction = Direction.ofVector(node);
var movement = npc.prepareMovement(direction);
layer.pushMovement(movement);
}
}
}
}

View File

@@ -1,10 +0,0 @@
package com.bartlomiejpluta.base.api.game.ai.pathfinding;
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
import org.joml.Vector2i;
import java.util.List;
public interface PathFinder {
List<Vector2i> findPath(ObjectLayer layer, Vector2i start, Vector2i end, int range);
}

View File

@@ -1,16 +1,17 @@
package com.bartlomiejpluta.base.api.game.ai.pathfinding; package com.bartlomiejpluta.base.api.util.pathfinding;
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer; import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.game.map.layer.object.PassageAbility; import com.bartlomiejpluta.base.api.game.map.layer.object.PassageAbility;
import org.joml.Vector2i; import org.joml.Vector2i;
import java.util.*; import java.util.LinkedList;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.function.Function; import java.util.function.Function;
import static java.lang.Math.abs; import static java.lang.Math.abs;
@SuppressWarnings({"RedundantCast", "rawtypes"}) public class AstarPathFinder implements PathFinder {
public class Astar implements PathFinder {
/* /*
* We are interested in following adjacent * We are interested in following adjacent
@@ -30,23 +31,23 @@ public class Astar implements PathFinder {
}; };
@Override @Override
public List<Vector2i> findPath(ObjectLayer layer, Vector2i start, Vector2i end, int range) { public LinkedList<Vector2i> findPath(ObjectLayer layer, Vector2i start, Vector2i end, int range) {
int columns = layer.getMap().getColumns(); var columns = layer.getMap().getColumns();
int rows = layer.getMap().getRows(); var rows = layer.getMap().getRows();
Node startNode = new Node(start); var startNode = new Node(start);
Node endNode = new Node(end); var endNode = new Node(end);
// The heuristic function defined as Manhattan distance to the end node // The heuristic function defined as Manhattan distance to the end node
Function h = createManhattanDistanceHeuristic(endNode); Function<Node, Float> h = node -> manhattanDistance(node.position, end);
// The start node has the actual cost 0 and estimated is a Manhattan distance to the end node // The start node has the actual cost 0 and estimated is a Manhattan distance to the end node
startNode.g = 0.0f; startNode.g = 0.0f;
startNode.f = (Float) h.apply(startNode); startNode.f = h.apply(startNode);
// We are starting with one open node (the start one) end empty closed lists // We are starting with one open node (the start one) end empty closed lists
Queue open = new PriorityQueue(); var open = new PriorityQueue<Node>();
List closed = new LinkedList(); var closed = new LinkedList<Node>();
open.add(startNode); open.add(startNode);
// As long as there are at least one open node // As long as there are at least one open node
@@ -56,7 +57,7 @@ public class Astar implements PathFinder {
// (That's the way the Astar.compare() comparator works) // (That's the way the Astar.compare() comparator works)
// And the same time we are removing the node from open list // And the same time we are removing the node from open list
// and pushing it to closed one as we no longer need to analyze this node // and pushing it to closed one as we no longer need to analyze this node
Node current = (Node) open.poll(); var current = open.poll();
closed.add(current); closed.add(current);
// If we found the node with f score and it is // If we found the node with f score and it is
@@ -70,7 +71,7 @@ public class Astar implements PathFinder {
// (we are analyzing the 4 neighbours, // (we are analyzing the 4 neighbours,
// as described in the commend above ADJACENT static field) // as described in the commend above ADJACENT static field)
for (Vector2i adjacent : ADJACENT) { for (Vector2i adjacent : ADJACENT) {
Vector2i position = new Vector2i(current.position).add(adjacent); var position = new Vector2i(current.position).add(adjacent);
// We are getting rid the neighbours beyond the map // We are getting rid the neighbours beyond the map
if (position.x < 0 || position.x >= columns || position.y < 0 || position.y >= rows) { if (position.x < 0 || position.x >= columns || position.y < 0 || position.y >= rows) {
@@ -85,18 +86,18 @@ public class Astar implements PathFinder {
} }
// Define new neighbour // Define new neighbour
Node neighbour = new Node(position); var neighbour = new Node(position);
// If we already analyzed this node, // If we already analyzed this node,
// we are free to skip it to not analyze it once again // we are free to skip it to not analyze it once again
for (Object closedNode : closed) { for (var closedNode : closed) {
if (((Node) closedNode).position.equals(position)) { if (closedNode.position.equals(position)) {
continue adjacent; continue adjacent;
} }
} }
// Get rid of nodes that are not reachable (blocked or something is staying on there) // Get rid of nodes that are not reachable (blocked or something is staying on there)
boolean reachable = layer.getPassageMap()[position.y][position.x] == PassageAbility.ALLOW; var reachable = layer.getPassageMap()[position.y][position.x] == PassageAbility.ALLOW;
if (!reachable) { if (!reachable) {
continue; continue;
} }
@@ -106,7 +107,7 @@ public class Astar implements PathFinder {
// path further // path further
neighbour.parent = current; neighbour.parent = current;
neighbour.g = current.g + 1; neighbour.g = current.g + 1;
neighbour.f = neighbour.g + (Float) h.apply(neighbour); neighbour.f = neighbour.g + h.apply(neighbour);
// If the node already exists in open list, // If the node already exists in open list,
// we need to compare current neighbour with existing node // we need to compare current neighbour with existing node
@@ -114,7 +115,7 @@ public class Astar implements PathFinder {
// If the neighbour is shorter, we can update the existing node // If the neighbour is shorter, we can update the existing node
// with neighbour's parameters // with neighbour's parameters
for (Object openNode : open) { for (Object openNode : open) {
Node node = (Node) openNode; var node = (Node) openNode;
if (node.position.equals(position) && neighbour.g < node.g) { if (node.position.equals(position) && neighbour.g < node.g) {
node.g = neighbour.g; node.g = neighbour.g;
node.parent = current; node.parent = current;
@@ -132,28 +133,16 @@ public class Astar implements PathFinder {
return new LinkedList<>(); return new LinkedList<>();
} }
@SuppressWarnings("Convert2Lambda")
private Function createManhattanDistanceHeuristic(final Node toNode) {
return new Function() {
@Override
public Object apply(Object node) {
return manhattanDistance(toNode.position, ((Node) node).position);
}
};
}
private float manhattanDistance(Vector2i a, Vector2i b) { private float manhattanDistance(Vector2i a, Vector2i b) {
return (abs(a.x - b.x) + abs(a.y - b.y)); return (abs(a.x - b.x) + abs(a.y - b.y));
} }
private List<Vector2i> recreatePath(Node node) { private LinkedList<Vector2i> recreatePath(Node node) {
Node current = node; var current = node;
List<Vector2i> list = new LinkedList<>(); var list = new LinkedList<Vector2i>();
list.add(((Node) node).position);
while (current.parent != null) { while (current.parent != null) {
list.add(((Node) current).parent.position); list.add(current.position);
current = current.parent; current = current.parent;
} }
@@ -167,8 +156,8 @@ public class Astar implements PathFinder {
tiles: tiles:
for (int column = 0; column < layer.getMap().getColumns(); ++column) { for (int column = 0; column < layer.getMap().getColumns(); ++column) {
for (Object node : nodes) { for (Vector2i node : nodes) {
if (((Vector2i) node).equals(column, row)) { if (node.equals(column, row)) {
System.out.print("#"); System.out.print("#");
continue tiles; continue tiles;
} }
@@ -181,7 +170,7 @@ public class Astar implements PathFinder {
} }
} }
private static class Node implements Comparable { private static class Node implements Comparable<Node> {
public Node parent; public Node parent;
public final Vector2i position; public final Vector2i position;
public float g = 0.0f; public float g = 0.0f;
@@ -195,7 +184,7 @@ public class Astar implements PathFinder {
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o; var node = (Node) o;
return position.equals(node.position); return position.equals(node.position);
} }
@@ -205,8 +194,8 @@ public class Astar implements PathFinder {
} }
@Override @Override
public int compareTo(Object o) { public int compareTo(Node o) {
return Float.compare(f, ((Node) o).f); return Float.compare(f, o.f);
} }
} }
} }

View File

@@ -0,0 +1,10 @@
package com.bartlomiejpluta.base.api.util.pathfinding;
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
import org.joml.Vector2i;
import java.util.LinkedList;
public interface PathFinder {
LinkedList<Vector2i> findPath(ObjectLayer layer, Vector2i start, Vector2i end, int range);
}