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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
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.TripleAUnit;
import games.strategy.triplea.ai.proAI.data.ProBattleResult;
import games.strategy.triplea.ai.proAI.data.ProOtherMoveOptions;
import games.strategy.triplea.ai.proAI.data.ProPurchaseOption;
import games.strategy.triplea.ai.proAI.data.ProTerritory;
import games.strategy.triplea.ai.proAI.data.ProTerritoryManager;
import games.strategy.triplea.ai.proAI.data.ProTransport;
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.ProMoveUtils;
import games.strategy.triplea.ai.proAI.util.ProOddsCalculator;
import games.strategy.triplea.ai.proAI.util.ProPurchaseUtils;
import games.strategy.triplea.ai.proAI.util.ProSortMoveOptionsUtils;
import games.strategy.triplea.ai.proAI.util.ProTerritoryValueUtils;
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.TransportTracker;
import games.strategy.triplea.delegate.remote.IMoveDelegate;
import games.strategy.util.Match;
/**
* Pro combat move AI.
*/
public class ProCombatMoveAI {
private static final int MIN_BOMBING_SCORE = 4; // Avoid bombing low production factories with AA
private final ProAI ai;
private final ProOddsCalculator calc;
private GameData data;
private PlayerID player;
private ProTerritoryManager territoryManager;
private boolean isDefensive;
private boolean isBombing;
public ProCombatMoveAI(final ProAI ai) {
this.ai = ai;
calc = ai.getCalc();
}
public Map<Territory, ProTerritory> doCombatMove(final IMoveDelegate moveDel) {
ProLogger.info("Starting combat move phase");
// Current data at the start of combat move
data = ProData.getData();
player = ProData.getPlayer();
territoryManager = new ProTerritoryManager(calc);
// Determine whether capital is threatened and I should be in a defensive stance
isDefensive =
!ProBattleUtils.territoryHasLocalLandSuperiority(ProData.myCapital, ProBattleUtils.MEDIUM_RANGE, player);
isBombing = false;
ProLogger.debug("Currently in defensive stance: " + isDefensive);
// Find the maximum number of units that can attack each territory and max enemy defenders
territoryManager.populateAttackOptions();
territoryManager.populateEnemyDefenseOptions();
// Remove territories that aren't worth attacking and prioritize the remaining ones
final List<ProTerritory> attackOptions = territoryManager.removeTerritoriesThatCantBeConquered();
List<Territory> clearedTerritories = new ArrayList<>();
for (final ProTerritory patd : attackOptions) {
clearedTerritories.add(patd.getTerritory());
}
territoryManager.populateEnemyAttackOptions(clearedTerritories, new ArrayList<>());
Set<Territory> territoriesToCheck = new HashSet<>(clearedTerritories);
territoriesToCheck.addAll(ProData.myUnitTerritories);
Map<Territory, Double> territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, new ArrayList<>(), clearedTerritories, territoriesToCheck);
determineTerritoriesThatCanBeHeld(attackOptions, territoryValueMap);
prioritizeAttackOptions(player, attackOptions);
removeTerritoriesThatArentWorthAttacking(attackOptions);
// Determine which territories to attack
determineTerritoriesToAttack(attackOptions);
// Determine which territories can be held and remove any that aren't worth attacking
clearedTerritories = new ArrayList<>();
final Set<Territory> possibleTransportTerritories = new HashSet<>();
for (final ProTerritory patd : attackOptions) {
clearedTerritories.add(patd.getTerritory());
if (!patd.getAmphibAttackMap().isEmpty()) {
possibleTransportTerritories.addAll(data.getMap().getNeighbors(patd.getTerritory(), Matches.TerritoryIsWater));
}
}
territoryManager.populateEnemyAttackOptions(clearedTerritories, new ArrayList<>(possibleTransportTerritories));
territoriesToCheck = new HashSet<>(clearedTerritories);
territoriesToCheck.addAll(ProData.myUnitTerritories);
territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, new ArrayList<>(), clearedTerritories, territoriesToCheck);
determineTerritoriesThatCanBeHeld(attackOptions, territoryValueMap);
removeTerritoriesThatArentWorthAttacking(attackOptions);
// Determine how many units to attack each territory with
final List<Unit> alreadyMovedUnits = moveOneDefenderToLandTerritoriesBorderingEnemy(attackOptions);
determineUnitsToAttackWith(attackOptions, alreadyMovedUnits);
// Get all transport final territories
ProMoveUtils.calculateAmphibRoutes(player, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
territoryManager.getAttackOptions().getTerritoryMap(), true);
// Determine max enemy counter attack units and remove territories where transports are exposed
removeTerritoriesWhereTransportsAreExposed();
// Determine if capital can be held if I still own it
if (ProData.myCapital != null && ProData.myCapital.getOwner().equals(player)) {
removeAttacksUntilCapitalCanBeHeld(attackOptions, ProData.purchaseOptions.getLandOptions());
}
// Check if any subs in contested territory that's not being attacked
checkContestedSeaTerritories();
// Calculate attack routes and perform moves
doMove(territoryManager.getAttackOptions().getTerritoryMap(), moveDel, data, player);
// Set strafing territories to avoid retreats
ai.setStoredStrafingTerritories(territoryManager.getStrafingTerritories());
ProLogger.info("Strafing territories: " + territoryManager.getStrafingTerritories());
// Log results
ProLogger.info("Logging results");
logAttackMoves(attackOptions);
return territoryManager.getAttackOptions().getTerritoryMap();
}
public void doMove(final Map<Territory, ProTerritory> attackMap, final IMoveDelegate moveDel, final GameData data,
final PlayerID player) {
this.data = data;
this.player = player;
final List<Collection<Unit>> moveUnits = new ArrayList<>();
final List<Route> moveRoutes = new ArrayList<>();
ProMoveUtils.calculateMoveRoutes(player, moveUnits, moveRoutes, attackMap, true);
ProMoveUtils.doMove(moveUnits, moveRoutes, moveDel);
moveUnits.clear();
moveRoutes.clear();
final List<Collection<Unit>> transportsToLoad = new ArrayList<>();
ProMoveUtils.calculateAmphibRoutes(player, moveUnits, moveRoutes, transportsToLoad, attackMap, true);
ProMoveUtils.doMove(moveUnits, moveRoutes, transportsToLoad, moveDel);
moveUnits.clear();
moveRoutes.clear();
ProMoveUtils.calculateBombardMoveRoutes(player, moveUnits, moveRoutes, attackMap);
ProMoveUtils.doMove(moveUnits, moveRoutes, moveDel);
moveUnits.clear();
moveRoutes.clear();
isBombing = true;
ProMoveUtils.calculateBombingRoutes(player, moveUnits, moveRoutes, attackMap);
ProMoveUtils.doMove(moveUnits, moveRoutes, moveDel);
isBombing = false;
}
public boolean isBombing() {
return isBombing;
}
private List<ProTerritory> prioritizeAttackOptions(final PlayerID player, final List<ProTerritory> attackOptions) {
ProLogger.info("Prioritizing territories to try to attack");
// Calculate value of attacking territory
for (final Iterator<ProTerritory> it = attackOptions.iterator(); it.hasNext();) {
final ProTerritory patd = it.next();
final Territory t = patd.getTerritory();
// Determine territory attack properties
final int isLand = !t.isWater() ? 1 : 0;
final int isNeutral = (!t.isWater() && t.getOwner().isNull()) ? 1 : 0;
final int isCanHold = patd.isCanHold() ? 1 : 0;
final int isAmphib = patd.isNeedAmphibUnits() ? 1 : 0;
final List<Unit> defendingUnits =
Match.getMatches(patd.getMaxEnemyDefenders(player, data), ProMatches.unitIsEnemyAndNotInfa(player, data));
final int isEmptyLand = (defendingUnits.isEmpty() && !patd.isNeedAmphibUnits()) ? 1 : 0;
final boolean isAdjacentToMyCapital =
!data.getMap().getNeighbors(t, Matches.territoryIs(ProData.myCapital)).isEmpty();
final int isNotNeutralAdjacentToMyCapital =
(isAdjacentToMyCapital && ProMatches.territoryIsEnemyNotNeutralLand(player, data).match(t)) ? 1 : 0;
final int isFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t) ? 1 : 0;
final int isFFA = ProUtils.isFFA(data, player) ? 1 : 0;
// Determine production value and if it is an enemy capital
int production = 0;
int isEnemyCapital = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
if (ta.isCapital()) {
isEnemyCapital = 1;
}
}
// Calculate attack value for prioritization
double TUVSwing = patd.getMaxBattleResult().getTUVSwing();
if (isFFA == 1 && TUVSwing > 0) {
TUVSwing *= 0.5;
}
final double territoryValue = (1 + isLand + isCanHold * (1 + 2 * isFFA)) * (1 + isEmptyLand) * (1 + isFactory)
* (1 - 0.5 * isAmphib) * production;
double attackValue = (TUVSwing + territoryValue) * (1 + 4 * isEnemyCapital)
* (1 + 2 * isNotNeutralAdjacentToMyCapital) * (1 - 0.9 * isNeutral);
// Check if a negative value neutral territory should be attacked
if (attackValue <= 0 && !patd.isNeedAmphibUnits() && !t.isWater() && t.getOwner().isNull()) {
// Determine enemy neighbor territory production value for neutral land territories
double nearbyEnemyValue = 0;
final List<Territory> cantReachEnemyTerritories = new ArrayList<>();
final Set<Territory> nearbyTerritories =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveLandUnits(player, data, true));
final List<Territory> nearbyEnemyTerritories =
Match.getMatches(nearbyTerritories, Matches.isTerritoryEnemy(player, data));
final List<Territory> nearbyTerritoriesWithOwnedUnits =
Match.getMatches(nearbyTerritories, Matches.territoryHasUnitsOwnedBy(player));
for (final Territory nearbyEnemyTerritory : nearbyEnemyTerritories) {
boolean allAlliedNeighborsHaveRoute = true;
for (final Territory nearbyAlliedTerritory : nearbyTerritoriesWithOwnedUnits) {
final int distance = data.getMap().getDistance_IgnoreEndForCondition(nearbyAlliedTerritory,
nearbyEnemyTerritory, ProMatches.territoryIsEnemyNotNeutralOrAllied(player, data));
if (distance < 0 || distance > 2) {
allAlliedNeighborsHaveRoute = false;
break;
}
}
if (!allAlliedNeighborsHaveRoute) {
final double value = ProTerritoryValueUtils.findTerritoryAttackValue(player, nearbyEnemyTerritory);
if (value > 0) {
nearbyEnemyValue += value;
}
cantReachEnemyTerritories.add(nearbyEnemyTerritory);
}
}
ProLogger.debug(
t.getName() + " calculated nearby enemy value=" + nearbyEnemyValue + " from " + cantReachEnemyTerritories);
if (nearbyEnemyValue > 0) {
ProLogger.trace(t.getName() + " updating negative neutral attack value=" + attackValue);
attackValue = nearbyEnemyValue * .001 / (1 - attackValue);
} else {
// Check if overwhelming attack strength (more than 5 times)
final double strengthDifference =
ProBattleUtils.estimateStrengthDifference(t, patd.getMaxUnits(), patd.getMaxEnemyDefenders(player, data));
ProLogger.debug(t.getName() + " calculated strengthDifference=" + strengthDifference);
if (strengthDifference > 500) {
ProLogger.trace(t.getName() + " updating negative neutral attack value=" + attackValue);
attackValue = strengthDifference * .00001 / (1 - attackValue);
}
}
}
// Remove negative value territories
patd.setValue(attackValue);
if (attackValue <= 0
|| (isDefensive && attackValue <= 8 && data.getMap().getDistance(ProData.myCapital, t) <= 3)) {
ProLogger.debug(
"Removing territory that has a negative attack value: " + t.getName() + ", AttackValue=" + patd.getValue());
it.remove();
}
}
// Sort attack territories by value
Collections.sort(attackOptions, (t1, t2) -> {
final double value1 = t1.getValue();
final double value2 = t2.getValue();
return Double.compare(value2, value1);
});
// Log prioritized territories
for (final ProTerritory patd : attackOptions) {
ProLogger.debug("AttackValue=" + patd.getValue() + ", TUVSwing=" + patd.getMaxBattleResult().getTUVSwing()
+ ", isAmphib=" + patd.isNeedAmphibUnits() + ", " + patd.getTerritory().getName());
}
return attackOptions;
}
private void determineTerritoriesToAttack(final List<ProTerritory> prioritizedTerritories) {
ProLogger.info("Determine which territories to attack");
// Assign units to territories by prioritization
int numToAttack = Math.min(1, prioritizedTerritories.size());
boolean haveRemovedAllAmphibTerritories = false;
while (true) {
final List<ProTerritory> territoriesToTryToAttack = prioritizedTerritories.subList(0, numToAttack);
ProLogger.debug("Current number of territories: " + numToAttack);
tryToAttackTerritories(territoriesToTryToAttack, new ArrayList<>());
// Determine if all attacks are successful
boolean areSuccessful = true;
for (final ProTerritory patd : territoriesToTryToAttack) {
final Territory t = patd.getTerritory();
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
ProLogger.trace(patd.getResultString() + " with attackers: " + patd.getUnits());
final double estimate =
ProBattleUtils.estimateStrengthDifference(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data));
final ProBattleResult result = patd.getBattleResult();
if (!patd.isStrafing() && estimate < patd.getStrengthEstimate()
&& (result.getWinPercentage() < ProData.minWinPercentage || !result.isHasLandUnitRemaining())) {
areSuccessful = false;
}
}
// Determine whether to try more territories, remove a territory, or end
if (areSuccessful) {
for (final ProTerritory patd : territoriesToTryToAttack) {
patd.setCanAttack(true);
final double estimate = ProBattleUtils.estimateStrengthDifference(patd.getTerritory(), patd.getUnits(),
patd.getMaxEnemyDefenders(player, data));
if (estimate < patd.getStrengthEstimate()) {
patd.setStrengthEstimate(estimate);
}
}
// If already used all transports then remove any remaining amphib territories
if (!haveRemovedAllAmphibTerritories) {
if (territoryManager.haveUsedAllAttackTransports()) {
final List<ProTerritory> amphibTerritoriesToRemove = new ArrayList<>();
for (int i = numToAttack; i < prioritizedTerritories.size(); i++) {
if (prioritizedTerritories.get(i).isNeedAmphibUnits()) {
amphibTerritoriesToRemove.add(prioritizedTerritories.get(i));
ProLogger.debug("Removing amphib territory since already used all transports: "
+ prioritizedTerritories.get(i).getTerritory().getName());
}
}
prioritizedTerritories.removeAll(amphibTerritoriesToRemove);
haveRemovedAllAmphibTerritories = true;
}
}
// Can attack all territories in list so end
numToAttack++;
if (numToAttack > prioritizedTerritories.size()) {
break;
}
} else {
ProLogger.debug("Removing territory: " + prioritizedTerritories.get(numToAttack - 1).getTerritory().getName());
prioritizedTerritories.remove(numToAttack - 1);
if (numToAttack > prioritizedTerritories.size()) {
numToAttack--;
}
}
}
ProLogger.debug("Final number of territories: " + (numToAttack - 1));
}
private void determineTerritoriesThatCanBeHeld(final List<ProTerritory> prioritizedTerritories,
final Map<Territory, Double> territoryValueMap) {
ProLogger.info("Check if we should try to hold attack territories");
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
// Determine which territories to try and hold
for (final ProTerritory patd : prioritizedTerritories) {
final Territory t = patd.getTerritory();
// If strafing then can't hold
if (patd.isStrafing()) {
patd.setCanHold(false);
ProLogger.debug(t + ", strafing so CanHold=false");
continue;
}
// Set max enemy attackers
if (enemyAttackOptions.getMax(t) != null) {
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
patd.setMaxEnemyUnits(new ArrayList<>(enemyAttackingUnits));
patd.setMaxEnemyBombardUnits(enemyAttackOptions.getMax(t).getMaxBombardUnits());
}
// Add strategic value for factories
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
isFactory = 1;
}
// Determine whether its worth trying to hold territory
double totalValue = 0.0;
final List<Unit> nonAirAttackers = Match.getMatches(patd.getMaxUnits(), Matches.UnitIsNotAir);
for (final Unit u : nonAirAttackers) {
totalValue += territoryValueMap.get(ProData.unitTerritoryMap.get(u));
}
final double averageValue = totalValue / nonAirAttackers.size() * 0.75;
final double territoryValue = territoryValueMap.get(t) * (1 + 4 * isFactory);
if (!t.isWater() && territoryValue < averageValue) {
attackMap.get(t).setCanHold(false);
ProLogger.debug(
t + ", CanHold=false, value=" + territoryValueMap.get(t) + ", averageAttackFromValue=" + averageValue);
continue;
}
if (enemyAttackOptions.getMax(t) != null) {
// Find max remaining defenders
final Set<Unit> attackingUnits = new HashSet<>(patd.getMaxUnits());
attackingUnits.addAll(patd.getMaxAmphibUnits());
final ProBattleResult result = calc.estimateAttackBattleResults(player, t, new ArrayList<>(attackingUnits),
patd.getMaxEnemyDefenders(player, data), patd.getMaxBombardUnits());
final List<Unit> remainingUnitsToDefendWith =
Match.getMatches(result.getAverageAttackersRemaining(), Matches.UnitIsAir.invert());
ProLogger.debug(t + ", value=" + territoryValueMap.get(t) + ", averageAttackFromValue=" + averageValue
+ ", MyAttackers=" + attackingUnits.size() + ", RemainingUnits=" + remainingUnitsToDefendWith.size());
// Determine counter attack results to see if I can hold it
final ProBattleResult result2 = calc.calculateBattleResults(player, t, patd.getMaxEnemyUnits(),
remainingUnitsToDefendWith, enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
final boolean canHold = (!result2.isHasLandUnitRemaining() && !t.isWater()) || (result2.getTUVSwing() < 0)
|| (result2.getWinPercentage() < ProData.minWinPercentage);
patd.setCanHold(canHold);
ProLogger.debug(
t + ", CanHold=" + canHold + ", MyDefenders=" + remainingUnitsToDefendWith.size() + ", EnemyAttackers="
+ patd.getMaxEnemyUnits().size() + ", win%=" + result2.getWinPercentage() + ", EnemyTUVSwing="
+ result2.getTUVSwing() + ", hasLandUnitRemaining=" + result2.isHasLandUnitRemaining());
} else {
attackMap.get(t).setCanHold(true);
ProLogger.debug(t + ", CanHold=true since no enemy counter attackers, value=" + territoryValueMap.get(t)
+ ", averageAttackFromValue=" + averageValue);
}
}
}
private void removeTerritoriesThatArentWorthAttacking(final List<ProTerritory> prioritizedTerritories) {
ProLogger.info("Remove territories that aren't worth attacking");
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Loop through all prioritized territories
for (final Iterator<ProTerritory> it = prioritizedTerritories.iterator(); it.hasNext();) {
final ProTerritory patd = it.next();
final Territory t = patd.getTerritory();
ProLogger
.debug("Checking territory=" + patd.getTerritory().getName() + " with isAmphib=" + patd.isNeedAmphibUnits());
// Remove empty convoy zones that can't be held
if (!patd.isCanHold() && enemyAttackOptions.getMax(t) != null && t.isWater()
&& !t.getUnits().someMatch(Matches.enemyUnit(player, data))) {
ProLogger.debug("Removing convoy zone that can't be held: " + t.getName() + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits());
it.remove();
continue;
}
// Remove neutral and low value amphib land territories that can't be held
final boolean isNeutral = t.getOwner().isNull();
final double strengthDifference =
ProBattleUtils.estimateStrengthDifference(t, patd.getMaxUnits(), patd.getMaxEnemyDefenders(player, data));
if (!patd.isCanHold() && enemyAttackOptions.getMax(t) != null && !t.isWater()) {
if (isNeutral && strengthDifference <= 500) {
// Remove neutral territories that can't be held and don't have overwhelming attack strength
ProLogger.debug("Removing neutral territory that can't be held: " + t.getName() + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits() + ", enemyAmphibAttackers="
+ enemyAttackOptions.getMax(t).getMaxAmphibUnits() + ", strengthDifference=" + strengthDifference);
it.remove();
continue;
} else if (patd.isNeedAmphibUnits() && patd.getValue() < 2) {
// Remove amphib territories that aren't worth attacking
ProLogger.debug("Removing low value amphib territory that can't be held: " + t.getName() + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits() + ", enemyAmphibAttackers="
+ enemyAttackOptions.getMax(t).getMaxAmphibUnits());
it.remove();
continue;
}
}
// Remove neutral territories where attackers are adjacent to enemy territories that aren't being attacked
if (isNeutral && !t.isWater() && strengthDifference <= 500) {
// Get list of territories I'm attacking
final List<Territory> prioritizedTerritoryList = new ArrayList<>();
for (final ProTerritory prioritizedTerritory : prioritizedTerritories) {
prioritizedTerritoryList.add(prioritizedTerritory.getTerritory());
}
// Find all territories units are attacking from that are adjacent to territory
final Set<Territory> attackFromTerritories = new HashSet<>();
for (final Unit u : patd.getMaxUnits()) {
attackFromTerritories.add(ProData.unitTerritoryMap.get(u));
}
attackFromTerritories.retainAll(data.getMap().getNeighbors(t));
// Determine if any of the attacking from territories has enemy neighbors that aren't being attacked
boolean attackersHaveEnemyNeighbors = false;
Territory attackFromTerritoryWithEnemyNeighbors = null;
for (final Territory attackFromTerritory : attackFromTerritories) {
final Set<Territory> enemyNeighbors =
data.getMap().getNeighbors(attackFromTerritory, ProMatches.territoryIsEnemyNotNeutralLand(player, data));
if (!prioritizedTerritoryList.containsAll(enemyNeighbors)) {
attackersHaveEnemyNeighbors = true;
attackFromTerritoryWithEnemyNeighbors = attackFromTerritory;
break;
}
}
if (attackersHaveEnemyNeighbors) {
ProLogger.debug("Removing neutral territory that has attackers that are adjacent to enemies: " + t.getName()
+ ", attackFromTerritory=" + attackFromTerritoryWithEnemyNeighbors);
it.remove();
}
}
}
}
private List<Unit> moveOneDefenderToLandTerritoriesBorderingEnemy(final List<ProTerritory> prioritizedTerritories) {
ProLogger.info("Determine which territories to defend with one land unit");
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getAttackOptions().getUnitMoveMap();
// Get list of territories to attack
final List<Territory> territoriesToAttack = new ArrayList<>();
for (final ProTerritory patd : prioritizedTerritories) {
territoriesToAttack.add(patd.getTerritory());
}
// Find land territories with no can't move units and adjacent to enemy land units
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t : ProData.myUnitTerritories) {
final boolean hasAlliedLandUnits = Match.someMatch(t.getUnits().getUnits(),
ProMatches.unitCantBeMovedAndIsAlliedDefenderAndNotInfra(player, data, t));
final Set<Territory> enemyNeighbors = data.getMap().getNeighbors(t,
Matches.territoryIsEnemyNonNeutralAndHasEnemyUnitMatching(data, player, Matches.UnitIsLand));
enemyNeighbors.removeAll(territoriesToAttack);
if (!t.isWater() && !hasAlliedLandUnits && !enemyNeighbors.isEmpty()) {
int minCost = Integer.MAX_VALUE;
Unit minUnit = null;
for (final Unit u : t.getUnits().getMatches(Matches.unitIsOwnedBy(player))) {
if (ProData.unitValueMap.getInt(u.getType()) < minCost) {
minCost = ProData.unitValueMap.getInt(u.getType());
minUnit = u;
}
}
if (minUnit != null) {
unitMoveMap.remove(minUnit);
alreadyMovedUnits.add(minUnit);
ProLogger.debug(t + ", added one land unit: " + minUnit);
}
}
}
return alreadyMovedUnits;
}
private void removeTerritoriesWhereTransportsAreExposed() {
ProLogger.info("Remove territories where transports are exposed");
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Find maximum defenders for each transport territory
final List<Territory> clearedTerritories = new ArrayList<>();
for (final Territory t : attackMap.keySet()) {
if (!attackMap.get(t).getUnits().isEmpty()) {
clearedTerritories.add(t);
}
}
territoryManager.populateDefenseOptions(clearedTerritories);
final Map<Territory, ProTerritory> defendMap = territoryManager.getDefendOptions().getTerritoryMap();
// Remove units that have already attacked
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final Territory t : attackMap.keySet()) {
alreadyAttackedWithUnits.addAll(attackMap.get(t).getUnits());
alreadyAttackedWithUnits.addAll(attackMap.get(t).getAmphibAttackMap().keySet());
}
for (final Territory t : defendMap.keySet()) {
defendMap.get(t).getMaxUnits().removeAll(alreadyAttackedWithUnits);
}
// Loop through all prioritized territories
for (final Territory t : attackMap.keySet()) {
final ProTerritory patd = attackMap.get(t);
ProLogger.debug("Checking territory=" + patd.getTerritory().getName() + " with tranports size="
+ patd.getTransportTerritoryMap().size());
if (!patd.getTerritory().isWater() && !patd.getTransportTerritoryMap().isEmpty()) {
// Find all transports for each unload territory
final Map<Territory, List<Unit>> territoryTransportAndBombardMap = new HashMap<>();
for (final Unit u : patd.getTransportTerritoryMap().keySet()) {
final Territory unloadTerritory = patd.getTransportTerritoryMap().get(u);
if (territoryTransportAndBombardMap.containsKey(unloadTerritory)) {
territoryTransportAndBombardMap.get(unloadTerritory).add(u);
} else {
final List<Unit> transports = new ArrayList<>();
transports.add(u);
territoryTransportAndBombardMap.put(unloadTerritory, transports);
}
}
// Find all bombard units for each unload territory
for (final Unit u : patd.getBombardTerritoryMap().keySet()) {
final Territory unloadTerritory = patd.getBombardTerritoryMap().get(u);
if (territoryTransportAndBombardMap.containsKey(unloadTerritory)) {
territoryTransportAndBombardMap.get(unloadTerritory).add(u);
} else {
final List<Unit> transports = new ArrayList<>();
transports.add(u);
territoryTransportAndBombardMap.put(unloadTerritory, transports);
}
}
// Determine counter attack results for each transport territory
double totalEnemyTUVSwing = 0.0;
for (final Territory unloadTerritory : territoryTransportAndBombardMap.keySet()) {
if (enemyAttackOptions.getMax(unloadTerritory) != null) {
final List<Unit> enemyAttackers = enemyAttackOptions.getMax(unloadTerritory).getMaxUnits();
final Set<Unit> defenders =
new HashSet<>(unloadTerritory.getUnits().getMatches(ProMatches.unitIsAlliedNotOwned(player, data)));
defenders.addAll(territoryTransportAndBombardMap.get(unloadTerritory));
if (defendMap.get(unloadTerritory) != null) {
defenders.addAll(defendMap.get(unloadTerritory).getMaxUnits());
}
final ProBattleResult result = calc.calculateBattleResults(player, unloadTerritory,
enemyAttackOptions.getMax(unloadTerritory).getMaxUnits(), new ArrayList<>(defenders), new HashSet<>(),
false);
final ProBattleResult minResult = calc.calculateBattleResults(player, unloadTerritory,
enemyAttackOptions.getMax(unloadTerritory).getMaxUnits(),
territoryTransportAndBombardMap.get(unloadTerritory), new HashSet<>(), false);
final double minTUVSwing = Math.min(result.getTUVSwing(), minResult.getTUVSwing());
if (minTUVSwing > 0) {
totalEnemyTUVSwing += minTUVSwing;
}
ProLogger.trace(unloadTerritory + ", EnemyAttackers=" + enemyAttackers.size() + ", MaxDefenders="
+ defenders.size() + ", MaxEnemyTUVSwing=" + result.getTUVSwing() + ", MinDefenders="
+ territoryTransportAndBombardMap.get(unloadTerritory).size() + ", MinEnemyTUVSwing="
+ minResult.getTUVSwing());
} else {
ProLogger.trace("Territory=" + unloadTerritory.getName() + " has no enemy attackers");
}
}
// Determine whether its worth attacking
final ProBattleResult result = calc.calculateBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet(), true);
int production = 0;
int isEnemyCapital = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
if (ta.isCapital()) {
isEnemyCapital = 1;
}
}
final double attackValue = result.getTUVSwing() + production * (1 + 3 * isEnemyCapital);
if (!patd.isStrafing() && (0.75 * totalEnemyTUVSwing) > attackValue) {
ProLogger.debug("Removing amphib territory: " + patd.getTerritory() + ", totalEnemyTUVSwing="
+ totalEnemyTUVSwing + ", attackValue=" + attackValue);
attackMap.get(t).getUnits().clear();
attackMap.get(t).getAmphibAttackMap().clear();
attackMap.get(t).getBombardTerritoryMap().clear();
} else {
ProLogger.debug("Keeping amphib territory: " + patd.getTerritory() + ", totalEnemyTUVSwing="
+ totalEnemyTUVSwing + ", attackValue=" + attackValue);
}
}
}
}
private void determineUnitsToAttackWith(final List<ProTerritory> prioritizedTerritories,
final List<Unit> alreadyMovedUnits) {
ProLogger.info("Determine units to attack each territory with");
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Unit, Set<Territory>> unitAttackMap = territoryManager.getAttackOptions().getUnitMoveMap();
// Assign units to territories by prioritization
while (true) {
Map<Unit, Set<Territory>> sortedUnitAttackOptions =
tryToAttackTerritories(prioritizedTerritories, alreadyMovedUnits);
// Clear bombers
for (final Territory t : attackMap.keySet()) {
attackMap.get(t).getBombers().clear();
}
// Get all units that have already moved
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final Territory t : attackMap.keySet()) {
alreadyAttackedWithUnits.addAll(attackMap.get(t).getUnits());
alreadyAttackedWithUnits.addAll(attackMap.get(t).getAmphibAttackMap().keySet());
}
// Check to see if any territories can be bombed
final Map<Unit, Set<Territory>> bomberMoveMap = territoryManager.getAttackOptions().getBomberMoveMap();
for (final Unit unit : bomberMoveMap.keySet()) {
if (alreadyAttackedWithUnits.contains(unit)) {
continue;
}
Optional<Territory> maxBombingTerritory = Optional.empty();
int maxBombingScore = MIN_BOMBING_SCORE;
for (final Territory t : bomberMoveMap.get(unit)) {
final boolean territoryCanBeBombed = t.getUnits().someMatch(Matches.UnitCanProduceUnitsAndCanBeDamaged);
if (territoryCanBeBombed && canAirSafelyLandAfterAttack(unit, t)) {
final int noAABombingDefense = t.getUnits().someMatch(Matches.UnitIsAAforBombingThisUnitOnly) ? 0 : 1;
int maxDamage = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
maxDamage = ta.getProduction();
}
final int numExistingBombers = attackMap.get(t).getBombers().size();
final int remainingDamagePotential = maxDamage - 3 * numExistingBombers;
final int bombingScore = (1 + 9 * noAABombingDefense) * remainingDamagePotential;
if (bombingScore >= maxBombingScore) {
maxBombingScore = bombingScore;
maxBombingTerritory = Optional.of(t);
}
}
}
if (maxBombingTerritory.isPresent()) {
final Territory t = maxBombingTerritory.get();
attackMap.get(t).getBombers().add(unit);
sortedUnitAttackOptions.remove(unit);
ProLogger.debug("Add bomber (" + unit + ") to " + t);
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Set air units in any territory with no AA (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (!isAirUnit) {
continue; // skip non-air units
}
Territory minWinTerritory = null;
double minWinPercentage = Double.MAX_VALUE;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isEnemyFactory = ProMatches.territoryHasInfraFactoryAndIsEnemyLand(player, data).match(t);
if (!isEnemyFactory && !canAirSafelyLandAfterAttack(unit, t)) {
continue;
}
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> attackingUnits = patd.getUnits();
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean isOverwhelmingWin =
ProBattleUtils.checkForOverwhelmingWin(player, t, attackingUnits, defendingUnits);
final boolean hasAA = Match.someMatch(defendingUnits, Matches.UnitIsAAforAnything);
if (!hasAA && !isOverwhelmingWin) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Find territory that we can try to hold that needs unit
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
Territory minWinTerritory = null;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (patd.isCanHold()) {
// Check if I already have enough attack units to win in 2 rounds
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
final List<Unit> attackingUnits = patd.getUnits();
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean isOverwhelmingWin =
ProBattleUtils.checkForOverwhelmingWin(player, t, attackingUnits, defendingUnits);
if (!isOverwhelmingWin && result.getBattleRounds() > 2) {
minWinTerritory = t;
break;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Add sea units to any territory that significantly increases TUV gain
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isSeaUnit = UnitAttachment.get(unit.getType()).getIsSea();
if (!isSeaUnit) {
continue; // skip non-sea units
}
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (attackMap.get(t).getBattleResult() == null) {
attackMap.get(t).setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = attackMap.get(t).getBattleResult();
final List<Unit> attackers = new ArrayList<>(patd.getUnits());
attackers.add(unit);
final ProBattleResult result2 = calc.estimateAttackBattleResults(player, t, attackers,
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet());
final double unitValue = ProData.unitValueMap.getInt(unit.getType());
if ((result2.getTUVSwing() - unitValue / 3) > result.getTUVSwing()) {
attackMap.get(t).addUnit(unit);
attackMap.get(t).setBattleResult(null);
it.remove();
break;
}
}
}
// Determine if all attacks are worth it
final List<Unit> usedUnits = new ArrayList<>();
for (final ProTerritory patd : prioritizedTerritories) {
usedUnits.addAll(patd.getUnits());
}
ProTerritory territoryToRemove = null;
for (final ProTerritory patd : prioritizedTerritories) {
final Territory t = patd.getTerritory();
// Find battle result
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
// Determine enemy counter attack results
boolean canHold = true;
double enemyCounterTUVSwing = 0;
if (enemyAttackOptions.getMax(t) != null
&& !ProMatches.territoryIsWaterAndAdjacentToOwnedFactory(player, data).match(t)) {
List<Unit> remainingUnitsToDefendWith =
Match.getMatches(result.getAverageAttackersRemaining(), Matches.UnitIsAir.invert());
ProBattleResult result2 = calc.calculateBattleResults(player, t, patd.getMaxEnemyUnits(),
remainingUnitsToDefendWith, patd.getMaxBombardUnits(), false);
if (patd.isCanHold() && result2.getTUVSwing() > 0) {
final List<Unit> unusedUnits = new ArrayList<>(patd.getMaxUnits());
unusedUnits.addAll(patd.getMaxAmphibUnits());
unusedUnits.removeAll(usedUnits);
unusedUnits.addAll(remainingUnitsToDefendWith);
final ProBattleResult result3 = calc.calculateBattleResults(player, t, patd.getMaxEnemyUnits(), unusedUnits,
patd.getMaxBombardUnits(), false);
if (result3.getTUVSwing() < result2.getTUVSwing()) {
result2 = result3;
remainingUnitsToDefendWith = unusedUnits;
}
}
canHold = (!result2.isHasLandUnitRemaining() && !t.isWater()) || (result2.getTUVSwing() < 0)
|| (result2.getWinPercentage() < ProData.minWinPercentage);
if (result2.getTUVSwing() > 0) {
enemyCounterTUVSwing = result2.getTUVSwing();
}
ProLogger.trace("Territory=" + t.getName() + ", CanHold=" + canHold + ", MyDefenders="
+ remainingUnitsToDefendWith.size() + ", EnemyAttackers=" + patd.getMaxEnemyUnits().size() + ", win%="
+ result2.getWinPercentage() + ", EnemyTUVSwing=" + result2.getTUVSwing() + ", hasLandUnitRemaining="
+ result2.isHasLandUnitRemaining());
}
// Find attack value
final boolean isNeutral = (!t.isWater() && t.getOwner().isNull());
final int isLand = !t.isWater() ? 1 : 0;
final int isCanHold = canHold ? 1 : 0;
final int isCantHoldAmphib = !canHold && !patd.getAmphibAttackMap().isEmpty() ? 1 : 0;
final int isFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t) ? 1 : 0;
final int isFFA = ProUtils.isFFA(data, player) ? 1 : 0;
final int production = TerritoryAttachment.getProduction(t);
double capitalValue = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null && ta.isCapital()) {
capitalValue = ProUtils.getPlayerProduction(t.getOwner(), data);
}
final double territoryValue =
(1 + isLand - isCantHoldAmphib + isFactory + isCanHold * (1 + 2 * isFFA + 2 * isFactory)) * production
+ capitalValue;
double TUVSwing = result.getTUVSwing();
if (isFFA == 1 && TUVSwing > 0) {
TUVSwing *= 0.5;
}
final double attackValue =
TUVSwing + territoryValue * result.getWinPercentage() / 100 - enemyCounterTUVSwing * 2 / 3;
boolean allUnitsCanAttackOtherTerritory = true;
if (isNeutral && attackValue < 0) {
for (final Unit u : patd.getUnits()) {
boolean canAttackOtherTerritory = false;
for (final ProTerritory patd2 : prioritizedTerritories) {
if (!patd.equals(patd2) && unitAttackMap.get(u) != null
&& unitAttackMap.get(u).contains(patd2.getTerritory())) {
canAttackOtherTerritory = true;
break;
}
}
if (!canAttackOtherTerritory) {
allUnitsCanAttackOtherTerritory = false;
break;
}
}
}
// Determine whether to remove attack
if (!patd.isStrafing() && (result.getWinPercentage() < ProData.minWinPercentage
|| !result.isHasLandUnitRemaining() || (isNeutral && !canHold)
|| (attackValue < 0 && (!isNeutral || allUnitsCanAttackOtherTerritory || result.getBattleRounds() >= 4)))) {
territoryToRemove = patd;
}
ProLogger.debug(patd.getResultString() + ", attackValue=" + attackValue + ", territoryValue=" + territoryValue
+ ", allUnitsCanAttackOtherTerritory=" + allUnitsCanAttackOtherTerritory + " with attackers="
+ patd.getUnits());
}
// Determine whether all attacks are successful or try to hold fewer territories
if (territoryToRemove == null) {
break;
} else {
prioritizedTerritories.remove(territoryToRemove);
ProLogger.debug("Removing " + territoryToRemove.getTerritory().getName());
}
}
}
private Map<Unit, Set<Territory>> tryToAttackTerritories(final List<ProTerritory> prioritizedTerritories,
final List<Unit> alreadyMovedUnits) {
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Unit, Set<Territory>> unitAttackMap = territoryManager.getAttackOptions().getUnitMoveMap();
final Map<Unit, Set<Territory>> transportAttackMap = territoryManager.getAttackOptions().getTransportMoveMap();
final Map<Unit, Set<Territory>> bombardMap = territoryManager.getAttackOptions().getBombardMap();
final List<ProTransport> transportMapList = territoryManager.getAttackOptions().getTransportList();
// Reset lists
for (final Territory t : attackMap.keySet()) {
attackMap.get(t).getUnits().clear();
attackMap.get(t).getBombardTerritoryMap().clear();
attackMap.get(t).getAmphibAttackMap().clear();
attackMap.get(t).getTransportTerritoryMap().clear();
attackMap.get(t).setBattleResult(null);
}
// Loop through all units and determine attack options
final Map<Unit, Set<Territory>> unitAttackOptions = new HashMap<>();
for (final Unit unit : unitAttackMap.keySet()) {
// Find number of attack options
final Set<Territory> canAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (unitAttackMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
// Add units with attack options to map
if (canAttackTerritories.size() >= 1) {
unitAttackOptions.put(unit, canAttackTerritories);
}
}
// Sort units by number of attack options and cost
Map<Unit, Set<Territory>> sortedUnitAttackOptions =
ProSortMoveOptionsUtils.sortUnitMoveOptions(player, unitAttackOptions);
// Try to set at least one destroyer in each sea territory with subs
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isDestroyerUnit = UnitAttachment.get(unit.getType()).getIsDestroyer();
if (!isDestroyerUnit) {
continue; // skip non-destroyer units
}
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
// Add destroyer if territory has subs and a destroyer has been already added
final List<Unit> defendingUnits = attackMap.get(t).getMaxEnemyDefenders(player, data);
if (Match.someMatch(defendingUnits, Matches.UnitIsSub)
&& Match.noneMatch(attackMap.get(t).getUnits(), Matches.UnitIsDestroyer)) {
attackMap.get(t).addUnit(unit);
it.remove();
break;
}
}
}
// Set enough land and sea units in territories to have at least a chance of winning
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (isAirUnit) {
continue; // skip air units
}
final TreeMap<Double, Territory> estimatesMap = new TreeMap<>();
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
if (t.isWater() && !attackMap.get(t).isCanHold()) {
continue; // ignore sea territories that can't be held
}
final List<Unit> defendingUnits = attackMap.get(t).getMaxEnemyDefenders(player, data);
double estimate = ProBattleUtils.estimateStrengthDifference(t, attackMap.get(t).getUnits(), defendingUnits);
final boolean hasAA = Match.someMatch(defendingUnits, Matches.UnitIsAAforAnything);
if (hasAA) {
estimate -= 10;
}
estimatesMap.put(estimate, t);
}
if (!estimatesMap.isEmpty() && estimatesMap.firstKey() < 40) {
final Territory minWinTerritory = estimatesMap.entrySet().iterator().next().getValue();
attackMap.get(minWinTerritory).addUnit(unit);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Set non-air units in territories that can be held
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (isAirUnit) {
continue; // skip air units
}
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!attackMap.get(t).isCurrentlyWins() && attackMap.get(t).isCanHold()) {
if (attackMap.get(t).getBattleResult() == null) {
attackMap.get(t).setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = attackMap.get(t).getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Set air units in territories that can't be held (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (!isAirUnit) {
continue; // skip non-air units
}
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins() && !patd.isCanHold()) {
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isEnemyCapital = ProUtils.getLiveEnemyCapitals(data, player).contains(t);
final boolean isAdjacentToAlliedCapital = Matches.territoryHasNeighborMatching(data,
Matches.territoryIsInList(ProUtils.getLiveAlliedCapitals(data, player))).match(t);
final int range = TripleAUnit.get(unit).getMovementLeft();
final int distance = data.getMap().getDistance_IgnoreEndForCondition(ProData.unitTerritoryMap.get(unit), t,
ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, true));
final boolean usesMoreThanHalfOfRange = distance > range / 2;
if (isAirUnit && !isEnemyCapital && !isAdjacentToAlliedCapital && usesMoreThanHalfOfRange) {
continue;
}
// Check battle results
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasNoDefenders =
Match.noneMatch(defendingUnits, ProMatches.unitIsEnemyAndNotInfa(player, data));
final boolean isOverwhelmingWin =
ProBattleUtils.checkForOverwhelmingWin(player, t, patd.getUnits(), defendingUnits);
final boolean hasAA = Match.someMatch(defendingUnits, Matches.UnitIsAAforAnything);
if (!hasNoDefenders && !isOverwhelmingWin && (!hasAA || result.getWinPercentage() < minWinPercentage)) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
if (patd.isStrafing()) {
break;
}
}
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions,
attackMap, ProData.unitTerritoryMap, calc);
// Set remaining units in any territory that needs it (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins()) {
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isAdjacentToAlliedFactory = Matches
.territoryHasNeighborMatching(data, ProMatches.territoryHasInfraFactoryAndIsAlliedLand(player, data))
.match(t);
final int range = TripleAUnit.get(unit).getMovementLeft();
final int distance = data.getMap().getDistance_IgnoreEndForCondition(ProData.unitTerritoryMap.get(unit), t,
ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, true));
final boolean usesMoreThanHalfOfRange = distance > range / 2;
final boolean territoryValueIsLessThanUnitValue =
patd.getValue() < ProData.unitValueMap.getInt(unit.getType());
if (isAirUnit && !isAdjacentToAlliedFactory && usesMoreThanHalfOfRange
&& (territoryValueIsLessThanUnitValue || (!t.isWater() && !patd.isCanHold()))) {
continue;
}
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasNoDefenders =
Match.noneMatch(defendingUnits, ProMatches.unitIsEnemyAndNotInfa(player, data));
final boolean isOverwhelmingWin =
ProBattleUtils.checkForOverwhelmingWin(player, t, patd.getUnits(), defendingUnits);
final boolean hasAA = Match.someMatch(defendingUnits, Matches.UnitIsAAforAnything);
if (!isAirUnit || (!hasNoDefenders && !isOverwhelmingWin
&& (!hasAA || result.getWinPercentage() < minWinPercentage))) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions =
ProSortMoveOptionsUtils.sortUnitNeededOptions(player, sortedUnitAttackOptions, attackMap, calc);
// If transports can take casualties try placing in naval battles first
final List<Unit> alreadyAttackedWithTransports = new ArrayList<>();
if (!Properties.getTransportCasualtiesRestricted(data)) {
// Loop through all my transports and see which territories they can attack from current list
final Map<Unit, Set<Territory>> transportAttackOptions = new HashMap<>();
for (final Unit unit : transportAttackMap.keySet()) {
// Find number of attack options
final Set<Territory> canAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (transportAttackMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canAttackTerritories.isEmpty()) {
transportAttackOptions.put(unit, canAttackTerritories);
}
}
// Loop through transports with attack options and determine if any naval battle needs it
for (final Unit transport : transportAttackOptions.keySet()) {
// Find current naval battle that needs transport if it isn't transporting units
for (final Territory t : transportAttackOptions.get(transport)) {
final ProTerritory patd = attackMap.get(t);
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
if (!patd.isCurrentlyWins() && !TransportTracker.isTransporting(transport) && !defendingUnits.isEmpty()) {
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < ProData.winPercentage || !result.isHasLandUnitRemaining()) {
patd.addUnit(transport);
patd.setBattleResult(null);
alreadyAttackedWithTransports.add(transport);
ProLogger.trace("Adding attack transport to: " + t.getName());
break;
}
}
}
}
}
// Loop through all my transports and see which can make amphib attack
final Map<Unit, Set<Territory>> amphibAttackOptions = new HashMap<>();
for (final ProTransport proTransportData : transportMapList) {
// If already used to attack then ignore
if (alreadyAttackedWithTransports.contains(proTransportData.getTransport())) {
continue;
}
// Find number of attack options
final Set<Territory> canAmphibAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (proTransportData.getTransportMap().containsKey(attackTerritoryData.getTerritory())) {
canAmphibAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canAmphibAttackTerritories.isEmpty()) {
amphibAttackOptions.put(proTransportData.getTransport(), canAmphibAttackTerritories);
}
}
// Loop through transports with amphib attack options and determine if any land battle needs it
for (final Unit transport : amphibAttackOptions.keySet()) {
// Find current land battle results for territories that unit can amphib attack
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
List<Unit> minAmphibUnitsToAdd = null;
Territory minUnloadFromTerritory = null;
for (final Territory t : amphibAttackOptions.get(transport)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins()) {
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
// Get all units that have already attacked
final List<Unit> alreadyAttackedWithUnits = new ArrayList<>(alreadyMovedUnits);
for (final Territory t2 : attackMap.keySet()) {
alreadyAttackedWithUnits.addAll(attackMap.get(t2).getUnits());
}
// Find units that haven't attacked and can be transported
for (final ProTransport proTransportData : transportMapList) {
if (proTransportData.getTransport().equals(transport)) {
// Find units to load
final Set<Territory> territoriesCanLoadFrom = proTransportData.getTransportMap().get(t);
final List<Unit> amphibUnitsToAdd = ProTransportUtils.getUnitsToTransportFromTerritories(player,
transport, territoriesCanLoadFrom, alreadyAttackedWithUnits);
if (amphibUnitsToAdd.isEmpty()) {
continue;
}
// Find best territory to move transport
double minStrengthDifference = Double.POSITIVE_INFINITY;
minUnloadFromTerritory = null;
final Set<Territory> territoriesToMoveTransport =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, false));
final Set<Territory> loadFromTerritories = new HashSet<>();
for (final Unit u : amphibUnitsToAdd) {
loadFromTerritories.add(ProData.unitTerritoryMap.get(u));
}
for (final Territory territoryToMoveTransport : territoriesToMoveTransport) {
if (proTransportData.getSeaTransportMap().containsKey(territoryToMoveTransport) && proTransportData
.getSeaTransportMap().get(territoryToMoveTransport).containsAll(loadFromTerritories)) {
List<Unit> attackers = new ArrayList<>();
if (enemyAttackOptions.getMax(territoryToMoveTransport) != null) {
attackers = enemyAttackOptions.getMax(territoryToMoveTransport).getMaxUnits();
}
final List<Unit> defenders =
territoryToMoveTransport.getUnits().getMatches(Matches.isUnitAllied(player, data));
defenders.add(transport);
final double strengthDifference =
ProBattleUtils.estimateStrengthDifference(territoryToMoveTransport, attackers, defenders);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minUnloadFromTerritory = territoryToMoveTransport;
}
}
}
minWinTerritory = t;
minWinPercentage = result.getWinPercentage();
minAmphibUnitsToAdd = amphibUnitsToAdd;
break;
}
}
}
}
}
if (minWinTerritory != null) {
if (minUnloadFromTerritory != null) {
attackMap.get(minWinTerritory).getTransportTerritoryMap().put(transport, minUnloadFromTerritory);
}
attackMap.get(minWinTerritory).addUnits(minAmphibUnitsToAdd);
attackMap.get(minWinTerritory).putAmphibAttackMap(transport, minAmphibUnitsToAdd);
attackMap.get(minWinTerritory).setBattleResult(null);
for (final Unit unit : minAmphibUnitsToAdd) {
sortedUnitAttackOptions.remove(unit);
}
ProLogger.trace("Adding amphibious attack to " + minWinTerritory + ", units=" + minAmphibUnitsToAdd.size()
+ ", unloadFrom=" + minUnloadFromTerritory);
}
}
// Get all units that have already moved
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final Territory t : attackMap.keySet()) {
alreadyAttackedWithUnits.addAll(attackMap.get(t).getUnits());
alreadyAttackedWithUnits.addAll(attackMap.get(t).getAmphibAttackMap().keySet());
}
// Loop through all my bombard units and see which can bombard
final Map<Unit, Set<Territory>> bombardOptions = new HashMap<>();
for (final Unit u : bombardMap.keySet()) {
// If already used to attack then ignore
if (alreadyAttackedWithUnits.contains(u)) {
continue;
}
// Find number of bombard options
final Set<Territory> canBombardTerritories = new HashSet<>();
for (final ProTerritory patd : prioritizedTerritories) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasDefenders = Match.someMatch(defendingUnits, Matches.UnitIsInfrastructure.invert());
if (bombardMap.get(u).contains(patd.getTerritory()) && !patd.getTransportTerritoryMap().isEmpty()
&& hasDefenders && !TransportTracker.isTransporting(u)) {
canBombardTerritories.add(patd.getTerritory());
}
}
if (!canBombardTerritories.isEmpty()) {
bombardOptions.put(u, canBombardTerritories);
}
}
// Loop through bombard units to see if any amphib battles need
for (final Unit u : bombardOptions.keySet()) {
// Find current land battle results for territories that unit can bombard
Territory minWinTerritory = null;
double minWinPercentage = Double.MAX_VALUE;
Territory minBombardFromTerritory = null;
for (final Territory t : bombardOptions.get(u)) {
final ProTerritory patd = attackMap.get(t);
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(player, t, patd.getUnits(),
patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage
|| (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
// Find territory to bombard from
Territory bombardFromTerritory = null;
for (final Territory unloadFromTerritory : patd.getTransportTerritoryMap().values()) {
if (patd.getBombardOptionsMap().get(u).contains(unloadFromTerritory)) {
bombardFromTerritory = unloadFromTerritory;
}
}
if (bombardFromTerritory != null) {
minWinTerritory = t;
minWinPercentage = result.getWinPercentage();
minBombardFromTerritory = bombardFromTerritory;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).getBombardTerritoryMap().put(u, minBombardFromTerritory);
attackMap.get(minWinTerritory).setBattleResult(null);
sortedUnitAttackOptions.remove(u);
ProLogger.trace(
"Adding bombard to " + minWinTerritory + ", units=" + u + ", bombardFrom=" + minBombardFromTerritory);
}
}
return sortedUnitAttackOptions;
}
private void removeAttacksUntilCapitalCanBeHeld(final List<ProTerritory> prioritizedTerritories,
final List<ProPurchaseOption> landPurchaseOptions) {
ProLogger.info("Check capital defenses after attack moves");
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final Territory myCapital = ProData.myCapital;
// Add max purchase defenders to capital for non-mobile factories (don't consider mobile factories since they may
// move elsewhere)
final List<Unit> placeUnits = new ArrayList<>();
if (ProMatches.territoryHasNonMobileInfraFactoryAndIsNotConqueredOwnedLand(player, data).match(myCapital)) {
placeUnits.addAll(ProPurchaseUtils.findMaxPurchaseDefenders(player, myCapital, landPurchaseOptions));
}
// Remove attack until capital can be defended
while (true) {
if (prioritizedTerritories.isEmpty()) {
break;
}
// Determine max enemy counter attack units
final List<Territory> territoriesToAttack = new ArrayList<>();
for (final ProTerritory t : prioritizedTerritories) {
territoriesToAttack.add(t.getTerritory());
}
ProLogger.trace("Remaining territories to attack=" + territoriesToAttack);
final List<Territory> territoriesToCheck = new ArrayList<>();
territoriesToCheck.add(myCapital);
territoryManager.populateEnemyAttackOptions(territoriesToAttack, territoriesToCheck);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
if (enemyAttackOptions.getMax(myCapital) == null) {
break;
}
// Find max remaining defenders
final Set<Territory> territoriesAdjacentToCapital =
data.getMap().getNeighbors(myCapital, Matches.TerritoryIsLand);
final List<Unit> defenders = myCapital.getUnits().getMatches(Matches.isUnitAllied(player, data));
defenders.addAll(placeUnits);
for (final Territory t : territoriesAdjacentToCapital) {
defenders.addAll(t.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedLand(player, false)));
}
for (final Territory t : attackMap.keySet()) {
defenders.removeAll(attackMap.get(t).getUnits());
}
// Determine counter attack results to see if I can hold it
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(myCapital).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(myCapital).getMaxAmphibUnits());
final ProBattleResult result = calc.estimateDefendBattleResults(player, myCapital,
new ArrayList<>(enemyAttackingUnits), defenders, enemyAttackOptions.getMax(myCapital).getMaxBombardUnits());
ProLogger.trace("Current capital result hasLandUnitRemaining=" + result.isHasLandUnitRemaining() + ", TUVSwing="
+ result.getTUVSwing() + ", defenders=" + defenders.size() + ", attackers=" + enemyAttackingUnits.size());
// Determine attack that uses the most units per value from capital and remove it
if (result.isHasLandUnitRemaining()) {
double maxUnitsNearCapitalPerValue = 0.0;
Territory maxTerritory = null;
final Set<Territory> territoriesNearCapital = data.getMap().getNeighbors(myCapital, Matches.TerritoryIsLand);
territoriesNearCapital.add(myCapital);
for (final Territory t : attackMap.keySet()) {
int unitsNearCapital = 0;
for (final Unit u : attackMap.get(t).getUnits()) {
if (territoriesNearCapital.contains(ProData.unitTerritoryMap.get(u))) {
unitsNearCapital++;
}
}
final double unitsNearCapitalPerValue = unitsNearCapital / attackMap.get(t).getValue();
ProLogger.trace(t.getName() + " has unit near capital per value: " + unitsNearCapitalPerValue);
if (unitsNearCapitalPerValue > maxUnitsNearCapitalPerValue) {
maxUnitsNearCapitalPerValue = unitsNearCapitalPerValue;
maxTerritory = t;
}
}
if (maxTerritory != null) {
prioritizedTerritories.remove(attackMap.get(maxTerritory));
attackMap.get(maxTerritory).getUnits().clear();
attackMap.get(maxTerritory).getAmphibAttackMap().clear();
attackMap.get(maxTerritory).setBattleResult(null);
ProLogger.debug("Removing territory to try to hold capital: " + maxTerritory.getName());
} else {
break;
}
} else {
ProLogger.debug("Can hold capital: " + myCapital.getName());
break;
}
}
}
private void checkContestedSeaTerritories() {
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
for (final Territory t : ProData.myUnitTerritories) {
if (t.isWater() && Matches.territoryHasEnemyUnits(player, data).match(t)
&& (attackMap.get(t) == null || attackMap.get(t).getUnits().isEmpty())) {
// Move into random adjacent safe sea territory
final Set<Territory> possibleMoveTerritories =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, true));
if (!possibleMoveTerritories.isEmpty()) {
final Territory moveToTerritory = possibleMoveTerritories.iterator().next();
final List<Unit> mySeaUnits = t.getUnits().getMatches(ProMatches.unitCanBeMovedAndIsOwnedSea(player, true));
if (attackMap.containsKey(moveToTerritory)) {
attackMap.get(moveToTerritory).addUnits(mySeaUnits);
} else {
final ProTerritory moveTerritoryData = new ProTerritory(moveToTerritory);
moveTerritoryData.addUnits(mySeaUnits);
attackMap.put(moveToTerritory, moveTerritoryData);
}
ProLogger.info(t + " is a contested territory so moving subs to " + moveToTerritory);
}
}
}
}
private void logAttackMoves(final List<ProTerritory> prioritizedTerritories) {
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
// Print prioritization
ProLogger.debug("Prioritized territories:");
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
ProLogger.trace(" " + attackTerritoryData.getMaxBattleResult().getTUVSwing() + " "
+ attackTerritoryData.getValue() + " " + attackTerritoryData.getTerritory().getName());
}
// Print enemy territories with enemy units vs my units
ProLogger.debug("Territories that can be attacked:");
int count = 0;
for (final Territory t : attackMap.keySet()) {
count++;
ProLogger.trace(count + ". ---" + t.getName());
final Set<Unit> combinedUnits = new HashSet<>(attackMap.get(t).getMaxUnits());
combinedUnits.addAll(attackMap.get(t).getMaxAmphibUnits());
ProLogger.trace(" --- My max units ---");
final Map<String, Integer> printMap = new HashMap<>();
for (final Unit unit : combinedUnits) {
if (printMap.containsKey(unit.toStringNoOwner())) {
printMap.put(unit.toStringNoOwner(), printMap.get(unit.toStringNoOwner()) + 1);
} else {
printMap.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printMap.keySet()) {
ProLogger.trace(" " + printMap.get(key) + " " + key);
}
ProLogger.trace(" --- My max bombard units ---");
final Map<String, Integer> printBombardMap = new HashMap<>();
for (final Unit unit : attackMap.get(t).getMaxBombardUnits()) {
if (printBombardMap.containsKey(unit.toStringNoOwner())) {
printBombardMap.put(unit.toStringNoOwner(), printBombardMap.get(unit.toStringNoOwner()) + 1);
} else {
printBombardMap.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printBombardMap.keySet()) {
ProLogger.trace(" " + printBombardMap.get(key) + " " + key);
}
final List<Unit> units3 = attackMap.get(t).getUnits();
ProLogger.trace(" --- My actual units ---");
final Map<String, Integer> printMap3 = new HashMap<>();
for (final Unit unit : units3) {
if (printMap3.containsKey(unit.toStringNoOwner())) {
printMap3.put(unit.toStringNoOwner(), printMap3.get(unit.toStringNoOwner()) + 1);
} else {
printMap3.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printMap3.keySet()) {
ProLogger.trace(" " + printMap3.get(key) + " " + key);
}
ProLogger.trace(" --- Enemy units ---");
final Map<String, Integer> printMap2 = new HashMap<>();
final List<Unit> units2 = attackMap.get(t).getMaxEnemyDefenders(player, data);
for (final Unit unit : units2) {
if (printMap2.containsKey(unit.toStringNoOwner())) {
printMap2.put(unit.toStringNoOwner(), printMap2.get(unit.toStringNoOwner()) + 1);
} else {
printMap2.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printMap2.keySet()) {
ProLogger.trace(" " + printMap2.get(key) + " " + key);
}
ProLogger.trace(" --- Enemy Counter Attack Units ---");
final Map<String, Integer> printMap4 = new HashMap<>();
final List<Unit> units4 = attackMap.get(t).getMaxEnemyUnits();
for (final Unit unit : units4) {
if (printMap4.containsKey(unit.toStringNoOwner())) {
printMap4.put(unit.toStringNoOwner(), printMap4.get(unit.toStringNoOwner()) + 1);
} else {
printMap4.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printMap4.keySet()) {
ProLogger.trace(" " + printMap4.get(key) + " " + key);
}
ProLogger.trace(" --- Enemy Counter Bombard Units ---");
final Map<String, Integer> printMap5 = new HashMap<>();
final Set<Unit> units5 = attackMap.get(t).getMaxEnemyBombardUnits();
for (final Unit unit : units5) {
if (printMap5.containsKey(unit.toStringNoOwner())) {
printMap5.put(unit.toStringNoOwner(), printMap5.get(unit.toStringNoOwner()) + 1);
} else {
printMap5.put(unit.toStringNoOwner(), 1);
}
}
for (final String key : printMap5.keySet()) {
ProLogger.trace(" " + printMap4.get(key) + " " + key);
}
}
}
private boolean canAirSafelyLandAfterAttack(final Unit unit, final Territory t) {
final boolean isAdjacentToAlliedFactory = Matches
.territoryHasNeighborMatching(data, ProMatches.territoryHasInfraFactoryAndIsAlliedLand(player, data)).match(t);
final int range = TripleAUnit.get(unit).getMovementLeft();
final int distance = data.getMap().getDistance_IgnoreEndForCondition(ProData.unitTerritoryMap.get(unit), t,
ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, true));
final boolean usesMoreThanHalfOfRange = distance > range / 2;
return isAdjacentToAlliedFactory || !usesMoreThanHalfOfRange;
}
}