mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Fix AStarPathfindingAlgorithm.
This commit is contained in:
@@ -1,140 +1,140 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
|
||||
/**
|
||||
* A {@link PathfindingAlgorithm} that utilises the A* algorithm to find a solution.
|
||||
* <p>
|
||||
* This implementation utilises a {@link PriorityQueue} of open {@link Node}s, in addition to the usual {@link HashSet}.
|
||||
* This allows for logarithmic-time finding of the cheapest element (as opposed to the linear time associated with
|
||||
* iterating over the set), whilst still maintaining the constant time contains and remove of the set.
|
||||
* <p>
|
||||
* This implementation also avoids the linear-time removal from the queue by polling until the first open node is found
|
||||
* when identifying the cheapest node.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* The heuristic.
|
||||
*/
|
||||
private final Heuristic heuristic;
|
||||
|
||||
/**
|
||||
* Creates the A* pathfinding algorithm with the specified heuristic.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
* @param heuristic The heuristic.
|
||||
*/
|
||||
public AStarPathfindingAlgorithm(RegionRepository repository, Heuristic heuristic) {
|
||||
super(repository);
|
||||
this.heuristic = heuristic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deque<Position> find(Position origin, Position target) {
|
||||
Map<Position, Node> nodes = new HashMap<>();
|
||||
Node start = new Node(origin), end = new Node(target);
|
||||
nodes.put(origin, start);
|
||||
nodes.put(target, end);
|
||||
|
||||
Set<Node> open = new HashSet<>();
|
||||
Queue<Node> sorted = new PriorityQueue<>();
|
||||
open.add(start);
|
||||
sorted.add(start);
|
||||
|
||||
do {
|
||||
Node active = getCheapest(sorted);
|
||||
Position position = active.getPosition();
|
||||
|
||||
if (position.equals(target)) {
|
||||
break;
|
||||
}
|
||||
|
||||
open.remove(active);
|
||||
active.close();
|
||||
|
||||
int x = position.getX(), y = position.getY();
|
||||
for (int nextX = x - 1; x <= x + 1; nextX++) {
|
||||
for (int nextY = y - 1; y <= y + 1; nextY++) {
|
||||
if (nextX == x && nextY == y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Position adjacent = new Position(nextX, nextY);
|
||||
if (traversable(adjacent)) {
|
||||
Node node = nodes.computeIfAbsent(adjacent, Node::new);
|
||||
compare(active, node, open, sorted, heuristic);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!open.isEmpty());
|
||||
|
||||
Deque<Position> shortest = new ArrayDeque<>();
|
||||
Node active = end;
|
||||
|
||||
if (active.hasParent()) {
|
||||
Position position = active.getPosition();
|
||||
|
||||
while (!origin.equals(position)) {
|
||||
shortest.addFirst(position);
|
||||
active = active.getParent(); // If the target has a parent then all of the others will.
|
||||
position = active.getPosition();
|
||||
}
|
||||
}
|
||||
|
||||
return shortest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two specified {@link Node}s, adding the other node to the open {@link Set} if the estimation is
|
||||
* cheaper than the current cost.
|
||||
*
|
||||
* @param active The active node.
|
||||
* @param other The node to compare the active node against.
|
||||
* @param open The set of open nodes.
|
||||
* @param sorted The sorted {@link Queue} of nodes.
|
||||
* @param heuristic The {@link Heuristic} used to estimate the cost of the node.
|
||||
*/
|
||||
private void compare(Node active, Node other, Set<Node> open, Queue<Node> sorted, Heuristic heuristic) {
|
||||
int cost = active.getCost() + heuristic.estimate(active.getPosition(), other.getPosition());
|
||||
|
||||
if (other.getCost() > cost) {
|
||||
open.remove(other);
|
||||
other.close();
|
||||
} else if (other.isOpen() && !open.contains(other)) {
|
||||
other.setCost(cost);
|
||||
other.setParent(active);
|
||||
open.add(other);
|
||||
sorted.add(other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cheapest open {@link Node} from the {@link Queue}.
|
||||
*
|
||||
* @param nodes The queue of nodes.
|
||||
* @return The cheapest node.
|
||||
*/
|
||||
private Node getCheapest(Queue<Node> nodes) {
|
||||
Node node = nodes.peek();
|
||||
while (!node.isOpen()) {
|
||||
nodes.poll();
|
||||
node = nodes.peek();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
|
||||
/**
|
||||
* A {@link PathfindingAlgorithm} that utilises the A* algorithm to find a solution.
|
||||
* <p>
|
||||
* This implementation utilises a {@link PriorityQueue} of open {@link Node}s, in addition to the usual {@link HashSet}.
|
||||
* This allows for logarithmic-time finding of the cheapest element (as opposed to the linear time associated with
|
||||
* iterating over the set), whilst still maintaining the constant time contains and remove of the set.
|
||||
* <p>
|
||||
* This implementation also avoids the linear-time removal from the queue by polling until the first open node is found
|
||||
* when identifying the cheapest node.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* The Heuristic used by this PathfindingAlgorithm.
|
||||
*/
|
||||
private final Heuristic heuristic;
|
||||
|
||||
/**
|
||||
* Creates the A* pathfinding algorithm with the specified {@link Heuristic}.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
* @param heuristic The Heuristic.
|
||||
*/
|
||||
public AStarPathfindingAlgorithm(RegionRepository repository, Heuristic heuristic) {
|
||||
super(repository);
|
||||
this.heuristic = heuristic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deque<Position> find(Position origin, Position target) {
|
||||
Map<Position, Node> nodes = new HashMap<>();
|
||||
Node start = new Node(origin), end = new Node(target);
|
||||
nodes.put(origin, start);
|
||||
nodes.put(target, end);
|
||||
|
||||
Set<Node> open = new HashSet<>();
|
||||
Queue<Node> sorted = new PriorityQueue<>();
|
||||
open.add(start);
|
||||
sorted.add(start);
|
||||
|
||||
do {
|
||||
Node active = getCheapest(sorted);
|
||||
Position position = active.getPosition();
|
||||
|
||||
if (position.equals(target)) {
|
||||
break;
|
||||
}
|
||||
|
||||
open.remove(active);
|
||||
active.close();
|
||||
|
||||
int x = position.getX(), y = position.getY();
|
||||
for (int nextX = x - 1; nextX <= x + 1; nextX++) {
|
||||
for (int nextY = y - 1; nextY <= y + 1; nextY++) {
|
||||
if (nextX == x && nextY == y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Position adjacent = new Position(nextX, nextY);
|
||||
if (traversable(adjacent)) {
|
||||
Node node = nodes.computeIfAbsent(adjacent, Node::new);
|
||||
compare(active, node, open, sorted, heuristic);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!open.isEmpty());
|
||||
|
||||
Deque<Position> shortest = new ArrayDeque<>();
|
||||
Node active = end;
|
||||
|
||||
if (active.hasParent()) {
|
||||
Position position = active.getPosition();
|
||||
|
||||
while (!origin.equals(position)) {
|
||||
shortest.addFirst(position);
|
||||
active = active.getParent(); // If the target has a parent then all of the others will.
|
||||
position = active.getPosition();
|
||||
}
|
||||
}
|
||||
|
||||
return shortest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two specified {@link Node}s, adding the other node to the open {@link Set} if the estimation is
|
||||
* cheaper than the current cost.
|
||||
*
|
||||
* @param active The active node.
|
||||
* @param other The node to compare the active node against.
|
||||
* @param open The set of open nodes.
|
||||
* @param sorted The sorted {@link Queue} of nodes.
|
||||
* @param heuristic The {@link Heuristic} used to estimate the cost of the node.
|
||||
*/
|
||||
private void compare(Node active, Node other, Set<Node> open, Queue<Node> sorted, Heuristic heuristic) {
|
||||
int cost = active.getCost() + heuristic.estimate(active.getPosition(), other.getPosition());
|
||||
|
||||
if (other.getCost() > cost) {
|
||||
open.remove(other);
|
||||
other.close();
|
||||
} else if (other.isOpen() && !open.contains(other)) {
|
||||
other.setCost(cost);
|
||||
other.setParent(active);
|
||||
open.add(other);
|
||||
sorted.add(other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cheapest open {@link Node} from the {@link Queue}.
|
||||
*
|
||||
* @param nodes The queue of nodes.
|
||||
* @return The cheapest node.
|
||||
*/
|
||||
private Node getCheapest(Queue<Node> nodes) {
|
||||
Node node = nodes.peek();
|
||||
while (!node.isOpen()) {
|
||||
nodes.poll();
|
||||
node = nodes.peek();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* The Chebyshev heuristic, ideal for a system that allows for 8-directional movement.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class ChebyshevHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public int estimate(Position current, Position goal) {
|
||||
int dx = Math.abs(current.getX() - goal.getX());
|
||||
int dy = Math.abs(current.getX() - goal.getY());
|
||||
return dx >= dy ? dx : dy;
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* The Chebyshev heuristic, ideal for a system that allows for 8-directional movement.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class ChebyshevHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public int estimate(Position current, Position goal) {
|
||||
int dx = Math.abs(current.getX() - goal.getX());
|
||||
int dy = Math.abs(current.getX() - goal.getY());
|
||||
return dx >= dy ? dx : dy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* A heuristic used by the A* algorithm.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
abstract class Heuristic {
|
||||
|
||||
/**
|
||||
* Estimates the value for this heuristic.
|
||||
*
|
||||
* @param current The current {@link Position}.
|
||||
* @param target The target position.
|
||||
* @return The heuristic value.
|
||||
*/
|
||||
public abstract int estimate(Position current, Position target);
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* A heuristic used by the A* algorithm.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
abstract class Heuristic {
|
||||
|
||||
/**
|
||||
* Estimates the value for this heuristic.
|
||||
*
|
||||
* @param current The current {@link Position}.
|
||||
* @param target The target position.
|
||||
* @return The heuristic value.
|
||||
*/
|
||||
public abstract int estimate(Position current, Position target);
|
||||
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* The Manhattan heuristic, ideal for a system that limits movement to 4 directions.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class ManhattanHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public int estimate(Position current, Position goal) {
|
||||
int dx = Math.abs(current.getX() - goal.getX());
|
||||
int dy = Math.abs(current.getX() - goal.getY());
|
||||
return dx + dy;
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
/**
|
||||
* The Manhattan heuristic, ideal for a system that limits movement to 4 directions.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class ManhattanHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public int estimate(Position current, Position goal) {
|
||||
int dx = Math.abs(current.getX() - goal.getX());
|
||||
int dy = Math.abs(current.getX() - goal.getY());
|
||||
return dx + dy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,149 +1,155 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
/**
|
||||
* A node representing a weighted {@link Position}.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class Node {
|
||||
|
||||
/**
|
||||
* The cost of this node.
|
||||
*/
|
||||
private int cost;
|
||||
|
||||
/**
|
||||
* Whether or not this node is open.
|
||||
*/
|
||||
private boolean open = true;
|
||||
|
||||
/**
|
||||
* The parent node of this node.
|
||||
*/
|
||||
private Optional<Node> parent = Optional.empty();
|
||||
|
||||
/**
|
||||
* The point this node represents.
|
||||
*/
|
||||
private final Position position;
|
||||
|
||||
/**
|
||||
* Creates the node with the specified {@link Position} and cost.
|
||||
*
|
||||
* @param position The position.
|
||||
*/
|
||||
public Node(Position position) {
|
||||
this(position, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the node with the specified {@link Position} and cost.
|
||||
*
|
||||
* @param position The position.
|
||||
* @param cost The cost of the node.
|
||||
*/
|
||||
public Node(Position position, int cost) {
|
||||
this.position = position;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this node.
|
||||
*/
|
||||
public void close() {
|
||||
open = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Node) {
|
||||
Node other = (Node) obj;
|
||||
|
||||
return position.equals(other.position);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cost of this node.
|
||||
*
|
||||
* @return The cost.
|
||||
*/
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent node of this node.
|
||||
*
|
||||
* @return The parent node.
|
||||
* @throws NoSuchElementException If this node does not have a parent.
|
||||
*/
|
||||
public Node getParent() {
|
||||
return parent.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Position} this node represents.
|
||||
*
|
||||
* @return The position.
|
||||
*/
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return position.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this node has a parent node.
|
||||
*
|
||||
* @return {@code true} if this node has a parent node, otherwise {@code false}.
|
||||
*/
|
||||
public boolean hasParent() {
|
||||
return parent.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this {@link Node} is open.
|
||||
*
|
||||
* @return {@code true} if this node is open, otherwise {@code false}.
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cost of this node.
|
||||
*
|
||||
* @param cost The cost.
|
||||
*/
|
||||
public void setCost(int cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent node of this node.
|
||||
*
|
||||
* @param parent The parent node. May be {@code null}.
|
||||
*/
|
||||
public void setParent(Node parent) {
|
||||
this.parent = Optional.ofNullable(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("position", position).add("open", open).add("cost", cost).toString();
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Position;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
/**
|
||||
* A Node representing a weighted {@link Position}.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
final class Node implements Comparable<Node> {
|
||||
|
||||
/**
|
||||
* The cost of this Node.
|
||||
*/
|
||||
private int cost;
|
||||
|
||||
/**
|
||||
* Whether or not this Node is open.
|
||||
*/
|
||||
private boolean open = true;
|
||||
|
||||
/**
|
||||
* The parent Node of this Node.
|
||||
*/
|
||||
private Optional<Node> parent = Optional.empty();
|
||||
|
||||
/**
|
||||
* The Position of this Node.
|
||||
*/
|
||||
private final Position position;
|
||||
|
||||
/**
|
||||
* Creates the Node with the specified {@link Position} and cost.
|
||||
*
|
||||
* @param position The Position.
|
||||
*/
|
||||
public Node(Position position) {
|
||||
this(position, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Node with the specified {@link Position} and cost.
|
||||
*
|
||||
* @param position The Position.
|
||||
* @param cost The cost of the Node.
|
||||
*/
|
||||
public Node(Position position, int cost) {
|
||||
this.position = position;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this Node.
|
||||
*/
|
||||
public void close() {
|
||||
open = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Node other) {
|
||||
return Integer.compare(cost, other.cost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Node) {
|
||||
Node other = (Node) obj;
|
||||
|
||||
return position.equals(other.position);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cost of this Node.
|
||||
*
|
||||
* @return The cost.
|
||||
*/
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent Node of this Node.
|
||||
*
|
||||
* @return The parent Node.
|
||||
* @throws NoSuchElementException If this Node does not have a parent.
|
||||
*/
|
||||
public Node getParent() {
|
||||
return parent.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Position} this Node represents.
|
||||
*
|
||||
* @return The position.
|
||||
*/
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return position.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this Node has a parent Node.
|
||||
*
|
||||
* @return {@code true} if this Node has a parent Node, otherwise {@code false}.
|
||||
*/
|
||||
public boolean hasParent() {
|
||||
return parent.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this {@link Node} is open.
|
||||
*
|
||||
* @return {@code true} if this Node is open, otherwise {@code false}.
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cost of this Node.
|
||||
*
|
||||
* @param cost The cost.
|
||||
*/
|
||||
public void setCost(int cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent Node of this Node.
|
||||
*
|
||||
* @param parent The parent Node. May be {@code null}.
|
||||
*/
|
||||
public void setParent(Node parent) {
|
||||
this.parent = Optional.ofNullable(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("position", position).add("open", open).add("cost", cost)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +1,111 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Direction;
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.Region;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
import org.apollo.game.model.entity.EntityType;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* An algorithm used to find a path between two {@link Position}s.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
abstract class PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* The RegionRepository.
|
||||
*/
|
||||
private final RegionRepository repository;
|
||||
|
||||
/**
|
||||
* Creates the PathfindingAlgorithm.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
*/
|
||||
public PathfindingAlgorithm(RegionRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a valid path from the origin {@link Position} to the target one.
|
||||
*
|
||||
* @param origin The origin Position.
|
||||
* @param target The target Position.
|
||||
* @return The {@link Deque} containing the Positions to go through.
|
||||
*/
|
||||
public abstract Deque<Position> find(Position origin, Position target);
|
||||
|
||||
/**
|
||||
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||
* to is traversable.
|
||||
*
|
||||
* @param current The current Position.
|
||||
* @param directions The Directions that should be checked.
|
||||
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||
*/
|
||||
protected boolean traversable(Position current, Direction... directions) {
|
||||
return traversable(current, Optional.empty(), directions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||
* to is traversable.
|
||||
*
|
||||
* @param current The current Position.
|
||||
* @param boundaries The {@link Optional} containing the Position boundaries.
|
||||
* @param directions The Directions that should be checked.
|
||||
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||
*/
|
||||
protected boolean traversable(Position current, Optional<Position[]> boundaries, Direction... directions) {
|
||||
Preconditions.checkArgument(directions != null && directions.length > 0, "Directions array cannot be null.");
|
||||
int height = current.getHeight();
|
||||
|
||||
Position[] positions = boundaries.isPresent() ? boundaries.get() : new Position[0];
|
||||
|
||||
for (Direction direction : directions) {
|
||||
int x = current.getX(), y = current.getY();
|
||||
int value = direction.toInteger();
|
||||
|
||||
if (value >= Direction.NORTH_WEST.toInteger() && value <= Direction.NORTH_EAST.toInteger()) {
|
||||
y++;
|
||||
} else if (value >= Direction.SOUTH_WEST.toInteger() && value <= Direction.SOUTH_EAST.toInteger()) {
|
||||
y--;
|
||||
}
|
||||
|
||||
if (direction == Direction.NORTH_EAST || direction == Direction.EAST || direction == Direction.SOUTH_EAST) {
|
||||
x++;
|
||||
} else if (direction == Direction.NORTH_WEST || direction == Direction.WEST || direction == Direction.SOUTH_WEST) {
|
||||
x--;
|
||||
}
|
||||
|
||||
Position next = new Position(x, y, height);
|
||||
Region region = repository.get(next.getRegionCoordinates());
|
||||
if (region.traversable(next, EntityType.NPC, direction) && (positions.length == 0 || inside(next, positions))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the specified {@link Position} is inside the specified {@code boundary}.
|
||||
*
|
||||
* @param position The Position.
|
||||
* @param boundary The boundary Positions.
|
||||
* @return {@code true} if the specified Position is inside the boundary, {@code false} if not.
|
||||
*/
|
||||
private boolean inside(Position position, Position[] boundary) {
|
||||
int x = position.getX(), y = position.getY();
|
||||
Position min = boundary[0], max = boundary[1];
|
||||
|
||||
return x >= min.getX() && y >= min.getY() && x <= max.getX() && y <= max.getY();
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Direction;
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.Region;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
import org.apollo.game.model.entity.EntityType;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* An algorithm used to find a path between two {@link Position}s.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
abstract class PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* The RegionRepository.
|
||||
*/
|
||||
private final RegionRepository repository;
|
||||
|
||||
/**
|
||||
* Creates the PathfindingAlgorithm.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
*/
|
||||
public PathfindingAlgorithm(RegionRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a valid path from the origin {@link Position} to the target one.
|
||||
*
|
||||
* @param origin The origin Position.
|
||||
* @param target The target Position.
|
||||
* @return The {@link Deque} containing the Positions to go through.
|
||||
*/
|
||||
public abstract Deque<Position> find(Position origin, Position target);
|
||||
|
||||
/**
|
||||
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||
* to is traversable.
|
||||
*
|
||||
* @param current The current Position.
|
||||
* @param directions The Directions that should be checked.
|
||||
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||
*/
|
||||
protected boolean traversable(Position current, Direction... directions) {
|
||||
return traversable(current, Optional.empty(), directions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||
* to is traversable.
|
||||
*
|
||||
* @param current The current Position.
|
||||
* @param boundaries The {@link Optional} containing the Position boundaries.
|
||||
* @param directions The Directions that should be checked.
|
||||
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||
*/
|
||||
protected boolean traversable(Position current, Optional<Position[]> boundaries, Direction... directions) {
|
||||
Preconditions.checkArgument(directions != null && directions.length > 0, "Directions array cannot be null.");
|
||||
int height = current.getHeight();
|
||||
|
||||
Position[] positions = boundaries.isPresent() ? boundaries.get() : new Position[0];
|
||||
|
||||
for (Direction direction : directions) {
|
||||
int x = current.getX(), y = current.getY();
|
||||
int value = direction.toInteger();
|
||||
|
||||
if (value >= Direction.NORTH_WEST.toInteger() && value <= Direction.NORTH_EAST.toInteger()) {
|
||||
y++;
|
||||
} else if (value >= Direction.SOUTH_WEST.toInteger() && value <= Direction.SOUTH_EAST.toInteger()) {
|
||||
y--;
|
||||
}
|
||||
|
||||
if (direction == Direction.NORTH_EAST || direction == Direction.EAST || direction == Direction.SOUTH_EAST) {
|
||||
x++;
|
||||
} else if (direction == Direction.NORTH_WEST || direction == Direction.WEST || direction == Direction.SOUTH_WEST) {
|
||||
x--;
|
||||
}
|
||||
|
||||
Position next = new Position(x, y, height);
|
||||
Region region = repository.get(next.getRegionCoordinates());
|
||||
if (region.traversable(next, EntityType.NPC, direction) && (positions.length == 0 || inside(next, positions))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the specified {@link Position} is inside the specified {@code boundary}.
|
||||
*
|
||||
* @param position The Position.
|
||||
* @param boundary The boundary Positions.
|
||||
* @return {@code true} if the specified Position is inside the boundary, {@code false} if not.
|
||||
*/
|
||||
private boolean inside(Position position, Position[] boundary) {
|
||||
int x = position.getX(), y = position.getY();
|
||||
Position min = boundary[0], max = boundary[1];
|
||||
|
||||
return x >= min.getX() && y >= min.getY() && x <= max.getX() && y <= max.getY();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,142 +1,143 @@
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Direction;
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
|
||||
/**
|
||||
* A very simple pathfinding algorithm that simply walks in the direction of the target until it either reaches it or is
|
||||
* blocked.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* Creates the SimplePathfindingAlgorithm.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
*/
|
||||
public SimplePathfindingAlgorithm(RegionRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Optional containing the boundary Positions.
|
||||
*/
|
||||
private Optional<Position[]> boundaries = Optional.empty();
|
||||
|
||||
@Override
|
||||
public Deque<Position> find(Position origin, Position target) {
|
||||
int approximation = (int) (origin.getLongestDelta(target) * 1.5);
|
||||
Deque<Position> positions = new ArrayDeque<>(approximation);
|
||||
|
||||
return addHorizontal(origin, target, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a valid path from the origin {@link Position} to the target one.
|
||||
*
|
||||
* @param origin The origin Position.
|
||||
* @param target The target Position.
|
||||
* @param boundaries The boundary Positions, which are marking as untraversable.
|
||||
* @return The {@link Deque} containing the Positions to go through.
|
||||
*/
|
||||
public Deque<Position> find(Position origin, Position target, Position[] boundaries) {
|
||||
this.boundaries = Optional.of(boundaries);
|
||||
return find(origin, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
||||
* <p>
|
||||
* This method:
|
||||
* <ul>
|
||||
* <li>Adds positions horizontally until we are either horizontally aligned with the target, or the next step is not
|
||||
* traversable.
|
||||
* <li>Checks if we are not at the target, and that either of the horizontally-adjacent positions are traversable:
|
||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||
* </ul>
|
||||
*
|
||||
* @param start The current position.
|
||||
* @param target The target position.
|
||||
* @param positions The deque of positions.
|
||||
* @return The deque of positions containing the path.
|
||||
*/
|
||||
private Deque<Position> addHorizontal(Position start, Position target, Deque<Position> positions) {
|
||||
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||
int dx = x - target.getX(), dy = y - target.getY();
|
||||
|
||||
if (dx > 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.WEST) && dx-- > 0) {
|
||||
current = new Position(--x, y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
} else if (dx < 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.EAST) && dx++ < 0) {
|
||||
current = new Position(++x, y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
}
|
||||
|
||||
Position last = new Position(x, y, height);
|
||||
if (!start.equals(last) && dy != 0 && traversable(last, boundaries, dy > 0 ? Direction.SOUTH : Direction.NORTH)) {
|
||||
return addVertical(last, target, positions);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary and possible vertical {@link Position}s to the existing {@link Deque}.
|
||||
* <p>
|
||||
* This method:
|
||||
* <ul>
|
||||
* <li>Adds positions vertically until we are either vertically aligned with the target, or the next step is not
|
||||
* traversable.
|
||||
* <li>Checks if we are not at the target, and that either of the horizontally-adjacent positions are traversable:
|
||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||
* </ul>
|
||||
*
|
||||
* @param start The current position.
|
||||
* @param target The target position.
|
||||
* @param positions The deque of positions.
|
||||
* @return The deque of positions containing the path.
|
||||
*/
|
||||
private Deque<Position> addVertical(Position start, Position target, Deque<Position> positions) {
|
||||
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||
int dy = y - target.getY(), dx = x - target.getX();
|
||||
|
||||
if (dy > 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.SOUTH) && dy-- > 0) {
|
||||
current = new Position(x, --y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
} else if (dy < 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.NORTH) && dy++ < 0) {
|
||||
current = new Position(x, ++y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
}
|
||||
|
||||
Position last = new Position(x, y, height);
|
||||
if (!last.equals(target) && dx != 0 && traversable(last, boundaries, dx > 0 ? Direction.WEST : Direction.EAST)) {
|
||||
return addHorizontal(last, target, positions);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
package org.apollo.game.model.entity.path;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apollo.game.model.Direction;
|
||||
import org.apollo.game.model.Position;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
|
||||
/**
|
||||
* A very simple pathfinding algorithm that simply walks in the direction of the target until it either reaches it or is
|
||||
* blocked.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
||||
|
||||
/**
|
||||
* Creates the SimplePathfindingAlgorithm.
|
||||
*
|
||||
* @param repository The {@link RegionRepository}.
|
||||
*/
|
||||
public SimplePathfindingAlgorithm(RegionRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Optional containing the boundary Positions.
|
||||
*/
|
||||
private Optional<Position[]> boundaries = Optional.empty();
|
||||
|
||||
@Override
|
||||
public Deque<Position> find(Position origin, Position target) {
|
||||
int approximation = (int) (origin.getLongestDelta(target) * 1.5);
|
||||
Deque<Position> positions = new ArrayDeque<>(approximation);
|
||||
|
||||
return addHorizontal(origin, target, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a valid path from the origin {@link Position} to the target one.
|
||||
*
|
||||
* @param origin The origin Position.
|
||||
* @param target The target Position.
|
||||
* @param boundaries The boundary Positions, which are marking as untraversable.
|
||||
* @return The {@link Deque} containing the Positions to go through.
|
||||
*/
|
||||
public Deque<Position> find(Position origin, Position target, Position[] boundaries) {
|
||||
this.boundaries = Optional.of(boundaries);
|
||||
return find(origin, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
||||
* <p>
|
||||
* This method:
|
||||
* <ul>
|
||||
* <li>Adds positions horizontally until we are either horizontally aligned with the target, or the next step is not
|
||||
* traversable.
|
||||
* <li>Checks if we are not at the target, and that either of the horizontally-adjacent positions are traversable:
|
||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||
* </ul>
|
||||
*
|
||||
* @param start The current position.
|
||||
* @param target The target position.
|
||||
* @param positions The deque of positions.
|
||||
* @return The deque of positions containing the path.
|
||||
*/
|
||||
private Deque<Position> addHorizontal(Position start, Position target, Deque<Position> positions) {
|
||||
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||
int dx = x - target.getX(), dy = y - target.getY();
|
||||
|
||||
if (dx > 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.WEST) && dx-- > 0) {
|
||||
current = new Position(--x, y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
} else if (dx < 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.EAST) && dx++ < 0) {
|
||||
current = new Position(++x, y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
}
|
||||
|
||||
Position last = new Position(x, y, height);
|
||||
if (!start.equals(last) && dy != 0 && traversable(last, boundaries, dy > 0 ? Direction.SOUTH : Direction.NORTH)) {
|
||||
return addVertical(last, target, positions);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary and possible vertical {@link Position}s to the existing {@link Deque}.
|
||||
* <p>
|
||||
* This method:
|
||||
* <ul>
|
||||
* <li>Adds positions vertically until we are either vertically aligned with the target, or the next step is not
|
||||
* traversable.
|
||||
* <li>Checks if we are not at the target, and that either of the horizontally-adjacent positions are traversable:
|
||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||
* </ul>
|
||||
*
|
||||
* @param start The current position.
|
||||
* @param target The target position.
|
||||
* @param positions The deque of positions.
|
||||
* @return The deque of positions containing the path.
|
||||
*/
|
||||
private Deque<Position> addVertical(Position start, Position target, Deque<Position> positions) {
|
||||
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||
int dy = y - target.getY(), dx = x - target.getX();
|
||||
|
||||
if (dy > 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.SOUTH) && dy-- > 0) {
|
||||
current = new Position(x, --y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
} else if (dy < 0) {
|
||||
Position current = start;
|
||||
|
||||
while (traversable(current, boundaries, Direction.NORTH) && dy++ < 0) {
|
||||
current = new Position(x, ++y, height);
|
||||
positions.addLast(current);
|
||||
}
|
||||
}
|
||||
|
||||
Position last = new Position(x, y, height);
|
||||
if (!last.equals(target) && dx != 0
|
||||
&& traversable(last, boundaries, dx > 0 ? Direction.WEST : Direction.EAST)) {
|
||||
return addHorizontal(last, target, positions);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* Contains pathfinding-related classes.
|
||||
*/
|
||||
/**
|
||||
* Contains pathfinding-related classes.
|
||||
*/
|
||||
package org.apollo.game.model.entity.path;
|
||||
Reference in New Issue
Block a user