/**
* Copyright (c) 2012, Hadyn Richard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.scapeemulator.game.model.pathfinding;
import java.util.HashSet;
import java.util.Set;
import net.scapeemulator.game.model.Position;
import net.scapeemulator.game.model.World;
/**
* @author Graham Edgecombe
*/
public final class AStarPathFinder extends PathFinder {
/**
* The cost of moving in a straight line.
*/
private static final int COST_STRAIGHT = 10;
/**
* Represents a node used by the A* algorithm.
* @author Graham Edgecombe
*/
private static class Node implements Comparable<Node> {
/**
* The parent node.
*/
private Node parent;
/**
* The cost.
*/
private int cost;
/**
* The heuristic.
*/
private int heuristic;
/**
* The depth.
*/
private int depth;
/**
* The x coordinate.
*/
private final int x;
/**
* The y coordinate.
*/
private final int y;
/**
* Creates a node.
* @param x The x coordinate.
* @param y The y coordinate.
*/
public Node(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Sets the parent.
* @param parent The parent.
*/
public void setParent(Node parent) {
this.parent = parent;
}
/**
* Gets the parent node.
* @return The parent node.
*/
public Node getParent() {
return parent;
}
public void setCost(int cost) {
this.cost = cost;
}
public int getCost() {
return cost;
}
/**
* Gets the X coordinate.
* @return The X coordinate.
*/
public int getX() {
return x;
}
/**
* Gets the Y coordinate.
* @return The Y coordinate.
*/
public int getY() {
return y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + cost;
result = prime * result + depth;
result = prime * result + heuristic;
result = prime * result
+ ((parent == null) ? 0 : parent.hashCode());
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;
if (cost != other.cost)
return false;
if (depth != other.depth)
return false;
if (heuristic != other.heuristic)
return false;
if (parent == null) {
if (other.parent != null)
return false;
} else if (!parent.equals(other.parent))
return false;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
@Override
public int compareTo(Node arg0) {
return cost - arg0.cost;
}
}
private Node current;
private Node[][] nodes;
private Set<Node> closed = new HashSet<>();
private Set<Node> open = new HashSet<>();
@Override
public Path find(Position position, int width, int length, int srcX, int srcY, int dstX, int dstY, int size) {
if(dstX < 0 || dstY < 0 || dstX >= width || dstY >= length) {
return null; // out of range
}
TraversalMap map = World.getWorld().getTraversalMap();
nodes = new Node[width][length];
for(int x = 0; x < width; x++) {
for(int y = 0; y < length; y++) {
nodes[x][y] = new Node(x, y);
}
}
open.add(nodes[srcX][srcY]);
while(open.size() > 0) {
current = getLowestCost();
if(current == nodes[dstX][dstY]) {
break;
}
open.remove(current);
closed.add(current);
int x = current.getX(), y = current.getY();
// south
if(y > 0 && map.isTraversableSouth(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x][y - 1];
examineNode(n);
}
// west
if(x > 0 && map.isTraversableWest(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x - 1][y];
examineNode(n);
}
// north
if(y < length - 1 && map.isTraversableNorth(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x][y + 1];
examineNode(n);
}
// east
if(x < width - 1 && map.isTraversableEast(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x + 1][y];
examineNode(n);
}
// south west
if(x > 0 && y > 0 && map.isTraversableSouthWest(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x - 1][y - 1];
examineNode(n);
}
// north west
if(x > 0 && y < length - 1 && map.isTraversableNorthWest(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x - 1][y + 1];
examineNode(n);
}
// south east
if(x < width - 1 && y > 0 && map.isTraversableSouthEast(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x + 1][y - 1];
examineNode(n);
}
// north east
if(x < width - 1 && y < length - 1 && map.isTraversableNorthEast(position.getHeight(), position.getX() + x, position.getY() + y, size)) {
Node n = nodes[x + 1][y + 1];
examineNode(n);
}
}
if(nodes[dstX][dstY].getParent() == null) {
return null;
}
Path p = new Path();
Node n = nodes[dstX][dstY];
while(n != nodes[srcX][srcY] && n != null) {
p.addFirst(new Position(n.getX() + position.getX(), n.getY() + position.getY()));
n = n.getParent();
}
return p;
}
private Node getLowestCost() {
Node curLowest = null;
for(Node n : open) {
if(curLowest == null) {
curLowest = n;
} else {
if(n.getCost() < curLowest.getCost()) {
curLowest = n;
}
}
}
return curLowest;
}
private void examineNode(Node n) {
int heuristic = estimateDistance(current, n);
int nextStepCost = current.getCost() + heuristic;
if(nextStepCost < n.getCost()) {
open.remove(n);
closed.remove(n);
}
if(!open.contains(n) && !closed.contains(n)) {
n.setParent(current);
n.setCost(nextStepCost);
open.add(n);
}
}
/**
* Estimates a distance between the two points.
* @param src The source node.
* @param dst The distance node.
* @return The distance.
*/
public int estimateDistance(Node src, Node dst) {
int deltaX = src.getX() - dst.getX();
int deltaY = src.getY() - dst.getY();
return (Math.abs(deltaX) + Math.abs(deltaY)) * COST_STRAIGHT;
}
}