/*******************************************************************************
* Copyright (c) 2015
*
* 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 jsettlers.algorithms.path.astar;
import java.util.BitSet;
import jsettlers.algorithms.path.IPathCalculatable;
import jsettlers.algorithms.path.InvalidStartPositionException;
import jsettlers.algorithms.path.Path;
import jsettlers.algorithms.path.astar.queues.bucket.AbstractBucketQueue;
import jsettlers.algorithms.path.astar.queues.bucket.ListMinBucketQueue;
import jsettlers.common.movable.EDirection;
import jsettlers.common.position.ShortPoint2D;
/**
* AStar algorithm to find paths from A to B on a hex grid
*
* @author Andreas Eberle
*
*/
public final class BucketQueueAStar extends AbstractAStar {
private static final byte[] xDeltaArray = EDirection.getXDeltaArray();
private static final byte[] yDeltaArray = EDirection.getYDeltaArray();
private final IAStarPathMap map;
private final short height;
private final short width;
private final BitSet openBitSet;
private final BitSet closedBitSet;
final float[] costs;
final int[] depthParentHeap;
private final AbstractBucketQueue open;
public BucketQueueAStar(IAStarPathMap map, short width, short height) {
this.map = map;
this.width = width;
this.height = height;
this.open = new ListMinBucketQueue(width * height);
this.openBitSet = new BitSet(width * height);
this.closedBitSet = new BitSet(width * height);
this.costs = new float[width * height];
this.depthParentHeap = new int[width * height * 2];
}
@Override
public final Path findPath(IPathCalculatable requester, ShortPoint2D target) {
ShortPoint2D pos = requester.getPos();
return findPath(requester, pos.x, pos.y, target.x, target.y);
}
@Override
public final Path findPath(IPathCalculatable requester, final short sx, final short sy, final short tx, final short ty) {
final short blockedAtStartPartition;
if (!isInBounds(sx, sy)) {
throw new InvalidStartPositionException("Start position is out of bounds!", sx, sy);
} else if (!isInBounds(tx, ty) || isBlocked(requester, tx, ty) || map.getBlockedPartition(sx, sy) != map.getBlockedPartition(tx, ty)) {
return null; // target can not be reached
} else if (sx == tx && sy == ty) {
return null;
} else if (isBlocked(requester, sx, sy)) {
blockedAtStartPartition = map.getBlockedPartition(sx, sy);
} else {
blockedAtStartPartition = -1;
}
final int targetFlatIdx = getFlatIdx(tx, ty);
closedBitSet.clear();
openBitSet.clear();
open.clear();
boolean found = false;
initStartNode(sx, sy, tx, ty);
while (!open.isEmpty()) {
int currFlatIdx = open.deleteMin();
final int x = getX(currFlatIdx);
final int y = getY(currFlatIdx);
setClosed(x, y);
if (targetFlatIdx == currFlatIdx) {
found = true;
break;
}
final float currPositionCosts = costs[currFlatIdx];
for (int i = 0; i < EDirection.NUMBER_OF_DIRECTIONS; i++) {
final int neighborX = x + xDeltaArray[i];
final int neighborY = y + yDeltaArray[i];
if (isValidPosition(requester, x, y, neighborX, neighborY, blockedAtStartPartition)) {
final int flatNeighborIdx = getFlatIdx(neighborX, neighborY);
if (!closedBitSet.get(flatNeighborIdx)) {
final float newCosts = currPositionCosts + map.getCost(x, y, neighborX, neighborY);
if (openBitSet.get(flatNeighborIdx)) {
final float oldCosts = costs[flatNeighborIdx];
if (oldCosts > newCosts) {
costs[flatNeighborIdx] = newCosts;
depthParentHeap[getDepthIdx(flatNeighborIdx)] = depthParentHeap[getDepthIdx(currFlatIdx)] + 1;
depthParentHeap[getParentIdx(flatNeighborIdx)] = currFlatIdx;
int heuristicCosts = getHeuristicCost(neighborX, neighborY, tx, ty);
open.increasedPriority(flatNeighborIdx, oldCosts + heuristicCosts, newCosts + heuristicCosts);
}
} else {
costs[flatNeighborIdx] = newCosts;
depthParentHeap[getDepthIdx(flatNeighborIdx)] = depthParentHeap[getDepthIdx(currFlatIdx)] + 1;
depthParentHeap[getParentIdx(flatNeighborIdx)] = currFlatIdx;
openBitSet.set(flatNeighborIdx);
open.insert(flatNeighborIdx, newCosts + getHeuristicCost(neighborX, neighborY, tx, ty));
map.markAsOpen(neighborX, neighborY);
}
}
}
}
}
if (found) {
int pathlength = depthParentHeap[getDepthIdx(getFlatIdx(tx, ty))];
Path path = new Path(pathlength);
int idx = pathlength;
int parentFlatIdx = targetFlatIdx;
while (idx > 0) {
idx--;
path.insertAt(idx, (short) getX(parentFlatIdx), (short) getY(parentFlatIdx));
parentFlatIdx = depthParentHeap[getParentIdx(parentFlatIdx)];
}
return path;
}
return null;
}
private static final int getDepthIdx(int flatIdx) {
return 2 * flatIdx;
}
private static final int getParentIdx(int flatIdx) {
return 2 * flatIdx + 1;
}
private final void setClosed(int x, int y) {
closedBitSet.set(getFlatIdx(x, y));
map.markAsClosed(x, y);
}
private final void initStartNode(int sx, int sy, int tx, int ty) {
int flatIdx = getFlatIdx(sx, sy);
depthParentHeap[getDepthIdx(flatIdx)] = 0;
depthParentHeap[getParentIdx(flatIdx)] = -1;
costs[flatIdx] = 0;
open.insert(flatIdx, 0 + getHeuristicCost(sx, sy, tx, ty));
openBitSet.set(flatIdx);
}
private final boolean isValidPosition(IPathCalculatable requester, int fromX, int fromY, int toX, int toY, short blockedAtStartPartition) {
return isInBounds(toX, toY)
&& (
!isBlocked(requester, toX, toY)
|| (
blockedAtStartPartition >= 0 // if the start position was blocked, we can use blocked positions on the same island until
&& map.getBlockedPartition(toX, toY) == blockedAtStartPartition // we leave the blocked area
&& isBlocked(requester, fromX, fromY) // prevent reentering blocked positions when we left them already
)
);
}
private final boolean isInBounds(int x, int y) {
return 0 <= x && x < width && 0 <= y && y < height;
}
private final boolean isBlocked(IPathCalculatable requester, int x, int y) {
return map.isBlocked(requester, x, y);
}
private final int getFlatIdx(int x, int y) {
return y * width + x;
}
private final int getX(int flatIdx) {
return flatIdx % width;
}
private final int getY(int flatIdx) {
return flatIdx / width;
}
private final int getHeuristicCost(final int sx, final int sy, final int tx, final int ty) {
final int dx = (tx - sx);
final int dy = (ty - sy);
final int absDx = Math.abs(dx);
final int absDy = Math.abs(dy);
if (dx * dy > 0) { // dx and dy go in the same direction
if (absDx > absDy) {
return absDx;
} else {
return absDy;
}
} else {
return absDx + absDy;
}
}
}