package polly.rx.core.orion; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.awt.image.RescaleOp; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.imageio.ImageIO; import polly.rx.MSG; import polly.rx.core.orion.model.AlienSpawn; import polly.rx.core.orion.model.DefaultResources; import polly.rx.core.orion.model.Production; import polly.rx.core.orion.model.Quadrant; import polly.rx.core.orion.model.Resources; import polly.rx.core.orion.model.Sector; import polly.rx.core.orion.model.SectorType; import polly.rx.core.orion.pathplanning.Graph; import polly.rx.core.orion.pathplanning.Graph.EdgeCosts; import polly.rx.core.orion.pathplanning.Graph.LazyBuilder; import polly.rx.entities.RxRessource; import polly.rx.httpv2.view.orion.Images; import polly.rx.parsing.ParseException; import de.skuzzle.polly.tools.io.FastByteArrayOutputStream; public final class QuadrantUtils { private final static ImageObserver NOP_IMAGE_OBSERVER = new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return false; } }; /** Semantical constant for {@link #reachableAliens(Sector, boolean)} parameter */ public final static boolean AGGRESSIVE_ONLY = true; /** A Builder which ignores wormholes */ private final static class LocalBuilder implements LazyBuilder<Sector, Costs> { private final Quadrant quadrant; private final Set<Sector> done = new HashSet<>(); public LocalBuilder(Quadrant quadrant) { this.quadrant = quadrant; } @Override public void collectIncident(Graph<Sector, Costs> source, Sector currentNode) { if (!this.done.add(currentNode)) { return; } for (int i = -1; i < 2; ++i) { for (int j = -1; j < 2; ++j) { if (i == 0 && j == 0) { // exclude sector itself continue; } final int x = currentNode.getX() + i; final int y = currentNode.getY() + j; final boolean diagonal = Math.abs(i) == 1 && Math.abs(j) == 1; if (x >= 0 && y >= 0 && x <= this.quadrant.getMaxX() && y <= this.quadrant.getMaxY()) { final Sector neighbor = this.quadrant.getSector(x, y); if (neighbor.getType() != SectorType.NONE) { final Graph<Sector, Costs>.Node current = source .getNode(currentNode); final Graph<Sector, Costs>.Node node = source.getNode( neighbor, neighbor); current.edgeTo(node, new Costs(diagonal)); } } } } } }; private QuadrantUtils() {} private final static EdgeCosts<Costs> COST_CALCULATOR = data -> data.isDiagonal ? 1.0 : 2.0; private static class Costs { private final boolean isDiagonal; public Costs(boolean isDiagonal) { this.isDiagonal = isDiagonal; } } public interface SectorFilter { public boolean accept(Sector sector); } public final static SectorFilter ACCEPT_ALL = new SectorFilter() { @Override public boolean accept(Sector sector) { return true; } }; public static String createMapKey(int x, int y) { return x + "_" + y; //$NON-NLS-1$ } public static String createMapKey(Sector sector) { return createMapKey(sector.getX(), sector.getY()); } /** * Parses a Sector specification given as * <tt><Quadname> <x> <y> * and returns the respective Sector instance retrieved from {@link Orion}. * * @param s * The String to parse. * @return The Sector. * @throws ParseException * If the String has the wrong format. */ public static Sector parse(String s) throws ParseException { // parse backwards try { // prepare string s = s.replace(",", " "); //$NON-NLS-1$//$NON-NLS-2$ s = s.replaceAll("\\s{2,}", " "); //$NON-NLS-1$//$NON-NLS-2$ int i = s.lastIndexOf(' '); final int y = Integer.parseInt(s.substring(i + 1)); s = s.substring(0, i); i = s.lastIndexOf(' '); final int x = Integer.parseInt(s.substring(i + 1)); final String quadName = s.substring(0, i); return Orion.INSTANCE.getQuadrantProvider().getQuadrant(quadName) .getSector(x, y); } catch (Exception e) { throw new ParseException(MSG.routeParseError, e); } } public static boolean reachable(Sector source, Sector target) { if (!source.getQuadName().equals(target.getQuadName())) { return false; } final Quadrant quad = Orion.INSTANCE.getQuadrantProvider().getQuadrant(source); final LocalBuilder builder = new LocalBuilder(quad); final Graph<Sector, Costs> g = new Graph<>(); return g.isReachable(source, target, builder); } public static int getDistance(Sector source, Sector target, final Quadrant quadrant) { final Graph<Sector, Costs> g = new Graph<>(); return getDistance(source, target, quadrant, g); } private static int getDistance(Sector source, Sector target, final Quadrant quadrant, Graph<Sector, Costs> g) { final LazyBuilder<Sector, Costs> builder = new LocalBuilder(quadrant); final Graph<Sector, Costs>.Path path = g.findShortestPath(source, target, builder, Graph.<Sector> noHeuristic(), COST_CALCULATOR); if (path.getPath().isEmpty() && !source.equals(target)) { // no path found <=> max distance return Integer.MAX_VALUE; } return path.getPath().size(); } public static List<AlienSpawn> reachableAliens(Sector source) { return reachableAliens(source, false); } public static List<AlienSpawn> reachableAliens(Sector source, boolean aggressiveOnly) { final List<? extends AlienSpawn> spawns = Orion.INSTANCE.getAlienManager() .getSpawnsByQuadrant(source.getQuadName()); final List<AlienSpawn> result = new ArrayList<>(spawns.size()); for (final AlienSpawn spawn : spawns) { if (aggressiveOnly && !spawn.getRace().isAggressive()) { continue; } if (reachable(source, spawn.getSector())) { result.add(spawn); } } return result; } public static Resources calculateHourlyProduction(Quadrant quad) { final Map<RxRessource, Double> production = new EnumMap<>(RxRessource.class); for (final RxRessource res : RxRessource.values()) { production.put(res, 0.); } for (final Sector sector : quad.getSectors()) { for (final Production prod : sector.getRessources()) { Double currentProd = production.get(prod.getRess()); currentProd += prod.getRate(); production.put(prod.getRess(), currentProd); } } return new DefaultResources(production); } public static List<Sector> getNearSectors(Sector source, Quadrant quadrant, int maxDistance, SectorFilter filter) { final List<Sector> result = new ArrayList<>(); final Graph<Sector, Costs> g = new Graph<>(); for (int i = -maxDistance; i < maxDistance; ++i) { for (int j = -maxDistance; j < maxDistance; ++j) { final int x = source.getX() + i; final int y = source.getY() + j; if (x >= 0 && y >= 0 && x <= quadrant.getMaxX() && y <= quadrant.getMaxY()) { final Sector sector = quadrant.getSector(x, y); if (sector.getType() != SectorType.NONE && filter.accept(sector)) { final int dist = getDistance(source, sector, quadrant, g); if (dist <= maxDistance) { result.add(sector); } } } } } return result; } public static enum DrawSectorMode { DARK(0.7f), BRIGHT(1.3f); private final float factor; private DrawSectorMode(float factor) { this.factor = factor; } } public static OutputStream createSectorImageAsPNGStream(SectorType type, DrawSectorMode mode) { final BufferedImage img = createSectorImage(type, mode); final OutputStream out = new FastByteArrayOutputStream(); try { ImageIO.write(img, "png", out); //$NON-NLS-1$ } catch (IOException e) { // should not be reachable e.printStackTrace(); } return out; } public static BufferedImage createSectorImage(SectorType type, DrawSectorMode mode) { final BufferedImage img = type.getImage(); final BufferedImage cpy = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB); cpy.getGraphics().drawImage(img, 0, 0, img.getWidth(), img.getHeight(), NOP_IMAGE_OBSERVER); final float scaleFactor = mode.factor; final RescaleOp op = new RescaleOp(scaleFactor, 0, null); return op.filter(cpy, null); } public static BufferedImage drawHeatMap(Quadrant quad, String user, FleetHeatMap heatMap) { final Map<Sector, Integer> map = heatMap.getSectorHeatMap(user, quad); final int max = getMax(map); final int min = getMinGreater0(map); return drawSingleHeatMap(quad, map, max, min); } public static Map<Quadrant, BufferedImage> drawUserHeatMap(String user, FleetHeatMap heatMap) { final Map<Quadrant, Map<Sector, Integer>> heatMaps = heatMap.getUserHeatMaps(user); final int min = getMinGreater02(heatMaps); final int max = getMax2(heatMaps); final Map<Quadrant, BufferedImage> result = new LinkedHashMap<>(heatMaps.size()); heatMaps.forEach((quad, map) -> { final BufferedImage img = drawSingleHeatMap(quad, map, max, min); result.put(quad, img); }); return result; } private static BufferedImage drawSingleHeatMap(Quadrant quad, final Map<Sector, Integer> map, final int max, final int min) { final int ss = 10; // sector size in pixels final Color background = new Color(51, 51, 102, 255); final BufferedImage img = new BufferedImage(quad.getMaxX() * ss, quad.getMaxY() * ss, BufferedImage.TYPE_INT_ARGB); final Graphics2D g = img.createGraphics(); g.setColor(background); g.fillRect(0, 0, img.getWidth(), img.getHeight()); for (int y = 0; y < quad.getMaxY(); ++y) { for (int x = 0; x < quad.getMaxX(); ++x) { final Sector s = quad.getSector(x + 1, y + 1); if (s.getType() != SectorType.NONE) { final Integer actual = map.get(s); if (actual == null || actual == 0) { final BufferedImage sectorImage = s.getType().getImage(); g.drawImage(sectorImage, x * ss, y * ss, ss, ss, NOP_IMAGE_OBSERVER); } else { final Color color = getColor(min, max, actual); g.setBackground(color); g.setColor(color); g.fillRect(x * ss, y * ss, ss, ss); } } } } return img; } private static Color getColor(int min, int max, int actual) { final double percentage = (double) (actual - min) / (double) (max - min); return Images.getGradientColor(percentage); } private static int getMax(Map<Sector, Integer> m) { return m.values().stream().max(Integer::compare).orElse(0); } private static int getMax2(Map<Quadrant, Map<Sector, Integer>> m) { return m.values().stream() .flatMap(map -> map.values().stream()) .max(Integer::compare) .orElse(0); } private static int getMinGreater02(Map<Quadrant, Map<Sector, Integer>> m) { return m.values().stream() .flatMap(map -> map.values().stream()) .filter(i -> i != null) .filter(i -> i > 0) .min(Integer::compare) .orElse(0); } private static int getMinGreater0(Map<Sector, Integer> m) { return m.values().stream() .filter(i -> i != null) .filter(i -> i > 0) .min(Integer::compare) .orElse(0); } public static BufferedImage createQuadImage(Quadrant quad) { final int ss = 10; // sector size in pixels final Color background = new Color(51, 51, 102, 255); final BufferedImage img = new BufferedImage(quad.getMaxX() * ss, quad.getMaxY() * ss, BufferedImage.TYPE_INT_ARGB); final Graphics2D g = img.createGraphics(); g.setColor(background); g.fillRect(0, 0, img.getWidth(), img.getHeight()); for (int y = 0; y < quad.getMaxY(); ++y) { for (int x = 0; x < quad.getMaxX(); ++x) { final Sector s = quad.getSector(x + 1, y + 1); if (s.getType() != SectorType.NONE) { final BufferedImage sectorImage = s.getType().getImage(); g.drawImage(sectorImage, x * ss, y * ss, ss, ss, NOP_IMAGE_OBSERVER); } } } return img; } }