package polly.rx.core.orion.pathplanning;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import polly.rx.core.orion.QuadrantProvider;
import polly.rx.core.orion.QuadrantUtils;
import polly.rx.core.orion.WormholeProvider;
import polly.rx.core.orion.model.AlienSpawn;
import polly.rx.core.orion.model.Quadrant;
import polly.rx.core.orion.model.QuadrantDecorator;
import polly.rx.core.orion.model.Sector;
import polly.rx.core.orion.model.SectorDecorator;
import polly.rx.core.orion.model.SectorType;
import polly.rx.core.orion.model.Wormhole;
import polly.rx.core.orion.pathplanning.Graph.EdgeCosts;
import polly.rx.core.orion.pathplanning.Graph.LazyBuilder;
public class PathPlanner {
private final static Comparator<Sector> SAFE_SPOT_COMP = new Comparator<Sector>() {
@Override
public int compare(Sector o1, Sector o2) {
// attacker bonus: the less, the better
int c = Integer.compare(o1.getAttackerBonus(), o2.getAttackerBonus());
if (c == 0) {
// defender bonus: the more, the better
c = Integer.compare(o2.getDefenderBonus(), o1.getDefenderBonus());
}
return c;
}
};
private final static EdgeCosts<EdgeData> EDGE_COSTS = new PathCostCalculator();
public static final int MAX_SAFE_SPOT_OUTPUT = 2;
private final QuadrantProvider quadProvider;
public PathPlanner(QuadrantProvider quadProvider, WormholeProvider holeProvider) {
this.quadProvider = quadProvider;
}
private final static class HighlightedSector extends SectorDecorator {
private final SectorType highlight;
public HighlightedSector(Sector wrapped, SectorType highlight) {
super(wrapped);
this.highlight = highlight;
}
@Override
public SectorType getType() {
return this.highlight;
}
}
public final static class HighlightedQuadrant extends QuadrantDecorator {
private static int IDS = 0;
private final Map<String, SectorType> highlights;
private final int id;
private final boolean renderDark;
public HighlightedQuadrant(Quadrant wrapped, boolean renderDark) {
super(wrapped);
this.highlights = new HashMap<>();
this.id = IDS++;
this.renderDark = renderDark;
}
public int getId() {
return this.id;
}
public void highlight(Sector sector, SectorType type) {
this.highlight(sector.getX(), sector.getY(), type);
}
public void highlight(int x, int y, SectorType type) {
final String key = x + "_" + y; //$NON-NLS-1$
SectorType st = this.highlights.get(key);
if (st == null) {
this.highlights.put(key, type);
} else {
// highlight for this sector already exists, keep the one with higher id
if (st.getId() < type.getId()) {
this.highlights.put(key, type);
}
}
}
@Override
public Sector getSector(int x, int y) {
final String key = QuadrantUtils.createMapKey(x, y);
final Sector sector = super.getSector(x, y);
final SectorType type = this.highlights.get(key);
if (type != null) {
return new HighlightedSector(sector, type);
} else if (renderDark && sector.getType() != SectorType.NONE){
// render all unrelated sectors dark
return new HighlightedSector(sector, SectorType.UNKNOWN);
}
return sector;
}
}
public final static class Group {
final static AtomicInteger IDS = new AtomicInteger();
final int id;
final List<Graph<Sector, EdgeData>.Edge> edges;
final HighlightedQuadrant quad;
private Group(Quadrant quadrant, RouteOptions options) {
super();
this.id = IDS.incrementAndGet();
this.quad = new HighlightedQuadrant(quadrant, options.doRenderDark());
this.edges = new ArrayList<>();
}
public int getId() {
return this.id;
}
public List<Graph<Sector, EdgeData>.Edge> getEdges() {
return this.edges;
}
public Quadrant getQuadrant() {
return this.quad;
}
public String getQuadName() {
return this.quad.getName();
}
}
public class UniversePath {
private final Graph<Sector, EdgeData>.Path path;
private final List<Group> groups;
private final LinkedList<Wormhole> wormholes;
private final int sectorJumps;
private final int quadJumps;
private final int minUnload;
private final int maxUnload;
private final int maxWaitTime;
private final int sumMinWaitingTime;
private final int sumMaxWaitingTime;
private final RouteOptions options;
/** Number used during path planning to block wormholes */
private int blockNr;
/** States used during path planning to block wormholes */
private boolean done;
private UniversePath(Graph<Sector, EdgeData>.Path path, RouteOptions options) {
this.path = path;
this.options = options;
this.groups = new ArrayList<>();
this.wormholes = new LinkedList<>();
Group currentGroup = null;
// always consider to be unloaded
final int jtMinutes = (int) (options.totalJumpTime.getSpan() / 60.0);
final int cjtMinutes = (int) (options.currentJumpTime.getSpan() / 60.0);
int currentMinUnload = cjtMinutes;
int currentMaxUnload = cjtMinutes;
int sumMinUnload = 0;
int sumMaxUnload = 0;
int sumMinWaitingTime = 0;
int sumMaxWaitingTime = 0;
int maximumWaitTime = 0;
boolean first = true;
Graph<Sector, EdgeData>.Edge lastEdge = null;
final Iterator<Graph<Sector, EdgeData>.Edge> it = path.getPath().iterator();
while (it.hasNext()) {
final Graph<Sector, EdgeData>.Edge e = it.next();
final Sector source = e.getSource().getData();
final Sector target = e.getTarget().getData();
SectorType highlight = SectorType.HIGHLIGHT_SECTOR;
// initialize first group
if (first) {
final Quadrant quad = quadProvider.getQuadrant(source.getQuadName());
currentGroup = new Group(quad, options);
this.groups.add(currentGroup);
}
if (lastEdge != null && lastEdge.getData().isWormhole()) {
// if last edge was a WH, current source node is a WH drop
highlight = SectorType.HIGHLIGHT_WH_DROP;
}
currentGroup.edges.add(e);
if (e.getData().isWormhole()) {
final Wormhole hole = e.getData().getWormhole();
sumMinUnload += hole.getMinUnload();
sumMaxUnload += hole.getMaxUnload();
this.wormholes.add(hole);
highlight = SectorType.HIGHLIGHT_WH_START;
switch (hole.requiresLoad()) {
case FULL:
e.getData().wait = new TimeRange(currentMinUnload, currentMaxUnload);
currentMinUnload = hole.getMinUnload();
currentMaxUnload = hole.getMaxUnload();
break;
case PARTIAL:
final int waitMin = Math.max((jtMinutes - currentMinUnload) - (jtMinutes - hole.getMaxUnload()), 0);
final int waitMax = Math.max((jtMinutes - currentMaxUnload) - (jtMinutes - hole.getMaxUnload()), 0);
e.getData().wait = new TimeRange(waitMax, waitMin);
currentMinUnload += hole.getMinUnload() - e.getData().wait.getMin();
currentMaxUnload += hole.getMaxUnload() - e.getData().wait.getMax();
break;
case NONE:
currentMinUnload += hole.getMinUnload();
currentMaxUnload += hole.getMaxUnload();
default:
}
maximumWaitTime = Math.max(maximumWaitTime, e.getData().wait.getMax());
e.getData().unloadAfter = new TimeRange(currentMinUnload, currentMaxUnload);
// find near aliens
final List<? extends AlienSpawn> spawns = QuadrantUtils.reachableAliens(
e.getSource().getData(), QuadrantUtils.AGGRESSIVE_ONLY);
e.getData().setSpawns(spawns);
for (final AlienSpawn spawn : e.getData().getSpawns()) {
currentGroup.quad.highlight(spawn.getSector(), SectorType.HIGHLIGHT_ALIEN_SPAWN);
}
if (e.getData().mustWait()) {
sumMinWaitingTime += e.getData().wait.getMin();
sumMaxWaitingTime += e.getData().wait.getMax();
// find good spots
final Quadrant quad = quadProvider.getQuadrant(source.getQuadName());
final List<Sector> spots = QuadrantUtils.getNearSectors(source,
quad, options.maxWaitSpotDistance, QuadrantUtils.ACCEPT_ALL);
Collections.sort(spots, SAFE_SPOT_COMP);
final int bound = Math.min(spots.size(), MAX_SAFE_SPOT_OUTPUT);
for (int i = 0; i < bound; ++i) {
final Sector safeSpot = spots.get(i);
e.getData().waitSpots.add(safeSpot);
if (safeSpot.equals(e.getSource().getData())) {
currentGroup.quad.highlight(safeSpot,
SectorType.HIGHLIGHT_SAFE_SPOT_WL);
} else {
currentGroup.quad.highlight(safeSpot,
SectorType.HIGHLIGHT_SAFE_SPOT);
}
}
}
}
if (first) {
highlight = SectorType.HIGHLIGHT_START;
}
currentGroup.quad.highlight(e.getSource().getData(), highlight);
first = false;
lastEdge = e;
// create new group if target is within a different quadrant
if (!source.getQuadName().equals(target.getQuadName())) {
final Quadrant quad = quadProvider.getQuadrant(target.getQuadName());
currentGroup = new Group(quad, options);
this.groups.add(currentGroup);
}
}
// lastEdge is null if no way was found
if (lastEdge != null) {
currentGroup.quad.highlight(lastEdge.getTarget().getData(),
SectorType.HIGHLIGHT_TARGET);
}
this.quadJumps = wormholes.size();
this.sectorJumps = path.getPath().size() - this.quadJumps;
this.minUnload = sumMinUnload;
this.maxUnload = sumMaxUnload;
this.maxWaitTime = maximumWaitTime;
this.sumMinWaitingTime = sumMinWaitingTime;
this.sumMaxWaitingTime = sumMaxWaitingTime;
}
public Wormhole getWormholeToBlock(RouteOptions options) {
if (this.wormholes.isEmpty()) {
return null;
}
final Iterator<Wormhole> it = options.doBlockTail()
? this.wormholes.descendingIterator()
: this.wormholes.iterator();
int i = 0;
while (it.hasNext()) {
final Wormhole hole = it.next();
if (!hole.getName().equals(SectorType.EINTRITTS_PORTAL.toString())
|| options.doBlockEntryPortal()) {
if (i++ == this.blockNr) {
++this.blockNr;
return hole;
}
}
}
return null;
}
public int getMaxSafeSpotDistance() {
return options.maxWaitSpotDistance;
}
public int getMaxWaitingTime() {
return this.maxWaitTime;
}
public boolean pathFound() {
return !this.path.getPath().isEmpty();
}
public int getMaxUnload() {
return this.maxUnload;
}
public int getMinUnload() {
return this.minUnload;
}
public int getSumMinWaitingTime() {
return this.sumMinWaitingTime;
}
public int getSumMaxWaitingTime() {
return this.sumMaxWaitingTime;
}
public int getSectorJumps() {
return this.sectorJumps;
}
public List<Wormhole> getWormholes() {
return this.wormholes;
}
public int getQuadJumps() {
return this.quadJumps;
}
public List<Group> getGroups() {
return this.groups;
}
}
private UniversePath findShortestPath(Sector start, Sector target,
LazyBuilder<Sector, EdgeData> builder, RouteOptions options) {
final Graph<Sector, EdgeData> graph = new Graph<>();
final Graph<Sector, EdgeData>.Path path = graph.findShortestPath(
start, target, builder, Graph.<Sector>noHeuristic(), EDGE_COSTS);
final UniversePath result = new UniversePath(path, options);
return result;
}
public UniversePath findShortestPath(Sector start, Sector target,
RouteOptions options) {
return this.findShortestPath(start, target, new UniverseBuilder(options), options);
}
public List<UniversePath> findShortestPaths(Sector start, Sector target,
RouteOptions options) {
final int K = 10;
final UniverseBuilder builder = new UniverseBuilder(options);
final List<UniversePath> result = new ArrayList<>(K);
final UniversePath shortest = this.findShortestPath(start, target,
builder, options);
result.add(shortest);
if (shortest.pathFound()) {
int pathsDone = 0;
int nextPathIdx = -1;
while (result.size() < K && pathsDone != result.size()) {
nextPathIdx = (nextPathIdx + 1) % result.size();
final UniversePath nextPath = result.get(nextPathIdx);
if (nextPath.done) continue;
final Wormhole block = nextPath.getWormholeToBlock(options);
if (block != null) {
builder.startOverAndBlock(block);
final UniversePath path = this.findShortestPath(start, target,
builder, options);
if (path.pathFound()) {
result.add(path);
} else {
// if no path found yet, there will be no path when blocking
// further holes, so this one's done
nextPath.done = true;
pathsDone++;
}
} else {
nextPath.done = true;
pathsDone++;
}
}
}
return result;
}
}