package games.strategy.triplea.ai.proAI.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
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.triplea.Properties;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.ai.proAI.data.ProPlaceTerritory;
import games.strategy.triplea.ai.proAI.data.ProPurchaseTerritory;
import games.strategy.triplea.ai.proAI.data.ProTerritory;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.delegate.BattleCalculator;
import games.strategy.triplea.delegate.DiceRoll;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.MoveValidator;
import games.strategy.triplea.delegate.TerritoryEffectHelper;
import games.strategy.triplea.delegate.UnitBattleComparator;
import games.strategy.util.Match;
/**
* Pro AI battle utilities.
*/
public class ProBattleUtils {
public static final int SHORT_RANGE = 2;
public static final int MEDIUM_RANGE = 3;
public static boolean checkForOverwhelmingWin(final PlayerID player, final Territory t,
final List<Unit> attackingUnits, final List<Unit> defendingUnits) {
final GameData data = ProData.getData();
if (defendingUnits.isEmpty() && !attackingUnits.isEmpty()) {
return true;
}
// Check that defender has at least 1 power
final double power = estimatePower(t, defendingUnits, attackingUnits, false);
if (power == 0 && !attackingUnits.isEmpty()) {
return true;
}
// Determine if enough attack power to win in 1 round
final List<Unit> sortedUnitsList = new ArrayList<>(attackingUnits);
Collections.sort(sortedUnitsList,
new UnitBattleComparator(false, ProData.unitValueMap, TerritoryEffectHelper.getEffects(t), data, false, false));
Collections.reverse(sortedUnitsList);
final int attackPower = DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList,
defendingUnits, false, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
final List<Unit> defendersWithHitPoints = Match.getMatches(defendingUnits, Matches.UnitIsInfrastructure.invert());
final int totalDefenderHitPoints = BattleCalculator.getTotalHitpointsLeft(defendersWithHitPoints);
return ((attackPower / data.getDiceSides()) >= totalDefenderHitPoints);
}
public static double estimateStrengthDifference(final Territory t, final List<Unit> attackingUnits,
final List<Unit> defendingUnits) {
if (attackingUnits.size() == 0) {
return 0;
}
final List<Unit> actualDefenders = Match.getMatches(defendingUnits, Matches.UnitIsInfrastructure.invert());
if (actualDefenders.size() == 0) {
return 100;
}
final double attackerStrength = estimateStrength(t, attackingUnits, actualDefenders, true);
final double defenderStrength = estimateStrength(t, actualDefenders, attackingUnits, false);
return ((attackerStrength - defenderStrength) / Math.pow(defenderStrength, 0.85) * 50 + 50);
}
public static double estimateStrength(final Territory t, final List<Unit> myUnits, final List<Unit> enemyUnits,
final boolean attacking) {
final GameData data = ProData.getData();
List<Unit> unitsThatCanFight =
Match.getMatches(myUnits, Matches.UnitCanBeInBattle(attacking, !t.isWater(), 1, false, true, true));
if (Properties.getTransportCasualtiesRestricted(data)) {
unitsThatCanFight = Match.getMatches(unitsThatCanFight, Matches.UnitIsTransportButNotCombatTransport.invert());
}
final int myHP = BattleCalculator.getTotalHitpointsLeft(unitsThatCanFight);
final double myPower = estimatePower(t, myUnits, enemyUnits, attacking);
return (2 * myHP) + myPower;
}
private static double estimatePower(final Territory t, final List<Unit> myUnits, final List<Unit> enemyUnits,
final boolean attacking) {
final GameData data = ProData.getData();
final List<Unit> unitsThatCanFight =
Match.getMatches(myUnits, Matches.UnitCanBeInBattle(attacking, !t.isWater(), 1, false, true, true));
final List<Unit> sortedUnitsList = new ArrayList<>(unitsThatCanFight);
Collections.sort(sortedUnitsList, new UnitBattleComparator(!attacking, ProData.unitValueMap,
TerritoryEffectHelper.getEffects(t), data, false, false));
Collections.reverse(sortedUnitsList);
final int myPower = DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList,
enemyUnits, !attacking, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
return (myPower * 6.0 / data.getDiceSides());
}
public static boolean territoryHasLocalLandSuperiority(final Territory t, final int distance, final PlayerID player) {
return territoryHasLocalLandSuperiority(t, distance, player, new HashMap<>());
}
public static boolean territoryHasLocalLandSuperiority(final Territory t, final int distance, final PlayerID player,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
final GameData data = ProData.getData();
if (t == null) {
return true;
}
for (int i = 2; i <= distance; i++) {
// Find enemy strength
final Set<Territory> nearbyTerritoriesForEnemy =
data.getMap().getNeighbors(t, i, ProMatches.territoryCanMoveLandUnits(player, data, false));
nearbyTerritoriesForEnemy.add(t);
final List<Unit> enemyUnits = new ArrayList<>();
for (final Territory nearbyTerritory : nearbyTerritoriesForEnemy) {
enemyUnits.addAll(nearbyTerritory.getUnits().getMatches(ProMatches.unitIsEnemyNotNeutral(player, data)));
}
// Find allied strength
final Set<Territory> nearbyTerritoriesForAllied =
data.getMap().getNeighbors(t, i - 1, ProMatches.territoryCanMoveLandUnits(player, data, false));
nearbyTerritoriesForAllied.add(t);
final List<Unit> alliedUnits = new ArrayList<>();
for (final Territory nearbyTerritory : nearbyTerritoriesForAllied) {
alliedUnits.addAll(nearbyTerritory.getUnits().getMatches(Matches.isUnitAllied(player, data)));
}
for (final Territory purchaseTerritory : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(purchaseTerritory).getCanPlaceTerritories()) {
if (nearbyTerritoriesForAllied.contains(ppt.getTerritory())) {
alliedUnits.addAll(ppt.getPlaceUnits());
}
}
}
// Determine strength difference
final double strengthDifference = estimateStrengthDifference(t, enemyUnits, alliedUnits);
ProLogger.trace(t + ", current enemy land strengthDifference=" + strengthDifference + ", distance=" + i
+ ", enemySize=" + enemyUnits.size() + ", alliedSize=" + alliedUnits.size());
if (strengthDifference > 50) {
return false;
}
}
return true;
}
public static boolean territoryHasLocalLandSuperiorityAfterMoves(final Territory t, final int distance,
final PlayerID player, final Map<Territory, ProTerritory> moveMap) {
final GameData data = ProData.getData();
// Find enemy strength
final Set<Territory> nearbyTerritoriesForEnemy =
data.getMap().getNeighbors(t, distance, ProMatches.territoryCanMoveLandUnits(player, data, false));
nearbyTerritoriesForEnemy.add(t);
final List<Unit> enemyUnits = new ArrayList<>();
for (final Territory nearbyTerritory : nearbyTerritoriesForEnemy) {
enemyUnits.addAll(nearbyTerritory.getUnits().getMatches(ProMatches.unitIsEnemyNotNeutral(player, data)));
}
// Find allied strength
final Set<Territory> nearbyTerritoriesForAllied =
data.getMap().getNeighbors(t, distance - 1, ProMatches.territoryCanMoveLandUnits(player, data, false));
nearbyTerritoriesForAllied.add(t);
final List<Unit> alliedUnits = new ArrayList<>();
for (final Territory nearbyTerritory : nearbyTerritoriesForAllied) {
if (moveMap.get(nearbyTerritory) != null) {
alliedUnits.addAll(moveMap.get(nearbyTerritory).getAllDefenders());
}
}
// Determine strength difference
final double strengthDifference = estimateStrengthDifference(t, enemyUnits, alliedUnits);
ProLogger.trace(t + ", current enemy land strengthDifference=" + strengthDifference + ", enemySize="
+ enemyUnits.size() + ", alliedSize=" + alliedUnits.size());
return strengthDifference <= 50;
}
public static boolean territoryHasLocalNavalSuperiority(final Territory t, final PlayerID player,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories, final List<Unit> unitsToPlace) {
final GameData data = ProData.getData();
int landDistance = ProUtils.getClosestEnemyLandTerritoryDistanceOverWater(data, player, t);
if (landDistance <= 0) {
landDistance = 10;
}
final int enemyDistance = Math.max(3, (landDistance + 1));
final int alliedDistance = (enemyDistance + 1) / 2;
final Set<Territory> nearbyTerritories = data.getMap().getNeighbors(t, enemyDistance);
final List<Territory> nearbyLandTerritories = Match.getMatches(nearbyTerritories, Matches.TerritoryIsLand);
final Set<Territory> nearbyEnemySeaTerritories =
data.getMap().getNeighbors(t, enemyDistance, Matches.TerritoryIsWater);
nearbyEnemySeaTerritories.add(t);
final Set<Territory> nearbyAlliedSeaTerritories =
data.getMap().getNeighbors(t, alliedDistance, Matches.TerritoryIsWater);
nearbyAlliedSeaTerritories.add(t);
final List<Unit> enemyUnitsInSeaTerritories = new ArrayList<>();
final List<Unit> enemyUnitsInLandTerritories = new ArrayList<>();
final List<Unit> myUnitsInSeaTerritories = new ArrayList<>();
final List<Unit> alliedUnitsInSeaTerritories = new ArrayList<>();
for (final Territory nearbyLandTerritory : nearbyLandTerritories) {
enemyUnitsInLandTerritories
.addAll(nearbyLandTerritory.getUnits().getMatches(ProMatches.unitIsEnemyAir(player, data)));
}
for (final Territory nearbySeaTerritory : nearbyEnemySeaTerritories) {
final List<Unit> enemySeaUnits =
nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsEnemyNotLand(player, data));
if (enemySeaUnits.isEmpty()) {
continue;
}
final Route route = data.getMap().getRoute_IgnoreEnd(t, nearbySeaTerritory, Matches.TerritoryIsWater);
if (route == null) {
continue;
}
if (MoveValidator.validateCanal(route, enemySeaUnits, enemySeaUnits.get(0).getOwner(), data) != null) {
continue;
}
final int routeLength = route.numberOfSteps();
if (routeLength <= enemyDistance) {
enemyUnitsInSeaTerritories.addAll(enemySeaUnits);
}
}
for (final Territory nearbySeaTerritory : nearbyAlliedSeaTerritories) {
myUnitsInSeaTerritories
.addAll(nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsOwnedNotLand(player, data)));
myUnitsInSeaTerritories.addAll(ProPurchaseUtils.getPlaceUnits(nearbySeaTerritory, purchaseTerritories));
alliedUnitsInSeaTerritories
.addAll(nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsAlliedNotOwned(player, data)));
}
ProLogger.trace(t + ", enemyDistance=" + enemyDistance + ", alliedDistance=" + alliedDistance + ", enemyAirUnits="
+ enemyUnitsInLandTerritories + ", enemySeaUnits=" + enemyUnitsInSeaTerritories + ", mySeaUnits="
+ myUnitsInSeaTerritories);
// Find current naval defense strength
final List<Unit> myUnits = new ArrayList<>(myUnitsInSeaTerritories);
myUnits.addAll(unitsToPlace);
myUnits.addAll(alliedUnitsInSeaTerritories);
final List<Unit> enemyAttackers = new ArrayList<>(enemyUnitsInSeaTerritories);
enemyAttackers.addAll(enemyUnitsInLandTerritories);
final double defenseStrengthDifference = estimateStrengthDifference(t, enemyAttackers, myUnits);
ProLogger.trace(t + ", current enemy naval attack strengthDifference=" + defenseStrengthDifference + ", enemySize="
+ enemyAttackers.size() + ", alliedSize=" + myUnits.size());
// Find current naval attack strength
double attackStrengthDifference = estimateStrengthDifference(t, myUnits, enemyUnitsInSeaTerritories);
attackStrengthDifference +=
0.5 * estimateStrengthDifference(t, alliedUnitsInSeaTerritories, enemyUnitsInSeaTerritories);
ProLogger.trace(t + ", current allied naval attack strengthDifference=" + attackStrengthDifference + ", alliedSize="
+ myUnits.size() + ", enemySize=" + enemyUnitsInSeaTerritories.size());
// If I have naval attack/defense superiority then break
return (defenseStrengthDifference < 50 && attackStrengthDifference > 50);
}
}