TD-5: A* Implementation
This commit is contained in:
parent
423fb9ec40
commit
ac434b8463
6 changed files with 155 additions and 15 deletions
|
@ -1,5 +1,7 @@
|
|||
package de.towerdefence.server.game;
|
||||
|
||||
import org.joml.Vector2i;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
|
@ -7,6 +9,5 @@ import lombok.Getter;
|
|||
@AllArgsConstructor
|
||||
public class Enemy {
|
||||
protected int id;
|
||||
protected int x;
|
||||
protected int y;
|
||||
protected Vector2i pos;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ import de.towerdefence.server.game.callbacks.PlayerHitpointsCallback;
|
|||
import de.towerdefence.server.game.exeptions.InvalidPlacementException;
|
||||
import de.towerdefence.server.game.exeptions.InvalidPlacementReason;
|
||||
import de.towerdefence.server.game.exeptions.WaveInProgressException;
|
||||
import de.towerdefence.server.game.pathfinding.AStar;
|
||||
import de.towerdefence.server.game.pathfinding.NoPathException;
|
||||
import de.towerdefence.server.game.pathfinding.OutOfBoundsException;
|
||||
import de.towerdefence.server.player.Player;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2i;
|
||||
|
@ -82,20 +85,37 @@ public class GameSession {
|
|||
}
|
||||
|
||||
private List<Enemy> moveEnemies() {
|
||||
// List<Enemy> changedEnemies = moveEnemies();
|
||||
List<Enemy> changedEnemies = moveEnemies();
|
||||
|
||||
// TODO: Implement Moving of Enemies (possibly through A*)
|
||||
throw new RuntimeException("NOT IMPLEMENTED");
|
||||
|
||||
// return changedEnemies;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next position to go to
|
||||
*/
|
||||
private Vector2i pathfinding(Vector2i start, Vector2i target) {
|
||||
for (int x = 0; x < enemies.length; x++) {
|
||||
Enemy[] column = enemies[x];
|
||||
for (int y = 0; y < column.length; y++) {
|
||||
Enemy enemy = column[y];
|
||||
if (changedEnemies.contains(enemy)) {
|
||||
continue;
|
||||
}
|
||||
AStar a = new AStar((Vector2i pos) -> {
|
||||
return pos.x >= 0
|
||||
&& pos.x < MAP_SIZE.x
|
||||
&& pos.y >= 0
|
||||
&& pos.y < MAP_SIZE.y
|
||||
&& (towers[pos.x] == null || towers[pos.x][pos.y] == null)
|
||||
&& (enemies[pos.x] == null || enemies[pos.x][pos.y] != null);
|
||||
});
|
||||
Vector2i next;
|
||||
try {
|
||||
next = a.next(enemy.pos, WAVE_TARGET);
|
||||
} catch (NoPathException | OutOfBoundsException exceptionO) {
|
||||
throw new RuntimeException("TODO: Implement");
|
||||
}
|
||||
enemy.pos = next;
|
||||
enemies[x][y] = null;
|
||||
enemies[next.x][next.y] = enemy;
|
||||
changedEnemies.add(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
return changedEnemies;
|
||||
}
|
||||
|
||||
public void placeTower(Tower tower, int x, int y) throws InvalidPlacementException {
|
||||
|
|
|
@ -1,5 +1,100 @@
|
|||
package de.towerdefence.server.game.pathfinding;
|
||||
|
||||
public class AStar {
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joml.Vector2i;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class AStar {
|
||||
private final WalkableCallback walkable;
|
||||
|
||||
public Vector2i next(Vector2i current, Vector2i target) throws NoPathException, OutOfBoundsException {
|
||||
if (!walkable.call(current) || !walkable.call(target)) {
|
||||
throw new OutOfBoundsException();
|
||||
}
|
||||
|
||||
if (current.equals(target)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
Map<Vector2i, PathNode> remaining = new HashMap<>();
|
||||
Set<Vector2i> evaluated = new HashSet<>();
|
||||
Map<Vector2i, Vector2i> takenPath = new HashMap<>();
|
||||
|
||||
PathNode origin = new PathNode(current, 0, current.gridDistance(target));
|
||||
remaining.put(current, origin);
|
||||
|
||||
while (!remaining.isEmpty()) {
|
||||
PathNode currentNode = getLowestEstimate(remaining);
|
||||
Vector2i currentPos = currentNode.pos;
|
||||
|
||||
if (currentPos.equals(target)) {
|
||||
return reconstructPath(takenPath, current);
|
||||
}
|
||||
|
||||
remaining.remove(currentPos);
|
||||
evaluated.add(currentPos);
|
||||
|
||||
long nextCost = currentNode.cost + 1;
|
||||
for (PathNode neighbor : getNeighbors(currentPos, nextCost, target)) {
|
||||
if (evaluated.contains(neighbor.pos)) {
|
||||
continue;
|
||||
}
|
||||
if (!remaining.containsKey(neighbor.pos) || nextCost < remaining.get(neighbor.pos).cost) {
|
||||
takenPath.put(neighbor.pos, currentPos);
|
||||
remaining.put(neighbor.pos, neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoPathException();
|
||||
}
|
||||
|
||||
private Vector2i reconstructPath(Map<Vector2i, Vector2i> takenPath, Vector2i origin) {
|
||||
Vector2i current = takenPath.keySet().stream().reduce((first, second) -> second).orElse(null);
|
||||
if (current == null)
|
||||
return null;
|
||||
while (takenPath.get(current) != null && !takenPath.get(current).equals(origin)) {
|
||||
current = takenPath.get(current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private PathNode getLowestEstimate(Map<Vector2i, PathNode> openSet) {
|
||||
PathNode lowestNode = null;
|
||||
for (PathNode node : openSet.values()) {
|
||||
if (lowestNode == null || node.estimate < lowestNode.estimate) {
|
||||
lowestNode = node;
|
||||
}
|
||||
}
|
||||
return lowestNode;
|
||||
}
|
||||
|
||||
private List<PathNode> getNeighbors(Vector2i pos, long nextCost, Vector2i target) {
|
||||
List<PathNode> neighbors = new ArrayList<>();
|
||||
|
||||
addNeighbor(neighbors, new Vector2i(pos.x + 1, pos.y), nextCost, target);
|
||||
addNeighbor(neighbors, new Vector2i(pos.x - 1, pos.y), nextCost, target);
|
||||
addNeighbor(neighbors, new Vector2i(pos.x, pos.y + 1), nextCost, target);
|
||||
addNeighbor(neighbors, new Vector2i(pos.x, pos.y - 1), nextCost, target);
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
private void addNeighbor(List<PathNode> accumulator, Vector2i pos, long nextCost, Vector2i target) {
|
||||
Vector2i right = new Vector2i(pos.x + 1, pos.y);
|
||||
if (!walkable.call(pos)) {
|
||||
return;
|
||||
}
|
||||
accumulator.add(new PathNode(
|
||||
right,
|
||||
nextCost,
|
||||
right.gridDistance(target) + nextCost));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package de.towerdefence.server.game.pathfinding;
|
||||
|
||||
public class OutOfBoundsException extends RuntimeException {
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package de.towerdefence.server.game.pathfinding;
|
||||
|
||||
import org.joml.Vector2i;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
final class PathNode {
|
||||
protected final Vector2i pos;
|
||||
protected final long cost; // This is the cost to this Node
|
||||
protected final long estimate; // This is the estimated remaining cost to the Target
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package de.towerdefence.server.game.pathfinding;
|
||||
|
||||
import org.joml.Vector2i;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface WalkableCallback {
|
||||
boolean call(Vector2i pos);
|
||||
}
|
Loading…
Add table
Reference in a new issue