From 02fb07b871e7a540bcd0b55b62ddc02d1aac6193 Mon Sep 17 00:00:00 2001 From: Major- Date: Sat, 1 Aug 2015 14:10:07 +0100 Subject: [PATCH] Fix AStarPathfindingAlgorithm. --- .../path/AStarPathfindingAlgorithm.java | 278 ++++++++-------- .../model/entity/path/ChebyshevHeuristic.java | 36 +-- .../game/model/entity/path/Heuristic.java | 40 +-- .../model/entity/path/ManhattanHeuristic.java | 36 +-- .../apollo/game/model/entity/path/Node.java | 302 +++++++++--------- .../entity/path/PathfindingAlgorithm.java | 220 ++++++------- .../path/SimplePathfindingAlgorithm.java | 283 ++++++++-------- .../game/model/entity/path/package-info.java | 6 +- 8 files changed, 604 insertions(+), 597 deletions(-) diff --git a/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java b/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java index 5f643f8f..b6609b2e 100644 --- a/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java +++ b/game/src/main/org/apollo/game/model/entity/path/AStarPathfindingAlgorithm.java @@ -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. - *

- * 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. - *

- * 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 find(Position origin, Position target) { - Map nodes = new HashMap<>(); - Node start = new Node(origin), end = new Node(target); - nodes.put(origin, start); - nodes.put(target, end); - - Set open = new HashSet<>(); - Queue 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 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 open, Queue 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 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. + *

+ * 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. + *

+ * 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 find(Position origin, Position target) { + Map nodes = new HashMap<>(); + Node start = new Node(origin), end = new Node(target); + nodes.put(origin, start); + nodes.put(target, end); + + Set open = new HashSet<>(); + Queue 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 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 open, Queue 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 nodes) { + Node node = nodes.peek(); + while (!node.isOpen()) { + nodes.poll(); + node = nodes.peek(); + } + + return node; + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java b/game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java index 33c2cdd9..00e8065f 100644 --- a/game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java +++ b/game/src/main/org/apollo/game/model/entity/path/ChebyshevHeuristic.java @@ -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; + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/Heuristic.java b/game/src/main/org/apollo/game/model/entity/path/Heuristic.java index f43717fd..468c65bd 100644 --- a/game/src/main/org/apollo/game/model/entity/path/Heuristic.java +++ b/game/src/main/org/apollo/game/model/entity/path/Heuristic.java @@ -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); + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java b/game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java index c44061b0..67ff7d1f 100644 --- a/game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java +++ b/game/src/main/org/apollo/game/model/entity/path/ManhattanHeuristic.java @@ -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; + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/Node.java b/game/src/main/org/apollo/game/model/entity/path/Node.java index a5632160..8be1d9ae 100644 --- a/game/src/main/org/apollo/game/model/entity/path/Node.java +++ b/game/src/main/org/apollo/game/model/entity/path/Node.java @@ -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 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 { + + /** + * 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 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(); + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java b/game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java index 297e0916..d96007d8 100644 --- a/game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java +++ b/game/src/main/org/apollo/game/model/entity/path/PathfindingAlgorithm.java @@ -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 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 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 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 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(); + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java b/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java index d784e451..caf97995 100644 --- a/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java +++ b/game/src/main/org/apollo/game/model/entity/path/SimplePathfindingAlgorithm.java @@ -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 boundaries = Optional.empty(); - - @Override - public Deque find(Position origin, Position target) { - int approximation = (int) (origin.getLongestDelta(target) * 1.5); - Deque 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 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}. - *

- * This method: - *

- * - * @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 addHorizontal(Position start, Position target, Deque 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}. - *

- * This method: - *

    - *
  • Adds positions vertically until we are either vertically aligned with the target, or the next step is not - * traversable. - *
  • 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. - *
- * - * @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 addVertical(Position start, Position target, Deque 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 boundaries = Optional.empty(); + + @Override + public Deque find(Position origin, Position target) { + int approximation = (int) (origin.getLongestDelta(target) * 1.5); + Deque 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 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}. + *

+ * This method: + *

    + *
  • Adds positions horizontally until we are either horizontally aligned with the target, or the next step is not + * traversable. + *
  • 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. + *
+ * + * @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 addHorizontal(Position start, Position target, Deque 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}. + *

+ * This method: + *

    + *
  • Adds positions vertically until we are either vertically aligned with the target, or the next step is not + * traversable. + *
  • 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. + *
+ * + * @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 addVertical(Position start, Position target, Deque 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; + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/entity/path/package-info.java b/game/src/main/org/apollo/game/model/entity/path/package-info.java index 2bc5b25b..6358758e 100644 --- a/game/src/main/org/apollo/game/model/entity/path/package-info.java +++ b/game/src/main/org/apollo/game/model/entity/path/package-info.java @@ -1,4 +1,4 @@ -/** - * Contains pathfinding-related classes. - */ +/** + * Contains pathfinding-related classes. + */ package org.apollo.game.model.entity.path; \ No newline at end of file