/*******************************************************************************
* Copyright (c) 2015 - 2017
*
* 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.ai.highlevel;
import jsettlers.common.map.shapes.IMapArea;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.coordinates.CoordinateStream;
import jsettlers.common.utils.coordinates.IBooleanCoordinateFunction;
import java.util.Arrays;
import java.util.Iterator;
/**
* This is a set of points on the map. It is optimized for range queries.
*
* @author Michael Zangl
*
*/
public class AiPositions implements IMapArea {
private static final int MIN_SIZE = 16;
private static final int SHORT_MASK = 0x7fff;
/**
*
*/
private static final long serialVersionUID = -1032477484624659731L;
/**
* Filters a position map.
*
* @author Michael Zangl
*
*/
public interface AiPositionFilter {
boolean contains(int x, int y);
}
public static class CombinedAiPositionFilter implements AiPositionFilter {
private AiPositionFilter firstFilter;
private AiPositionFilter secondFilter;
public CombinedAiPositionFilter(AiPositionFilter firstFilter, AiPositionFilter secondFilter) {
this.firstFilter = firstFilter;
this.secondFilter = secondFilter;
}
@Override
public boolean contains(int x, int y) {
return firstFilter.contains(x, y) && secondFilter.contains(x, y);
}
}
/**
* Rates each position. A lower rating means better.
*
* @author Michael Zangl
*
*/
public interface PositionRater {
int RATE_INVALID = Integer.MAX_VALUE;
/**
* Rates a given position.
*
* @param x
* @param y
* @param currentBestRating
* The best rating found so far, you can return {@link #RATE_INVALID} if yours is worse.
* @return The rating or {@link #RATE_INVALID} if this position is not possible.
*/
int rate(int x, int y, int currentBestRating);
}
private class PositionsIterator implements Iterator<ShortPoint2D> {
private int index;
@Override
public boolean hasNext() {
return index < size;
}
@Override
public ShortPoint2D next() {
int next = points[index];
index++;
return new ShortPoint2D(unpackX(next), unpackY(next));
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private boolean sorted = false;
private int[] points = new int[MIN_SIZE];
private int size = 0;
public void add(int x, int y) {
if (!contains(x, y)) {
addNoCollission(x, y);
}
}
/**
* Add a position of which we are sure that it is not in this set.
*
* @param x
* @param y
*/
public void addNoCollission(int x, int y) {
int pos = pack(x, y);
if (points.length == size) {
resizeTo(points.length * 2);
}
points[size] = pos;
size++;
sorted = false;
}
public void addAllNoCollision(AiPositions otherAiPositions) {
int newSize = size + otherAiPositions.size;
if (newSize > points.length) {
resizeTo(newSize * 2);
}
System.arraycopy(otherAiPositions.points, 0, points, size, otherAiPositions.size);
size = newSize;
sorted = false;
}
public void remove(int x, int y) {
ensureSorted();
int index = indexOf(x, y);
if (index >= 0) {
if (index < size - 1) {
points[index] = points[size - 1];
sorted = false;
}
// TODO: shrink array.
size--;
}
}
@Override
public boolean contains(ShortPoint2D position) {
return contains(position.x, position.y);
}
@Override
public boolean contains(int x, int y) {
ensureSorted();
return indexOf(x, y) >= 0;
}
private int indexOf(int x, int y) {
return Arrays.binarySearch(points, 0, size, pack(x, y));
}
@Override
public Iterator<ShortPoint2D> iterator() {
ensureSorted();
return new PositionsIterator();
}
private void ensureSorted() {
if (!sorted) {
Arrays.sort(points, 0, size);
sorted = true;
}
}
private static int pack(int x, int y) {
return ((x & SHORT_MASK) << 16) | (y & SHORT_MASK);
}
private static int unpackX(int pos) {
return pos >> 16;
}
private static int unpackY(int pos) {
return pos & SHORT_MASK;
}
private void resizeTo(int arraySize) {
points = Arrays.copyOf(points, arraySize);
}
public void clear() {
size = 0;
points = new int[MIN_SIZE];
}
public ShortPoint2D getNearestPoint(ShortPoint2D center, int maxDistance) {
return getNearestPoint(center, maxDistance, null);
}
public ShortPoint2D getNearestPoint(ShortPoint2D center, int maxDistance, AiPositionFilter filter) {
ensureSorted();
int resX = -1, resY = -1;
int median = findClosestIndex(center.x, center.y);
if (median >= size) {
median = size - 1;
}
int l = median, r = median + 1;
while (true) {
int current;
int rDist = r < size ? unpackX(points[r]) - center.x : maxDistance;
if (l >= 0 && center.x - unpackX(points[l]) <= rDist) {
current = points[l];
l--;
} else if (r < size && rDist < maxDistance) {
current = points[r];
r++;
} else {
break;
}
int x = unpackX(current);
int y = unpackY(current);
if (filter != null && !filter.contains(x, y)) {
continue;
}
int pDist = ShortPoint2D.getOnGridDist(center.x - x, center.y - y);
if (pDist < maxDistance) {
resX = x;
resY = y;
maxDistance = pDist;
}
}
return resY >= 0 ? new ShortPoint2D(resX, resY) : null;
}
public ShortPoint2D get(int index) {
return new ShortPoint2D(unpackX(points[index]), unpackY(points[index]));
}
private int findClosestIndex(int x, int y) {
return Math.abs(indexOf(x, y));
}
public int size() {
return size;
}
public boolean isEmpty() {
return size <= 0;
}
@Override
public String toString() {
final int maxLen = 100;
StringBuilder pointsStr = new StringBuilder();
for (ShortPoint2D p : this) {
if (pointsStr.length() > 0) {
pointsStr.append(" ");
}
if (pointsStr.length() > maxLen) {
pointsStr.append("...");
break;
}
pointsStr.append(p.x);
pointsStr.append(",");
pointsStr.append(p.y);
}
return "AiPositions [sorted=" + sorted + ", size=" + size + ", points="
+ pointsStr + "]";
}
public ShortPoint2D getBestRatedPoint(PositionRater rater) {
// TODO: Parallel ?
int currentBestRating = PositionRater.RATE_INVALID;
ShortPoint2D currentBest = null;
for (int i = 0; i < size; i++) {
int x = unpackX(points[i]);
int y = unpackY(points[i]);
int rating = rater.rate(x, y, currentBestRating);
if (rating < currentBestRating) {
currentBestRating = rating;
currentBest = new ShortPoint2D(x, y);
}
}
return currentBest;
}
public CoordinateStream stream() {
return new CoordinateStream() {
@Override
public boolean iterate(IBooleanCoordinateFunction function) {
for (int i = 0; i < size; i++) {
int packedCoordinate = points[i];
int x = unpackX(packedCoordinate);
int y = unpackY(packedCoordinate);
if (!function.apply(x, y)) {
return false;
}
}
return true;
}
};
}
}