package games.strategy.triplea.ai.proAI.data; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Route; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.triplea.Properties; import games.strategy.triplea.TripleAUnit; import games.strategy.triplea.ai.proAI.ProData; import games.strategy.triplea.ai.proAI.logging.ProLogger; import games.strategy.triplea.ai.proAI.util.ProBattleUtils; import games.strategy.triplea.ai.proAI.util.ProMatches; import games.strategy.triplea.ai.proAI.util.ProOddsCalculator; import games.strategy.triplea.ai.proAI.util.ProTransportUtils; import games.strategy.triplea.ai.proAI.util.ProUtils; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.MoveValidator; import games.strategy.triplea.delegate.TerritoryEffectHelper; import games.strategy.triplea.delegate.TransportTracker; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.CompositeMatchOr; import games.strategy.util.Match; /** * Manages info about territories. */ public class ProTerritoryManager { private final ProOddsCalculator calc; private final PlayerID player; private ProMyMoveOptions attackOptions; private ProMyMoveOptions potentialAttackOptions; private ProMyMoveOptions defendOptions; private ProOtherMoveOptions alliedAttackOptions; private ProOtherMoveOptions enemyDefendOptions; private ProOtherMoveOptions enemyAttackOptions; public ProTerritoryManager(final ProOddsCalculator calc) { this.calc = calc; player = ProData.getPlayer(); attackOptions = new ProMyMoveOptions(); potentialAttackOptions = new ProMyMoveOptions(); defendOptions = new ProMyMoveOptions(); alliedAttackOptions = new ProOtherMoveOptions(); enemyDefendOptions = new ProOtherMoveOptions(); enemyAttackOptions = new ProOtherMoveOptions(); } public ProTerritoryManager(final ProOddsCalculator calc, final ProTerritoryManager territoryManager) { this(calc); attackOptions = new ProMyMoveOptions(territoryManager.attackOptions); potentialAttackOptions = new ProMyMoveOptions(territoryManager.potentialAttackOptions); defendOptions = new ProMyMoveOptions(territoryManager.defendOptions); alliedAttackOptions = territoryManager.getAlliedAttackOptions(); enemyDefendOptions = territoryManager.getEnemyDefendOptions(); enemyAttackOptions = territoryManager.getEnemyAttackOptions(); } public void populateAttackOptions() { findAttackOptions(player, ProData.myUnitTerritories, attackOptions.getTerritoryMap(), attackOptions.getUnitMoveMap(), attackOptions.getTransportMoveMap(), attackOptions.getBombardMap(), attackOptions.getTransportList(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false, false); findBombingOptions(); alliedAttackOptions = findAlliedAttackOptions(player); } public void populatePotentialAttackOptions() { findPotentialAttackOptions(player, ProData.myUnitTerritories, potentialAttackOptions.getTerritoryMap(), potentialAttackOptions.getUnitMoveMap(), potentialAttackOptions.getTransportMoveMap(), potentialAttackOptions.getBombardMap(), potentialAttackOptions.getTransportList()); } public void populateDefenseOptions(final List<Territory> clearedTerritories) { findDefendOptions(player, ProData.myUnitTerritories, defendOptions.getTerritoryMap(), defendOptions.getUnitMoveMap(), defendOptions.getTransportMoveMap(), defendOptions.getTransportList(), clearedTerritories, false); } public void populateEnemyAttackOptions(final List<Territory> clearedTerritories, final List<Territory> territoriesToCheck) { enemyAttackOptions = findEnemyAttackOptions(player, clearedTerritories, territoriesToCheck); } public void populateEnemyDefenseOptions() { findScrambleOptions(player, attackOptions.getTerritoryMap()); enemyDefendOptions = findEnemyDefendOptions(player); } public List<ProTerritory> removeTerritoriesThatCantBeConquered() { return removeTerritoriesThatCantBeConquered(player, attackOptions.getTerritoryMap(), attackOptions.getUnitMoveMap(), attackOptions.getTransportMoveMap(), alliedAttackOptions, enemyDefendOptions, false); } public List<ProTerritory> removePotentialTerritoriesThatCantBeConquered() { return removeTerritoriesThatCantBeConquered(player, potentialAttackOptions.getTerritoryMap(), potentialAttackOptions.getUnitMoveMap(), potentialAttackOptions.getTransportMoveMap(), alliedAttackOptions, enemyDefendOptions, true); } public ProMyMoveOptions getAttackOptions() { return attackOptions; } public ProMyMoveOptions getPotentialAttackOptions() { return potentialAttackOptions; } public ProMyMoveOptions getDefendOptions() { return defendOptions; } public ProOtherMoveOptions getAlliedAttackOptions() { return alliedAttackOptions; } public ProOtherMoveOptions getEnemyDefendOptions() { return enemyDefendOptions; } public ProOtherMoveOptions getEnemyAttackOptions() { return enemyAttackOptions; } public List<Territory> getDefendTerritories() { return new ArrayList<>(defendOptions.getTerritoryMap().keySet()); } public List<Territory> getStrafingTerritories() { final List<Territory> strafingTerritories = new ArrayList<>(); for (final Territory t : attackOptions.getTerritoryMap().keySet()) { if (attackOptions.getTerritoryMap().get(t).isStrafing()) { strafingTerritories.add(t); } } return strafingTerritories; } public List<Territory> getCantHoldTerritories() { final List<Territory> territoriesThatCantBeHeld = new ArrayList<>(); for (final Territory t : defendOptions.getTerritoryMap().keySet()) { if (!defendOptions.getTerritoryMap().get(t).isCanHold()) { territoriesThatCantBeHeld.add(t); } } return territoriesThatCantBeHeld; } public boolean haveUsedAllAttackTransports() { final Set<Unit> movedTransports = new HashSet<>(); for (final ProTerritory patd : attackOptions.getTerritoryMap().values()) { movedTransports.addAll(patd.getAmphibAttackMap().keySet()); movedTransports.addAll(Match.getMatches(patd.getUnits(), Matches.UnitIsTransport)); } return movedTransports.size() >= attackOptions.getTransportList().size(); } private void findScrambleOptions(final PlayerID player, final Map<Territory, ProTerritory> moveMap) { final GameData data = ProData.getData(); if (!Properties.getScramble_Rules_In_Effect(data)) { return; } // Find scramble properties final boolean fromIslandOnly = Properties.getScramble_From_Island_Only(data); final boolean toSeaOnly = Properties.getScramble_To_Sea_Only(data); int maxScrambleDistance = 0; final Iterator<UnitType> utIter = data.getUnitTypeList().iterator(); while (utIter.hasNext()) { final UnitAttachment ua = UnitAttachment.get(utIter.next()); if (ua.getCanScramble() && maxScrambleDistance < ua.getMaxScrambleDistance()) { maxScrambleDistance = ua.getMaxScrambleDistance(); } } final Match<Unit> airbasesCanScramble = new CompositeMatchAnd<>(Matches.unitIsEnemyOf(data, player), Matches.UnitIsAirBase, Matches.UnitIsNotDisabled, Matches.unitIsBeingTransported().invert()); final CompositeMatchAnd<Territory> canScramble = new CompositeMatchAnd<>( new CompositeMatchOr<>(Matches.TerritoryIsWater, Matches.isTerritoryEnemy(player, data)), Matches.territoryHasUnitsThatMatch(new CompositeMatchAnd<>(Matches.UnitCanScramble, Matches.unitIsEnemyOf(data, player), Matches.UnitIsNotDisabled)), Matches.territoryHasUnitsThatMatch(airbasesCanScramble)); if (fromIslandOnly) { canScramble.add(Matches.TerritoryIsIsland); } // Find potential territories to scramble from final HashMap<Territory, HashSet<Territory>> scrambleTerrs = new HashMap<>(); for (final Territory t : moveMap.keySet()) { if (t.isWater() || !toSeaOnly) { final HashSet<Territory> canScrambleFrom = new HashSet<>(Match.getMatches(data.getMap().getNeighbors(t, maxScrambleDistance), canScramble)); if (!canScrambleFrom.isEmpty()) { scrambleTerrs.put(t, canScrambleFrom); } } } if (scrambleTerrs.isEmpty()) { return; } // Find potential max units that can be scrambled to each territory for (final Territory to : scrambleTerrs.keySet()) { for (final Territory from : scrambleTerrs.get(to)) { // Find potential scramble units from territory final Collection<Unit> airbases = from.getUnits().getMatches(airbasesCanScramble); final int maxCanScramble = getMaxScrambleCount(airbases); final Route toBattleRoute = data.getMap().getRoute_IgnoreEnd(from, to, Matches.TerritoryIsNotImpassable); List<Unit> canScrambleAir = from.getUnits() .getMatches(new CompositeMatchAnd<>(Matches.unitIsEnemyOf(data, player), Matches.UnitCanScramble, Matches.UnitIsNotDisabled, Matches.UnitWasScrambled.invert(), Matches.unitCanScrambleOnRouteDistance(toBattleRoute))); // Add max scramble units if (maxCanScramble > 0 && !canScrambleAir.isEmpty()) { if (maxCanScramble < canScrambleAir.size()) { Collections.sort(canScrambleAir, (o1, o2) -> { final double strength1 = ProBattleUtils.estimateStrength(to, Collections.singletonList(o1), new ArrayList<>(), false); final double strength2 = ProBattleUtils.estimateStrength(to, Collections.singletonList(o2), new ArrayList<>(), false); return Double.compare(strength2, strength1); }); canScrambleAir = canScrambleAir.subList(0, maxCanScramble); } moveMap.get(to).getMaxScrambleUnits().addAll(canScrambleAir); } } } } private static int getMaxScrambleCount(final Collection<Unit> airbases) { if (!Match.allMatch(airbases, new CompositeMatchAnd<>(Matches.UnitIsAirBase, Matches.UnitIsNotDisabled))) { throw new IllegalStateException("All units must be viable airbases"); } // find how many is the max this territory can scramble int maxScrambled = 0; for (final Unit base : airbases) { final UnitAttachment ua = UnitAttachment.get(base.getType()); final int baseMax = ua.getMaxScrambleCount(); if (baseMax == -1) { return Integer.MAX_VALUE; } maxScrambled += baseMax; } return maxScrambled; } private void findAttackOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Map<Unit, Set<Territory>> transportMoveMap, final Map<Unit, Set<Territory>> bombardMap, final List<ProTransport> transportMapList, final List<Territory> enemyTerritories, final List<Territory> alliedTerritories, final List<Territory> territoriesToCheck, final boolean isCheckingEnemyAttacks, final boolean isIgnoringRelationships) { final GameData data = ProData.getData(); final Map<Territory, Set<Territory>> landRoutesMap = new HashMap<>(); final List<Territory> territoriesThatCantBeHeld = new ArrayList<>(enemyTerritories); territoriesThatCantBeHeld.addAll(territoriesToCheck); findNavalMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, transportMoveMap, ProMatches.territoryIsEnemyOrHasEnemyUnitsOrCantBeHeld(player, data, territoriesThatCantBeHeld), enemyTerritories, true, isCheckingEnemyAttacks); findLandMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, landRoutesMap, ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld), enemyTerritories, alliedTerritories, true, isCheckingEnemyAttacks, isIgnoringRelationships); findAirMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, ProMatches.territoryHasEnemyUnitsOrCantBeHeld(player, data, territoriesThatCantBeHeld), enemyTerritories, alliedTerritories, true, isCheckingEnemyAttacks, isIgnoringRelationships); findAmphibMoveOptions(player, myUnitTerritories, moveMap, transportMapList, landRoutesMap, ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld), true, isCheckingEnemyAttacks, isIgnoringRelationships); findBombardOptions(player, myUnitTerritories, moveMap, bombardMap, transportMapList, isCheckingEnemyAttacks); } private void findBombingOptions() { for (final Unit unit : attackOptions.getUnitMoveMap().keySet()) { if (Matches.UnitIsStrategicBomber.match(unit)) { attackOptions.getBomberMoveMap().put(unit, new HashSet<>(attackOptions.getUnitMoveMap().get(unit))); } } } private ProOtherMoveOptions findAlliedAttackOptions(final PlayerID player) { final GameData data = ProData.getData(); // Get enemy players in order of turn final List<PlayerID> alliedPlayers = ProUtils.getAlliedPlayersInTurnOrder(player); final List<Map<Territory, ProTerritory>> alliedAttackMaps = new ArrayList<>(); // Loop through each enemy to determine the maximum number of enemy units that can attack each territory for (final PlayerID alliedPlayer : alliedPlayers) { final List<Territory> alliedUnitTerritories = Match.getMatches(data.getMap().getTerritories(), Matches.territoryHasUnitsOwnedBy(alliedPlayer)); final Map<Territory, ProTerritory> attackMap = new HashMap<>(); final Map<Unit, Set<Territory>> unitAttackMap = new HashMap<>(); final Map<Unit, Set<Territory>> transportAttackMap = new HashMap<>(); final Map<Unit, Set<Territory>> bombardMap = new HashMap<>(); final List<ProTransport> transportMapList = new ArrayList<>(); alliedAttackMaps.add(attackMap); findAttackOptions(alliedPlayer, alliedUnitTerritories, attackMap, unitAttackMap, transportAttackMap, bombardMap, transportMapList, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false, false); } return new ProOtherMoveOptions(alliedAttackMaps, player, true); } private ProOtherMoveOptions findEnemyAttackOptions(final PlayerID player, final List<Territory> clearedTerritories, final List<Territory> territoriesToCheck) { final GameData data = ProData.getData(); // Get enemy players in order of turn final List<PlayerID> enemyPlayers = ProUtils.getEnemyPlayersInTurnOrder(player); final List<Map<Territory, ProTerritory>> enemyAttackMaps = new ArrayList<>(); final Set<Territory> alliedTerritories = new HashSet<>(); final List<Territory> enemyTerritories = new ArrayList<>(clearedTerritories); // Loop through each enemy to determine the maximum number of enemy units that can attack each territory for (final PlayerID enemyPlayer : enemyPlayers) { final List<Territory> enemyUnitTerritories = Match.getMatches(data.getMap().getTerritories(), Matches.territoryHasUnitsOwnedBy(enemyPlayer)); enemyUnitTerritories.removeAll(clearedTerritories); final Map<Territory, ProTerritory> attackMap = new HashMap<>(); final Map<Unit, Set<Territory>> unitAttackMap = new HashMap<>(); final Map<Unit, Set<Territory>> transportAttackMap = new HashMap<>(); final Map<Unit, Set<Territory>> bombardMap = new HashMap<>(); final List<ProTransport> transportMapList = new ArrayList<>(); enemyAttackMaps.add(attackMap); findAttackOptions(enemyPlayer, enemyUnitTerritories, attackMap, unitAttackMap, transportAttackMap, bombardMap, transportMapList, enemyTerritories, new ArrayList<>(alliedTerritories), territoriesToCheck, true, true); alliedTerritories.addAll(Match.getMatches(attackMap.keySet(), Matches.TerritoryIsLand)); enemyTerritories.removeAll(alliedTerritories); } return new ProOtherMoveOptions(enemyAttackMaps, player, true); } private void findPotentialAttackOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Map<Unit, Set<Territory>> transportMoveMap, final Map<Unit, Set<Territory>> bombardMap, final List<ProTransport> transportMapList) { final GameData data = ProData.getData(); final Map<Territory, Set<Territory>> landRoutesMap = new HashMap<>(); final List<PlayerID> otherPlayers = ProUtils.getPotentialEnemyPlayers(player); findNavalMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, transportMoveMap, ProMatches.territoryIsPotentialEnemyOrHasPotentialEnemyUnits(player, data, otherPlayers), new ArrayList<>(), true, false); findLandMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, landRoutesMap, ProMatches.territoryIsPotentialEnemy(player, data, otherPlayers), new ArrayList<>(), new ArrayList<>(), true, false, true); findAirMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, ProMatches.territoryHasPotentialEnemyUnits(player, data, otherPlayers), new ArrayList<>(), new ArrayList<>(), true, false, true); findAmphibMoveOptions(player, myUnitTerritories, moveMap, transportMapList, landRoutesMap, ProMatches.territoryIsPotentialEnemy(player, data, otherPlayers), true, false, true); findBombardOptions(player, myUnitTerritories, moveMap, bombardMap, transportMapList, false); } private void findDefendOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Map<Unit, Set<Territory>> transportMoveMap, final List<ProTransport> transportMapList, final List<Territory> clearedTerritories, final boolean isCheckingEnemyAttacks) { final GameData data = ProData.getData(); final Map<Territory, Set<Territory>> landRoutesMap = new HashMap<>(); findNavalMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, transportMoveMap, ProMatches.territoryHasNoEnemyUnitsOrCleared(player, data, clearedTerritories), clearedTerritories, false, isCheckingEnemyAttacks); findLandMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, landRoutesMap, Matches.isTerritoryAllied(player, data), new ArrayList<>(), clearedTerritories, false, isCheckingEnemyAttacks, false); findAirMoveOptions(player, myUnitTerritories, moveMap, unitMoveMap, ProMatches.territoryIsNotConqueredAlliedLand(player, data), new ArrayList<>(), new ArrayList<>(), false, isCheckingEnemyAttacks, false); findAmphibMoveOptions(player, myUnitTerritories, moveMap, transportMapList, landRoutesMap, Matches.isTerritoryAllied(player, data), false, isCheckingEnemyAttacks, false); } private ProOtherMoveOptions findEnemyDefendOptions(final PlayerID player) { final GameData data = ProData.getData(); // Get enemy players in order of turn final List<PlayerID> enemyPlayers = ProUtils.getEnemyPlayersInTurnOrder(player); final List<Map<Territory, ProTerritory>> enemyMoveMaps = new ArrayList<>(); final List<Territory> clearedTerritories = Match.getMatches(data.getMap().getTerritories(), Matches.isTerritoryAllied(player, data)); // Loop through each enemy to determine the maximum number of enemy units that can defend each territory for (final PlayerID enemyPlayer : enemyPlayers) { final List<Territory> enemyUnitTerritories = Match.getMatches(data.getMap().getTerritories(), Matches.territoryHasUnitsOwnedBy(enemyPlayer)); final Map<Territory, ProTerritory> moveMap = new HashMap<>(); final Map<Unit, Set<Territory>> unitMoveMap = new HashMap<>(); final Map<Unit, Set<Territory>> transportMoveMap = new HashMap<>(); final List<ProTransport> transportMapList = new ArrayList<>(); enemyMoveMaps.add(moveMap); findDefendOptions(enemyPlayer, enemyUnitTerritories, moveMap, unitMoveMap, transportMoveMap, transportMapList, clearedTerritories, true); } return new ProOtherMoveOptions(enemyMoveMaps, player, false); } private void findNavalMoveOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Map<Unit, Set<Territory>> transportMoveMap, final Match<Territory> moveToTerritoryMatch, final List<Territory> clearedTerritories, final boolean isCombatMove, final boolean isCheckingEnemyAttacks) { final GameData data = ProData.getData(); for (final Territory myUnitTerritory : myUnitTerritories) { // Find my naval units that have movement left final List<Unit> mySeaUnits = myUnitTerritory.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedSea(player, isCombatMove)); // Check each sea unit individually since they can have different ranges for (final Unit mySeaUnit : mySeaUnits) { // If my combat move and carrier has dependent allied fighters then skip it if (isCombatMove && !isCheckingEnemyAttacks) { final Map<Unit, Collection<Unit>> carrierMustMoveWith = MoveValidator.carrierMustMoveWith(myUnitTerritory.getUnits().getUnits(), myUnitTerritory, data, player); if (carrierMustMoveWith.containsKey(mySeaUnit) && !carrierMustMoveWith.get(mySeaUnit).isEmpty()) { continue; } } // Find range int range = TripleAUnit.get(mySeaUnit).getMovementLeft(); if (isCheckingEnemyAttacks) { range = UnitAttachment.get(mySeaUnit.getType()).getMovement(player); if (Matches.UnitCanBeGivenBonusMovementByFacilitiesInItsTerritory(myUnitTerritory, player, data) .match(mySeaUnit)) { range++; // assumes bonus of +1 for now } } // Find list of potential territories to move to final Set<Territory> possibleMoveTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanMoveSeaUnits(player, data, isCombatMove)); possibleMoveTerritories.add(myUnitTerritory); final Set<Territory> potentialTerritories = new HashSet<>(Match.getMatches(possibleMoveTerritories, moveToTerritoryMatch)); if (!isCombatMove) { potentialTerritories.add(myUnitTerritory); } for (final Territory potentialTerritory : potentialTerritories) { // Find route over water boolean hasNoRoute = true; final List<Territory> eliminatedTerritories = new ArrayList<>(); while (true) { // Need a loop to consider different route combinations to avoid canals Route myRoute = data.getMap().getRoute_IgnoreEnd(myUnitTerritory, potentialTerritory, ProMatches.territoryCanMoveSeaUnitsThroughOrClearedAndNotInList(player, data, isCombatMove, clearedTerritories, eliminatedTerritories)); if (isCheckingEnemyAttacks) { myRoute = data.getMap().getRoute_IgnoreEnd(myUnitTerritory, potentialTerritory, ProMatches.territoryCanMoveSeaUnitsAndNotInList(player, data, isCombatMove, eliminatedTerritories)); } if (myRoute == null) { break; } if (MoveValidator.validateCanal(myRoute, Collections.singletonList(mySeaUnit), player, data) != null) { if (!myRoute.getMiddleSteps().isEmpty()) { eliminatedTerritories.addAll(myRoute.getMiddleSteps()); // Add failed canal territories to list continue; } else { break; } } final int myRouteLength = myRoute.numberOfSteps(); if (myRouteLength > range) { break; } hasNoRoute = false; break; } if (hasNoRoute) { continue; } // Populate territories with sea unit if (moveMap.containsKey(potentialTerritory)) { moveMap.get(potentialTerritory).addMaxUnit(mySeaUnit); } else { final ProTerritory moveTerritoryData = new ProTerritory(potentialTerritory); moveTerritoryData.addMaxUnit(mySeaUnit); moveMap.put(potentialTerritory, moveTerritoryData); } // Populate appropriate unit move options map if (Matches.UnitIsTransport.match(mySeaUnit)) { if (transportMoveMap.containsKey(mySeaUnit)) { transportMoveMap.get(mySeaUnit).add(potentialTerritory); } else { final Set<Territory> unitMoveTerritories = new HashSet<>(); unitMoveTerritories.add(potentialTerritory); transportMoveMap.put(mySeaUnit, unitMoveTerritories); } } else { if (unitMoveMap.containsKey(mySeaUnit)) { unitMoveMap.get(mySeaUnit).add(potentialTerritory); } else { final Set<Territory> unitMoveTerritories = new HashSet<>(); unitMoveTerritories.add(potentialTerritory); unitMoveMap.put(mySeaUnit, unitMoveTerritories); } } } } } } private void findLandMoveOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Map<Territory, Set<Territory>> landRoutesMap, final Match<Territory> moveToTerritoryMatch, final List<Territory> enemyTerritories, final List<Territory> clearedTerritories, final boolean isCombatMove, final boolean isCheckingEnemyAttacks, final boolean isIgnoringRelationships) { final GameData data = ProData.getData(); for (final Territory myUnitTerritory : myUnitTerritories) { // Find my land units that have movement left final List<Unit> myLandUnits = myUnitTerritory.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedLand(player, isCombatMove)); // Check each land unit individually since they can have different ranges for (final Unit myLandUnit : myLandUnits) { final Territory startTerritory = ProData.unitTerritoryMap.get(myLandUnit); final int range = TripleAUnit.get(myLandUnit).getMovementLeft(); Set<Territory> possibleMoveTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanMoveSpecificLandUnit(player, data, isCombatMove, myLandUnit)); if (isIgnoringRelationships) { possibleMoveTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanPotentiallyMoveSpecificLandUnit(player, data, isCombatMove, myLandUnit)); } possibleMoveTerritories.add(myUnitTerritory); final Set<Territory> potentialTerritories = new HashSet<>(Match.getMatches(possibleMoveTerritories, moveToTerritoryMatch)); if (!isCombatMove) { potentialTerritories.add(myUnitTerritory); } for (final Territory potentialTerritory : potentialTerritories) { // Find route over land checking whether unit can blitz Route myRoute = data.getMap().getRoute_IgnoreEnd(myUnitTerritory, potentialTerritory, ProMatches.territoryCanMoveLandUnitsThrough(player, data, myLandUnit, startTerritory, isCombatMove, enemyTerritories)); if (isCheckingEnemyAttacks) { myRoute = data.getMap().getRoute_IgnoreEnd(myUnitTerritory, potentialTerritory, ProMatches.territoryCanMoveLandUnitsThroughIgnoreEnemyUnits(player, data, myLandUnit, startTerritory, isCombatMove, enemyTerritories, clearedTerritories)); } if (myRoute == null) { continue; } if (myRoute.hasMoreThenOneStep() && Match.someMatch(myRoute.getMiddleSteps(), Matches.isTerritoryEnemy(player, data)) && Matches.unitIsOfTypes(TerritoryEffectHelper.getUnitTypesThatLostBlitz(myRoute.getAllTerritories())) .match(myLandUnit)) { continue; // If blitzing then make sure none of the territories cause blitz ability to be lost } final int myRouteLength = myRoute.numberOfSteps(); if (myRouteLength > range) { continue; } // Add to route map if (landRoutesMap.containsKey(potentialTerritory)) { landRoutesMap.get(potentialTerritory).add(myUnitTerritory); } else { final Set<Territory> territories = new HashSet<>(); territories.add(myUnitTerritory); landRoutesMap.put(potentialTerritory, territories); } // Populate territories with land units if (moveMap.containsKey(potentialTerritory)) { moveMap.get(potentialTerritory).addMaxUnit(myLandUnit); } else { final ProTerritory moveTerritoryData = new ProTerritory(potentialTerritory); moveTerritoryData.addMaxUnit(myLandUnit); moveMap.put(potentialTerritory, moveTerritoryData); } // Populate unit move options map if (unitMoveMap.containsKey(myLandUnit)) { unitMoveMap.get(myLandUnit).add(potentialTerritory); } else { final Set<Territory> unitMoveTerritories = new HashSet<>(); unitMoveTerritories.add(potentialTerritory); unitMoveMap.put(myLandUnit, unitMoveTerritories); } } } } } private void findAirMoveOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final Match<Territory> moveToTerritoryMatch, final List<Territory> enemyTerritories, final List<Territory> alliedTerritories, final boolean isCombatMove, final boolean isCheckingEnemyAttacks, final boolean isIgnoringRelationships) { final GameData data = ProData.getData(); // TODO: add carriers to landing possibilities for non-enemy attacks // Find possible carrier landing territories final Set<Territory> possibleCarrierTerritories = new HashSet<>(); if (isCheckingEnemyAttacks || !isCombatMove) { final Map<Unit, Set<Territory>> unitMoveMap2 = new HashMap<>(); findNavalMoveOptions(player, myUnitTerritories, new HashMap<>(), unitMoveMap2, new HashMap<>(), Matches.TerritoryIsWater, enemyTerritories, false, true); for (final Unit u : unitMoveMap2.keySet()) { if (Matches.UnitIsCarrier.match(u)) { possibleCarrierTerritories.addAll(unitMoveMap2.get(u)); } } for (final Territory t : data.getMap().getTerritories()) { if (t.getUnits().someMatch(Matches.UnitIsAlliedCarrier(player, data))) { possibleCarrierTerritories.add(t); } } } for (final Territory myUnitTerritory : myUnitTerritories) { // Find my air units that have movement left final List<Unit> myAirUnits = myUnitTerritory.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedAir(player, isCombatMove)); // Check each air unit individually since they can have different ranges for (final Unit myAirUnit : myAirUnits) { // Find range int range = TripleAUnit.get(myAirUnit).getMovementLeft(); if (isCheckingEnemyAttacks) { range = UnitAttachment.get(myAirUnit.getType()).getMovement(player); if (Matches.UnitCanBeGivenBonusMovementByFacilitiesInItsTerritory(myUnitTerritory, player, data) .match(myAirUnit)) { range++; // assumes bonus of +1 for now } } // Find potential territories to move to Set<Territory> possibleMoveTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanMoveAirUnits(player, data, isCombatMove)); if (isIgnoringRelationships) { possibleMoveTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanPotentiallyMoveAirUnits(player, data, isCombatMove)); } possibleMoveTerritories.add(myUnitTerritory); final Set<Territory> potentialTerritories = new HashSet<>(Match.getMatches(possibleMoveTerritories, moveToTerritoryMatch)); if (!isCombatMove && Matches.UnitCanLandOnCarrier.match(myAirUnit)) { potentialTerritories .addAll(Match.getMatches(possibleMoveTerritories, Matches.territoryIsInList(possibleCarrierTerritories))); } for (final Territory potentialTerritory : potentialTerritories) { // Find route ignoring impassable and territories with AA Match<Territory> canFlyOverMatch = ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, isCombatMove); if (isCheckingEnemyAttacks) { canFlyOverMatch = ProMatches.territoryCanMoveAirUnits(player, data, isCombatMove); } final Route myRoute = data.getMap().getRoute_IgnoreEnd(myUnitTerritory, potentialTerritory, canFlyOverMatch); if (myRoute == null) { continue; } final int myRouteLength = myRoute.numberOfSteps(); final int remainingMoves = range - myRouteLength; if (remainingMoves < 0) { continue; } // Check if unit can land if (isCombatMove && (remainingMoves < myRouteLength || myUnitTerritory.isWater())) { final Set<Territory> possibleLandingTerritories = data.getMap().getNeighbors(potentialTerritory, remainingMoves, canFlyOverMatch); final List<Territory> landingTerritories = Match.getMatches(possibleLandingTerritories, ProMatches.territoryCanLandAirUnits(player, data, isCombatMove, enemyTerritories, alliedTerritories)); List<Territory> carrierTerritories = new ArrayList<>(); if (Matches.UnitCanLandOnCarrier.match(myAirUnit)) { carrierTerritories = Match.getMatches(possibleLandingTerritories, Matches.territoryIsInList(possibleCarrierTerritories)); } if (landingTerritories.isEmpty() && carrierTerritories.isEmpty()) { continue; } } // Populate enemy territories with air unit if (moveMap.containsKey(potentialTerritory)) { moveMap.get(potentialTerritory).addMaxUnit(myAirUnit); } else { final ProTerritory moveTerritoryData = new ProTerritory(potentialTerritory); moveTerritoryData.addMaxUnit(myAirUnit); moveMap.put(potentialTerritory, moveTerritoryData); } // Populate unit attack options map if (unitMoveMap.containsKey(myAirUnit)) { unitMoveMap.get(myAirUnit).add(potentialTerritory); } else { final Set<Territory> unitMoveTerritories = new HashSet<>(); unitMoveTerritories.add(potentialTerritory); unitMoveMap.put(myAirUnit, unitMoveTerritories); } } } } } private void findAmphibMoveOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final List<ProTransport> transportMapList, final Map<Territory, Set<Territory>> landRoutesMap, final Match<Territory> moveAmphibToTerritoryMatch, final boolean isCombatMove, final boolean isCheckingEnemyAttacks, final boolean isIgnoringRelationships) { final GameData data = ProData.getData(); for (final Territory myUnitTerritory : myUnitTerritories) { // Find my transports and amphibious units that have movement left final List<Unit> myTransportUnits = myUnitTerritory.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedTransport(player, isCombatMove)); Match<Territory> unloadAmphibTerritoryMatch = new CompositeMatchAnd<>( ProMatches.territoryCanMoveLandUnits(player, data, isCombatMove), moveAmphibToTerritoryMatch); if (isIgnoringRelationships) { unloadAmphibTerritoryMatch = new CompositeMatchAnd<>( ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, isCombatMove), moveAmphibToTerritoryMatch); } // Check each transport unit individually since they can have different ranges for (final Unit myTransportUnit : myTransportUnits) { // Get remaining moves int movesLeft = TripleAUnit.get(myTransportUnit).getMovementLeft(); if (isCheckingEnemyAttacks) { movesLeft = UnitAttachment.get(myTransportUnit.getType()).getMovement(player); if (Matches.UnitCanBeGivenBonusMovementByFacilitiesInItsTerritory(myUnitTerritory, player, data) .match(myTransportUnit)) { movesLeft++; // assumes bonus of +1 for now } } // Find units to load and territories to unload final ProTransport proTransportData = new ProTransport(myTransportUnit); transportMapList.add(proTransportData); final Set<Territory> currentTerritories = new HashSet<>(); currentTerritories.add(myUnitTerritory); while (movesLeft >= 0) { final Set<Territory> nextTerritories = new HashSet<>(); for (final Territory currentTerritory : currentTerritories) { // Find neighbors I can move to final Set<Territory> possibleNeighborTerritories = data.getMap().getNeighbors(currentTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); for (final Territory possibleNeighborTerritory : possibleNeighborTerritories) { if (MoveValidator.validateCanal(new Route(currentTerritory, possibleNeighborTerritory), Collections.singletonList(myTransportUnit), player, data) == null) { nextTerritories.add(possibleNeighborTerritory); } } // Get loaded units or get units that can be loaded into current territory if no enemies present final List<Unit> units = new ArrayList<>(); final Set<Territory> myUnitsToLoadTerritories = new HashSet<>(); if (TransportTracker.isTransporting(myTransportUnit)) { units.addAll(TransportTracker.transporting(myTransportUnit)); } else if (Matches.territoryHasEnemySeaUnits(player, data).invert().match(currentTerritory)) { final Set<Territory> possibleLoadTerritories = data.getMap().getNeighbors(currentTerritory); for (final Territory possibleLoadTerritory : possibleLoadTerritories) { List<Unit> possibleUnits = possibleLoadTerritory.getUnits() .getMatches(ProMatches.unitIsOwnedTransportableUnitAndCanBeLoaded(player, isCombatMove)); if (isCheckingEnemyAttacks) { possibleUnits = possibleLoadTerritory.getUnits() .getMatches(ProMatches.unitIsOwnedCombatTransportableUnit(player)); } for (final Unit possibleUnit : possibleUnits) { if (UnitAttachment.get(possibleUnit.getType()).getTransportCost() <= UnitAttachment .get(myTransportUnit.getType()).getTransportCapacity()) { units.add(possibleUnit); myUnitsToLoadTerritories.add(possibleLoadTerritory); } } } } // If there are any units to be transported if (!units.isEmpty()) { // Find all water territories I can move to final Set<Territory> seaMoveTerritories = new HashSet<>(); seaMoveTerritories.add(currentTerritory); if (movesLeft > 0) { Set<Territory> neighborTerritories = data.getMap().getNeighbors(currentTerritory, movesLeft, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); if (isCheckingEnemyAttacks) { neighborTerritories = data.getMap().getNeighbors(currentTerritory, movesLeft, ProMatches.territoryCanMoveSeaUnits(player, data, isCombatMove)); } for (final Territory neighborTerritory : neighborTerritories) { final Route myRoute = data.getMap().getRoute_IgnoreEnd(currentTerritory, neighborTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); if (myRoute == null) { continue; } if (MoveValidator.validateCanal(myRoute, Collections.singletonList(myTransportUnit), player, data) != null) { continue; } seaMoveTerritories.add(neighborTerritory); } } // Find possible unload territories final Set<Territory> amphibTerritories = new HashSet<>(); for (final Territory seaMoveTerritory : seaMoveTerritories) { amphibTerritories.addAll(data.getMap().getNeighbors(seaMoveTerritory, unloadAmphibTerritoryMatch)); } // Add to transport map proTransportData.addTerritories(amphibTerritories, myUnitsToLoadTerritories); proTransportData.addSeaTerritories(seaMoveTerritories, myUnitsToLoadTerritories, data); } } currentTerritories.clear(); currentTerritories.addAll(nextTerritories); movesLeft--; } } } // Remove any territories from transport map that I can move to on land and transports with no amphib options for (final ProTransport proTransportData : transportMapList) { final Map<Territory, Set<Territory>> transportMap = proTransportData.getTransportMap(); final List<Territory> transportTerritoriesToRemove = new ArrayList<>(); for (final Territory t : transportMap.keySet()) { final Set<Territory> transportMoveTerritories = transportMap.get(t); final Set<Territory> landMoveTerritories = landRoutesMap.get(t); if (landMoveTerritories != null) { transportMoveTerritories.removeAll(landMoveTerritories); if (transportMoveTerritories.isEmpty()) { transportTerritoriesToRemove.add(t); } } } for (final Territory t : transportTerritoriesToRemove) { transportMap.remove(t); } } // Add transport units to attack map for (final ProTransport proTransportData : transportMapList) { final Map<Territory, Set<Territory>> transportMap = proTransportData.getTransportMap(); final Unit transport = proTransportData.getTransport(); for (final Territory moveTerritory : transportMap.keySet()) { // Get units to transport final Set<Territory> territoriesCanLoadFrom = transportMap.get(moveTerritory); List<Unit> alreadyAddedToMaxAmphibUnits = new ArrayList<>(); if (moveMap.containsKey(moveTerritory)) { alreadyAddedToMaxAmphibUnits = moveMap.get(moveTerritory).getMaxAmphibUnits(); } List<Unit> amphibUnits = ProTransportUtils.getUnitsToTransportFromTerritories(player, transport, territoriesCanLoadFrom, alreadyAddedToMaxAmphibUnits); if (isCheckingEnemyAttacks) { amphibUnits = ProTransportUtils.getUnitsToTransportFromTerritories(player, transport, territoriesCanLoadFrom, alreadyAddedToMaxAmphibUnits, ProMatches.unitIsOwnedCombatTransportableUnit(player)); } // Add amphib units to attack map if (moveMap.containsKey(moveTerritory)) { moveMap.get(moveTerritory).addMaxAmphibUnits(amphibUnits); } else { final ProTerritory moveTerritoryData = new ProTerritory(moveTerritory); moveTerritoryData.addMaxAmphibUnits(amphibUnits); moveMap.put(moveTerritory, moveTerritoryData); } } } } private void findBombardOptions(final PlayerID player, final List<Territory> myUnitTerritories, final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> bombardMap, final List<ProTransport> transportMapList, final boolean isCheckingEnemyAttacks) { final GameData data = ProData.getData(); // Find all transport unload from and to territories final Set<Territory> unloadFromTerritories = new HashSet<>(); final Set<Territory> unloadToTerritories = new HashSet<>(); for (final ProTransport amphibData : transportMapList) { unloadFromTerritories.addAll(amphibData.getSeaTransportMap().keySet()); unloadToTerritories.addAll(amphibData.getTransportMap().keySet()); } // Loop through territories with my units for (final Territory myUnitTerritory : myUnitTerritories) { // Find my bombard units that have movement left final List<Unit> mySeaUnits = myUnitTerritory.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedBombard(player)); // Check each sea unit individually since they can have different ranges for (final Unit mySeaUnit : mySeaUnits) { // Find range int range = TripleAUnit.get(mySeaUnit).getMovementLeft(); if (isCheckingEnemyAttacks) { range = UnitAttachment.get(mySeaUnit.getType()).getMovement(player); if (Matches.UnitCanBeGivenBonusMovementByFacilitiesInItsTerritory(myUnitTerritory, player, data) .match(mySeaUnit)) { range++; // assumes bonus of +1 for now } } // Find list of potential territories to move to final Set<Territory> potentialTerritories = data.getMap().getNeighbors(myUnitTerritory, range, ProMatches.territoryCanMoveSeaUnits(player, data, true)); potentialTerritories.add(myUnitTerritory); potentialTerritories.retainAll(unloadFromTerritories); for (final Territory bombardFromTerritory : potentialTerritories) { // Find route over water with no enemy units blocking Route myRoute = data.getMap().getRoute(myUnitTerritory, bombardFromTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, true)); if (isCheckingEnemyAttacks) { myRoute = data.getMap().getRoute(myUnitTerritory, bombardFromTerritory, ProMatches.territoryCanMoveSeaUnits(player, data, true)); } if (myRoute == null) { continue; } if (MoveValidator.validateCanal(myRoute, Collections.singletonList(mySeaUnit), player, data) != null) { continue; } final int myRouteLength = myRoute.numberOfSteps(); if (myRouteLength > range) { continue; } // Find potential unload to territories final Set<Territory> bombardToTerritories = new HashSet<>(data.getMap().getNeighbors(bombardFromTerritory)); bombardToTerritories.retainAll(unloadToTerritories); // Populate attack territories with bombard unit for (final Territory bombardToTerritory : bombardToTerritories) { if (moveMap.containsKey(bombardToTerritory)) { // Should always contain it moveMap.get(bombardToTerritory).addMaxBombardUnit(mySeaUnit); moveMap.get(bombardToTerritory).addBombardOptionsMap(mySeaUnit, bombardFromTerritory); } } // Populate bombard options map if (bombardMap.containsKey(mySeaUnit)) { bombardMap.get(mySeaUnit).addAll(bombardToTerritories); } else { bombardMap.put(mySeaUnit, bombardToTerritories); } } } } } private List<ProTerritory> removeTerritoriesThatCantBeConquered(final PlayerID player, final Map<Territory, ProTerritory> attackMap, final Map<Unit, Set<Territory>> unitAttackMap, final Map<Unit, Set<Territory>> transportAttackMap, final ProOtherMoveOptions alliedAttackOptions, final ProOtherMoveOptions enemyDefendOptions, final boolean isIgnoringRelationships) { ProLogger.info("Removing territories that can't be conquered"); final GameData data = ProData.getData(); // Determine if territory can be successfully attacked with max possible attackers final List<Territory> territoriesToRemove = new ArrayList<>(); for (final Territory t : attackMap.keySet()) { final ProTerritory patd = attackMap.get(t); // Check if I can win without amphib units and ignore AA since max units might have lots of planes List<Unit> defenders = Match.getMatches(patd.getMaxEnemyDefenders(player, data), ProMatches.unitIsEnemyAndNotAA(player, data)); if (isIgnoringRelationships) { defenders = new ArrayList<>(t.getUnits().getUnits()); } patd.setMaxBattleResult( calc.estimateAttackBattleResults(player, t, patd.getMaxUnits(), defenders, new HashSet<>())); // Add in amphib units if I can't win without them if (patd.getMaxBattleResult().getWinPercentage() < ProData.winPercentage && !patd.getMaxAmphibUnits().isEmpty()) { final Set<Unit> combinedUnits = new HashSet<>(patd.getMaxUnits()); combinedUnits.addAll(patd.getMaxAmphibUnits()); patd.setMaxBattleResult(calc.estimateAttackBattleResults(player, t, new ArrayList<>(combinedUnits), defenders, patd.getMaxBombardUnits())); patd.setNeedAmphibUnits(true); } // Check strafing and using allied attack if enemy capital/factory boolean isEnemyCapitalOrFactory = false; final TerritoryAttachment ta = TerritoryAttachment.get(t); if (!t.getOwner().isNull() && ((ta != null && ta.isCapital()) || ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t))) { isEnemyCapitalOrFactory = true; } if (patd.getMaxBattleResult().getWinPercentage() < ProData.minWinPercentage && isEnemyCapitalOrFactory && alliedAttackOptions.getMax(t) != null) { // Check for allied attackers final ProTerritory alliedAttack = alliedAttackOptions.getMax(t); final Set<Unit> alliedUnits = new HashSet<>(alliedAttack.getMaxUnits()); alliedUnits.addAll(alliedAttack.getMaxAmphibUnits()); if (!alliedUnits.isEmpty()) { // Make sure allies' capital isn't next to territory final PlayerID alliedPlayer = alliedUnits.iterator().next().getOwner(); final Territory capital = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(alliedPlayer, data); if (capital != null && !data.getMap().getNeighbors(capital).contains(t)) { // Get max enemy defenders final Set<Unit> additionalEnemyDefenders = new HashSet<>(); final List<PlayerID> players = ProUtils.getOtherPlayersInTurnOrder(player); for (final ProTerritory enemyDefendOption : enemyDefendOptions.getAll(t)) { final Set<Unit> enemyUnits = new HashSet<>(enemyDefendOption.getMaxUnits()); enemyUnits.addAll(enemyDefendOption.getMaxAmphibUnits()); if (!enemyUnits.isEmpty()) { final PlayerID enemyPlayer = enemyUnits.iterator().next().getOwner(); if (ProUtils.isPlayersTurnFirst(players, enemyPlayer, alliedPlayer)) { additionalEnemyDefenders.addAll(enemyUnits); } } } // Check allied result without strafe final Set<Unit> enemyDefendersBeforeStrafe = new HashSet<>(defenders); enemyDefendersBeforeStrafe.addAll(additionalEnemyDefenders); final ProBattleResult result = calc.estimateAttackBattleResults(alliedPlayer, t, new ArrayList<>(alliedUnits), new ArrayList<>(enemyDefendersBeforeStrafe), alliedAttack.getMaxBombardUnits()); if (result.getWinPercentage() < ProData.winPercentage) { patd.setStrafing(true); // Try to strafe to allow allies to conquer territory final Set<Unit> combinedUnits = new HashSet<>(patd.getMaxUnits()); combinedUnits.addAll(patd.getMaxAmphibUnits()); final ProBattleResult strafeResult = calc.callBattleCalculator(player, t, new ArrayList<>(combinedUnits), defenders, patd.getMaxBombardUnits(), true); // Check allied result with strafe final Set<Unit> enemyDefendersAfterStrafe = new HashSet<>(strafeResult.getAverageDefendersRemaining()); enemyDefendersAfterStrafe.addAll(additionalEnemyDefenders); patd.setMaxBattleResult(calc.estimateAttackBattleResults(alliedPlayer, t, new ArrayList<>(alliedUnits), new ArrayList<>(enemyDefendersAfterStrafe), alliedAttack.getMaxBombardUnits())); ProLogger.debug("Checking strafing territory: " + t + ", alliedPlayer=" + alliedUnits.iterator().next().getOwner().getName() + ", maxWin%=" + patd.getMaxBattleResult().getWinPercentage() + ", maxAttackers=" + alliedUnits.size() + ", maxDefenders=" + enemyDefendersAfterStrafe.size()); if (patd.getMaxBattleResult().getWinPercentage() >= ProData.winPercentage) { System.out.println(data.getSequence().getRound() + " - " + player.getName() + ". strafing territory: " + t + ", alliedPlayer=" + alliedUnits.iterator().next().getOwner().getName() + ", maxWin%=" + patd.getMaxBattleResult().getWinPercentage() + ", maxAttackers=" + alliedUnits.size() + ", maxDefenders=" + enemyDefendersAfterStrafe.size()); } } } } } if (patd.getMaxBattleResult().getWinPercentage() < ProData.minWinPercentage || (patd.isStrafing() && (patd.getMaxBattleResult().getWinPercentage() < ProData.winPercentage || !patd.getMaxBattleResult().isHasLandUnitRemaining()))) { territoriesToRemove.add(t); } } // Remove territories that can't be successfully attacked Collections.sort(territoriesToRemove); final List<ProTerritory> result = new ArrayList<>(attackMap.values()); for (final Territory t : territoriesToRemove) { final ProTerritory proTerritoryToRemove = attackMap.get(t); final Set<Unit> combinedUnits = new HashSet<>(proTerritoryToRemove.getMaxUnits()); combinedUnits.addAll(proTerritoryToRemove.getMaxAmphibUnits()); ProLogger.debug("Removing territory that we can't successfully attack: " + t + ", maxWin%=" + proTerritoryToRemove.getMaxBattleResult().getWinPercentage() + ", maxAttackers=" + combinedUnits.size()); result.remove(proTerritoryToRemove); for (final Set<Territory> territories : unitAttackMap.values()) { territories.remove(t); } for (final Set<Territory> territories : transportAttackMap.values()) { territories.remove(t); } } return result; } }