package games.strategy.triplea.ai.proAI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; 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.Territory; import games.strategy.engine.data.Unit; import games.strategy.triplea.ai.proAI.data.ProBattleResult; 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.ProSortMoveOptionsUtils; import games.strategy.triplea.delegate.BattleDelegate; import games.strategy.triplea.delegate.DelegateFinder; import games.strategy.triplea.delegate.IBattle; import games.strategy.triplea.delegate.IBattle.BattleType; import games.strategy.util.Tuple; /** * Pro scramble AI. */ public class ProScrambleAI { private final ProOddsCalculator calc; public ProScrambleAI(final ProAI ai) { calc = ai.getCalc(); } public HashMap<Territory, Collection<Unit>> scrambleUnitsQuery(final Territory scrambleTo, final Map<Territory, Tuple<Collection<Unit>, Collection<Unit>>> possibleScramblers) { // Get battle data final GameData data = ProData.getData(); final PlayerID player = ProData.getPlayer(); final BattleDelegate delegate = DelegateFinder.battleDelegate(data); final IBattle battle = delegate.getBattleTracker().getPendingBattle(scrambleTo, false, BattleType.NORMAL); // Check if defense already wins final List<Unit> attackers = (List<Unit>) battle.getAttackingUnits(); final List<Unit> defenders = (List<Unit>) battle.getDefendingUnits(); final Set<Unit> bombardingUnits = new HashSet<>(battle.getBombardingUnits()); final ProBattleResult minResult = calc.calculateBattleResults(player, scrambleTo, attackers, defenders, bombardingUnits, false); ProLogger .debug(scrambleTo + ", minTUVSwing=" + minResult.getTUVSwing() + ", minWin%=" + minResult.getWinPercentage()); if (minResult.getTUVSwing() <= 0 && minResult.getWinPercentage() < (100 - ProData.minWinPercentage)) { return null; } // Check if max defense is worse final Set<Unit> allScramblers = new HashSet<>(); final Map<Territory, List<Unit>> possibleMaxScramblerMap = new HashMap<>(); for (final Territory t : possibleScramblers.keySet()) { final int maxCanScramble = BattleDelegate.getMaxScrambleCount(possibleScramblers.get(t).getFirst()); List<Unit> canScrambleAir = new ArrayList<>(possibleScramblers.get(t).getSecond()); if (maxCanScramble < canScrambleAir.size()) { Collections.sort(canScrambleAir, (o1, o2) -> { final double strength1 = ProBattleUtils.estimateStrength(scrambleTo, Collections.singletonList(o1), new ArrayList<>(), false); final double strength2 = ProBattleUtils.estimateStrength(scrambleTo, Collections.singletonList(o2), new ArrayList<>(), false); return Double.compare(strength2, strength1); }); canScrambleAir = canScrambleAir.subList(0, maxCanScramble); } allScramblers.addAll(canScrambleAir); possibleMaxScramblerMap.put(t, canScrambleAir); } defenders.addAll(allScramblers); final ProBattleResult maxResult = calc.calculateBattleResults(player, scrambleTo, attackers, defenders, bombardingUnits, false); ProLogger .debug(scrambleTo + ", maxTUVSwing=" + maxResult.getTUVSwing() + ", maxWin%=" + maxResult.getWinPercentage()); if (maxResult.getTUVSwing() >= minResult.getTUVSwing()) { return null; } // Loop through all units and determine attack options final Map<Unit, Set<Territory>> unitDefendOptions = new HashMap<>(); for (final Territory t : possibleMaxScramblerMap.keySet()) { final Set<Territory> possibleTerritories = data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, true)); possibleTerritories.add(t); final Set<Territory> battleTerritories = new HashSet<>(); for (final Territory possibleTerritory : possibleTerritories) { final IBattle possibleBattle = delegate.getBattleTracker().getPendingBattle(possibleTerritory, false, BattleType.NORMAL); if (possibleBattle != null) { battleTerritories.add(possibleTerritory); } } for (final Unit u : possibleMaxScramblerMap.get(t)) { unitDefendOptions.put(u, battleTerritories); } } // Sort units by number of defend options and cost final Map<Unit, Set<Territory>> sortedUnitDefendOptions = ProSortMoveOptionsUtils.sortUnitMoveOptions(player, unitDefendOptions); // Add one scramble unit at a time and check if final result is better than min result final List<Unit> unitsToScramble = new ArrayList<>(); ProBattleResult result = minResult; for (final Unit u : sortedUnitDefendOptions.keySet()) { unitsToScramble.add(u); final List<Unit> currentDefenders = (List<Unit>) battle.getDefendingUnits(); currentDefenders.addAll(unitsToScramble); result = calc.calculateBattleResults(player, scrambleTo, attackers, currentDefenders, bombardingUnits, false); ProLogger.debug(scrambleTo + ", TUVSwing=" + result.getTUVSwing() + ", Win%=" + result.getWinPercentage() + ", addedUnit=" + u); if (result.getTUVSwing() <= 0 && result.getWinPercentage() < (100 - ProData.minWinPercentage)) { break; } } if (result.getTUVSwing() >= minResult.getTUVSwing()) { return null; } // Return units to scramble final HashMap<Territory, Collection<Unit>> scrambleMap = new HashMap<>(); for (final Territory t : possibleScramblers.keySet()) { for (final Unit u : possibleScramblers.get(t).getSecond()) { if (unitsToScramble.contains(u)) { if (scrambleMap.containsKey(t)) { scrambleMap.get(t).add(u); } else { final Collection<Unit> units = new ArrayList<>(); units.add(u); scrambleMap.put(t, units); } } } } return scrambleMap; } }