diff --git a/core/src/main/java/ru/trader/analysis/PowerPlayAnalyzator.java b/core/src/main/java/ru/trader/analysis/PowerPlayAnalyzator.java new file mode 100644 index 0000000..4881992 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/PowerPlayAnalyzator.java @@ -0,0 +1,186 @@ +package ru.trader.analysis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Market; +import ru.trader.core.Place; +import ru.trader.core.StarSystemFilter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class PowerPlayAnalyzator { + private final static Logger LOG = LoggerFactory.getLogger(PowerPlayAnalyzator.class); + private final static double CONTROLLING_RADIUS = 15; + + private final Market market; + private StarSystemFilter starSystemFilter; + + public PowerPlayAnalyzator(Market market) { + this.market = market; + } + + public StarSystemFilter getStarSystemFilter() { + return starSystemFilter; + } + + public void setStarSystemFilter(StarSystemFilter starSystemFilter) { + this.starSystemFilter = starSystemFilter; + } + + public Collection getNear(Collection starSystems, Collection centers, double radius, double maxDistance){ + return starSystems.stream() + .filter(new FarDropper(centers, maxDistance)) + .filter(intersectsAnyPredicate(centers, radius).negate()) + .sorted(new DistanceComparator(centers)) + .collect(Collectors.toList()); + } + + + + + public Collection getIntersects(Place checkedSystem, Collection starSystems, Collection centers, double radius){ + return starSystems.stream() + .filter(new FarDropper(centers, radius)) + .filter(intersectsPredicate(checkedSystem, centers, radius)) + .collect(Collectors.toList()); + } + + public Collection getIntersects(Collection starSystems, Collection centers, double radius){ + return starSystems.stream() + .filter(new FarDropper(centers, radius)) + .filter(intersectsPredicate(centers, radius)) + .collect(Collectors.toList()); + } + + private Predicate intersectsAnyPredicate(Collection places, double radius){ + Predicate intersects = null; + for (Place place : places) { + if (intersects == null) intersects = new Controlling(place, radius); + else intersects = intersects.or(new Controlling(place, radius)); + } + return intersects; + } + + + private Predicate intersectsPredicate(Collection places, double radius){ + Predicate intersects = null; + for (Place place : places) { + if (intersects == null) intersects = new Controlling(place, radius); + else intersects = intersects.and(new Controlling(place, radius)); + } + return intersects; + } + + private Predicate intersectsPredicate(Place checkedPlace, Collection places, double radius){ + return new Controlling(checkedPlace, radius).and(intersectsAnyPredicate(places, radius)); + } + + private class Controlling implements Predicate { + private final Place center; + private final double radius; + + private Controlling(Place center, double radius) { + this.center = center; + this.radius = radius; + } + + @Override + public boolean test(Place place) { + double distance = center.getDistance(place); + LOG.trace("Check {}, distance to {} = {}, radius = {}", place, center, distance, radius); + return distance <= radius; + } + } + + + private class FarDropper implements Predicate { + private double minX; + private double maxX; + private double minY; + private double maxY; + private double minZ; + private double maxZ; + + private FarDropper(Collection centers, double radius) { + minX = Double.NaN; maxX = Double.NaN; + minY = Double.NaN; maxY = Double.NaN; + minZ = Double.NaN; maxZ = Double.NaN; + for (Place center : centers) { + if (Double.isNaN(minX) || minX > center.getX()) minX = center.getX(); + if (Double.isNaN(minY) || minY > center.getY()) minY = center.getY(); + if (Double.isNaN(minZ) || minZ > center.getZ()) minZ = center.getZ(); + if (Double.isNaN(maxX) || maxX < center.getX()) maxX = center.getX(); + if (Double.isNaN(maxY) || maxY < center.getY()) maxY = center.getY(); + if (Double.isNaN(maxZ) || maxZ < center.getZ()) maxZ = center.getZ(); + } + minX -= radius; + minY -= radius; + minZ -= radius; + maxX += radius; + maxY += radius; + maxZ += radius; + } + + @Override + public boolean test(Place place) { + boolean res = place.getX() < minX || place.getX() > maxX + || place.getY() < minY || place.getY() > maxY + || place.getZ() < minZ || place.getZ() > maxZ; + LOG.trace("Test {}, dropper = {}, faraway = {}", place, this, res); + return !res; + } + + @Override + public String toString() { + return "FarDropper{" + + "minX=" + minX + + ", maxX=" + maxX + + ", minY=" + minY + + ", maxY=" + maxY + + ", minZ=" + minZ + + ", maxZ=" + maxZ + + '}'; + } + } + + private class DistanceComparator implements Comparator { + private final Collection centers; + private final HashMap distances; + + + private DistanceComparator(Collection centers) { + this.centers = centers; + distances = new HashMap<>(100); + + } + + private double getMinDistance(Place place){ + Double distance = distances.get(place); + if (distance != null) return distance; + + Collection ds = new LimitedQueue<>(3, Comparator.naturalOrder()); + for (Place center : centers) { + double d = center.getDistance(place); + ds.add(d); + } + double dist = 0; + for (Double d : ds) { + dist += d; + } + distances.put(place, dist); + return dist; + } + + @Override + public int compare(Place o1, Place o2) { + return Double.compare(getMinDistance(o1), getMinDistance(o2)); + } + } + +} + diff --git a/core/src/main/java/ru/trader/core/StarSystemFilter.java b/core/src/main/java/ru/trader/core/StarSystemFilter.java new file mode 100644 index 0000000..c341a01 --- /dev/null +++ b/core/src/main/java/ru/trader/core/StarSystemFilter.java @@ -0,0 +1,185 @@ +package ru.trader.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class StarSystemFilter { + private final static Logger LOG = LoggerFactory.getLogger(StarSystemFilter.class); + + private final Collection distances; + private final EnumSet powers; + private final EnumSet states; + private final EnumSet factions; + private final EnumSet governments; + private final Collection excludes; + + public StarSystemFilter() { + this.powers = EnumSet.noneOf(POWER.class); + this.states = EnumSet.noneOf(POWER_STATE.class); + this.factions = EnumSet.noneOf(FACTION.class); + this.governments = EnumSet.noneOf(GOVERNMENT.class); + this.excludes = new ArrayList<>(); + this.distances = new ArrayList<>(); + } + + public Collection getDistanceFilters() { + return distances; + } + + public void add(Place center, double radius){ + distances.add(new DistanceFilter(center, radius)); + } + + public void add(DistanceFilter distanceFilter){ + distances.add(distanceFilter); + } + + public void remove(Place center){ + distances.removeIf(d -> d.center.equals(center)); + } + + public void remove(DistanceFilter distanceFilter){ + distances.remove(distanceFilter); + } + + public void clearDistanceFilters(){ + distances.clear(); + } + + public Collection getPowers(){ + return powers; + } + + public void add(POWER power){ + powers.add(power); + } + + public void remove(POWER power){ + powers.remove(power); + } + + public void clearPower(){ + powers.clear(); + } + + public Collection getPowerStates(){ + return states; + } + + public void add(POWER_STATE powerState){ + states.add(powerState); + } + + public void remove(POWER_STATE powerState){ + states.remove(powerState); + } + + public void clearPowerStates(){ + states.clear(); + } + + + public Collection getExcludes(){ + return excludes; + } + + public void addExclude(Place starSystem){ + excludes.add(starSystem); + } + + public void removeExclude(Place starSystem){ + excludes.remove(starSystem); + } + + public void clearExcludes(){ + excludes.clear(); + } + + public Collection getFactions(){ + return factions; + } + + public void add(FACTION faction){ + factions.add(faction); + } + + public void remove(FACTION faction){ + factions.remove(faction); + } + + public void clearFactions(){ + factions.clear(); + } + + public Collection getGovernments(){ + return governments; + } + + public void add(GOVERNMENT government){ + governments.add(government); + } + + public void remove(GOVERNMENT government){ + governments.remove(government); + } + + public void clearGovernments(){ + governments.clear(); + } + + public boolean isFiltered(Place starSystem){ + for (DistanceFilter distanceFilter : distances) { + if (distanceFilter.isFiltered(starSystem)){ + return true; + } + } + if (excludes.contains(starSystem)) return true; + POWER power = starSystem.getPower(); + if (power != null && !powers.isEmpty() && !powers.contains(power)) return true; + POWER_STATE state = starSystem.getPowerState(); + if (state != null && !states.isEmpty() && !states.contains(state)) return true; + FACTION faction = starSystem.getFaction(); + if (faction != null && !factions.isEmpty() && !factions.contains(faction)) return true; + GOVERNMENT government = starSystem.getGovernment(); + if (government != null && !governments.isEmpty() && !governments.contains(government)) return true; + return false; + } + + @Override + public String toString() { + return "StarSystemFilter{" + + "distances=" + distances + + ", powers=" + powers + + ", states=" + states + + ", factions=" + factions + + ", governments=" + governments + + ", excludes=" + excludes + + '}'; + } + + public class DistanceFilter { + private final Place center; + private final double distance; + + public DistanceFilter(Place center, double distance) { + this.center = center; + this.distance = distance; + } + + public Place getCenter() { + return center; + } + + public double getDistance() { + return distance; + } + + public boolean isFiltered(Place starSystem){ + return center.getDistance(starSystem) <= distance; + } + } + + +} diff --git a/core/src/test/java/ru/trader/analysis/PowerPlayAnalyzatorTest.java b/core/src/test/java/ru/trader/analysis/PowerPlayAnalyzatorTest.java new file mode 100644 index 0000000..c733c97 --- /dev/null +++ b/core/src/test/java/ru/trader/analysis/PowerPlayAnalyzatorTest.java @@ -0,0 +1,169 @@ +package ru.trader.analysis; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.Market; +import ru.trader.core.Place; +import ru.trader.store.simple.Store; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +public class PowerPlayAnalyzatorTest extends Assert { + private final static Logger LOG = LoggerFactory.getLogger(RouteSearcherTest.class); + + private Market world; + private Place lhs3262; + private Place aulin; + private Place morgor; + private Place lhs417; + + @Before + public void setUp() throws Exception { + InputStream is = getClass().getResourceAsStream("/world.xml"); + world = Store.loadFromFile(is); + lhs3262 = world.get("LHS 3262"); + aulin = world.get("Aulin"); + morgor = world.get("Morgor"); + lhs417 = world.get("LHS 417"); + + } + + @Test + public void intersectTest() throws Exception { + Collection starSystems = world.get(); + Collection controllingLhs3262 = starSystems.stream().filter(p -> p.getDistance(lhs3262) <= 15).collect(Collectors.toList()); + Collection controllingAulin = starSystems.stream().filter(p -> p.getDistance(aulin) <= 15).collect(Collectors.toList()); + Collection controllingMorgor = starSystems.stream().filter(p -> p.getDistance(morgor) <= 15).collect(Collectors.toList()); + Collection expectedIntersect = controllingLhs3262.stream().filter(controllingAulin::contains).collect(Collectors.toList()); + Collection expected3Intersect = controllingLhs3262.stream().filter(controllingAulin::contains).filter(controllingMorgor::contains).collect(Collectors.toList()); + + PowerPlayAnalyzator analyzator = new PowerPlayAnalyzator(world); + Collection centers = new ArrayList<>(); + centers.add(lhs3262); + + Collection intersects = analyzator.getIntersects(starSystems, centers, 15); + LOG.info("Test intersects by LHS 3262, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 15); + } + assertTrue(intersects.containsAll(controllingLhs3262)); + assertEquals(controllingLhs3262.size(), intersects.size()); + + centers.add(aulin); + intersects = analyzator.getIntersects(starSystems, centers, 15); + LOG.info("Test intersects by LHS 3262 and Aulin, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 15); + assertTrue(aulin.getDistance(intersect) <= 15); + } + assertTrue(intersects.containsAll(expectedIntersect)); + assertEquals(expectedIntersect.size(), intersects.size()); + + centers.add(morgor); + intersects = analyzator.getIntersects(starSystems, centers, 15); + LOG.info("Test intersects by LHS 3262 and Aulin and Morgor, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 15); + assertTrue(aulin.getDistance(intersect) <= 15); + assertTrue(morgor.getDistance(intersect) <= 15); + } + assertTrue(intersects.containsAll(expected3Intersect)); + assertEquals(expected3Intersect.size(), intersects.size()); + + intersects = analyzator.getIntersects(starSystems, centers, 12); + LOG.info("Test intersects by LHS 3262 and Aulin and Morgor, 12 radius, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 12); + assertTrue(aulin.getDistance(intersect) <= 12); + assertTrue(morgor.getDistance(intersect) <= 12); + } + assertEquals(0, intersects.size()); + + } + + @Test + public void intersectTest2() throws Exception { + Collection starSystems = world.get(); + Collection controllingLhs3262 = starSystems.stream().filter(p -> p.getDistance(lhs3262) <= 12).collect(Collectors.toList()); + Collection controllingAulin = starSystems.stream().filter(p -> p.getDistance(aulin) <= 12).collect(Collectors.toList()); + Collection controllingMorgor = starSystems.stream().filter(p -> p.getDistance(morgor) <= 12).collect(Collectors.toList()); + Collection controllingLhs417 = starSystems.stream().filter(p -> p.getDistance(lhs417) <= 12).collect(Collectors.toList()); + Collection expectedIntersect = controllingLhs417.stream().filter(p -> controllingLhs3262.contains(p) || controllingAulin.contains(p)).collect(Collectors.toList()); + Collection expected3Intersect = controllingLhs417.stream().filter(p -> controllingLhs3262.contains(p) || controllingAulin.contains(p) || controllingMorgor.contains(p)).collect(Collectors.toList()); + Collection expected2Intersect = controllingLhs417.stream().filter(p -> controllingAulin.contains(p) || controllingMorgor.contains(p)).collect(Collectors.toList()); + + PowerPlayAnalyzator analyzator = new PowerPlayAnalyzator(world); + Collection centers = new ArrayList<>(); + centers.add(lhs3262); + centers.add(aulin); + Collection intersects = analyzator.getIntersects(lhs417, starSystems, centers, 12); + LOG.info("Test LHS 417 intersect LHS 3262 or Aulin, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 12 || aulin.getDistance(intersect) <= 12); + assertTrue(lhs417.getDistance(intersect) <= 12); + } + assertTrue(intersects.containsAll(expectedIntersect)); + assertEquals(expectedIntersect.size(), intersects.size()); + + centers.add(morgor); + intersects = analyzator.getIntersects(lhs417, starSystems, centers, 12); + LOG.info("Test LHS 417 intersect LHS 3262 or Aulin or Morgor, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(lhs3262.getDistance(intersect) <= 12 || aulin.getDistance(intersect) <= 12 || morgor.getDistance(intersect) <= 12); + assertTrue(lhs417.getDistance(intersect) <= 12); + } + assertTrue(intersects.containsAll(expected3Intersect)); + assertEquals(expected3Intersect.size(), intersects.size()); + + centers.clear(); + centers.add(morgor); + intersects = analyzator.getIntersects(lhs417, starSystems, centers, 12); + LOG.info("Test LHS 417 intersect Morgor, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(morgor.getDistance(intersect) <= 12); + assertTrue(lhs417.getDistance(intersect) <= 12); + } + assertEquals(0, intersects.size()); + + centers.add(aulin); + intersects = analyzator.getIntersects(lhs417, starSystems, centers, 12); + LOG.info("Test LHS 417 intersect Aulin or Morgor, found {}", intersects.size()); + for (Place intersect : intersects) { + assertTrue(aulin.getDistance(intersect) <= 12 || morgor.getDistance(intersect) <= 12); + assertTrue(lhs417.getDistance(intersect) <= 12); + } + assertTrue(intersects.containsAll(expected2Intersect)); + assertEquals(expected2Intersect.size(), intersects.size()); + + + } + + @Test + public void nearTest() throws Exception { + Collection starSystems = world.get(); + Collection controllingLhs3262 = starSystems.stream().filter(p -> p.getDistance(lhs3262) <= 15).collect(Collectors.toList()); + Collection controllingAulin = starSystems.stream().filter(p -> p.getDistance(aulin) <= 15).collect(Collectors.toList()); + Collection controllingMorgor = starSystems.stream().filter(p -> p.getDistance(morgor) <= 15).collect(Collectors.toList()); + + PowerPlayAnalyzator analyzator = new PowerPlayAnalyzator(world); + Collection centers = new ArrayList<>(); + centers.add(lhs3262); + centers.add(aulin); + Collection near = analyzator.getNear(starSystems, centers, 15, 30); + LOG.info("Test near by LHS 3262 and Aulin, found {}", near.size()); + assertTrue(near.size() > 0); + for (Place place : near) { + Collection intersect = analyzator.getIntersects(place, starSystems, centers, 15); + assertEquals(0, intersect.size()); + } + + } + + +}