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.LinkedHashSet;
import java.util.List;
import java.util.Map;
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.ProPlaceTerritory;
import games.strategy.triplea.ai.proAI.data.ProPurchaseOption;
import games.strategy.triplea.ai.proAI.data.ProPurchaseTerritory;
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.AbstractMoveDelegate;
import games.strategy.triplea.delegate.BattleCalculator;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.MoveValidator;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.triplea.delegate.dataObjects.MoveValidationResult;
import games.strategy.triplea.delegate.remote.IMoveDelegate;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.Match;
/**
* Pro non-combat move AI.
*/
public class ProNonCombatMoveAI {
private final ProOddsCalculator calc;
private GameData data;
private PlayerID player;
private Map<Unit, Territory> unitTerritoryMap;
private ProTerritoryManager territoryManager;
public ProNonCombatMoveAI(final ProAI ai) {
calc = ai.getCalc();
}
public Map<Territory, ProTerritory> simulateNonCombatMove(final IMoveDelegate moveDel) {
return doNonCombatMove(null, null, moveDel);
}
public Map<Territory, ProTerritory> doNonCombatMove(Map<Territory, ProTerritory> factoryMoveMap,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories, final IMoveDelegate moveDel) {
ProLogger.info("Starting non-combat move phase");
// Current data at the start of non-combat move
data = ProData.getData();
player = ProData.getPlayer();
unitTerritoryMap = ProData.unitTerritoryMap;
territoryManager = new ProTerritoryManager(calc);
// Find the max number of units that can move to each allied territory
territoryManager.populateDefenseOptions(new ArrayList<>());
// Find number of units in each move territory that can't move and all infra units
findUnitsThatCantMove(purchaseTerritories, ProData.purchaseOptions.getLandOptions());
final Map<Unit, Set<Territory>> infraUnitMoveMap = findInfraUnitsThatCanMove();
// Try to have one land unit in each territory that is bordering an enemy territory
final List<Territory> movedOneDefenderToTerritories = moveOneDefenderToLandTerritoriesBorderingEnemy();
// Determine max enemy attack units and if territories can be held
territoryManager.populateEnemyAttackOptions(movedOneDefenderToTerritories, territoryManager.getDefendTerritories());
determineIfMoveTerritoriesCanBeHeld();
// Get list of territories that can't be held and find move value for each territory
final List<Territory> territoriesThatCantBeHeld = territoryManager.getCantHoldTerritories();
final Map<Territory, Double> territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, territoriesThatCantBeHeld, new ArrayList<>());
final Map<Territory, Double> seaTerritoryValueMap =
ProTerritoryValueUtils.findSeaTerritoryValues(player, territoriesThatCantBeHeld);
// Prioritize territories to defend
final List<ProTerritory> prioritizedTerritories = prioritizeDefendOptions(factoryMoveMap, territoryValueMap);
// Determine which territories to defend and how many units each one needs
final int enemyDistance = ProUtils.getClosestEnemyLandTerritoryDistance(data, player, ProData.myCapital);
moveUnitsToDefendTerritories(prioritizedTerritories, enemyDistance, territoryValueMap);
// Copy data in case capital defense needs increased
final ProTerritoryManager territoryManagerCopy = new ProTerritoryManager(calc, territoryManager);
// Use loop to ensure capital is protected after moves
if (ProData.myCapital != null) {
int defenseRange = -1;
while (true) {
// Add value to territories near capital if necessary
for (final Territory t : territoryManager.getDefendTerritories()) {
double value = territoryValueMap.get(t);
final int distance = data.getMap().getDistance(ProData.myCapital, t,
ProMatches.territoryCanMoveLandUnits(player, data, false));
if (distance >= 0 && distance <= defenseRange) {
value *= 10;
}
territoryManager.getDefendOptions().getTerritoryMap().get(t).setValue(value);
if (t.isWater()) {
territoryManager.getDefendOptions().getTerritoryMap().get(t).setSeaValue(seaTerritoryValueMap.get(t));
}
}
moveUnitsToBestTerritories();
// Check if capital has local land superiority
ProLogger.info("Checking if capital has local land superiority with enemyDistance=" + enemyDistance);
if (enemyDistance >= 2 && enemyDistance <= 3 && defenseRange == -1
&& !ProBattleUtils.territoryHasLocalLandSuperiorityAfterMoves(ProData.myCapital, enemyDistance, player,
territoryManager.getDefendOptions().getTerritoryMap())) {
defenseRange = enemyDistance - 1;
territoryManager = territoryManagerCopy;
ProLogger.debug("Capital doesn't have local land superiority so setting defensive stance");
} else {
break;
}
}
} else {
moveUnitsToBestTerritories();
}
// Determine where to move infra units
factoryMoveMap = moveInfraUnits(factoryMoveMap, infraUnitMoveMap);
// Log a warning if any units not assigned to a territory (skip infrastructure for now)
for (final Unit u : territoryManager.getDefendOptions().getUnitMoveMap().keySet()) {
if (Matches.UnitIsInfrastructure.invert().match(u)) {
ProLogger.warn(player + ": " + unitTerritoryMap.get(u) + " has unmoved unit: " + u + " with options: "
+ territoryManager.getDefendOptions().getUnitMoveMap().get(u));
}
}
// Calculate move routes and perform moves
doMove(territoryManager.getDefendOptions().getTerritoryMap(), moveDel, data, player);
// Log results
ProLogger.info("Logging results");
logAttackMoves(prioritizedTerritories);
return factoryMoveMap;
}
public void doMove(final Map<Territory, ProTerritory> moveMap, final IMoveDelegate moveDel, final GameData data,
final PlayerID player) {
this.data = data;
this.player = player;
// Calculate move routes and perform moves
final List<Collection<Unit>> moveUnits = new ArrayList<>();
final List<Route> moveRoutes = new ArrayList<>();
ProMoveUtils.calculateMoveRoutes(player, moveUnits, moveRoutes, moveMap, false);
ProMoveUtils.doMove(moveUnits, moveRoutes, moveDel);
// Calculate amphib move routes and perform moves
moveUnits.clear();
moveRoutes.clear();
final List<Collection<Unit>> transportsToLoad = new ArrayList<>();
ProMoveUtils.calculateAmphibRoutes(player, moveUnits, moveRoutes, transportsToLoad, moveMap, false);
ProMoveUtils.doMove(moveUnits, moveRoutes, transportsToLoad, moveDel);
}
private void findUnitsThatCantMove(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPurchaseOption> landPurchaseOptions) {
ProLogger.info("Find units that can't move");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getDefendOptions().getUnitMoveMap();
final List<ProTransport> transportMapList = territoryManager.getDefendOptions().getTransportList();
// Add all units that can't move (allied units, 0 move units, etc)
for (final Territory t : moveMap.keySet()) {
moveMap.get(t).getCantMoveUnits()
.addAll(t.getUnits().getMatches(ProMatches.unitCantBeMovedAndIsAlliedDefender(player, data, t)));
}
// Add all units that only have 1 move option and can't be transported
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (unitMoveMap.get(u).size() == 1) {
final Territory onlyTerritory = unitMoveMap.get(u).iterator().next();
if (onlyTerritory.equals(unitTerritoryMap.get(u))) {
boolean canBeTransported = false;
for (final ProTransport pad : transportMapList) {
for (final Territory t : pad.getTransportMap().keySet()) {
if (pad.getTransportMap().get(t).contains(onlyTerritory)) {
canBeTransported = true;
}
}
for (final Territory t : pad.getSeaTransportMap().keySet()) {
if (pad.getSeaTransportMap().get(t).contains(onlyTerritory)) {
canBeTransported = true;
}
}
}
if (!canBeTransported) {
moveMap.get(onlyTerritory).getCantMoveUnits().add(u);
it.remove();
}
}
}
}
// Check if purchase units are known yet
if (purchaseTerritories != null) {
// Add all units that will be purchased
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (moveMap.get(t) != null) {
moveMap.get(t).getCantMoveUnits().addAll(placeTerritory.getPlaceUnits());
}
}
}
} else {
// Add max defenders that can be purchased to each territory
for (final Territory t : moveMap.keySet()) {
if (ProMatches.territoryHasNonMobileInfraFactoryAndIsNotConqueredOwnedLand(player, data).match(t)) {
moveMap.get(t).getCantMoveUnits()
.addAll(ProPurchaseUtils.findMaxPurchaseDefenders(player, t, landPurchaseOptions));
}
}
}
// Log can't move units per territory
for (final Territory t : moveMap.keySet()) {
if (!moveMap.get(t).getCantMoveUnits().isEmpty()) {
ProLogger.trace(t + " has units that can't move: " + moveMap.get(t).getCantMoveUnits());
}
}
}
private Map<Unit, Set<Territory>> findInfraUnitsThatCanMove() {
ProLogger.info("Find non-combat infra units that can move");
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getDefendOptions().getUnitMoveMap();
// Add all units that are infra
final Map<Unit, Set<Territory>> infraUnitMoveMap = new HashMap<>();
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (ProMatches.unitCanBeMovedAndIsOwnedNonCombatInfra(player).match(u)) {
infraUnitMoveMap.put(u, unitMoveMap.get(u));
ProLogger.trace(u + " is infra unit with move options: " + unitMoveMap.get(u));
it.remove();
}
}
return infraUnitMoveMap;
}
private List<Territory> moveOneDefenderToLandTerritoriesBorderingEnemy() {
ProLogger.info("Determine which territories to defend with one land unit");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getDefendOptions().getUnitMoveMap();
// Find land territories with no can't move units and adjacent to enemy land units
final List<Territory> territoriesToDefendWithOneUnit = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
final boolean hasAlliedLandUnits =
Match.someMatch(moveMap.get(t).getCantMoveUnits(), ProMatches.unitIsAlliedLandAndNotInfra(player, data));
if (!t.isWater() && !hasAlliedLandUnits
&& ProMatches
.territoryHasNeighborOwnedByAndHasLandUnit(data, player, ProUtils.getPotentialEnemyPlayers(player))
.match(t)) {
territoriesToDefendWithOneUnit.add(t);
}
}
final List<Territory> result = new ArrayList<>(territoriesToDefendWithOneUnit);
// Sort units by number of defend options and cost
final Map<Unit, Set<Territory>> sortedUnitMoveOptions =
ProSortMoveOptionsUtils.sortUnitMoveOptions(player, unitMoveMap);
// Set unit with the fewest move options in each territory
for (final Unit unit : sortedUnitMoveOptions.keySet()) {
if (Matches.UnitIsLand.match(unit)) {
for (final Territory t : sortedUnitMoveOptions.get(unit)) {
final int unitValue = ProData.unitValueMap.getInt(unit.getType());
int production = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
}
// Only defend territories that either already have units (avoid abandoning territories)
// or where unit value is less than production + 3 (avoid sacrificing expensive units to block)
if (territoriesToDefendWithOneUnit.contains(t)
&& (unitValue <= (production + 3) || Matches.territoryHasUnitsOwnedBy(player).match(t))) {
moveMap.get(t).addUnit(unit);
unitMoveMap.remove(unit);
territoriesToDefendWithOneUnit.remove(t);
ProLogger.debug(t + ", added one land unit: " + unit);
break;
}
}
if (territoriesToDefendWithOneUnit.isEmpty()) {
break;
}
}
}
// Only return territories that received a defender
result.removeAll(territoriesToDefendWithOneUnit);
return result;
}
private void determineIfMoveTerritoriesCanBeHeld() {
ProLogger.info("Find max enemy attackers and if territories can be held");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Determine which territories can possibly be held
for (final Territory t : moveMap.keySet()) {
final ProTerritory patd = moveMap.get(t);
// Check if no enemy attackers
if (enemyAttackOptions.getMax(t) == null) {
ProLogger.debug("Territory=" + t.getName() + ", CanHold=true since has no enemy attackers");
continue;
}
// Check if min defenders can hold it (not considering AA)
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());
final List<Unit> minDefendingUnitsAndNotAA =
Match.getMatches(patd.getCantMoveUnits(), Matches.UnitIsAAforAnything.invert());
final ProBattleResult minResult = calc.calculateBattleResults(player, t, new ArrayList<>(enemyAttackingUnits),
minDefendingUnitsAndNotAA, enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
patd.setMinBattleResult(minResult);
if (minResult.getTUVSwing() <= 0 && !minDefendingUnitsAndNotAA.isEmpty()) {
ProLogger.debug("Territory=" + t.getName() + ", CanHold=true" + ", MinDefenders="
+ minDefendingUnitsAndNotAA.size() + ", EnemyAttackers=" + enemyAttackingUnits.size() + ", win%="
+ minResult.getWinPercentage() + ", EnemyTUVSwing=" + minResult.getTUVSwing() + ", hasLandUnitRemaining="
+ minResult.isHasLandUnitRemaining());
continue;
}
// Check if max defenders can hold it (not considering AA)
final Set<Unit> defendingUnits = new HashSet<>(patd.getMaxUnits());
defendingUnits.addAll(patd.getMaxAmphibUnits());
defendingUnits.addAll(patd.getCantMoveUnits());
final List<Unit> defendingUnitsAndNotAA = Match.getMatches(defendingUnits, Matches.UnitIsAAforAnything.invert());
final ProBattleResult result = calc.calculateBattleResults(player, t, new ArrayList<>(enemyAttackingUnits),
defendingUnitsAndNotAA, enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
isFactory = 1;
}
int isMyCapital = 0;
if (t.equals(ProData.myCapital)) {
isMyCapital = 1;
}
final List<Unit> extraUnits = new ArrayList<>(defendingUnitsAndNotAA);
extraUnits.removeAll(minDefendingUnitsAndNotAA);
final double extraUnitValue = BattleCalculator.getTUV(extraUnits, ProData.unitValueMap);
final double holdValue = extraUnitValue / 8 * (1 + 0.5 * isFactory) * (1 + 2 * isMyCapital);
if (minDefendingUnitsAndNotAA.size() != defendingUnitsAndNotAA.size()
&& (result.getTUVSwing() - holdValue) < minResult.getTUVSwing()) {
ProLogger
.debug("Territory=" + t.getName() + ", CanHold=true" + ", MaxDefenders=" + defendingUnitsAndNotAA.size()
+ ", EnemyAttackers=" + enemyAttackingUnits.size() + ", minTUVSwing=" + minResult.getTUVSwing()
+ ", win%=" + result.getWinPercentage() + ", EnemyTUVSwing=" + result.getTUVSwing()
+ ", hasLandUnitRemaining=" + result.isHasLandUnitRemaining() + ", holdValue=" + holdValue);
continue;
}
// Can't hold territory
patd.setCanHold(false);
ProLogger.debug("Can't hold Territory=" + t.getName() + ", MaxDefenders=" + defendingUnitsAndNotAA.size()
+ ", EnemyAttackers=" + enemyAttackingUnits.size() + ", minTUVSwing=" + minResult.getTUVSwing() + ", win%="
+ result.getWinPercentage() + ", EnemyTUVSwing=" + result.getTUVSwing() + ", hasLandUnitRemaining="
+ result.isHasLandUnitRemaining() + ", holdValue=" + holdValue);
}
}
private List<ProTerritory> prioritizeDefendOptions(final Map<Territory, ProTerritory> factoryMoveMap,
final Map<Territory, Double> territoryValueMap) {
ProLogger.info("Prioritizing territories to try to defend");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Calculate value of defending territory
for (final Territory t : moveMap.keySet()) {
// Determine if it is my capital or adjacent to my capital
int isMyCapital = 0;
if (t.equals(ProData.myCapital)) {
isMyCapital = 1;
}
// Determine if it has a factory
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)
|| (factoryMoveMap != null && factoryMoveMap.containsKey(t))) {
isFactory = 1;
}
// Determine production value and if it is an enemy capital
int production = 0;
int isEnemyOrAlliedCapital = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
if (ta.isCapital() && !t.equals(ProData.myCapital)) {
isEnemyOrAlliedCapital = 1;
}
}
// Determine neighbor value
double neighborValue = 0;
if (!t.isWater()) {
final Set<Territory> landNeighbors = data.getMap().getNeighbors(t, Matches.TerritoryIsLand);
for (final Territory neighbor : landNeighbors) {
double neighborProduction = TerritoryAttachment.getProduction(neighbor);
if (Matches.isTerritoryAllied(player, data).match(neighbor)) {
neighborProduction = 0.1 * neighborProduction;
}
neighborValue += neighborProduction;
}
}
// Determine defending unit value
final int cantMoveUnitValue = BattleCalculator.getTUV(moveMap.get(t).getCantMoveUnits(), ProData.unitValueMap);
double unitOwnerMultiplier = 1;
if (Match.noneMatch(moveMap.get(t).getCantMoveUnits(), Matches.unitIsOwnedBy(player))) {
if (t.isWater()
&& Match.noneMatch(moveMap.get(t).getCantMoveUnits(), Matches.UnitIsTransportButNotCombatTransport)) {
unitOwnerMultiplier = 0;
} else {
unitOwnerMultiplier = 0.5;
}
}
// Calculate defense value for prioritization
final double territoryValue =
unitOwnerMultiplier * (2 * production + 10 * isFactory + 0.5 * cantMoveUnitValue + 0.5 * neighborValue)
* (1 + 10 * isMyCapital) * (1 + 4 * isEnemyOrAlliedCapital);
moveMap.get(t).setValue(territoryValue);
}
// Sort attack territories by value
final List<ProTerritory> prioritizedTerritories = new ArrayList<>(moveMap.values());
Collections.sort(prioritizedTerritories, (t1, t2) -> {
final double value1 = t1.getValue();
final double value2 = t2.getValue();
return Double.compare(value2, value1);
});
// Remove territories that I'm not going to try to defend
for (final Iterator<ProTerritory> it = prioritizedTerritories.iterator(); it.hasNext();) {
final ProTerritory patd = it.next();
final Territory t = patd.getTerritory();
final boolean hasFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t);
final ProBattleResult minResult = patd.getMinBattleResult();
final int cantMoveUnitValue = BattleCalculator.getTUV(moveMap.get(t).getCantMoveUnits(), ProData.unitValueMap);
final boolean isLandAndCanOnlyBeAttackedByAir =
!t.isWater() && Match.allMatch(patd.getMaxEnemyUnits(), Matches.UnitIsAir);
final boolean isNotFactoryAndShouldHold =
!hasFactory && (minResult.getTUVSwing() <= 0 || !minResult.isHasLandUnitRemaining());
final boolean canAlreadyBeHeld =
minResult.getTUVSwing() <= 0 && minResult.getWinPercentage() < (100 - ProData.winPercentage);
final boolean isNotFactoryAndHasNoEnemyNeighbors = !t.isWater() && !hasFactory
&& !ProMatches
.territoryHasNeighborOwnedByAndHasLandUnit(data, player, ProUtils.getPotentialEnemyPlayers(player))
.match(t);
final boolean isNotFactoryAndOnlyAmphib = !t.isWater() && !hasFactory
&& Match.noneMatch(moveMap.get(t).getMaxUnits(), Matches.UnitIsLand) && cantMoveUnitValue < 5;
if (!patd.isCanHold() || patd.getValue() <= 0 || isLandAndCanOnlyBeAttackedByAir || isNotFactoryAndShouldHold
|| canAlreadyBeHeld || isNotFactoryAndHasNoEnemyNeighbors || isNotFactoryAndOnlyAmphib) {
final double TUVSwing = minResult.getTUVSwing();
final boolean hasRemainingLandUnit = minResult.isHasLandUnitRemaining();
ProLogger.debug("Removing territory=" + t.getName() + ", value=" + patd.getValue() + ", CanHold="
+ patd.isCanHold() + ", isLandAndCanOnlyBeAttackedByAir=" + isLandAndCanOnlyBeAttackedByAir
+ ", isNotFactoryAndShouldHold=" + isNotFactoryAndShouldHold + ", canAlreadyBeHeld=" + canAlreadyBeHeld
+ ", isNotFactoryAndHasNoEnemyNeighbors=" + isNotFactoryAndHasNoEnemyNeighbors
+ ", isNotFactoryAndOnlyAmphib=" + isNotFactoryAndOnlyAmphib + ", TUVSwing=" + TUVSwing
+ ", hasRemainingLandUnit=" + hasRemainingLandUnit + ", maxEnemyUnits=" + patd.getMaxEnemyUnits().size());
it.remove();
}
}
// Add best sea production territory for sea factories
List<Territory> seaFactories = Match.getMatches(data.getMap().getTerritories(),
ProMatches.territoryHasInfraFactoryAndIsNotConqueredOwnedLand(player, data));
seaFactories =
Match.getMatches(seaFactories, ProMatches.territoryHasInfraFactoryAndIsOwnedLandAdjacentToSea(player, data));
for (final Territory t : seaFactories) {
if (territoryValueMap.get(t) >= 1) {
continue;
}
final Set<Territory> neighbors =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, true));
double maxValue = 0;
Territory maxTerritory = null;
for (final Territory neighbor : neighbors) {
if (moveMap.get(neighbor) != null && moveMap.get(neighbor).isCanHold()
&& territoryValueMap.get(neighbor) > maxValue) {
maxTerritory = neighbor;
maxValue = territoryValueMap.get(neighbor);
}
}
if (maxTerritory != null && enemyAttackOptions.getMax(maxTerritory) != null) {
boolean alreadyAdded = false;
for (final ProTerritory patd : prioritizedTerritories) {
if (patd.getTerritory().equals(maxTerritory)) {
alreadyAdded = true;
}
}
if (!alreadyAdded) {
prioritizedTerritories.add(moveMap.get(maxTerritory));
}
}
}
// Log prioritized territories
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
ProLogger.debug("Value=" + attackTerritoryData.getValue() + ", " + attackTerritoryData.getTerritory().getName());
}
return prioritizedTerritories;
}
private void moveUnitsToDefendTerritories(final List<ProTerritory> prioritizedTerritories, final int enemyDistance,
final Map<Territory, Double> territoryValueMap) {
ProLogger.info("Determine units to defend territories with");
if (prioritizedTerritories.isEmpty()) {
return;
}
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getDefendOptions().getUnitMoveMap();
final Map<Unit, Set<Territory>> transportMoveMap = territoryManager.getDefendOptions().getTransportMoveMap();
final List<ProTransport> transportMapList = territoryManager.getDefendOptions().getTransportList();
// Assign units to territories by prioritization
int numToDefend = 1;
while (true) {
// Reset lists
for (final Territory t : moveMap.keySet()) {
moveMap.get(t).getTempUnits().clear();
moveMap.get(t).getTempAmphibAttackMap().clear();
moveMap.get(t).getTransportTerritoryMap().clear();
moveMap.get(t).setBattleResult(null);
}
// Determine number of territories to defend
if (numToDefend <= 0) {
break;
}
final List<ProTerritory> territoriesToTryToDefend = prioritizedTerritories.subList(0, numToDefend);
// Loop through all units and determine defend options
final Map<Unit, Set<Territory>> unitDefendOptions = new HashMap<>();
for (final Unit unit : unitMoveMap.keySet()) {
// Find number of move options
final Set<Territory> canDefendTerritories = new LinkedHashSet<>();
for (final ProTerritory attackTerritoryData : territoriesToTryToDefend) {
if (unitMoveMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canDefendTerritories.add(attackTerritoryData.getTerritory());
}
}
unitDefendOptions.put(unit, canDefendTerritories);
}
// Sort units by number of defend options and cost
final Map<Unit, Set<Territory>> sortedUnitMoveOptions =
ProSortMoveOptionsUtils.sortUnitMoveOptions(player, unitDefendOptions);
// Set enough units in territories to have at least a chance of winning
for (final Iterator<Unit> it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (isAirUnit || Matches.UnitIsCarrier.match(unit)) {
continue; // skip air and carrier units
}
final TreeMap<Double, Territory> estimatesMap = new TreeMap<>();
for (final Territory t : sortedUnitMoveOptions.get(unit)) {
List<Unit> defendingUnits = Match.getMatches(moveMap.get(t).getAllDefenders(),
ProMatches.unitIsAlliedNotOwnedAir(player, data).invert());
if (t.isWater()) {
defendingUnits = moveMap.get(t).getAllDefenders();
}
final double estimate =
ProBattleUtils.estimateStrengthDifference(t, moveMap.get(t).getMaxEnemyUnits(), defendingUnits);
estimatesMap.put(estimate, t);
}
if (!estimatesMap.isEmpty() && estimatesMap.lastKey() > 60) {
final Territory minWinTerritory = estimatesMap.lastEntry().getValue();
moveMap.get(minWinTerritory).addTempUnit(unit);
it.remove();
}
}
// Set non-air units in territories
for (final Iterator<Unit> it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
if (Matches.UnitCanLandOnCarrier.match(unit)) {
continue;
}
Territory maxWinTerritory = null;
double maxWinPercentage = -1;
for (final Territory t : sortedUnitMoveOptions.get(unit)) {
List<Unit> defendingUnits = Match.getMatches(moveMap.get(t).getAllDefenders(),
ProMatches.unitIsAlliedNotOwnedAir(player, data).invert());
if (t.isWater()) {
defendingUnits = moveMap.get(t).getAllDefenders();
}
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
final boolean hasFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t);
if (result.getWinPercentage() > maxWinPercentage
&& ((t.equals(ProData.myCapital) && result.getWinPercentage() > (100 - ProData.winPercentage))
|| (hasFactory && result.getWinPercentage() > (100 - ProData.minWinPercentage))
|| result.getTUVSwing() >= 0)) {
maxWinTerritory = t;
maxWinPercentage = result.getWinPercentage();
}
}
if (maxWinTerritory != null) {
moveMap.get(maxWinTerritory).addTempUnit(unit);
moveMap.get(maxWinTerritory).setBattleResult(null);
it.remove();
// If carrier has dependent allied fighters then move them too
if (Matches.UnitIsCarrier.match(unit)) {
final Territory unitTerritory = unitTerritoryMap.get(unit);
final Map<Unit, Collection<Unit>> carrierMustMoveWith =
MoveValidator.carrierMustMoveWith(unitTerritory.getUnits().getUnits(), unitTerritory, data, player);
if (carrierMustMoveWith.containsKey(unit)) {
moveMap.get(maxWinTerritory).getTempUnits().addAll(carrierMustMoveWith.get(unit));
}
}
}
}
// Set air units in territories
for (final Iterator<Unit> it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) {
final Unit unit = it.next();
Territory maxWinTerritory = null;
double maxWinPercentage = -1;
for (final Territory t : sortedUnitMoveOptions.get(unit)) {
if (t.isWater() && Matches.UnitIsAir.match(unit)) {
if (!ProTransportUtils.validateCarrierCapacity(player, t,
moveMap.get(t).getAllDefendersForCarrierCalcs(data, player), unit)) {
continue; // skip moving air to water if not enough carrier capacity
}
}
if (!t.isWater() && !t.getOwner().equals(player) && Matches.UnitIsAir.match(unit)
&& !ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
continue; // skip moving air units to allied land without a factory
}
List<Unit> defendingUnits = Match.getMatches(moveMap.get(t).getAllDefenders(),
ProMatches.unitIsAlliedNotOwnedAir(player, data).invert());
if (t.isWater()) {
defendingUnits = moveMap.get(t).getAllDefenders();
}
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
final boolean hasFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t);
if (result.getWinPercentage() > maxWinPercentage
&& ((t.equals(ProData.myCapital) && result.getWinPercentage() > (100 - ProData.winPercentage))
|| (hasFactory && result.getWinPercentage() > (100 - ProData.minWinPercentage))
|| result.getTUVSwing() >= 0)) {
maxWinTerritory = t;
maxWinPercentage = result.getWinPercentage();
}
}
if (maxWinTerritory != null) {
moveMap.get(maxWinTerritory).addTempUnit(unit);
moveMap.get(maxWinTerritory).setBattleResult(null);
it.remove();
}
}
// Loop through all my transports and see which territories they can defend from current list
final List<Unit> alreadyMovedTransports = new ArrayList<>();
if (!Properties.getTransportCasualtiesRestricted(data)) {
final Map<Unit, Set<Territory>> transportDefendOptions = new HashMap<>();
for (final Unit unit : transportMoveMap.keySet()) {
// Find number of defend options
final Set<Territory> canDefendTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : territoriesToTryToDefend) {
if (transportMoveMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canDefendTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canDefendTerritories.isEmpty()) {
transportDefendOptions.put(unit, canDefendTerritories);
}
}
// Loop through transports with move options and determine if any naval defense needs it
for (final Unit transport : transportDefendOptions.keySet()) {
// Find current naval defense that needs transport if it isn't transporting units
for (final Territory t : transportDefendOptions.get(transport)) {
if (!TransportTracker.isTransporting(transport)) {
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
if (result.getTUVSwing() > 0) {
moveMap.get(t).addTempUnit(transport);
moveMap.get(t).setBattleResult(null);
alreadyMovedTransports.add(transport);
ProLogger.trace("Adding defend transport to: " + t.getName());
break;
}
}
}
}
}
// Loop through all my transports and see which can make amphib move
final Map<Unit, Set<Territory>> amphibMoveOptions = new HashMap<>();
for (final ProTransport proTransportData : transportMapList) {
// If already used to defend then ignore
if (alreadyMovedTransports.contains(proTransportData.getTransport())) {
continue;
}
// Find number of amphib move options
final Set<Territory> canAmphibMoveTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : territoriesToTryToDefend) {
if (proTransportData.getTransportMap().containsKey(attackTerritoryData.getTerritory())) {
canAmphibMoveTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canAmphibMoveTerritories.isEmpty()) {
amphibMoveOptions.put(proTransportData.getTransport(), canAmphibMoveTerritories);
}
}
// Loop through transports with amphib move options and determine if any land defense needs it
for (final Unit transport : amphibMoveOptions.keySet()) {
// Find current land defense results for territories that unit can amphib move
for (final Territory t : amphibMoveOptions.get(transport)) {
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
final boolean hasFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t);
if ((t.equals(ProData.myCapital) && result.getWinPercentage() > (100 - ProData.winPercentage))
|| (hasFactory && result.getWinPercentage() > (100 - ProData.minWinPercentage))
|| result.getTUVSwing() > 0) {
// Get all units that have already moved
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t2 : moveMap.keySet()) {
alreadyMovedUnits.addAll(moveMap.get(t2).getUnits());
alreadyMovedUnits.addAll(moveMap.get(t2).getTempUnits());
}
// Find units that haven't moved and can be transported
boolean addedAmphibUnits = false;
for (final ProTransport proTransportData : transportMapList) {
if (proTransportData.getTransport().equals(transport)) {
// Find units to transport
final Set<Territory> territoriesCanLoadFrom = proTransportData.getTransportMap().get(t);
final List<Unit> amphibUnitsToAdd = ProTransportUtils.getUnitsToTransportFromTerritories(player,
transport, territoriesCanLoadFrom, alreadyMovedUnits);
if (amphibUnitsToAdd.isEmpty()) {
continue;
}
// Find safest territory to unload from
double minStrengthDifference = Double.POSITIVE_INFINITY;
Territory minTerritory = 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(unitTerritoryMap.get(u));
}
for (final Territory territoryToMoveTransport : territoriesToMoveTransport) {
if (proTransportData.getSeaTransportMap().containsKey(territoryToMoveTransport)
&& proTransportData.getSeaTransportMap().get(territoryToMoveTransport)
.containsAll(loadFromTerritories)
&& moveMap.get(territoryToMoveTransport) != null
&& (moveMap.get(territoryToMoveTransport).isCanHold() || hasFactory)) {
final List<Unit> attackers = moveMap.get(territoryToMoveTransport).getMaxEnemyUnits();
final List<Unit> defenders = moveMap.get(territoryToMoveTransport).getAllDefenders();
defenders.add(transport);
final double strengthDifference =
ProBattleUtils.estimateStrengthDifference(territoryToMoveTransport, attackers, defenders);
if (strengthDifference < minStrengthDifference) {
minTerritory = territoryToMoveTransport;
minStrengthDifference = strengthDifference;
}
}
}
if (minTerritory != null) {
// Add amphib defense
moveMap.get(t).getTransportTerritoryMap().put(transport, minTerritory);
moveMap.get(t).addTempUnits(amphibUnitsToAdd);
moveMap.get(t).putTempAmphibAttackMap(transport, amphibUnitsToAdd);
moveMap.get(t).setBattleResult(null);
for (final Unit unit : amphibUnitsToAdd) {
sortedUnitMoveOptions.remove(unit);
}
ProLogger.trace("Adding amphibious defense to: " + t + ", units=" + amphibUnitsToAdd
+ ", unloadTerritory=" + minTerritory);
addedAmphibUnits = true;
break;
}
}
}
if (addedAmphibUnits) {
break;
}
}
}
}
// Determine if all defenses are successful
boolean areSuccessful = true;
boolean containsCapital = false;
ProLogger.debug("Current number of territories: " + numToDefend);
for (final ProTerritory patd : territoriesToTryToDefend) {
final Territory t = patd.getTerritory();
// Find defense result and hold value based on used defenders TUV
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
moveMap.get(t).setBattleResult(calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits(), false));
final ProBattleResult result = patd.getBattleResult();
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
isFactory = 1;
}
int isMyCapital = 0;
if (t.equals(ProData.myCapital)) {
isMyCapital = 1;
containsCapital = true;
}
final double extraUnitValue = BattleCalculator.getTUV(moveMap.get(t).getTempUnits(), ProData.unitValueMap);
final List<Unit> unsafeTransports = new ArrayList<>();
for (final Unit transport : moveMap.get(t).getTransportTerritoryMap().keySet()) {
final Territory transportTerritory = moveMap.get(t).getTransportTerritoryMap().get(transport);
if (!moveMap.get(transportTerritory).isCanHold()) {
unsafeTransports.add(transport);
}
}
final int unsafeTransportValue = BattleCalculator.getTUV(unsafeTransports, ProData.unitValueMap);
final double holdValue =
extraUnitValue / 8 * (1 + 0.5 * isFactory) * (1 + 2 * isMyCapital) - unsafeTransportValue;
// Find strategic value
boolean hasHigherStrategicValue = true;
if (!t.isWater() && !t.equals(ProData.myCapital)
&& !ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
double totalValue = 0.0;
final List<Unit> nonAirDefenders = Match.getMatches(moveMap.get(t).getTempUnits(), Matches.UnitIsNotAir);
for (final Unit u : nonAirDefenders) {
totalValue += territoryValueMap.get(unitTerritoryMap.get(u));
}
final double averageValue = totalValue / nonAirDefenders.size();
if (territoryValueMap.get(t) < averageValue) {
hasHigherStrategicValue = false;
ProLogger.trace(t + " has lower value then move from with value=" + territoryValueMap.get(t)
+ ", averageMoveFromValue=" + averageValue);
}
}
// Check if its worth defending
if ((result.getTUVSwing() - holdValue) > patd.getMinBattleResult().getTUVSwing() || (!hasHigherStrategicValue
&& (result.getTUVSwing() + extraUnitValue / 2) >= patd.getMinBattleResult().getTUVSwing())) {
areSuccessful = false;
}
ProLogger.debug(patd.getResultString() + ", holdValue=" + holdValue + ", minTUVSwing="
+ patd.getMinBattleResult().getTUVSwing() + ", hasHighStrategicValue=" + hasHigherStrategicValue
+ ", defenders=" + defendingUnits + ", attackers=" + moveMap.get(t).getMaxEnemyUnits());
}
final Territory currentTerritory = prioritizedTerritories.get(numToDefend - 1).getTerritory();
if (ProData.myCapital != null) {
// Check capital defense
if (containsCapital && !currentTerritory.equals(ProData.myCapital)
&& moveMap.get(ProData.myCapital).getBattleResult().getWinPercentage() > (100 - ProData.winPercentage)) {
if (!Collections.disjoint(moveMap.get(currentTerritory).getAllDefenders(),
moveMap.get(ProData.myCapital).getMaxDefenders())) {
areSuccessful = false;
ProLogger.debug("Capital isn't safe after defense moves with winPercentage="
+ moveMap.get(ProData.myCapital).getBattleResult().getWinPercentage());
}
}
// Check capital local superiority
if (!currentTerritory.isWater() && enemyDistance >= 2 && enemyDistance <= 3) {
final int distance = data.getMap().getDistance(ProData.myCapital, currentTerritory,
ProMatches.territoryCanMoveLandUnits(player, data, true));
if (distance > 0 && (enemyDistance == distance || enemyDistance == (distance - 1)) && !ProBattleUtils
.territoryHasLocalLandSuperiorityAfterMoves(ProData.myCapital, enemyDistance, player, moveMap)) {
areSuccessful = false;
ProLogger.debug(
"Capital doesn't have local land superiority after defense moves with enemyDistance=" + enemyDistance);
}
}
}
// Determine whether to try more territories, remove a territory, or end
if (areSuccessful) {
numToDefend++;
for (final ProTerritory patd : territoriesToTryToDefend) {
patd.setCanAttack(true);
}
// Can defend all territories in list so end
if (numToDefend > prioritizedTerritories.size()) {
break;
}
} else {
// Remove territory last territory in prioritized list since we can't hold them all
ProLogger.debug("Removing territory: " + currentTerritory);
prioritizedTerritories.get(numToDefend - 1).setCanHold(false);
prioritizedTerritories.remove(numToDefend - 1);
if (numToDefend > prioritizedTerritories.size()) {
numToDefend--;
}
}
}
// Add temp units to move lists
for (final Territory t : moveMap.keySet()) {
// Handle allied units such as fighters on carriers
final List<Unit> alliedUnits =
Match.getMatches(moveMap.get(t).getTempUnits(), Matches.unitIsOwnedBy(player).invert());
for (final Unit alliedUnit : alliedUnits) {
moveMap.get(t).addCantMoveUnit(alliedUnit);
moveMap.get(t).getTempUnits().remove(alliedUnit);
}
moveMap.get(t).addUnits(moveMap.get(t).getTempUnits());
moveMap.get(t).putAllAmphibAttackMap(moveMap.get(t).getTempAmphibAttackMap());
for (final Unit u : moveMap.get(t).getTempUnits()) {
if (Matches.UnitIsTransport.match(u)) {
transportMoveMap.remove(u);
for (final Iterator<ProTransport> it = transportMapList.iterator(); it.hasNext();) {
if (it.next().getTransport().equals(u)) {
it.remove();
}
}
} else {
unitMoveMap.remove(u);
}
}
for (final Unit u : moveMap.get(t).getTempAmphibAttackMap().keySet()) {
transportMoveMap.remove(u);
for (final Iterator<ProTransport> it = transportMapList.iterator(); it.hasNext();) {
if (it.next().getTransport().equals(u)) {
it.remove();
}
}
}
moveMap.get(t).getTempUnits().clear();
moveMap.get(t).getTempAmphibAttackMap().clear();
}
ProLogger.debug("Final number of territories: " + (numToDefend - 1));
}
private void moveUnitsToBestTerritories() {
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final Map<Unit, Set<Territory>> unitMoveMap = territoryManager.getDefendOptions().getUnitMoveMap();
final Map<Unit, Set<Territory>> transportMoveMap = territoryManager.getDefendOptions().getTransportMoveMap();
final List<ProTransport> transportMapList = territoryManager.getDefendOptions().getTransportList();
while (true) {
ProLogger.info("Move units to best value territories");
final Set<Territory> territoriesToDefend = new HashSet<>();
final Map<Unit, Set<Territory>> currentUnitMoveMap = new HashMap<>(unitMoveMap);
final Map<Unit, Set<Territory>> currentTransportMoveMap = new HashMap<>(transportMoveMap);
final List<ProTransport> currentTransportMapList = new ArrayList<>(transportMapList);
// Reset lists
for (final Territory t : moveMap.keySet()) {
moveMap.get(t).getTempUnits().clear();
for (final Unit transport : moveMap.get(t).getTempAmphibAttackMap().keySet()) {
moveMap.get(t).getTransportTerritoryMap().remove(transport);
}
moveMap.get(t).getTempAmphibAttackMap().clear();
moveMap.get(t).setBattleResult(null);
}
ProLogger.debug("Move amphib units");
// Transport amphib units to best territory
for (final Iterator<ProTransport> it = currentTransportMapList.iterator(); it.hasNext();) {
final ProTransport amphibData = it.next();
final Unit transport = amphibData.getTransport();
// Get all units that have already moved
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
alreadyMovedUnits.addAll(moveMap.get(t).getUnits());
alreadyMovedUnits.addAll(moveMap.get(t).getTempUnits());
}
// Transport amphib units to best land territory
Territory maxValueTerritory = null;
List<Unit> maxAmphibUnitsToAdd = null;
double maxValue = Double.MIN_VALUE;
double maxSeaValue = 0;
Territory maxUnloadFromTerritory = null;
for (final Territory t : amphibData.getTransportMap().keySet()) {
if (moveMap.get(t).getValue() >= maxValue) {
// Find units to load
final Set<Territory> territoriesCanLoadFrom = amphibData.getTransportMap().get(t);
final List<Unit> amphibUnitsToAdd =
ProTransportUtils.getUnitsToTransportThatCantMoveToHigherValue(player, transport,
territoriesCanLoadFrom, alreadyMovedUnits, moveMap, currentUnitMoveMap, moveMap.get(t).getValue());
if (amphibUnitsToAdd.isEmpty()) {
continue;
}
// Find best territory to move transport
final Set<Territory> loadFromTerritories = new HashSet<>();
for (final Unit u : amphibUnitsToAdd) {
loadFromTerritories.add(unitTerritoryMap.get(u));
}
final Set<Territory> territoriesToMoveTransport =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, false));
for (final Territory territoryToMoveTransport : territoriesToMoveTransport) {
if (amphibData.getSeaTransportMap().containsKey(territoryToMoveTransport)
&& amphibData.getSeaTransportMap().get(territoryToMoveTransport).containsAll(loadFromTerritories)
&& moveMap.get(territoryToMoveTransport) != null && moveMap.get(territoryToMoveTransport).isCanHold()
&& (moveMap.get(t).getValue() > maxValue
|| moveMap.get(territoryToMoveTransport).getValue() > maxSeaValue)) {
maxValueTerritory = t;
maxAmphibUnitsToAdd = amphibUnitsToAdd;
maxValue = moveMap.get(t).getValue();
maxSeaValue = moveMap.get(territoryToMoveTransport).getValue();
maxUnloadFromTerritory = territoryToMoveTransport;
}
}
}
}
if (maxValueTerritory != null) {
ProLogger.trace(transport + " moved to " + maxUnloadFromTerritory + " and unloading to best land at "
+ maxValueTerritory + " with " + maxAmphibUnitsToAdd + ", value=" + maxValue);
moveMap.get(maxValueTerritory).addTempUnits(maxAmphibUnitsToAdd);
moveMap.get(maxValueTerritory).putTempAmphibAttackMap(transport, maxAmphibUnitsToAdd);
moveMap.get(maxValueTerritory).getTransportTerritoryMap().put(transport, maxUnloadFromTerritory);
currentTransportMoveMap.remove(transport);
for (final Unit unit : maxAmphibUnitsToAdd) {
currentUnitMoveMap.remove(unit);
}
territoriesToDefend.add(maxUnloadFromTerritory);
it.remove();
continue;
}
// Transport amphib units to best sea territory
for (final Territory t : amphibData.getSeaTransportMap().keySet()) {
if (moveMap.get(t) != null && moveMap.get(t).getValue() > maxValue) {
// Find units to load
final Set<Territory> territoriesCanLoadFrom = amphibData.getSeaTransportMap().get(t);
territoriesCanLoadFrom.removeAll(data.getMap().getNeighbors(t)); // Don't transport adjacent units
final List<Unit> amphibUnitsToAdd = ProTransportUtils.getUnitsToTransportThatCantMoveToHigherValue(player,
transport, territoriesCanLoadFrom, alreadyMovedUnits, moveMap, currentUnitMoveMap, 0.1);
if (!amphibUnitsToAdd.isEmpty()) {
maxValueTerritory = t;
maxAmphibUnitsToAdd = amphibUnitsToAdd;
maxValue = moveMap.get(t).getValue();
}
}
}
if (maxValueTerritory != null) {
final Set<Territory> possibleUnloadTerritories = data.getMap().getNeighbors(maxValueTerritory,
ProMatches.territoryCanMoveLandUnitsAndIsAllied(player, data));
Territory unloadToTerritory = null;
int maxNumSeaNeighbors = 0;
for (final Territory t : possibleUnloadTerritories) {
final int numSeaNeighbors = data.getMap().getNeighbors(t, Matches.TerritoryIsWater).size();
final boolean isAdjacentToEnemy =
ProMatches.territoryIsOrAdjacentToEnemyNotNeutralLand(player, data).match(t);
if (moveMap.get(t) != null && (moveMap.get(t).isCanHold() || !isAdjacentToEnemy)
&& numSeaNeighbors > maxNumSeaNeighbors) {
unloadToTerritory = t;
maxNumSeaNeighbors = numSeaNeighbors;
}
}
if (unloadToTerritory != null) {
moveMap.get(unloadToTerritory).addTempUnits(maxAmphibUnitsToAdd);
moveMap.get(unloadToTerritory).putTempAmphibAttackMap(transport, maxAmphibUnitsToAdd);
moveMap.get(unloadToTerritory).getTransportTerritoryMap().put(transport, maxValueTerritory);
ProLogger.trace(transport + " moved to best sea at " + maxValueTerritory + " and unloading to "
+ unloadToTerritory + " with " + maxAmphibUnitsToAdd + ", value=" + maxValue);
} else {
moveMap.get(maxValueTerritory).addTempUnits(maxAmphibUnitsToAdd);
moveMap.get(maxValueTerritory).putTempAmphibAttackMap(transport, maxAmphibUnitsToAdd);
moveMap.get(maxValueTerritory).getTransportTerritoryMap().put(transport, maxValueTerritory);
ProLogger.trace(transport + " moved to best sea at " + maxValueTerritory + " with " + maxAmphibUnitsToAdd
+ ", value=" + maxValue);
}
currentTransportMoveMap.remove(transport);
for (final Unit unit : maxAmphibUnitsToAdd) {
currentUnitMoveMap.remove(unit);
}
territoriesToDefend.add(maxValueTerritory);
it.remove();
}
}
ProLogger.debug("Move empty transports to best loading territory");
// Move remaining transports to best loading territory if safe
// TODO: consider which territory is 'safest'
for (final Iterator<Unit> it = currentTransportMoveMap.keySet().iterator(); it.hasNext();) {
final Unit transport = it.next();
final Territory currentTerritory = unitTerritoryMap.get(transport);
final int moves = TripleAUnit.get(transport).getMovementLeft();
if (TransportTracker.isTransporting(transport) || moves <= 0) {
continue;
}
final List<ProTerritory> priorizitedLoadTerritories = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
// Check if land with adjacent sea that can be reached and that I'm not already adjacent to
final boolean territoryHasTransportableUnits =
Matches.territoryHasUnitsThatMatch(ProMatches.unitIsOwnedTransportableUnitAndCanBeLoaded(player, false))
.match(t);
final int distance = data.getMap().getDistance_IgnoreEndForCondition(currentTerritory, t,
ProMatches.territoryCanMoveSeaUnits(player, data, true));
final boolean hasSeaNeighbor = Matches.territoryHasNeighborMatching(data, Matches.TerritoryIsWater).match(t);
final boolean hasFactory = ProMatches.territoryHasInfraFactoryAndIsOwnedLand(player).match(t);
if (!t.isWater() && hasSeaNeighbor && distance > 0
&& !(distance == 1 && territoryHasTransportableUnits && !hasFactory)) {
// TODO: add calculation of transports vs units
final double territoryValue = moveMap.get(t).getValue();
final int numUnitsToLoad = Match
.getMatches(moveMap.get(t).getAllDefenders(), ProMatches.unitIsOwnedTransportableUnit(player)).size();
final boolean hasUnconqueredFactory = ProMatches.territoryHasInfraFactoryAndIsOwnedLand(player).match(t)
&& !AbstractMoveDelegate.getBattleTracker(data).wasConquered(t);
int factoryProduction = 0;
if (hasUnconqueredFactory) {
factoryProduction = TerritoryAttachment.getProduction(t);
}
int numTurnsAway = (distance - 1) / moves;
if (distance <= moves) {
numTurnsAway = 0;
}
final double value = territoryValue + 0.5 * numTurnsAway - 0.1 * numUnitsToLoad - 0.1 * factoryProduction;
moveMap.get(t).setLoadValue(value);
priorizitedLoadTerritories.add(moveMap.get(t));
}
}
// Sort prioritized territories
Collections.sort(priorizitedLoadTerritories, (t1, t2) -> {
final double value1 = t1.getLoadValue();
final double value2 = t2.getLoadValue();
return Double.compare(value1, value2);
});
// Move towards best loading territory if route is safe
for (final ProTerritory patd : priorizitedLoadTerritories) {
boolean movedTransport = false;
final Set<Territory> cantHoldTerritories = new HashSet<>();
while (true) {
final Match<Territory> match =
new CompositeMatchAnd<>(ProMatches.territoryCanMoveSeaUnitsThrough(player, data, false),
Matches.territoryIsInList(cantHoldTerritories).invert());
final Route route = data.getMap().getRoute_IgnoreEnd(currentTerritory, patd.getTerritory(), match);
if (route == null
|| MoveValidator.validateCanal(route, Collections.singletonList(transport), player, data) != null) {
break;
}
final List<Territory> territories = route.getAllTerritories();
territories.remove(territories.size() - 1);
final Territory moveToTerritory = territories.get(Math.min(territories.size() - 1, moves));
final ProTerritory patd2 = moveMap.get(moveToTerritory);
if (patd2 != null && patd2.isCanHold()) {
ProLogger.trace(transport + " moved towards best loading territory " + patd.getTerritory()
+ " and moved to " + moveToTerritory);
patd2.addTempUnit(transport);
territoriesToDefend.add(moveToTerritory);
it.remove();
movedTransport = true;
break;
}
if (!cantHoldTerritories.add(moveToTerritory)) {
break;
}
}
if (movedTransport) {
break;
}
}
}
ProLogger.debug("Move remaining transports to safest territory");
// Move remaining transports to safest territory
for (final Iterator<Unit> it = currentTransportMoveMap.keySet().iterator(); it.hasNext();) {
final Unit transport = it.next();
// Get all units that have already moved
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
alreadyMovedUnits.addAll(moveMap.get(t).getUnits());
}
// Find safest territory
double minStrengthDifference = Double.POSITIVE_INFINITY;
Territory minTerritory = null;
for (final Territory t : currentTransportMoveMap.get(transport)) {
final List<Unit> attackers = moveMap.get(t).getMaxEnemyUnits();
final List<Unit> defenders = moveMap.get(t).getMaxDefenders();
defenders.removeAll(alreadyMovedUnits);
defenders.addAll(moveMap.get(t).getUnits());
defenders.removeAll(ProTransportUtils.getAirThatCantLandOnCarrier(player, t, defenders));
final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackers, defenders);
// TODO: add logic to move towards closest factory
ProLogger.trace(transport + " at " + t + ", strengthDifference=" + strengthDifference + ", attackers="
+ attackers + ", defenders=" + defenders);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minTerritory = t;
}
}
if (minTerritory != null) {
// If transporting units then unload to safe territory
// TODO: consider which is 'safest'
if (TransportTracker.isTransporting(transport)) {
final List<Unit> amphibUnits = (List<Unit>) TransportTracker.transporting(transport);
final Set<Territory> possibleUnloadTerritories =
data.getMap().getNeighbors(minTerritory, ProMatches.territoryCanMoveLandUnitsAndIsAllied(player, data));
if (!possibleUnloadTerritories.isEmpty()) {
// Find best unload territory
Territory unloadToTerritory = possibleUnloadTerritories.iterator().next();
for (final Territory t : possibleUnloadTerritories) {
if (moveMap.get(t) != null && moveMap.get(t).isCanHold()) {
unloadToTerritory = t;
}
}
ProLogger.trace(transport + " moved to safest territory at " + minTerritory + " and unloading to "
+ unloadToTerritory + " with " + amphibUnits + ", strengthDifference=" + minStrengthDifference);
moveMap.get(unloadToTerritory).addTempUnits(amphibUnits);
moveMap.get(unloadToTerritory).putTempAmphibAttackMap(transport, amphibUnits);
moveMap.get(unloadToTerritory).getTransportTerritoryMap().put(transport, minTerritory);
for (final Unit unit : amphibUnits) {
currentUnitMoveMap.remove(unit);
}
it.remove();
} else {
// Move transport with units since no unload options
ProLogger.trace(transport + " moved to safest territory at " + minTerritory + " with " + amphibUnits
+ ", strengthDifference=" + minStrengthDifference);
moveMap.get(minTerritory).addTempUnits(amphibUnits);
moveMap.get(minTerritory).putTempAmphibAttackMap(transport, amphibUnits);
moveMap.get(minTerritory).getTransportTerritoryMap().put(transport, minTerritory);
for (final Unit unit : amphibUnits) {
currentUnitMoveMap.remove(unit);
}
it.remove();
}
} else {
// If not transporting units
ProLogger.trace(transport + " moved to safest territory at " + minTerritory + ", strengthDifference="
+ minStrengthDifference);
moveMap.get(minTerritory).addTempUnit(transport);
it.remove();
}
}
}
// Get all transport final territories
ProMoveUtils.calculateAmphibRoutes(player, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), moveMap,
false);
for (final Territory t : moveMap.keySet()) {
for (final Unit u : moveMap.get(t).getTransportTerritoryMap().keySet()) {
if (moveMap.get(moveMap.get(t).getTransportTerritoryMap().get(u)) != null) {
moveMap.get(moveMap.get(t).getTransportTerritoryMap().get(u)).addTempUnit(u);
}
}
}
ProLogger.debug("Move sea units");
// Move sea units to defend transports
for (final Iterator<Unit> it = currentUnitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsSea.match(u)) {
for (final Territory t : currentUnitMoveMap.get(u)) {
if (moveMap.get(t).isCanHold() && !moveMap.get(t).getAllDefenders().isEmpty()
&& Match.someMatch(moveMap.get(t).getAllDefenders(), ProMatches.unitIsOwnedTransport(player))) {
final List<Unit> defendingUnits =
Match.getMatches(moveMap.get(t).getAllDefenders(), Matches.UnitIsNotLand);
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
ProLogger.trace(t.getName() + " TUVSwing=" + result.getTUVSwing() + ", Win%=" + result.getWinPercentage()
+ ", enemyAttackers=" + moveMap.get(t).getMaxEnemyUnits().size() + ", defenders="
+ defendingUnits.size());
if (result.getWinPercentage() > (100 - ProData.winPercentage) || result.getTUVSwing() > 0) {
ProLogger.trace(u + " added sea to defend transport at " + t);
moveMap.get(t).addTempUnit(u);
moveMap.get(t).setBattleResult(null);
territoriesToDefend.add(t);
it.remove();
// If carrier has dependent allied fighters then move them too
if (Matches.UnitIsCarrier.match(u)) {
final Territory unitTerritory = unitTerritoryMap.get(u);
final Map<Unit, Collection<Unit>> carrierMustMoveWith = MoveValidator
.carrierMustMoveWith(unitTerritory.getUnits().getUnits(), unitTerritory, data, player);
if (carrierMustMoveWith.containsKey(u)) {
moveMap.get(t).getTempUnits().addAll(carrierMustMoveWith.get(u));
}
}
break;
}
}
}
}
}
// Move air units to defend transports
for (final Iterator<Unit> it = currentUnitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitCanLandOnCarrier.match(u)) {
for (final Territory t : currentUnitMoveMap.get(u)) {
if (t.isWater() && moveMap.get(t).isCanHold() && !moveMap.get(t).getAllDefenders().isEmpty()
&& Match.someMatch(moveMap.get(t).getAllDefenders(), ProMatches.unitIsOwnedTransport(player))) {
if (!ProTransportUtils.validateCarrierCapacity(player, t,
moveMap.get(t).getAllDefendersForCarrierCalcs(data, player), u)) {
continue;
}
final List<Unit> defendingUnits =
Match.getMatches(moveMap.get(t).getAllDefenders(), Matches.UnitIsNotLand);
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.estimateDefendBattleResults(player, t,
moveMap.get(t).getMaxEnemyUnits(), defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits()));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
ProLogger.trace(t.getName() + " TUVSwing=" + result.getTUVSwing() + ", Win%=" + result.getWinPercentage()
+ ", enemyAttackers=" + moveMap.get(t).getMaxEnemyUnits().size() + ", defenders="
+ defendingUnits.size());
if (result.getWinPercentage() > (100 - ProData.winPercentage) || result.getTUVSwing() > 0) {
ProLogger.trace(u + " added air to defend transport at " + t);
moveMap.get(t).addTempUnit(u);
moveMap.get(t).setBattleResult(null);
territoriesToDefend.add(t);
it.remove();
break;
}
}
}
}
}
// Move sea units to best location or safest location
for (final Iterator<Unit> it = currentUnitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsSea.match(u)) {
Territory maxValueTerritory = null;
double maxValue = 0;
for (final Territory t : currentUnitMoveMap.get(u)) {
if (moveMap.get(t).isCanHold()) {
final int transports =
Match.countMatches(moveMap.get(t).getAllDefenders(), ProMatches.unitIsOwnedTransport(player));
final double value = (1 + transports) * moveMap.get(t).getSeaValue()
+ (1 + transports * 100) * moveMap.get(t).getValue() / 10000;
ProLogger.trace(t + ", value=" + value + ", seaValue=" + moveMap.get(t).getSeaValue() + ", tValue="
+ moveMap.get(t).getValue() + ", transports=" + transports);
if (value > maxValue) {
maxValue = value;
maxValueTerritory = t;
}
}
}
if (maxValueTerritory != null) {
ProLogger.trace(u + " added to best territory " + maxValueTerritory + ", value=" + maxValue);
moveMap.get(maxValueTerritory).addTempUnit(u);
moveMap.get(maxValueTerritory).setBattleResult(null);
territoriesToDefend.add(maxValueTerritory);
it.remove();
// If carrier has dependent allied fighters then move them too
if (Matches.UnitIsCarrier.match(u)) {
final Territory unitTerritory = unitTerritoryMap.get(u);
final Map<Unit, Collection<Unit>> carrierMustMoveWith =
MoveValidator.carrierMustMoveWith(unitTerritory.getUnits().getUnits(), unitTerritory, data, player);
if (carrierMustMoveWith.containsKey(u)) {
moveMap.get(maxValueTerritory).getTempUnits().addAll(carrierMustMoveWith.get(u));
}
}
} else {
// Get all units that have already moved
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
alreadyMovedUnits.addAll(moveMap.get(t).getUnits());
}
// Find safest territory
double minStrengthDifference = Double.POSITIVE_INFINITY;
Territory minTerritory = null;
for (final Territory t : currentUnitMoveMap.get(u)) {
final List<Unit> attackers = moveMap.get(t).getMaxEnemyUnits();
final List<Unit> defenders = moveMap.get(t).getMaxDefenders();
defenders.removeAll(alreadyMovedUnits);
defenders.addAll(moveMap.get(t).getUnits());
final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackers, defenders);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minTerritory = t;
}
}
if (minTerritory != null) {
ProLogger.trace(u + " moved to safest territory at " + minTerritory + ", strengthDifference="
+ minStrengthDifference);
moveMap.get(minTerritory).addTempUnit(u);
moveMap.get(minTerritory).setBattleResult(null);
it.remove();
// If carrier has dependent allied fighters then move them too
if (Matches.UnitIsCarrier.match(u)) {
final Territory unitTerritory = unitTerritoryMap.get(u);
final Map<Unit, Collection<Unit>> carrierMustMoveWith =
MoveValidator.carrierMustMoveWith(unitTerritory.getUnits().getUnits(), unitTerritory, data, player);
if (carrierMustMoveWith.containsKey(u)) {
moveMap.get(minTerritory).getTempUnits().addAll(carrierMustMoveWith.get(u));
}
}
} else {
final Territory currentTerritory = unitTerritoryMap.get(u);
ProLogger.trace(u + " added to current territory since no better options at " + currentTerritory);
moveMap.get(currentTerritory).addTempUnit(u);
moveMap.get(currentTerritory).setBattleResult(null);
it.remove();
}
}
}
}
// Determine if all defenses are successful
ProLogger.debug("Checking if all sea moves are safe for " + territoriesToDefend);
boolean areSuccessful = true;
for (final Territory t : territoriesToDefend) {
// Find result with temp units
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
moveMap.get(t).setBattleResult(calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits(), false));
final ProBattleResult result = moveMap.get(t).getBattleResult();
int isWater = 0;
if (t.isWater()) {
isWater = 1;
}
final double extraUnitValue = BattleCalculator.getTUV(moveMap.get(t).getTempUnits(), ProData.unitValueMap);
final double holdValue = result.getTUVSwing() - (extraUnitValue / 8 * (1 + isWater));
// Find min result without temp units
final List<Unit> minDefendingUnits = new ArrayList<>(defendingUnits);
minDefendingUnits.removeAll(moveMap.get(t).getTempUnits());
final ProBattleResult minResult = calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
minDefendingUnits, moveMap.get(t).getMaxEnemyBombardUnits(), false);
// Check if territory is worth defending with temp units
if (holdValue > minResult.getTUVSwing()) {
areSuccessful = false;
moveMap.get(t).setCanHold(false);
moveMap.get(t).setValue(0);
moveMap.get(t).setSeaValue(0);
ProLogger.trace(t + " unable to defend so removing with holdValue=" + holdValue + ", minTUVSwing="
+ minResult.getTUVSwing() + ", defenders=" + defendingUnits + ", enemyAttackers="
+ moveMap.get(t).getMaxEnemyUnits());
}
ProLogger.trace(
moveMap.get(t).getResultString() + ", holdValue=" + holdValue + ", minTUVSwing=" + minResult.getTUVSwing());
}
// Determine whether to try more territories, remove a territory, or end
if (areSuccessful) {
break;
}
}
// Add temp units to move lists
for (final Territory t : moveMap.keySet()) {
// Handle allied units such as fighters on carriers
final List<Unit> alliedUnits =
Match.getMatches(moveMap.get(t).getTempUnits(), Matches.unitIsOwnedBy(player).invert());
for (final Unit alliedUnit : alliedUnits) {
moveMap.get(t).addCantMoveUnit(alliedUnit);
moveMap.get(t).getTempUnits().remove(alliedUnit);
}
moveMap.get(t).addUnits(moveMap.get(t).getTempUnits());
moveMap.get(t).putAllAmphibAttackMap(moveMap.get(t).getTempAmphibAttackMap());
for (final Unit u : moveMap.get(t).getTempUnits()) {
if (Matches.UnitIsTransport.match(u)) {
transportMoveMap.remove(u);
for (final Iterator<ProTransport> it = transportMapList.iterator(); it.hasNext();) {
if (it.next().getTransport().equals(u)) {
it.remove();
}
}
} else {
unitMoveMap.remove(u);
}
}
for (final Unit u : moveMap.get(t).getTempAmphibAttackMap().keySet()) {
transportMoveMap.remove(u);
for (final Iterator<ProTransport> it = transportMapList.iterator(); it.hasNext();) {
if (it.next().getTransport().equals(u)) {
it.remove();
}
}
}
moveMap.get(t).getTempUnits().clear();
moveMap.get(t).getTempAmphibAttackMap().clear();
}
ProLogger.info("Move land units");
// Move land units to territory with highest value and highest transport capacity
// TODO: consider if territory ends up being safe
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsLand.match(u)) {
Territory maxValueTerritory = null;
double maxValue = 0;
int maxNeedAmphibUnitValue = Integer.MIN_VALUE;
for (final Territory t : unitMoveMap.get(u)) {
if (moveMap.get(t).isCanHold() && moveMap.get(t).getValue() >= maxValue) {
// Find transport capacity of neighboring (distance 1) transports
final List<Unit> transports1 = new ArrayList<>();
final Set<Territory> seaNeighbors =
data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, true));
for (final Territory neighborTerritory : seaNeighbors) {
if (moveMap.containsKey(neighborTerritory)) {
transports1.addAll(Match.getMatches(moveMap.get(neighborTerritory).getAllDefenders(),
ProMatches.unitIsOwnedTransport(player)));
}
}
int transportCapacity1 = 0;
for (final Unit transport : transports1) {
transportCapacity1 += UnitAttachment.get(transport.getType()).getTransportCapacity();
}
// Find transport capacity of nearby (distance 2) transports
final List<Unit> transports2 = new ArrayList<>();
final Set<Territory> nearbySeaTerritories =
data.getMap().getNeighbors(t, 2, ProMatches.territoryCanMoveSeaUnits(player, data, true));
nearbySeaTerritories.removeAll(seaNeighbors);
for (final Territory neighborTerritory : nearbySeaTerritories) {
if (moveMap.containsKey(neighborTerritory)) {
transports2.addAll(Match.getMatches(moveMap.get(neighborTerritory).getAllDefenders(),
ProMatches.unitIsOwnedTransport(player)));
}
}
int transportCapacity2 = 0;
for (final Unit transport : transports2) {
transportCapacity2 += UnitAttachment.get(transport.getType()).getTransportCapacity();
}
final List<Unit> unitsToTransport =
Match.getMatches(moveMap.get(t).getAllDefenders(), ProMatches.unitIsOwnedTransportableUnit(player));
// Find transport cost of potential amphib units
int transportCost = 0;
for (final Unit unit : unitsToTransport) {
transportCost += UnitAttachment.get(unit.getType()).getTransportCost();
}
// Find territory that needs amphib units that most
int hasFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsOwnedLandAdjacentToSea(player, data).match(t)) {
hasFactory = 1;
}
final int neededNeighborTransportValue = Math.max(0, transportCapacity1 - transportCost);
final int neededNearbyTransportValue = Math.max(0, transportCapacity1 + transportCapacity2 - transportCost);
final int needAmphibUnitValue =
1000 * neededNeighborTransportValue + 100 * neededNearbyTransportValue + (1 + 10 * hasFactory)
* data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, true)).size();
if (moveMap.get(t).getValue() > maxValue || needAmphibUnitValue > maxNeedAmphibUnitValue) {
maxValue = moveMap.get(t).getValue();
maxNeedAmphibUnitValue = needAmphibUnitValue;
maxValueTerritory = t;
}
}
}
if (maxValueTerritory != null) {
ProLogger.trace(u + " moved to " + maxValueTerritory + " with value=" + maxValue
+ ", numNeededTransportUnits=" + maxNeedAmphibUnitValue);
moveMap.get(maxValueTerritory).addUnit(u);
it.remove();
}
}
}
// Move land units towards nearest factory that is adjacent to the sea
final Set<Territory> myFactoriesAdjacentToSea = new HashSet<>(Match.getMatches(data.getMap().getTerritories(),
ProMatches.territoryHasInfraFactoryAndIsOwnedLandAdjacentToSea(player, data)));
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsLand.match(u)) {
int minDistance = Integer.MAX_VALUE;
Territory minTerritory = null;
for (final Territory t : unitMoveMap.get(u)) {
if (moveMap.get(t).isCanHold()) {
for (final Territory factory : myFactoriesAdjacentToSea) {
int distance =
data.getMap().getDistance(t, factory, ProMatches.territoryCanMoveLandUnits(player, data, true));
if (distance < 0) {
distance = 10 * data.getMap().getDistance(t, factory);
}
if (distance >= 0 && distance < minDistance) {
minDistance = distance;
minTerritory = t;
}
}
}
}
if (minTerritory != null) {
ProLogger.trace(
u.getType().getName() + " moved towards closest factory adjacent to sea at " + minTerritory.getName());
moveMap.get(minTerritory).addUnit(u);
it.remove();
}
}
}
ProLogger.info("Move land units to safest territory");
// Move any remaining land units to safest territory (this is rarely used)
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsLand.match(u)) {
// Get all units that have already moved
final List<Unit> alreadyMovedUnits = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
alreadyMovedUnits.addAll(moveMap.get(t).getUnits());
}
// Find safest territory
double minStrengthDifference = Double.POSITIVE_INFINITY;
Territory minTerritory = null;
for (final Territory t : unitMoveMap.get(u)) {
final List<Unit> attackers = moveMap.get(t).getMaxEnemyUnits();
final List<Unit> defenders = moveMap.get(t).getMaxDefenders();
defenders.removeAll(alreadyMovedUnits);
defenders.addAll(moveMap.get(t).getUnits());
final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackers, defenders);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minTerritory = t;
}
}
if (minTerritory != null) {
ProLogger.debug(u.getType().getName() + " moved to safest territory at " + minTerritory.getName()
+ " with strengthDifference=" + minStrengthDifference);
moveMap.get(minTerritory).addUnit(u);
it.remove();
}
}
}
ProLogger.info("Move air units");
// Get list of territories that can't be held
final List<Territory> territoriesThatCantBeHeld = new ArrayList<>();
for (final Territory t : moveMap.keySet()) {
if (!moveMap.get(t).isCanHold()) {
territoriesThatCantBeHeld.add(t);
}
}
// Move air units to safe territory with most attack options
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsNotAir.match(u)) {
continue;
}
double maxAirValue = 0;
Territory maxTerritory = null;
for (final Territory t : unitMoveMap.get(u)) {
if (!moveMap.get(t).isCanHold()) {
continue;
}
if (t.isWater() && !ProTransportUtils.validateCarrierCapacity(player, t,
moveMap.get(t).getAllDefendersForCarrierCalcs(data, player), u)) {
ProLogger.trace(t + " already at MAX carrier capacity");
continue;
}
// Check to see if the territory is safe
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
defendingUnits.add(u);
if (moveMap.get(t).getBattleResult() == null) {
moveMap.get(t).setBattleResult(calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits(), false));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
ProLogger.trace(t + ", TUVSwing=" + result.getTUVSwing() + ", win%=" + result.getWinPercentage()
+ ", defendingUnits=" + defendingUnits + ", enemyAttackers=" + moveMap.get(t).getMaxEnemyUnits());
if (result.getWinPercentage() >= ProData.minWinPercentage || result.getTUVSwing() > 0) {
moveMap.get(t).setCanHold(false);
continue;
}
// Determine if territory can be held with owned units
final List<Unit> myDefenders = Match.getMatches(defendingUnits, Matches.unitIsOwnedBy(player));
final ProBattleResult result2 = calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
myDefenders, moveMap.get(t).getMaxEnemyBombardUnits(), false);
int cantHoldWithoutAllies = 0;
if (result2.getWinPercentage() >= ProData.minWinPercentage || result2.getTUVSwing() > 0) {
cantHoldWithoutAllies = 1;
}
// Find number of potential attack options next turn
final int range = TripleAUnit.get(u).getMaxMovementAllowed();
final Set<Territory> possibleAttackTerritories =
data.getMap().getNeighbors(t, range / 2, ProMatches.territoryCanMoveAirUnits(player, data, true));
final int numEnemyAttackTerritories =
Match.countMatches(possibleAttackTerritories, ProMatches.territoryIsEnemyNotNeutralLand(player, data));
final int numLandAttackTerritories = Match.countMatches(possibleAttackTerritories,
ProMatches.territoryIsEnemyOrCantBeHeldAndIsAdjacentToMyLandUnits(player, data, territoriesThatCantBeHeld));
final int numSeaAttackTerritories =
Match.countMatches(possibleAttackTerritories, Matches.territoryHasEnemySeaUnits(player, data));
final Set<Territory> possibleMoveTerritories =
data.getMap().getNeighbors(t, range, ProMatches.territoryCanMoveAirUnits(player, data, true));
final int numNearbyEnemyTerritories =
Match.countMatches(possibleMoveTerritories, ProMatches.territoryIsEnemyNotNeutralLand(player, data));
// Check if number of attack territories and value are max
final int isntFactory = ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t) ? 0 : 1;
final int hasOwnedCarrier =
Match.someMatch(moveMap.get(t).getAllDefenders(), ProMatches.UnitIsOwnedCarrier(player)) ? 1 : 0;
final double airValue = (200.0 * numSeaAttackTerritories + 100 * numLandAttackTerritories
+ 10 * numEnemyAttackTerritories + numNearbyEnemyTerritories) / (1 + cantHoldWithoutAllies)
/ (1 + cantHoldWithoutAllies * isntFactory) * (1 + hasOwnedCarrier);
if (airValue > maxAirValue) {
maxAirValue = airValue;
maxTerritory = t;
}
ProLogger.trace("Safe territory: " + t + ", airValue=" + airValue + ", numLandAttackOptions="
+ numLandAttackTerritories + ", numSeaAttackTerritories=" + numSeaAttackTerritories
+ ", numEnemyAttackTerritories=" + numEnemyAttackTerritories);
}
if (maxTerritory != null) {
ProLogger.debug(u.getType().getName() + " added to safe territory with most attack options " + maxTerritory
+ ", maxAirValue=" + maxAirValue);
moveMap.get(maxTerritory).addUnit(u);
moveMap.get(maxTerritory).setBattleResult(null);
it.remove();
}
}
// Move air units to safest territory
for (final Iterator<Unit> it = unitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
if (Matches.UnitIsNotAir.match(u)) {
continue;
}
double minStrengthDifference = Double.POSITIVE_INFINITY;
Territory minTerritory = null;
for (final Territory t : unitMoveMap.get(u)) {
if (t.isWater() && !ProTransportUtils.validateCarrierCapacity(player, t,
moveMap.get(t).getAllDefendersForCarrierCalcs(data, player), u)) {
ProLogger.trace(t + " already at MAX carrier capacity");
continue;
}
final List<Unit> attackers = moveMap.get(t).getMaxEnemyUnits();
final List<Unit> defenders = moveMap.get(t).getAllDefenders();
defenders.add(u);
final double strengthDifference = ProBattleUtils.estimateStrengthDifference(t, attackers, defenders);
ProLogger.trace("Unsafe territory: " + t + " with strengthDifference=" + strengthDifference);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minTerritory = t;
}
}
if (minTerritory != null) {
ProLogger.debug(u.getType().getName() + " added to safest territory at " + minTerritory
+ " with strengthDifference=" + minStrengthDifference);
moveMap.get(minTerritory).addUnit(u);
it.remove();
}
}
}
private Map<Territory, ProTerritory> moveInfraUnits(Map<Territory, ProTerritory> factoryMoveMap,
final Map<Unit, Set<Territory>> infraUnitMoveMap) {
ProLogger.info("Determine where to move infra units");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
// Move factory units
if (factoryMoveMap == null) {
ProLogger.debug("Creating factory move map");
// Determine and store where to move factories
factoryMoveMap = new HashMap<>();
for (final Iterator<Unit> it = infraUnitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
// Only check factory units
if (Matches.UnitCanProduceUnits.match(u)) {
Territory maxValueTerritory = null;
double maxValue = 0;
for (final Territory t : infraUnitMoveMap.get(u)) {
if (!moveMap.get(t).isCanHold()) {
continue;
}
// Check if territory is safe after all current moves
if (moveMap.get(t).getBattleResult() == null) {
final List<Unit> defendingUnits = moveMap.get(t).getAllDefenders();
moveMap.get(t).setBattleResult(calc.calculateBattleResults(player, t, moveMap.get(t).getMaxEnemyUnits(),
defendingUnits, moveMap.get(t).getMaxEnemyBombardUnits(), false));
}
final ProBattleResult result = moveMap.get(t).getBattleResult();
if (result.getWinPercentage() >= ProData.minWinPercentage || result.getTUVSwing() > 0) {
moveMap.get(t).setCanHold(false);
continue;
}
// Find value by checking if territory is not conquered and doesn't already have a factory
final List<Unit> units = new ArrayList<>(moveMap.get(t).getCantMoveUnits());
units.addAll(moveMap.get(t).getUnits());
final int production = TerritoryAttachment.get(t).getProduction();
double value = 0.1 * moveMap.get(t).getValue();
if (ProMatches.territoryIsNotConqueredOwnedLand(player, data).match(t)
&& Match.noneMatch(units, Matches.UnitCanProduceUnitsAndIsInfrastructure)) {
value = moveMap.get(t).getValue() * production + 0.01 * production;
}
ProLogger.trace(t.getName() + " has value=" + value + ", strategicValue=" + moveMap.get(t).getValue()
+ ", production=" + production);
if (value > maxValue) {
maxValue = value;
maxValueTerritory = t;
}
}
if (maxValueTerritory != null) {
ProLogger
.debug(u.getType().getName() + " moved to " + maxValueTerritory.getName() + " with value=" + maxValue);
moveMap.get(maxValueTerritory).addUnit(u);
if (factoryMoveMap.containsKey(maxValueTerritory)) {
factoryMoveMap.get(maxValueTerritory).addUnit(u);
} else {
final ProTerritory patd = new ProTerritory(maxValueTerritory);
patd.addUnit(u);
factoryMoveMap.put(maxValueTerritory, patd);
}
it.remove();
}
}
}
} else {
ProLogger.debug("Using stored factory move map");
// Transfer stored factory moves to move map
for (final Territory t : factoryMoveMap.keySet()) {
moveMap.get(t).addUnits(factoryMoveMap.get(t).getUnits());
}
}
ProLogger.debug("Move infra AA units");
// Move AA units
for (final Iterator<Unit> it = infraUnitMoveMap.keySet().iterator(); it.hasNext();) {
final Unit u = it.next();
final Territory currentTerritory = unitTerritoryMap.get(u);
// Only check AA units whose territory can't be held and don't have factories
if (Matches.UnitIsAAforAnything.match(u) && !moveMap.get(currentTerritory).isCanHold()
&& !ProMatches.territoryHasInfraFactoryAndIsLand(player).match(currentTerritory)) {
Territory maxValueTerritory = null;
double maxValue = 0;
for (final Territory t : infraUnitMoveMap.get(u)) {
if (!moveMap.get(t).isCanHold()) {
continue;
}
// Consider max stack of 1 AA in classic
final Route r = data.getMap().getRoute_IgnoreEnd(currentTerritory, t,
ProMatches.territoryCanMoveLandUnitsThrough(player, data, u, currentTerritory, false, new ArrayList<>()));
final MoveValidationResult mvr = MoveValidator.validateMove(Collections.singletonList(u), r, player,
new ArrayList<>(), new HashMap<>(), true, null, data);
if (!mvr.isMoveValid()) {
continue;
}
// Find value and try to move to territory that doesn't already have AA
final List<Unit> units = new ArrayList<>(moveMap.get(t).getCantMoveUnits());
units.addAll(moveMap.get(t).getUnits());
final boolean hasAA = Match.someMatch(units, Matches.UnitIsAAforAnything);
double value = moveMap.get(t).getValue();
if (hasAA) {
value *= 0.01;
}
ProLogger.trace(t.getName() + " has value=" + value);
if (value > maxValue) {
maxValue = value;
maxValueTerritory = t;
}
}
if (maxValueTerritory != null) {
ProLogger
.debug(u.getType().getName() + " moved to " + maxValueTerritory.getName() + " with value=" + maxValue);
moveMap.get(maxValueTerritory).addUnit(u);
it.remove();
}
}
}
return factoryMoveMap;
}
private void logAttackMoves(final List<ProTerritory> prioritizedTerritories) {
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
// Print prioritization
ProLogger.debug("Prioritized territories:");
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
ProLogger.trace(" " + 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 : moveMap.keySet()) {
count++;
ProLogger.trace(count + ". ---" + t.getName());
final Set<Unit> combinedUnits = new HashSet<>(moveMap.get(t).getMaxUnits());
combinedUnits.addAll(moveMap.get(t).getMaxAmphibUnits());
combinedUnits.addAll(moveMap.get(t).getCantMoveUnits());
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 amphib units ---");
final Map<String, Integer> printMap5 = new HashMap<>();
for (final Unit unit : moveMap.get(t).getMaxAmphibUnits()) {
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(" " + printMap5.get(key) + " " + key);
}
final List<Unit> units3 = moveMap.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 = moveMap.get(t).getMaxEnemyUnits();
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 bombard units ---");
final Map<String, Integer> printMap4 = new HashMap<>();
final Set<Unit> units4 = moveMap.get(t).getMaxEnemyBombardUnits();
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);
}
}
}
}