package games.strategy.triplea.ai.proAI.util; import java.util.ArrayList; import java.util.List; 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.Properties; import games.strategy.triplea.ai.proAI.ProData; import games.strategy.triplea.ai.proAI.data.ProBattleResult; import games.strategy.triplea.delegate.BattleCalculator; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.TerritoryEffectHelper; import games.strategy.triplea.oddsCalculator.ta.AggregateResults; import games.strategy.triplea.oddsCalculator.ta.IOddsCalculator; import games.strategy.util.Match; /** * Pro AI odds calculator. */ public class ProOddsCalculator { private final IOddsCalculator calc; private boolean isCanceled = false; public ProOddsCalculator(final IOddsCalculator calc) { this.calc = calc; } public void setData(final GameData data) { calc.setGameData(data); } public void cancelCalcs() { calc.cancel(); isCanceled = true; } public ProBattleResult estimateAttackBattleResults(final PlayerID player, final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits, final Set<Unit> bombardingUnits) { final ProBattleResult result = checkIfNoAttackersOrDefenders(t, attackingUnits, defendingUnits); if (result != null) { return result; } // Determine if attackers have no chance final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackingUnits, defendingUnits); if (strengthDifference < 45) { return new ProBattleResult(0, -999, false, new ArrayList<>(), defendingUnits, 1); } return callBattleCalculator(player, t, attackingUnits, defendingUnits, bombardingUnits); } public ProBattleResult estimateDefendBattleResults(final PlayerID player, final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits, final Set<Unit> bombardingUnits) { final ProBattleResult result = checkIfNoAttackersOrDefenders(t, attackingUnits, defendingUnits); if (result != null) { return result; } // Determine if defenders have no chance final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackingUnits, defendingUnits); if (strengthDifference > 55) { final boolean isLandAndCanOnlyBeAttackedByAir = !t.isWater() && Match.allMatch(attackingUnits, Matches.UnitIsAir); return new ProBattleResult(100 + strengthDifference, 999 + strengthDifference, !isLandAndCanOnlyBeAttackedByAir, attackingUnits, new ArrayList<>(), 1); } return callBattleCalculator(player, t, attackingUnits, defendingUnits, bombardingUnits); } public ProBattleResult calculateBattleResults(final PlayerID player, final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits, final Set<Unit> bombardingUnits, final boolean isAttacker) { final ProBattleResult result = checkIfNoAttackersOrDefenders(t, attackingUnits, defendingUnits); if (result != null) { return result; } return callBattleCalculator(player, t, attackingUnits, defendingUnits, bombardingUnits); } private ProBattleResult checkIfNoAttackersOrDefenders(final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits) { final GameData data = ProData.getData(); final boolean hasNoDefenders = Match.noneMatch(defendingUnits, Matches.UnitIsNotInfrastructure); final boolean isLandAndCanOnlyBeAttackedByAir = !t.isWater() && Match.allMatch(attackingUnits, Matches.UnitIsAir); if (attackingUnits.size() == 0) { return new ProBattleResult(); } else if (hasNoDefenders && isLandAndCanOnlyBeAttackedByAir) { return new ProBattleResult(); } else if (hasNoDefenders) { return new ProBattleResult(100, 0.1, true, attackingUnits, new ArrayList<>(), 0); } else if (Properties.getSubRetreatBeforeBattle(data) && Match.allMatch(defendingUnits, Matches.UnitIsSub) && Match.noneMatch(attackingUnits, Matches.UnitIsDestroyer)) { return new ProBattleResult(); } return null; } public ProBattleResult callBattleCalculator(final PlayerID player, final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits, final Set<Unit> bombardingUnits) { return callBattleCalculator(player, t, attackingUnits, defendingUnits, bombardingUnits, false); } public ProBattleResult callBattleCalculator(final PlayerID player, final Territory t, final List<Unit> attackingUnits, final List<Unit> defendingUnits, final Set<Unit> bombardingUnits, final boolean retreatWhenOnlyAirLeft) { final GameData data = ProData.getData(); if (isCanceled || attackingUnits.isEmpty() || defendingUnits.isEmpty()) { return new ProBattleResult(); } // Use battle calculator (hasLandUnitRemaining is always true for naval territories) AggregateResults results = null; final int minArmySize = Math.min(attackingUnits.size(), defendingUnits.size()); final int runCount = Math.max(16, 100 - minArmySize); final PlayerID attacker = attackingUnits.get(0).getOwner(); final PlayerID defender = defendingUnits.get(0).getOwner(); if (retreatWhenOnlyAirLeft) { calc.setRetreatWhenOnlyAirLeft(true); } results = calc.setCalculateDataAndCalculate(attacker, defender, t, attackingUnits, defendingUnits, new ArrayList<>(bombardingUnits), TerritoryEffectHelper.getEffects(t), runCount); if (retreatWhenOnlyAirLeft) { calc.setRetreatWhenOnlyAirLeft(false); } // Find battle result statistics final double winPercentage = results.getAttackerWinPercent() * 100; final List<Unit> averageAttackersRemaining = results.getAverageAttackingUnitsRemaining(); final List<Unit> averageDefendersRemaining = results.getAverageDefendingUnitsRemaining(); final List<Unit> mainCombatAttackers = Match.getMatches(attackingUnits, Matches.UnitCanBeInBattle(true, !t.isWater(), 1, false, true, true)); final List<Unit> mainCombatDefenders = Match.getMatches(defendingUnits, Matches.UnitCanBeInBattle(false, !t.isWater(), 1, false, true, true)); double TUVswing = results.getAverageTUVswing(attacker, mainCombatAttackers, defender, mainCombatDefenders, data); if (Matches.TerritoryIsNeutralButNotWater.match(t)) { // Set TUV swing for neutrals final double attackingUnitValue = BattleCalculator.getTUV(mainCombatAttackers, ProData.unitValueMap); final double remainingUnitValue = results.getAverageTUVofUnitsLeftOver(ProData.unitValueMap, ProData.unitValueMap).getFirst(); TUVswing = remainingUnitValue - attackingUnitValue; } final List<Unit> defendingTransportedUnits = Match.getMatches(defendingUnits, Matches.unitIsBeingTransported()); if (t.isWater() && !defendingTransportedUnits.isEmpty()) { // Add TUV swing for transported units final double transportedUnitValue = BattleCalculator.getTUV(defendingTransportedUnits, ProData.unitValueMap); TUVswing += transportedUnitValue * winPercentage / 100; } // Create battle result object final List<Territory> tList = new ArrayList<>(); tList.add(t); if (Match.allMatch(tList, Matches.TerritoryIsLand)) { return new ProBattleResult(winPercentage, TUVswing, Match.someMatch(averageAttackersRemaining, Matches.UnitIsLand), averageAttackersRemaining, averageDefendersRemaining, results.getAverageBattleRoundsFought()); } else { return new ProBattleResult(winPercentage, TUVswing, !averageAttackersRemaining.isEmpty(), averageAttackersRemaining, averageDefendersRemaining, results.getAverageBattleRoundsFought()); } } }