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.Set;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.ProductionRule;
import games.strategy.engine.data.RepairRule;
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.ProPurchaseOptionMap;
import games.strategy.triplea.ai.proAI.data.ProPurchaseTerritory;
import games.strategy.triplea.ai.proAI.data.ProResourceTracker;
import games.strategy.triplea.ai.proAI.data.ProTerritoryManager;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.ai.proAI.logging.ProMetricUtils;
import games.strategy.triplea.ai.proAI.util.ProBattleUtils;
import games.strategy.triplea.ai.proAI.util.ProMatches;
import games.strategy.triplea.ai.proAI.util.ProOddsCalculator;
import games.strategy.triplea.ai.proAI.util.ProPurchaseUtils;
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.BattleCalculator;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.MoveValidator;
import games.strategy.triplea.delegate.dataObjects.PlaceableUnits;
import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate;
import games.strategy.triplea.delegate.remote.IPurchaseDelegate;
import games.strategy.util.CompositeMatch;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
/**
* Pro purchase AI.
*/
public class ProPurchaseAI {
private final ProOddsCalculator calc;
private GameData data;
private GameData startOfTurnData; // Used to count current units on map for maxBuiltPerPlayer
private PlayerID player;
private ProResourceTracker resourceTracker;
private ProTerritoryManager territoryManager;
public ProPurchaseAI(final ProAI ai) {
calc = ai.getCalc();
}
public int repair(int PUsRemaining, final IPurchaseDelegate purchaseDelegate, final GameData data,
final PlayerID player) {
ProLogger.info("Repairing factories with PUsRemaining=" + PUsRemaining);
// Current data at the start of combat move
this.data = data;
this.player = player;
final CompositeMatch<Unit> ourFactories = new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player),
Matches.UnitCanProduceUnits, Matches.UnitIsInfrastructure);
final List<Territory> rfactories = Match.getMatches(data.getMap().getTerritories(),
ProMatches.territoryHasInfraFactoryAndIsNotConqueredOwnedLand(player, data));
if (player.getRepairFrontier() != null
&& games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) {
ProLogger.debug("Factories can be damaged");
final Map<Unit, Territory> unitsThatCanProduceNeedingRepair = new HashMap<>();
for (final Territory fixTerr : rfactories) {
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.UnitCanProduceUnitsAndCanBeDamaged)
.match(fixTerr)) {
continue;
}
final Unit possibleFactoryNeedingRepair = TripleAUnit.getBiggestProducer(
Match.getMatches(fixTerr.getUnits().getUnits(), ourFactories), fixTerr, player, data, false);
if (Matches.UnitHasTakenSomeBombingUnitDamage.match(possibleFactoryNeedingRepair)) {
unitsThatCanProduceNeedingRepair.put(possibleFactoryNeedingRepair, fixTerr);
}
}
ProLogger.debug("Factories that need repaired: " + unitsThatCanProduceNeedingRepair);
final List<RepairRule> rrules = player.getRepairFrontier().getRules();
for (final RepairRule rrule : rrules) {
for (final Unit fixUnit : unitsThatCanProduceNeedingRepair.keySet()) {
if (fixUnit == null || !fixUnit.getType().equals(rrule.getResults().keySet().iterator().next())) {
continue;
}
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.UnitCanProduceUnitsAndCanBeDamaged)
.match(unitsThatCanProduceNeedingRepair.get(fixUnit))) {
continue;
}
final TripleAUnit taUnit = (TripleAUnit) fixUnit;
final int diff = taUnit.getUnitDamage();
if (diff > 0) {
final IntegerMap<RepairRule> repairMap = new IntegerMap<>();
repairMap.add(rrule, diff);
final HashMap<Unit, IntegerMap<RepairRule>> repair = new HashMap<>();
repair.put(fixUnit, repairMap);
PUsRemaining -= diff;
ProLogger.debug("Repairing factory=" + fixUnit + ", damage=" + diff + ", repairRule=" + rrule);
purchaseDelegate.purchaseRepair(repair);
}
}
}
}
return PUsRemaining;
}
public Map<Territory, ProPurchaseTerritory> purchase(final IPurchaseDelegate purchaseDelegate,
final GameData startOfTurnData) {
// Current data fields
data = ProData.getData();
this.startOfTurnData = startOfTurnData;
player = ProData.getPlayer();
resourceTracker = new ProResourceTracker(player);
territoryManager = new ProTerritoryManager(calc);
final ProPurchaseOptionMap purchaseOptions = ProData.purchaseOptions;
ProLogger.info("Starting purchase phase with resources: " + resourceTracker);
if (!player.getUnits().getUnits().isEmpty()) {
ProLogger.info("Starting purchase phase with unplaced units=" + player.getUnits().getUnits());
}
// Find all purchase/place territories
final Map<Territory, ProPurchaseTerritory> purchaseTerritories = ProPurchaseUtils.findPurchaseTerritories(player);
final Set<Territory> placeTerritories = new HashSet<>();
placeTerritories.addAll(Match.getMatches(data.getMap().getTerritoriesOwnedBy(player), Matches.TerritoryIsLand));
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
placeTerritories.add(ppt.getTerritory());
}
}
// Determine max enemy attack units and current allied defenders
territoryManager.populateEnemyAttackOptions(new ArrayList<>(), new ArrayList<>(placeTerritories));
findDefendersInPlaceTerritories(purchaseTerritories);
// Prioritize land territories that need defended and purchase additional defenders
final List<ProPlaceTerritory> needToDefendLandTerritories =
prioritizeTerritoriesToDefend(purchaseTerritories, true);
purchaseDefenders(purchaseTerritories, needToDefendLandTerritories, purchaseOptions.getLandFodderOptions(),
purchaseOptions.getAirOptions(), true);
// Find strategic value for each territory
ProLogger.info("Find strategic value for place territories");
final Map<Territory, Double> territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, new ArrayList<>(), new ArrayList<>());
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
ppt.setStrategicValue(territoryValueMap.get(ppt.getTerritory()));
ProLogger.debug(ppt.getTerritory() + ", strategicValue=" + territoryValueMap.get(ppt.getTerritory()));
}
}
// Prioritize land place options purchase AA then land units
final List<ProPlaceTerritory> prioritizedLandTerritories = prioritizeLandTerritories(purchaseTerritories);
purchaseAAUnits(purchaseTerritories, prioritizedLandTerritories, purchaseOptions.getAAOptions());
purchaseLandUnits(purchaseTerritories, prioritizedLandTerritories, purchaseOptions, territoryValueMap);
// Prioritize sea territories that need defended and purchase additional defenders
final List<ProPlaceTerritory> needToDefendSeaTerritories =
prioritizeTerritoriesToDefend(purchaseTerritories, false);
purchaseDefenders(purchaseTerritories, needToDefendSeaTerritories, purchaseOptions.getSeaDefenseOptions(),
purchaseOptions.getAirOptions(), false);
// Determine whether to purchase new land factory
final Map<Territory, ProPurchaseTerritory> factoryPurchaseTerritories = new HashMap<>();
purchaseFactory(factoryPurchaseTerritories, purchaseTerritories, prioritizedLandTerritories, purchaseOptions,
false);
// Prioritize sea place options and purchase units
final List<ProPlaceTerritory> prioritizedSeaTerritories = prioritizeSeaTerritories(purchaseTerritories);
purchaseSeaAndAmphibUnits(purchaseTerritories, prioritizedSeaTerritories, territoryValueMap, purchaseOptions);
// Try to use any remaining PUs on high value units
purchaseUnitsWithRemainingProduction(purchaseTerritories, purchaseOptions.getLandOptions(),
purchaseOptions.getAirOptions());
upgradeUnitsWithRemainingPUs(purchaseTerritories, purchaseOptions);
// Try to purchase land/sea factory with extra PUs
purchaseFactory(factoryPurchaseTerritories, purchaseTerritories, prioritizedLandTerritories, purchaseOptions, true);
// Add factory purchase territory to list if not empty
if (!factoryPurchaseTerritories.isEmpty()) {
purchaseTerritories.putAll(factoryPurchaseTerritories);
}
// Determine final count of each production rule
final IntegerMap<ProductionRule> purchaseMap = populateProductionRuleMap(purchaseTerritories, purchaseOptions);
// Purchase units
ProMetricUtils.collectPurchaseStats(purchaseMap);
final String error = purchaseDelegate.purchase(purchaseMap);
if (error != null) {
ProLogger.warn("Purchase error: " + error);
}
return purchaseTerritories;
}
public void place(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final IAbstractPlaceDelegate placeDelegate) {
ProLogger.info("Starting place phase");
data = ProData.getData();
player = ProData.getPlayer();
territoryManager = new ProTerritoryManager(calc);
if (purchaseTerritories != null) {
// Place all units calculated during purchase phase (land then sea to reduce failed placements)
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
if (!ppt.getTerritory().isWater()) {
final Collection<Unit> myUnits = player.getUnits().getUnits();
final List<Unit> unitsToPlace = new ArrayList<>();
for (final Unit placeUnit : ppt.getPlaceUnits()) {
for (final Unit myUnit : myUnits) {
if (myUnit.getUnitType().equals(placeUnit.getUnitType()) && !unitsToPlace.contains(myUnit)) {
unitsToPlace.add(myUnit);
break;
}
}
}
doPlace(data.getMap().getTerritory(ppt.getTerritory().getName()), unitsToPlace, placeDelegate);
ProLogger.debug(ppt.getTerritory() + " placed units: " + unitsToPlace);
}
}
}
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
if (ppt.getTerritory().isWater()) {
final Collection<Unit> myUnits = player.getUnits().getUnits();
final List<Unit> unitsToPlace = new ArrayList<>();
for (final Unit placeUnit : ppt.getPlaceUnits()) {
for (final Unit myUnit : myUnits) {
if (myUnit.getUnitType().equals(placeUnit.getUnitType()) && !unitsToPlace.contains(myUnit)) {
unitsToPlace.add(myUnit);
break;
}
}
}
doPlace(data.getMap().getTerritory(ppt.getTerritory().getName()), unitsToPlace, placeDelegate);
ProLogger.debug(ppt.getTerritory() + " placed units: " + unitsToPlace);
}
}
}
}
// Place remaining units (currently only implemented to handle land units, ex. WW2v3 China)
if (player.getUnits().getUnits().isEmpty()) {
return;
}
// Current data at the start of place
ProLogger.debug("Remaining units to place: " + player.getUnits().getUnits());
// Find all place territories
final Map<Territory, ProPurchaseTerritory> placeNonConstructionTerritories =
ProPurchaseUtils.findPurchaseTerritories(player);
// Determine max enemy attack units and current allied defenders
findDefendersInPlaceTerritories(placeNonConstructionTerritories);
// Prioritize land territories that need defended and place additional defenders
final List<ProPlaceTerritory> needToDefendLandTerritories =
prioritizeTerritoriesToDefend(placeNonConstructionTerritories, true);
placeDefenders(placeNonConstructionTerritories, needToDefendLandTerritories, placeDelegate);
// Find strategic value for each territory
ProLogger.info("Find strategic value for place territories");
final Map<Territory, Double> territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, new ArrayList<>(), new ArrayList<>());
for (final Territory t : placeNonConstructionTerritories.keySet()) {
for (final ProPlaceTerritory ppt : placeNonConstructionTerritories.get(t).getCanPlaceTerritories()) {
ppt.setStrategicValue(territoryValueMap.get(ppt.getTerritory()));
ProLogger.debug(ppt.getTerritory() + ", strategicValue=" + territoryValueMap.get(ppt.getTerritory()));
}
}
// Prioritize land place territories, add all territories, and then place units
final List<ProPlaceTerritory> prioritizedLandTerritories =
prioritizeLandTerritories(placeNonConstructionTerritories);
for (final ProPurchaseTerritory ppt : placeNonConstructionTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (!t.isWater() && !prioritizedLandTerritories.contains(placeTerritory)) {
prioritizedLandTerritories.add(placeTerritory);
}
}
}
// Place regular land units
placeLandUnits(prioritizedLandTerritories, placeDelegate, false);
// Place isConstruction land units (needs separated since placeDelegate.getPlaceableUnits doesn't handle combined)
placeLandUnits(prioritizedLandTerritories, placeDelegate, true);
}
private void findDefendersInPlaceTerritories(final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
ProLogger.info("Find defenders in possible place territories");
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
final List<Unit> units = t.getUnits().getMatches(Matches.isUnitAllied(player, data));
placeTerritory.setDefendingUnits(units);
ProLogger.debug(t + " has numDefenders=" + units.size());
}
}
}
private List<ProPlaceTerritory> prioritizeTerritoriesToDefend(
final Map<Territory, ProPurchaseTerritory> purchaseTerritories, final boolean isLand) {
ProLogger.info("Prioritize territories to defend with isLand=" + isLand);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Determine which territories need defended
final Set<ProPlaceTerritory> needToDefendTerritories = new HashSet<>();
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
// Check if any of the place territories can't be held with current defenders
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (enemyAttackOptions.getMax(t) == null || (t.isWater() && placeTerritory.getDefendingUnits().isEmpty())
|| (isLand && t.isWater()) || (!isLand && !t.isWater())) {
continue;
}
// Find current battle result
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
final ProBattleResult result = calc.calculateBattleResults(player, t, new ArrayList<>(enemyAttackingUnits),
placeTerritory.getDefendingUnits(), enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
placeTerritory.setMinBattleResult(result);
double holdValue = 0;
if (t.isWater()) {
final double unitValue = BattleCalculator.getTUV(
Match.getMatches(placeTerritory.getDefendingUnits(), Matches.unitIsOwnedBy(player)),
ProData.unitValueMap);
holdValue = unitValue / 8;
}
ProLogger.trace(t.getName() + " TUVSwing=" + result.getTUVSwing() + ", win%=" + result.getWinPercentage()
+ ", hasLandUnitRemaining=" + result.isHasLandUnitRemaining() + ", holdValue=" + holdValue
+ ", enemyAttackers=" + enemyAttackingUnits + ", defenders=" + placeTerritory.getDefendingUnits());
// If it can't currently be held then add to list
final boolean isLandAndCanOnlyBeAttackedByAir =
!t.isWater() && Match.allMatch(enemyAttackingUnits, Matches.UnitIsAir);
if ((!t.isWater() && result.isHasLandUnitRemaining()) || result.getTUVSwing() > holdValue
|| (t.equals(ProData.myCapital) && !isLandAndCanOnlyBeAttackedByAir
&& result.getWinPercentage() > (100 - ProData.winPercentage))) {
needToDefendTerritories.add(placeTerritory);
}
}
}
// Calculate value of defending territory
for (final ProPlaceTerritory placeTerritory : needToDefendTerritories) {
final Territory t = placeTerritory.getTerritory();
// 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.territoryHasInfraFactoryAndIsOwnedLand(player).match(t)) {
isFactory = 1;
}
// Determine production value and if it is an enemy capital
int production = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
}
// Determine defending unit value
double defendingUnitValue = BattleCalculator.getTUV(placeTerritory.getDefendingUnits(), ProData.unitValueMap);
if (t.isWater() && Match.noneMatch(placeTerritory.getDefendingUnits(), Matches.unitIsOwnedBy(player))) {
defendingUnitValue = 0;
}
// Calculate defense value for prioritization
final double territoryValue =
(2 * production + 4 * isFactory + 0.5 * defendingUnitValue) * (1 + isFactory) * (1 + 10 * isMyCapital);
placeTerritory.setDefenseValue(territoryValue);
}
// Remove any territories with negative defense value
for (final Iterator<ProPlaceTerritory> it = needToDefendTerritories.iterator(); it.hasNext();) {
final ProPlaceTerritory ppt = it.next();
if (ppt.getDefenseValue() <= 0) {
it.remove();
}
}
// Sort territories by value
final List<ProPlaceTerritory> sortedTerritories = new ArrayList<>(needToDefendTerritories);
Collections.sort(sortedTerritories, (t1, t2) -> {
final double value1 = t1.getDefenseValue();
final double value2 = t2.getDefenseValue();
return Double.compare(value2, value1);
});
for (final ProPlaceTerritory placeTerritory : sortedTerritories) {
ProLogger.debug(placeTerritory.toString() + " defenseValue=" + placeTerritory.getDefenseValue());
}
return sortedTerritories;
}
private void purchaseDefenders(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPlaceTerritory> needToDefendTerritories, final List<ProPurchaseOption> defensePurchaseOptions,
final List<ProPurchaseOption> airPurchaseOptions, final boolean isLand) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Purchase defenders with resources: " + resourceTracker + ", isLand=" + isLand);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Loop through prioritized territories and purchase defenders
for (final ProPlaceTerritory placeTerritory : needToDefendTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Purchasing defenders for " + t.getName() + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits() + ", amphibEnemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxAmphibUnits() + ", defenders=" + placeTerritory.getDefendingUnits());
// Find local owned units
final List<Unit> ownedLocalUnits = t.getUnits().getMatches(Matches.unitIsOwnedBy(player));
int unusedCarrierCapacity = Math.min(0, ProTransportUtils.getUnusedCarrierCapacity(player, t, new ArrayList<>()));
int unusedLocalCarrierCapacity = ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, new ArrayList<>());
ProLogger.trace(t + ", unusedCarrierCapacity=" + unusedCarrierCapacity + ", unusedLocalCarrierCapacity="
+ unusedLocalCarrierCapacity);
// Determine if need destroyer
boolean needDestroyer = false;
if (Match.someMatch(enemyAttackOptions.getMax(t).getMaxUnits(), Matches.UnitIsSub)
&& Match.noneMatch(ownedLocalUnits, Matches.UnitIsDestroyer)) {
needDestroyer = true;
}
// Find all purchase territories for place territory
final List<Unit> unitsToPlace = new ArrayList<>();
ProBattleResult finalResult = new ProBattleResult();
final List<ProPurchaseTerritory> selectedPurchaseTerritories =
getPurchaseTerritories(placeTerritory, purchaseTerritories);
for (final ProPurchaseTerritory purchaseTerritory : selectedPurchaseTerritories) {
// Check remaining production
int remainingUnitProduction = purchaseTerritory.getRemainingUnitProduction();
ProLogger.debug(purchaseTerritory.getTerritory() + ", remainingUnitProduction=" + remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Find defenders that can be produced in this territory
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, defensePurchaseOptions, t);
purchaseOptionsForTerritory.addAll(airPurchaseOptions);
// Purchase necessary defenders
while (true) {
// Remove options that cost too many resources or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, unitsToPlace, purchaseTerritories);
if (purchaseOptionsForTerritory.isEmpty()) {
break;
}
// Select purchase option
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
if (isLand) {
defenseEfficiencies.put(ppo, ppo.getDefenseEfficiency2(1, data, ownedLocalUnits, unitsToPlace));
} else {
defenseEfficiencies.put(ppo, ppo.getSeaDefenseEfficiency(data, ownedLocalUnits, unitsToPlace,
needDestroyer, unusedCarrierCapacity, unusedLocalCarrierCapacity));
}
}
final ProPurchaseOption selectedOption =
ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Defense");
if (selectedOption.isDestroyer()) {
needDestroyer = false;
}
// Create new temp units
resourceTracker.tempPurchase(selectedOption);
remainingUnitProduction -= selectedOption.getQuantity();
unitsToPlace.addAll(selectedOption.getUnitType().create(selectedOption.getQuantity(), player, true));
if (selectedOption.isCarrier() || selectedOption.isAir()) {
unusedCarrierCapacity = ProTransportUtils.getUnusedCarrierCapacity(player, t, unitsToPlace);
unusedLocalCarrierCapacity = ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, unitsToPlace);
}
ProLogger.trace("Selected unit=" + selectedOption.getUnitType().getName() + ", unusedCarrierCapacity="
+ unusedCarrierCapacity + ", unusedLocalCarrierCapacity=" + unusedLocalCarrierCapacity);
// Find current battle result
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
final List<Unit> defenders = new ArrayList<>(placeTerritory.getDefendingUnits());
defenders.addAll(unitsToPlace);
finalResult = calc.calculateBattleResults(player, t, new ArrayList<>(enemyAttackingUnits), defenders,
enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
// Break if it can be held
if ((!t.equals(ProData.myCapital) && !finalResult.isHasLandUnitRemaining() && finalResult.getTUVSwing() <= 0)
|| (t.equals(ProData.myCapital) && finalResult.getWinPercentage() < (100 - ProData.winPercentage)
&& finalResult.getTUVSwing() <= 0)) {
break;
}
}
}
// Check to see if its worth trying to defend the territory
final boolean hasLocalSuperiority =
ProBattleUtils.territoryHasLocalLandSuperiority(t, ProBattleUtils.SHORT_RANGE, player, purchaseTerritories);
if (!finalResult.isHasLandUnitRemaining()
|| (finalResult.getTUVSwing() - resourceTracker.getTempPUs(data) / 2) < placeTerritory.getMinBattleResult()
.getTUVSwing()
|| t.equals(ProData.myCapital) || (!t.isWater() && hasLocalSuperiority)) {
resourceTracker.confirmTempPurchases();
ProLogger.trace(
t + ", placedUnits=" + unitsToPlace + ", TUVSwing=" + finalResult.getTUVSwing() + ", hasLandUnitRemaining="
+ finalResult.isHasLandUnitRemaining() + ", hasLocalSuperiority=" + hasLocalSuperiority);
addUnitsToPlaceTerritory(placeTerritory, unitsToPlace, purchaseTerritories);
} else {
resourceTracker.clearTempPurchases();
setCantHoldPlaceTerritory(placeTerritory, purchaseTerritories);
ProLogger.trace(t + ", unable to defend with placedUnits=" + unitsToPlace + ", TUVSwing="
+ finalResult.getTUVSwing() + ", minTUVSwing=" + placeTerritory.getMinBattleResult().getTUVSwing());
}
}
}
private List<ProPlaceTerritory> prioritizeLandTerritories(
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
ProLogger.info("Prioritize land territories to place");
// Get all land place territories
final List<ProPlaceTerritory> prioritizedLandTerritories = new ArrayList<>();
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (!t.isWater() && placeTerritory.getStrategicValue() >= 1 && placeTerritory.isCanHold()) {
final boolean hasEnemyNeighbors =
!data.getMap().getNeighbors(t, ProMatches.territoryIsEnemyLand(player, data)).isEmpty();
final Set<Territory> nearbyLandTerritories =
data.getMap().getNeighbors(t, 9, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, false));
final int numNearbyEnemyTerritories = Match.countMatches(nearbyLandTerritories,
Matches.isTerritoryOwnedBy(ProUtils.getPotentialEnemyPlayers(player)));
final boolean hasLocalLandSuperiority =
ProBattleUtils.territoryHasLocalLandSuperiority(t, ProBattleUtils.SHORT_RANGE, player);
if (hasEnemyNeighbors || numNearbyEnemyTerritories >= 3 || !hasLocalLandSuperiority) {
prioritizedLandTerritories.add(placeTerritory);
}
}
}
}
// Sort territories by value
Collections.sort(prioritizedLandTerritories, (t1, t2) -> {
final double value1 = t1.getStrategicValue();
final double value2 = t2.getStrategicValue();
return Double.compare(value2, value1);
});
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
ProLogger.debug(placeTerritory.toString() + " strategicValue=" + placeTerritory.getStrategicValue());
}
return prioritizedLandTerritories;
}
private void purchaseAAUnits(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPlaceTerritory> prioritizedLandTerritories, final List<ProPurchaseOption> specialPurchaseOptions) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Purchase AA units with resources: " + resourceTracker);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Loop through prioritized territories and purchase AA units
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking AA place for " + t);
// Check if any enemy attackers
if (enemyAttackOptions.getMax(t) == null) {
continue;
}
// Check remaining production
int remainingUnitProduction = purchaseTerritories.get(t).getRemainingUnitProduction();
ProLogger.debug(t + ", remainingUnitProduction=" + remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Check if territory needs AA
final boolean enemyCanBomb =
Match.someMatch(enemyAttackOptions.getMax(t).getMaxUnits(), Matches.UnitIsStrategicBomber);
final boolean territoryCanBeBombed = t.getUnits().someMatch(Matches.UnitCanProduceUnitsAndCanBeDamaged);
final boolean hasAABombingDefense = t.getUnits().someMatch(Matches.UnitIsAAforBombingThisUnitOnly);
ProLogger.debug(t + ", enemyCanBomb=" + enemyCanBomb + ", territoryCanBeBombed=" + territoryCanBeBombed
+ ", hasAABombingDefense=" + hasAABombingDefense);
if (!enemyCanBomb || !territoryCanBeBombed || hasAABombingDefense) {
continue;
}
// Remove options that cost too much PUs or production
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, specialPurchaseOptions, t);
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, new ArrayList<>(), purchaseTerritories);
if (purchaseOptionsForTerritory.isEmpty()) {
continue;
}
// Determine most cost efficient units that can be produced in this territory
ProPurchaseOption bestAAOption = null;
int minCost = Integer.MAX_VALUE;
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
final boolean isAAForBombing = Matches.UnitTypeIsAAforBombingThisUnitOnly.match(ppo.getUnitType());
if (isAAForBombing && ppo.getCost() < minCost
&& !Matches.UnitTypeConsumesUnitsOnCreation.match(ppo.getUnitType())) {
bestAAOption = ppo;
minCost = ppo.getCost();
}
}
// Check if there aren't any available units
if (bestAAOption == null) {
continue;
}
ProLogger.trace("Best AA unit: " + bestAAOption.getUnitType().getName());
// Create new temp units
resourceTracker.purchase(bestAAOption);
remainingUnitProduction -= bestAAOption.getQuantity();
final List<Unit> unitsToPlace = bestAAOption.getUnitType().create(bestAAOption.getQuantity(), player, true);
placeTerritory.getPlaceUnits().addAll(unitsToPlace);
ProLogger.trace(t + ", placedUnits=" + unitsToPlace);
}
}
private void purchaseLandUnits(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPlaceTerritory> prioritizedLandTerritories, final ProPurchaseOptionMap purchaseOptions,
final Map<Territory, Double> territoryValueMap) {
final List<Unit> unplacedUnits = player.getUnits().getMatches(Matches.UnitIsNotSea);
if (resourceTracker.isEmpty() && unplacedUnits.isEmpty()) {
return;
}
ProLogger.info("Purchase land units with resources: " + resourceTracker);
if (!unplacedUnits.isEmpty()) {
ProLogger.info("Purchase land units with unplaced units=" + unplacedUnits);
}
// Loop through prioritized territories and purchase land units
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking land place for " + t.getName());
// Check remaining production
int remainingUnitProduction = purchaseTerritories.get(t).getRemainingUnitProduction();
ProLogger.debug(t + ", remainingUnitProduction=" + remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Determine most cost efficient units that can be produced in this territory
final List<ProPurchaseOption> landFodderOptions =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getLandFodderOptions(), t);
final List<ProPurchaseOption> landAttackOptions =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getLandAttackOptions(), t);
final List<ProPurchaseOption> landDefenseOptions =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getLandDefenseOptions(), t);
// Determine enemy distance and locally owned units
int enemyDistance = ProUtils.getClosestEnemyOrNeutralLandTerritoryDistance(data, player, t, territoryValueMap);
if (enemyDistance <= 0) {
enemyDistance = 10;
}
final int fodderPercent = 80 - enemyDistance * 5;
ProLogger.debug(t + ", enemyDistance=" + enemyDistance + ", fodderPercent=" + fodderPercent);
final Set<Territory> neighbors =
data.getMap().getNeighbors(t, 2, ProMatches.territoryCanMoveLandUnits(player, data, false));
neighbors.add(t);
final List<Unit> ownedLocalUnits = new ArrayList<>();
for (final Territory neighbor : neighbors) {
ownedLocalUnits.addAll(neighbor.getUnits().getMatches(Matches.unitIsOwnedBy(player)));
}
// Check for unplaced units
final List<Unit> unitsToPlace = new ArrayList<>();
for (final Iterator<Unit> it = unplacedUnits.iterator(); it.hasNext();) {
final Unit u = it.next();
if (remainingUnitProduction > 0 && ProPurchaseUtils.canUnitsBePlaced(Collections.singletonList(u), player, t)) {
remainingUnitProduction--;
unitsToPlace.add(u);
it.remove();
ProLogger.trace("Selected unplaced unit=" + u);
}
}
// Purchase as many units as possible
int addedFodderUnits = 0;
double attackAndDefenseDifference = 0;
boolean selectFodderUnit = true;
while (true) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, landFodderOptions, resourceTracker,
remainingUnitProduction, unitsToPlace, purchaseTerritories);
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, landAttackOptions, resourceTracker,
remainingUnitProduction, unitsToPlace, purchaseTerritories);
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, landDefenseOptions, resourceTracker,
remainingUnitProduction, unitsToPlace, purchaseTerritories);
// Select purchase option
ProPurchaseOption selectedOption = null;
if (!selectFodderUnit && attackAndDefenseDifference > 0 && !landDefenseOptions.isEmpty()) {
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : landDefenseOptions) {
defenseEfficiencies.put(ppo, ppo.getDefenseEfficiency2(enemyDistance, data, ownedLocalUnits, unitsToPlace));
}
selectedOption = ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Land Defense");
} else if (!selectFodderUnit && !landAttackOptions.isEmpty()) {
final Map<ProPurchaseOption, Double> attackEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : landAttackOptions) {
attackEfficiencies.put(ppo, ppo.getAttackEfficiency2(enemyDistance, data, ownedLocalUnits, unitsToPlace));
}
selectedOption = ProPurchaseUtils.randomizePurchaseOption(attackEfficiencies, "Land Attack");
} else if (!landFodderOptions.isEmpty()) {
final Map<ProPurchaseOption, Double> fodderEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : landFodderOptions) {
fodderEfficiencies.put(ppo, ppo.getFodderEfficiency(enemyDistance, data, ownedLocalUnits, unitsToPlace));
}
selectedOption = ProPurchaseUtils.randomizePurchaseOption(fodderEfficiencies, "Land Fodder");
addedFodderUnits += selectedOption.getQuantity();
} else {
break;
}
// Create new temp units
resourceTracker.purchase(selectedOption);
remainingUnitProduction -= selectedOption.getQuantity();
unitsToPlace.addAll(selectedOption.getUnitType().create(selectedOption.getQuantity(), player, true));
attackAndDefenseDifference += (selectedOption.getAttack() - selectedOption.getDefense());
selectFodderUnit = ((double) addedFodderUnits / unitsToPlace.size() * 100) <= fodderPercent;
ProLogger.trace("Selected unit=" + selectedOption.getUnitType().getName());
}
// Add units to place territory
placeTerritory.getPlaceUnits().addAll(unitsToPlace);
ProLogger.debug(t + ", placedUnits=" + unitsToPlace);
}
}
private void purchaseFactory(final Map<Territory, ProPurchaseTerritory> factoryPurchaseTerritories,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPlaceTerritory> prioritizedLandTerritories, final ProPurchaseOptionMap purchaseOptions,
final boolean hasExtraPUs) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Purchase factory with resources: " + resourceTracker + ", hasExtraPUs=" + hasExtraPUs);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Only try to purchase a factory if all production was used in prioritized land territories
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
for (final Territory t : purchaseTerritories.keySet()) {
if (placeTerritory.getTerritory().equals(t) && purchaseTerritories.get(t).getRemainingUnitProduction() > 0) {
ProLogger.debug("Not purchasing a factory since remaining land production in " + t);
return;
}
}
}
// Find all owned land territories that weren't conquered and don't already have a factory
final List<Territory> possibleFactoryTerritories = Match.getMatches(data.getMap().getTerritories(),
ProMatches.territoryHasNoInfraFactoryAndIsNotConqueredOwnedLand(player, data));
possibleFactoryTerritories.removeAll(factoryPurchaseTerritories.keySet());
final Set<Territory> purchaseFactoryTerritories = new HashSet<>();
final List<Territory> territoriesThatCantBeHeld = new ArrayList<>();
for (final Territory t : possibleFactoryTerritories) {
// Only consider territories with production of at least 3 unless there are still remaining PUs
final int production = TerritoryAttachment.get(t).getProduction();
if ((production < 3 && !hasExtraPUs) || production < 2) {
continue;
}
// Check if no enemy attackers and that it wasn't conquered this turn
if (enemyAttackOptions.getMax(t) == null) {
purchaseFactoryTerritories.add(t);
ProLogger.trace("Possible factory since no enemy attackers: " + t.getName());
} else {
// Find current battle result
final List<Unit> defenders = t.getUnits().getMatches(Matches.isUnitAllied(player, data));
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
final ProBattleResult result = calc.estimateDefendBattleResults(player, t, new ArrayList<>(enemyAttackingUnits),
defenders, enemyAttackOptions.getMax(t).getMaxBombardUnits());
// Check if it can't be held or if it can then that it wasn't conquered this turn
if (result.isHasLandUnitRemaining() || result.getTUVSwing() > 0) {
territoriesThatCantBeHeld.add(t);
ProLogger.trace("Can't hold territory: " + t.getName() + ", hasLandUnitRemaining="
+ result.isHasLandUnitRemaining() + ", TUVSwing=" + result.getTUVSwing() + ", enemyAttackers="
+ enemyAttackingUnits.size() + ", myDefenders=" + defenders.size());
} else {
purchaseFactoryTerritories.add(t);
ProLogger.trace("Possible factory: " + t.getName() + ", hasLandUnitRemaining="
+ result.isHasLandUnitRemaining() + ", TUVSwing=" + result.getTUVSwing() + ", enemyAttackers="
+ enemyAttackingUnits.size() + ", myDefenders=" + defenders.size());
}
}
}
ProLogger.debug("Possible factory territories: " + purchaseFactoryTerritories);
// Remove any territories that don't have local land superiority
if (!hasExtraPUs) {
for (final Iterator<Territory> it = purchaseFactoryTerritories.iterator(); it.hasNext();) {
final Territory t = it.next();
if (!ProBattleUtils.territoryHasLocalLandSuperiority(t, ProBattleUtils.MEDIUM_RANGE, player,
purchaseTerritories)) {
it.remove();
}
}
ProLogger.debug("Possible factory territories that have land superiority: " + purchaseFactoryTerritories);
}
// Find strategic value for each territory
final Map<Territory, Double> territoryValueMap =
ProTerritoryValueUtils.findTerritoryValues(player, territoriesThatCantBeHeld, new ArrayList<>());
double maxValue = 0.0;
Territory maxTerritory = null;
for (final Territory t : purchaseFactoryTerritories) {
final int production = TerritoryAttachment.get(t).getProduction();
final double value = territoryValueMap.get(t) * production + 0.1 * production;
final boolean isAdjacentToSea = Matches.territoryHasNeighborMatching(data, Matches.TerritoryIsWater).match(t);
final Set<Territory> nearbyLandTerritories =
data.getMap().getNeighbors(t, 9, ProMatches.territoryCanMoveLandUnits(player, data, false));
final int numNearbyEnemyTerritories =
Match.countMatches(nearbyLandTerritories, Matches.isTerritoryEnemy(player, data));
ProLogger.trace(t + ", strategic value=" + territoryValueMap.get(t) + ", value=" + value
+ ", numNearbyEnemyTerritories=" + numNearbyEnemyTerritories);
if (value > maxValue
&& ((numNearbyEnemyTerritories >= 4 && territoryValueMap.get(t) >= 1) || (isAdjacentToSea && hasExtraPUs))) {
maxValue = value;
maxTerritory = t;
}
}
ProLogger.debug("Try to purchase factory for territory: " + maxTerritory);
// Determine whether to purchase factory
if (maxTerritory != null) {
// Find most expensive placed land unit to consider removing for a factory
ProPurchaseOption maxPlacedOption = null;
ProPlaceTerritory maxPlacedTerritory = null;
Unit maxPlacedUnit = null;
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
for (final Unit u : placeTerritory.getPlaceUnits()) {
for (final ProPurchaseOption ppo : purchaseOptions.getLandOptions()) {
if (u.getType().equals(ppo.getUnitType()) && ppo.getQuantity() == 1
&& (maxPlacedOption == null || ppo.getCost() >= maxPlacedOption.getCost())) {
maxPlacedOption = ppo;
maxPlacedTerritory = placeTerritory;
maxPlacedUnit = u;
}
}
}
}
// Determine units that can be produced in this territory
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getFactoryOptions(), maxTerritory);
resourceTracker.removeTempPurchase(maxPlacedOption);
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, 1, new ArrayList<>(), purchaseTerritories);
resourceTracker.clearTempPurchases();
// Determine most expensive factory option (currently doesn't buy mobile factories)
ProPurchaseOption bestFactoryOption = null;
double maxFactoryEfficiency = 0;
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
if (ppo.getMovement() == 0 && ppo.getCost() > maxFactoryEfficiency) {
bestFactoryOption = ppo;
maxFactoryEfficiency = ppo.getCost();
}
}
// Check if there are enough PUs to buy a factory
if (bestFactoryOption != null) {
ProLogger.debug("Best factory unit: " + bestFactoryOption.getUnitType().getName());
final ProPurchaseTerritory factoryPurchaseTerritory = new ProPurchaseTerritory(maxTerritory, data, player, 0);
factoryPurchaseTerritories.put(maxTerritory, factoryPurchaseTerritory);
for (final ProPlaceTerritory ppt : factoryPurchaseTerritory.getCanPlaceTerritories()) {
if (ppt.getTerritory().equals(maxTerritory)) {
final List<Unit> factory =
bestFactoryOption.getUnitType().create(bestFactoryOption.getQuantity(), player, true);
ppt.getPlaceUnits().addAll(factory);
if (resourceTracker.hasEnough(bestFactoryOption)) {
resourceTracker.purchase(bestFactoryOption);
ProLogger.debug(maxTerritory + ", placedFactory=" + factory);
} else {
resourceTracker.purchase(bestFactoryOption);
resourceTracker.removePurchase(maxPlacedOption);
maxPlacedTerritory.getPlaceUnits().remove(maxPlacedUnit);
ProLogger.debug(maxTerritory + ", placedFactory=" + factory + ", removedUnit=" + maxPlacedUnit);
}
}
}
}
}
}
private List<ProPlaceTerritory> prioritizeSeaTerritories(
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
ProLogger.info("Prioritize sea territories");
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Determine which sea territories can be placed in
final Set<ProPlaceTerritory> seaPlaceTerritories = new HashSet<>();
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (t.isWater() && placeTerritory.getStrategicValue() > 0 && placeTerritory.isCanHold()) {
seaPlaceTerritories.add(placeTerritory);
}
}
}
// Calculate value of territory
ProLogger.debug("Determine sea place value:");
for (final ProPlaceTerritory placeTerritory : seaPlaceTerritories) {
final Territory t = placeTerritory.getTerritory();
// Find number of local naval units
final List<Unit> units = new ArrayList<>(placeTerritory.getDefendingUnits());
units.addAll(ProPurchaseUtils.getPlaceUnits(t, purchaseTerritories));
final List<Unit> myUnits = Match.getMatches(units, Matches.unitIsOwnedBy(player));
final int numMyTransports = Match.countMatches(myUnits, Matches.UnitIsTransport);
final int numSeaDefenders = Match.countMatches(units, Matches.UnitIsNotTransport);
// Determine needed defense strength
int needDefenders = 0;
if (enemyAttackOptions.getMax(t) != null) {
final double strengthDifference =
ProBattleUtils.estimateStrengthDifference(t, enemyAttackOptions.getMax(t).getMaxUnits(), units);
if (strengthDifference > 50) {
needDefenders = 1;
}
}
final boolean hasLocalNavalSuperiority =
ProBattleUtils.territoryHasLocalNavalSuperiority(t, player, null, new ArrayList<>());
if (!hasLocalNavalSuperiority) {
needDefenders = 1;
}
// Calculate sea value for prioritization
final double territoryValue =
placeTerritory.getStrategicValue() * (1 + numMyTransports + 0.1 * numSeaDefenders) / (1 + 3 * needDefenders);
ProLogger.debug(t + ", value=" + territoryValue + ", strategicValue=" + placeTerritory.getStrategicValue()
+ ", numMyTransports=" + numMyTransports + ", numSeaDefenders=" + numSeaDefenders + ", needDefenders="
+ needDefenders);
placeTerritory.setStrategicValue(territoryValue);
}
// Sort territories by value
final List<ProPlaceTerritory> sortedTerritories = new ArrayList<>(seaPlaceTerritories);
Collections.sort(sortedTerritories, (t1, t2) -> {
final double value1 = t1.getStrategicValue();
final double value2 = t2.getStrategicValue();
return Double.compare(value2, value1);
});
ProLogger.debug("Sorted sea territories:");
for (final ProPlaceTerritory placeTerritory : sortedTerritories) {
ProLogger.debug(placeTerritory.toString() + " value=" + placeTerritory.getStrategicValue());
}
return sortedTerritories;
}
private void purchaseSeaAndAmphibUnits(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPlaceTerritory> prioritizedSeaTerritories, final Map<Territory, Double> territoryValueMap,
final ProPurchaseOptionMap purchaseOptions) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Purchase sea and amphib units with resources: " + resourceTracker);
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Loop through prioritized territories and purchase sea units
for (final ProPlaceTerritory placeTerritory : prioritizedSeaTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking sea place for " + t.getName());
// Find all purchase territories for place territory
final List<ProPurchaseTerritory> selectedPurchaseTerritories =
getPurchaseTerritories(placeTerritory, purchaseTerritories);
// Find local owned units
final Set<Territory> neighbors =
data.getMap().getNeighbors(t, 2, ProMatches.territoryCanMoveSeaUnits(player, data, false));
neighbors.add(t);
final List<Unit> ownedLocalUnits = new ArrayList<>();
for (final Territory neighbor : neighbors) {
ownedLocalUnits.addAll(neighbor.getUnits().getMatches(Matches.unitIsOwnedBy(player)));
}
int unusedCarrierCapacity = Math.min(0, ProTransportUtils.getUnusedCarrierCapacity(player, t, new ArrayList<>()));
int unusedLocalCarrierCapacity = ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, new ArrayList<>());
boolean needDestroyer = false;
ProLogger.trace(t + ", unusedCarrierCapacity=" + unusedCarrierCapacity + ", unusedLocalCarrierCapacity="
+ unusedLocalCarrierCapacity);
// If any enemy attackers then purchase sea defenders until it can be held
if (enemyAttackOptions.getMax(t) != null) {
// Determine if need destroyer
if (Match.someMatch(enemyAttackOptions.getMax(t).getMaxUnits(), Matches.UnitIsSub)
&& Match.noneMatch(t.getUnits().getMatches(Matches.unitIsOwnedBy(player)), Matches.UnitIsDestroyer)) {
needDestroyer = true;
}
ProLogger.trace(t + ", needDestroyer=" + needDestroyer + ", checking defense since has enemy attackers: "
+ enemyAttackOptions.getMax(t).getMaxUnits());
final List<Unit> unitsToPlace = new ArrayList<>();
final List<Unit> initialDefendingUnits = new ArrayList<>(placeTerritory.getDefendingUnits());
initialDefendingUnits.addAll(ProPurchaseUtils.getPlaceUnits(t, purchaseTerritories));
ProBattleResult result = calc.calculateBattleResults(player, t, enemyAttackOptions.getMax(t).getMaxUnits(),
initialDefendingUnits, enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
boolean hasOnlyRetreatingSubs =
Properties.getSubRetreatBeforeBattle(data) && Match.allMatch(initialDefendingUnits, Matches.UnitIsSub)
&& Match.noneMatch(enemyAttackOptions.getMax(t).getMaxUnits(), Matches.UnitIsDestroyer);
for (final ProPurchaseTerritory purchaseTerritory : selectedPurchaseTerritories) {
// Check remaining production
int remainingUnitProduction = purchaseTerritory.getRemainingUnitProduction();
ProLogger.trace(t + ", purchaseTerritory=" + purchaseTerritory.getTerritory() + ", remainingUnitProduction="
+ remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Determine sea and transport units that can be produced in this territory
final List<ProPurchaseOption> seaPurchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getSeaDefenseOptions(), t);
seaPurchaseOptionsForTerritory.addAll(purchaseOptions.getAirOptions());
// Purchase enough sea defenders to hold territory
while (true) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, seaPurchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, unitsToPlace, purchaseTerritories);
if (seaPurchaseOptionsForTerritory.isEmpty()) {
break;
}
// If it can be held then break
if (!hasOnlyRetreatingSubs
&& (result.getTUVSwing() < -1 || result.getWinPercentage() < ProData.winPercentage)) {
break;
}
// Select purchase option
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaPurchaseOptionsForTerritory) {
defenseEfficiencies.put(ppo, ppo.getSeaDefenseEfficiency(data, ownedLocalUnits, unitsToPlace,
needDestroyer, unusedCarrierCapacity, unusedLocalCarrierCapacity));
}
final ProPurchaseOption selectedOption =
ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Sea Defense");
if (selectedOption.isDestroyer()) {
needDestroyer = false;
}
// Create new temp defenders
resourceTracker.tempPurchase(selectedOption);
remainingUnitProduction -= selectedOption.getQuantity();
unitsToPlace.addAll(selectedOption.getUnitType().create(selectedOption.getQuantity(), player, true));
if (selectedOption.isCarrier() || selectedOption.isAir()) {
unusedCarrierCapacity = ProTransportUtils.getUnusedCarrierCapacity(player, t, unitsToPlace);
unusedLocalCarrierCapacity = ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, unitsToPlace);
}
ProLogger
.trace(t + ", added sea defender for defense: " + selectedOption.getUnitType().getName() + ", TUVSwing="
+ result.getTUVSwing() + ", win%=" + result.getWinPercentage() + ", unusedCarrierCapacity="
+ unusedCarrierCapacity + ", unusedLocalCarrierCapacity=" + unusedLocalCarrierCapacity);
// Find current battle result
final List<Unit> defendingUnits = new ArrayList<>(placeTerritory.getDefendingUnits());
defendingUnits.addAll(ProPurchaseUtils.getPlaceUnits(t, purchaseTerritories));
defendingUnits.addAll(unitsToPlace);
result = calc.estimateDefendBattleResults(player, t, enemyAttackOptions.getMax(t).getMaxUnits(),
defendingUnits, enemyAttackOptions.getMax(t).getMaxBombardUnits());
hasOnlyRetreatingSubs =
Properties.getSubRetreatBeforeBattle(data) && Match.allMatch(defendingUnits, Matches.UnitIsSub)
&& Match.noneMatch(enemyAttackOptions.getMax(t).getMaxUnits(), Matches.UnitIsDestroyer);
}
}
// Check to see if its worth trying to defend the territory
if (result.getTUVSwing() < 0 || result.getWinPercentage() < ProData.winPercentage) {
resourceTracker.confirmTempPurchases();
ProLogger.trace(t + ", placedUnits=" + unitsToPlace + ", TUVSwing=" + result.getTUVSwing()
+ ", hasLandUnitRemaining=" + result.isHasLandUnitRemaining());
addUnitsToPlaceTerritory(placeTerritory, unitsToPlace, purchaseTerritories);
} else {
resourceTracker.clearTempPurchases();
setCantHoldPlaceTerritory(placeTerritory, purchaseTerritories);
ProLogger.trace(t + ", can't defend TUVSwing=" + result.getTUVSwing() + ", win%=" + result.getWinPercentage()
+ ", tried to placeDefenders=" + unitsToPlace + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits());
continue;
}
}
// TODO: update to use ProBattleUtils method
// Check to see if local naval superiority
int landDistance = ProUtils.getClosestEnemyLandTerritoryDistanceOverWater(data, player, t);
if (landDistance <= 0) {
landDistance = 10;
}
final int enemyDistance = Math.max(3, (landDistance + 1));
final int alliedDistance = (enemyDistance + 1) / 2;
final Set<Territory> nearbyTerritories =
data.getMap().getNeighbors(t, enemyDistance, ProMatches.territoryCanMoveAirUnits(player, data, false));
final List<Territory> nearbyLandTerritories = Match.getMatches(nearbyTerritories, Matches.TerritoryIsLand);
final Set<Territory> nearbyEnemySeaTerritories =
data.getMap().getNeighbors(t, enemyDistance, Matches.TerritoryIsWater);
nearbyEnemySeaTerritories.add(t);
final Set<Territory> nearbyAlliedSeaTerritories =
data.getMap().getNeighbors(t, alliedDistance, Matches.TerritoryIsWater);
nearbyAlliedSeaTerritories.add(t);
final List<Unit> enemyUnitsInSeaTerritories = new ArrayList<>();
final List<Unit> enemyUnitsInLandTerritories = new ArrayList<>();
final List<Unit> myUnitsInSeaTerritories = new ArrayList<>();
for (final Territory nearbyLandTerritory : nearbyLandTerritories) {
enemyUnitsInLandTerritories
.addAll(nearbyLandTerritory.getUnits().getMatches(ProMatches.unitIsEnemyAir(player, data)));
}
for (final Territory nearbySeaTerritory : nearbyEnemySeaTerritories) {
final List<Unit> enemySeaUnits =
nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsEnemyNotLand(player, data));
if (enemySeaUnits.isEmpty()) {
continue;
}
final Route route = data.getMap().getRoute_IgnoreEnd(t, nearbySeaTerritory, Matches.TerritoryIsWater);
if (route == null) {
continue;
}
if (MoveValidator.validateCanal(route, enemySeaUnits, enemySeaUnits.get(0).getOwner(), data) != null) {
continue;
}
final int routeLength = route.numberOfSteps();
if (routeLength <= enemyDistance) {
enemyUnitsInSeaTerritories.addAll(enemySeaUnits);
}
}
for (final Territory nearbySeaTerritory : nearbyAlliedSeaTerritories) {
myUnitsInSeaTerritories
.addAll(nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsOwnedNotLand(player, data)));
myUnitsInSeaTerritories.addAll(ProPurchaseUtils.getPlaceUnits(nearbySeaTerritory, purchaseTerritories));
}
// Check if destroyer is needed
final int numEnemySubs = Match.countMatches(enemyUnitsInSeaTerritories, Matches.UnitIsSub);
final int numMyDestroyers = Match.countMatches(myUnitsInSeaTerritories, Matches.UnitIsDestroyer);
if (numEnemySubs > 2 * numMyDestroyers) {
needDestroyer = true;
}
ProLogger.trace(t + ", enemyDistance=" + enemyDistance + ", alliedDistance=" + alliedDistance + ", enemyAirUnits="
+ enemyUnitsInLandTerritories + ", enemySeaUnits=" + enemyUnitsInSeaTerritories + ", mySeaUnits="
+ myUnitsInSeaTerritories + ", needDestroyer=" + needDestroyer);
// Purchase naval defenders until I have local naval superiority
final List<Unit> unitsToPlace = new ArrayList<>();
for (final ProPurchaseTerritory purchaseTerritory : selectedPurchaseTerritories) {
// Check remaining production
int remainingUnitProduction = purchaseTerritory.getRemainingUnitProduction();
ProLogger.trace(t + ", purchaseTerritory=" + purchaseTerritory.getTerritory() + ", remainingUnitProduction="
+ remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Determine sea and transport units that can be produced in this territory
final List<ProPurchaseOption> seaPurchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getSeaDefenseOptions(), t);
seaPurchaseOptionsForTerritory.addAll(purchaseOptions.getAirOptions());
while (true) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, seaPurchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, unitsToPlace, purchaseTerritories);
if (seaPurchaseOptionsForTerritory.isEmpty()) {
break;
}
// If I have naval attack/defense superiority then break
if (ProBattleUtils.territoryHasLocalNavalSuperiority(t, player, purchaseTerritories, unitsToPlace)) {
break;
}
// Select purchase option
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaPurchaseOptionsForTerritory) {
defenseEfficiencies.put(ppo, ppo.getSeaDefenseEfficiency(data, ownedLocalUnits, unitsToPlace, needDestroyer,
unusedCarrierCapacity, unusedLocalCarrierCapacity));
}
final ProPurchaseOption selectedOption =
ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Sea Defense");
if (selectedOption.isDestroyer()) {
needDestroyer = false;
}
// Create new temp units
resourceTracker.purchase(selectedOption);
remainingUnitProduction -= selectedOption.getQuantity();
unitsToPlace.addAll(selectedOption.getUnitType().create(selectedOption.getQuantity(), player, true));
if (selectedOption.isCarrier() || selectedOption.isAir()) {
unusedCarrierCapacity = ProTransportUtils.getUnusedCarrierCapacity(player, t, unitsToPlace);
unusedLocalCarrierCapacity = ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, unitsToPlace);
}
ProLogger.trace(t + ", added sea defender for naval superiority: " + selectedOption.getUnitType().getName()
+ ", unusedCarrierCapacity=" + unusedCarrierCapacity + ", unusedLocalCarrierCapacity="
+ unusedLocalCarrierCapacity);
}
}
// Add sea defender units to place territory
addUnitsToPlaceTerritory(placeTerritory, unitsToPlace, purchaseTerritories);
// Loop through adjacent purchase territories and purchase transport/amphib units
final int distance = ProTransportUtils.findMaxMovementForTransports(purchaseOptions.getSeaTransportOptions());
ProLogger.trace(t + ", transportMovement=" + distance);
for (final ProPurchaseTerritory purchaseTerritory : selectedPurchaseTerritories) {
final Territory landTerritory = purchaseTerritory.getTerritory();
// Check if territory can produce units and has remaining production
int remainingUnitProduction = purchaseTerritory.getRemainingUnitProduction();
ProLogger
.trace(t + ", purchaseTerritory=" + landTerritory + ", remainingUnitProduction=" + remainingUnitProduction);
if (remainingUnitProduction <= 0) {
continue;
}
// Find local owned units
final List<Unit> ownedLocalAmphibUnits = landTerritory.getUnits().getMatches(Matches.unitIsOwnedBy(player));
// Determine sea and transport units that can be produced in this territory
final List<ProPurchaseOption> seaTransportPurchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getSeaTransportOptions(), t);
final List<ProPurchaseOption> amphibPurchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getLandOptions(), landTerritory);
// Find transports that need loaded and units to ignore that are already paired up
final List<Unit> transportsThatNeedUnits = new ArrayList<>();
final Set<Unit> potentialUnitsToLoad = new HashSet<>();
final Set<Territory> seaTerritories = data.getMap().getNeighbors(landTerritory, distance,
ProMatches.territoryCanMoveSeaUnits(player, data, false));
for (final Territory seaTerritory : seaTerritories) {
final List<Unit> unitsInTerritory = ProPurchaseUtils.getPlaceUnits(seaTerritory, purchaseTerritories);
unitsInTerritory.addAll(seaTerritory.getUnits().getUnits());
final List<Unit> transports = Match.getMatches(unitsInTerritory, ProMatches.unitIsOwnedTransport(player));
for (final Unit transport : transports) {
transportsThatNeedUnits.add(transport);
final Set<Territory> territoriesToLoadFrom =
new HashSet<>(data.getMap().getNeighbors(seaTerritory, distance));
for (final Iterator<Territory> it = territoriesToLoadFrom.iterator(); it.hasNext();) {
final Territory potentialTerritory = it.next();
if (potentialTerritory.isWater() || territoryValueMap.get(potentialTerritory) > 0.25) {
it.remove();
}
}
final List<Unit> units =
ProTransportUtils.getUnitsToTransportFromTerritories(player, transport, territoriesToLoadFrom,
new ArrayList<>(potentialUnitsToLoad), ProMatches.unitIsOwnedCombatTransportableUnit(player));
potentialUnitsToLoad.addAll(units);
}
}
// Determine whether transports, amphib units, or both are needed
final Set<Territory> landNeighbors = data.getMap().getNeighbors(t, Matches.TerritoryIsLand);
for (final Territory neighbor : landNeighbors) {
if (territoryValueMap.get(neighbor) <= 0.25) {
final List<Unit> unitsInTerritory = new ArrayList<>(neighbor.getUnits().getUnits());
unitsInTerritory.addAll(ProPurchaseUtils.getPlaceUnits(neighbor, purchaseTerritories));
potentialUnitsToLoad
.addAll(Match.getMatches(unitsInTerritory, ProMatches.unitIsOwnedCombatTransportableUnit(player)));
}
}
ProLogger.trace(t + ", potentialUnitsToLoad=" + potentialUnitsToLoad + ", transportsThatNeedUnits="
+ transportsThatNeedUnits);
// Purchase transports and amphib units
final List<Unit> amphibUnitsToPlace = new ArrayList<>();
final List<Unit> transportUnitsToPlace = new ArrayList<>();
while (true) {
if (!transportsThatNeedUnits.isEmpty()) {
// Get next empty transport and find its capacity
final Unit transport = transportsThatNeedUnits.get(0);
int transportCapacity = UnitAttachment.get(transport.getType()).getTransportCapacity();
// Find any existing units that can be transported
final List<Unit> selectedUnits =
ProTransportUtils.selectUnitsToTransportFromList(transport, new ArrayList<>(potentialUnitsToLoad));
if (!selectedUnits.isEmpty()) {
potentialUnitsToLoad.removeAll(selectedUnits);
transportCapacity -= ProTransportUtils.findUnitsTransportCost(selectedUnits);
}
// Purchase units until transport is full
while (transportCapacity > 0) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, amphibPurchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, amphibUnitsToPlace, purchaseTerritories);
if (amphibPurchaseOptionsForTerritory.isEmpty()) {
break;
}
// Find amphib purchase option
final Map<ProPurchaseOption, Double> amphibEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : amphibPurchaseOptionsForTerritory) {
if (ppo.getTransportCost() <= transportCapacity) {
amphibEfficiencies.put(ppo, ppo.getAmphibEfficiency(data, ownedLocalAmphibUnits, amphibUnitsToPlace));
}
}
if (amphibEfficiencies.isEmpty()) {
break;
}
// Select amphib purchase option and add units
final ProPurchaseOption ppo = ProPurchaseUtils.randomizePurchaseOption(amphibEfficiencies, "Amphib");
final List<Unit> amphibUnits = ppo.getUnitType().create(ppo.getQuantity(), player, true);
amphibUnitsToPlace.addAll(amphibUnits);
resourceTracker.purchase(ppo);
remainingUnitProduction -= ppo.getQuantity();
transportCapacity -= ppo.getTransportCost();
ProLogger.trace("Selected unit=" + ppo.getUnitType().getName());
}
transportsThatNeedUnits.remove(transport);
} else {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData,
seaTransportPurchaseOptionsForTerritory, resourceTracker, remainingUnitProduction,
transportUnitsToPlace, purchaseTerritories);
if (seaTransportPurchaseOptionsForTerritory.isEmpty()) {
break;
}
// Select purchase option
final Map<ProPurchaseOption, Double> transportEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaTransportPurchaseOptionsForTerritory) {
transportEfficiencies.put(ppo, ppo.getTransportEfficiency(data));
}
final ProPurchaseOption ppo =
ProPurchaseUtils.randomizePurchaseOption(transportEfficiencies, "Sea Transport");
// Add transports
final List<Unit> transports = ppo.getUnitType().create(ppo.getQuantity(), player, true);
transportUnitsToPlace.addAll(transports);
resourceTracker.purchase(ppo);
remainingUnitProduction -= ppo.getQuantity();
transportsThatNeedUnits.addAll(transports);
ProLogger.trace("Selected unit=" + ppo.getUnitType().getName() + ", potentialUnitsToLoad="
+ potentialUnitsToLoad + ", transportsThatNeedUnits=" + transportsThatNeedUnits);
}
}
// Add transport units to sea place territory and amphib units to land place territory
for (final ProPlaceTerritory ppt : purchaseTerritory.getCanPlaceTerritories()) {
if (landTerritory.equals(ppt.getTerritory())) {
ppt.getPlaceUnits().addAll(amphibUnitsToPlace);
} else if (placeTerritory.equals(ppt)) {
ppt.getPlaceUnits().addAll(transportUnitsToPlace);
}
}
ProLogger.trace(t + ", purchaseTerritory=" + landTerritory + ", transportUnitsToPlace=" + transportUnitsToPlace
+ ", amphibUnitsToPlace=" + amphibUnitsToPlace);
}
}
}
private void purchaseUnitsWithRemainingProduction(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final List<ProPurchaseOption> landPurchaseOptions, final List<ProPurchaseOption> airPurchaseOptions) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Purchase units in territories with remaining production with resources: " + resourceTracker);
// Get all safe/unsafe land place territories with remaining production
final List<ProPlaceTerritory> prioritizedLandTerritories = new ArrayList<>();
final List<ProPlaceTerritory> prioritizedCantHoldLandTerritories = new ArrayList<>();
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (!t.isWater() && placeTerritory.isCanHold() && purchaseTerritories.get(t).getRemainingUnitProduction() > 0) {
prioritizedLandTerritories.add(placeTerritory);
} else if (!t.isWater() && purchaseTerritories.get(t).getRemainingUnitProduction() > 0) {
prioritizedCantHoldLandTerritories.add(placeTerritory);
}
}
}
// Sort territories by value
Collections.sort(prioritizedLandTerritories, (t1, t2) -> {
final double value1 = t1.getStrategicValue();
final double value2 = t2.getStrategicValue();
return Double.compare(value2, value1);
});
ProLogger.debug("Sorted land territories with remaining production: " + prioritizedLandTerritories);
// Loop through territories and purchase long range attack units
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking territory: " + t);
// Determine units that can be produced in this territory
final List<ProPurchaseOption> airAndLandPurchaseOptions = new ArrayList<>(airPurchaseOptions);
airAndLandPurchaseOptions.addAll(landPurchaseOptions);
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, airAndLandPurchaseOptions, t);
// Purchase long range attack units for any remaining production
int remainingUnitProduction = purchaseTerritories.get(t).getRemainingUnitProduction();
while (true) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, new ArrayList<>(), purchaseTerritories);
if (purchaseOptionsForTerritory.isEmpty()) {
break;
}
// Determine best long range attack option (prefer air units)
ProPurchaseOption bestAttackOption = null;
double maxAttackEfficiency = 0;
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
double attackEfficiency = ppo.getAttackEfficiency() * ppo.getMovement() / ppo.getQuantity();
if (ppo.isAir()) {
attackEfficiency *= 10;
}
if (attackEfficiency > maxAttackEfficiency) {
bestAttackOption = ppo;
maxAttackEfficiency = attackEfficiency;
}
}
if (bestAttackOption == null) {
break;
}
// Purchase unit
resourceTracker.purchase(bestAttackOption);
remainingUnitProduction -= bestAttackOption.getQuantity();
final List<Unit> newUnit = bestAttackOption.getUnitType().create(bestAttackOption.getQuantity(), player, true);
placeTerritory.getPlaceUnits().addAll(newUnit);
ProLogger.trace(t + ", addedUnit=" + newUnit);
}
}
// Sort territories by value
Collections.sort(prioritizedCantHoldLandTerritories, (t1, t2) -> {
final double value1 = t1.getDefenseValue();
final double value2 = t2.getDefenseValue();
return Double.compare(value2, value1);
});
ProLogger
.debug("Sorted can't hold land territories with remaining production: " + prioritizedCantHoldLandTerritories);
// Loop through territories and purchase defense units
for (final ProPlaceTerritory placeTerritory : prioritizedCantHoldLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking territory: " + t);
// Find local owned units
final List<Unit> ownedLocalUnits = t.getUnits().getMatches(Matches.unitIsOwnedBy(player));
// Determine units that can be produced in this territory
final List<ProPurchaseOption> airAndLandPurchaseOptions = new ArrayList<>(airPurchaseOptions);
airAndLandPurchaseOptions.addAll(landPurchaseOptions);
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, airAndLandPurchaseOptions, t);
// Purchase defense units for any remaining production
int remainingUnitProduction = purchaseTerritories.get(t).getRemainingUnitProduction();
while (true) {
// Remove options that cost too much PUs or production
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, remainingUnitProduction, new ArrayList<>(), purchaseTerritories);
if (purchaseOptionsForTerritory.isEmpty()) {
break;
}
// Select purchase option
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
defenseEfficiencies.put(ppo, Math.pow(ppo.getCost(), 2)
* ppo.getDefenseEfficiency2(1, data, ownedLocalUnits, placeTerritory.getPlaceUnits()));
}
final ProPurchaseOption selectedOption =
ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Defense");
// Purchase unit
resourceTracker.purchase(selectedOption);
remainingUnitProduction -= selectedOption.getQuantity();
final List<Unit> newUnit = selectedOption.getUnitType().create(selectedOption.getQuantity(), player, true);
placeTerritory.getPlaceUnits().addAll(newUnit);
ProLogger.trace(t + ", addedUnit=" + newUnit);
}
}
}
private void upgradeUnitsWithRemainingPUs(final Map<Territory, ProPurchaseTerritory> purchaseTerritories,
final ProPurchaseOptionMap purchaseOptions) {
if (resourceTracker.isEmpty()) {
return;
}
ProLogger.info("Upgrade units with resources: " + resourceTracker);
// Get all safe land place territories
final List<ProPlaceTerritory> prioritizedLandTerritories = new ArrayList<>();
for (final ProPurchaseTerritory ppt : purchaseTerritories.values()) {
for (final ProPlaceTerritory placeTerritory : ppt.getCanPlaceTerritories()) {
final Territory t = placeTerritory.getTerritory();
if (!t.isWater() && placeTerritory.isCanHold()) {
prioritizedLandTerritories.add(placeTerritory);
}
}
}
// Sort territories by ascending value (try upgrading units in far away territories first)
Collections.sort(prioritizedLandTerritories, (t1, t2) -> {
final double value1 = t1.getStrategicValue();
final double value2 = t2.getStrategicValue();
return Double.compare(value1, value2);
});
ProLogger.debug("Sorted land territories: " + prioritizedLandTerritories);
// Loop through territories and upgrade units to long range attack units
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking territory: " + t);
// Determine units that can be produced in this territory
final List<ProPurchaseOption> airAndLandPurchaseOptions = new ArrayList<>(purchaseOptions.getAirOptions());
airAndLandPurchaseOptions.addAll(purchaseOptions.getLandOptions());
final List<ProPurchaseOption> purchaseOptionsForTerritory =
ProPurchaseUtils.findPurchaseOptionsForTerritory(player, airAndLandPurchaseOptions, t);
// Purchase long range attack units for any remaining production
int remainingUpgradeUnits = purchaseTerritories.get(t).getUnitProduction() / 3;
while (true) {
if (remainingUpgradeUnits <= 0) {
break;
}
// Find cheapest placed purchase option
ProPurchaseOption minPurchaseOption = null;
for (final Unit u : placeTerritory.getPlaceUnits()) {
for (final ProPurchaseOption ppo : airAndLandPurchaseOptions) {
if (u.getType().equals(ppo.getUnitType())
&& (minPurchaseOption == null || ppo.getCost() < minPurchaseOption.getCost())) {
minPurchaseOption = ppo;
}
}
}
if (minPurchaseOption == null) {
break;
}
// Remove options that cost too much PUs or production
resourceTracker.removeTempPurchase(minPurchaseOption);
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, purchaseOptionsForTerritory,
resourceTracker, 1, new ArrayList<>(), purchaseTerritories);
resourceTracker.clearTempPurchases();
if (purchaseOptionsForTerritory.isEmpty()) {
break;
}
// Determine best long range attack option (prefer air units)
ProPurchaseOption bestAttackOption = null;
double maxAttackEfficiency = minPurchaseOption.getAttackEfficiency() * minPurchaseOption.getMovement()
* minPurchaseOption.getCost() / minPurchaseOption.getQuantity();
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
if (ppo.getCost() > minPurchaseOption.getCost() && (ppo.isAir() || placeTerritory.getStrategicValue() >= 0.25
|| ppo.getTransportCost() <= minPurchaseOption.getTransportCost())) {
double attackEfficiency = ppo.getAttackEfficiency() * ppo.getMovement() * ppo.getCost() / ppo.getQuantity();
if (ppo.isAir()) {
attackEfficiency *= 10;
}
if (ppo.getCarrierCost() > 0) {
final int unusedLocalCarrierCapacity =
ProTransportUtils.getUnusedLocalCarrierCapacity(player, t, placeTerritory.getPlaceUnits());
final int neededFighters = unusedLocalCarrierCapacity / ppo.getCarrierCost();
attackEfficiency *= (1 + neededFighters);
}
if (attackEfficiency > maxAttackEfficiency) {
bestAttackOption = ppo;
maxAttackEfficiency = attackEfficiency;
}
}
}
if (bestAttackOption == null) {
airAndLandPurchaseOptions.remove(minPurchaseOption);
continue;
}
// Find units to remove
final List<Unit> unitsToRemove = new ArrayList<>();
int numUnitsToRemove = minPurchaseOption.getQuantity();
for (final Unit u : placeTerritory.getPlaceUnits()) {
if (numUnitsToRemove <= 0) {
break;
}
if (u.getType().equals(minPurchaseOption.getUnitType())) {
unitsToRemove.add(u);
numUnitsToRemove--;
}
}
if (numUnitsToRemove > 0) {
airAndLandPurchaseOptions.remove(minPurchaseOption);
continue;
}
// Replace units
resourceTracker.removePurchase(minPurchaseOption);
remainingUpgradeUnits -= minPurchaseOption.getQuantity();
placeTerritory.getPlaceUnits().removeAll(unitsToRemove);
ProLogger.trace(t + ", removedUnits=" + unitsToRemove);
for (int i = 0; i < unitsToRemove.size(); i++) {
if (resourceTracker.hasEnough(bestAttackOption)) {
resourceTracker.purchase(bestAttackOption);
final List<Unit> newUnit =
bestAttackOption.getUnitType().create(bestAttackOption.getQuantity(), player, true);
placeTerritory.getPlaceUnits().addAll(newUnit);
ProLogger.trace(t + ", addedUnit=" + newUnit);
}
}
}
}
}
private IntegerMap<ProductionRule> populateProductionRuleMap(
final Map<Territory, ProPurchaseTerritory> purchaseTerritories, final ProPurchaseOptionMap purchaseOptions) {
ProLogger.info("Populate production rule map");
final List<Unit> unplacedUnits = player.getUnits().getMatches(Matches.UnitIsNotSea);
final IntegerMap<ProductionRule> purchaseMap = new IntegerMap<>();
for (final ProPurchaseOption ppo : purchaseOptions.getAllOptions()) {
int numUnits = 0;
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
for (final Unit u : ppt.getPlaceUnits()) {
if (u.getUnitType().equals(ppo.getUnitType()) && !unplacedUnits.contains(u)) {
numUnits++;
}
}
}
}
if (numUnits > 0) {
final int numProductionRule = numUnits / ppo.getQuantity();
purchaseMap.put(ppo.getProductionRule(), numProductionRule);
ProLogger.info(numProductionRule + " " + ppo.getProductionRule());
}
}
return purchaseMap;
}
private void placeDefenders(final Map<Territory, ProPurchaseTerritory> placeNonConstructionTerritories,
final List<ProPlaceTerritory> needToDefendTerritories, final IAbstractPlaceDelegate placeDelegate) {
ProLogger.info("Place defenders with units=" + player.getUnits().getUnits());
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Loop through prioritized territories and purchase defenders
for (final ProPlaceTerritory placeTerritory : needToDefendTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Placing defenders for " + t.getName() + ", enemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxUnits() + ", amphibEnemyAttackers="
+ enemyAttackOptions.getMax(t).getMaxAmphibUnits() + ", defenders=" + placeTerritory.getDefendingUnits());
// Check if any units can be placed
final PlaceableUnits placeableUnits =
placeDelegate.getPlaceableUnits(player.getUnits().getMatches(Matches.UnitIsNotConstruction), t);
if (placeableUnits.isError()) {
ProLogger.trace(t + " can't place units with error: " + placeableUnits.getErrorMessage());
continue;
}
// Find remaining unit production
int remainingUnitProduction = placeableUnits.getMaxUnits();
if (remainingUnitProduction == -1) {
remainingUnitProduction = Integer.MAX_VALUE;
}
ProLogger.trace(t + ", remainingUnitProduction=" + remainingUnitProduction);
// Place defenders and check battle results
final List<Unit> unitsThatCanBePlaced = new ArrayList<>(placeableUnits.getUnits());
final int landPlaceCount = Math.min(remainingUnitProduction, unitsThatCanBePlaced.size());
final List<Unit> unitsToPlace = new ArrayList<>();
ProBattleResult finalResult = new ProBattleResult();
for (int i = 0; i < landPlaceCount; i++) {
// Add defender
unitsToPlace.add(unitsThatCanBePlaced.get(i));
// Find current battle result
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
final List<Unit> defenders = new ArrayList<>(placeTerritory.getDefendingUnits());
defenders.addAll(unitsToPlace);
finalResult = calc.calculateBattleResults(player, t, new ArrayList<>(enemyAttackingUnits), defenders,
enemyAttackOptions.getMax(t).getMaxBombardUnits(), false);
// Break if it can be held
if ((!t.equals(ProData.myCapital) && !finalResult.isHasLandUnitRemaining() && finalResult.getTUVSwing() <= 0)
|| (t.equals(ProData.myCapital) && finalResult.getWinPercentage() < (100 - ProData.winPercentage)
&& finalResult.getTUVSwing() <= 0)) {
break;
}
}
// Check to see if its worth trying to defend the territory
if (!finalResult.isHasLandUnitRemaining()
|| finalResult.getTUVSwing() < placeTerritory.getMinBattleResult().getTUVSwing()
|| t.equals(ProData.myCapital)) {
ProLogger.trace(t + ", placedUnits=" + unitsToPlace + ", TUVSwing=" + finalResult.getTUVSwing());
doPlace(t, unitsToPlace, placeDelegate);
} else {
setCantHoldPlaceTerritory(placeTerritory, placeNonConstructionTerritories);
ProLogger.trace(t + ", unable to defend with placedUnits=" + unitsToPlace + ", TUVSwing="
+ finalResult.getTUVSwing() + ", minTUVSwing=" + placeTerritory.getMinBattleResult().getTUVSwing());
}
}
}
private void placeLandUnits(final List<ProPlaceTerritory> prioritizedLandTerritories,
final IAbstractPlaceDelegate placeDelegate, final boolean isConstruction) {
ProLogger.info("Place land with isConstruction=" + isConstruction + ", units=" + player.getUnits().getUnits());
Match<Unit> unitMatch = Matches.UnitIsNotConstruction;
if (isConstruction) {
unitMatch = Matches.UnitIsConstruction;
}
// Loop through prioritized territories and place land units
for (final ProPlaceTerritory placeTerritory : prioritizedLandTerritories) {
final Territory t = placeTerritory.getTerritory();
ProLogger.debug("Checking land place for " + t.getName());
// Check if any units can be placed
final PlaceableUnits placeableUnits = placeDelegate.getPlaceableUnits(player.getUnits().getMatches(unitMatch), t);
if (placeableUnits.isError()) {
ProLogger.trace(t + " can't place units with error: " + placeableUnits.getErrorMessage());
continue;
}
// Find remaining unit production
int remainingUnitProduction = placeableUnits.getMaxUnits();
if (remainingUnitProduction == -1) {
remainingUnitProduction = Integer.MAX_VALUE;
}
ProLogger.trace(t + ", remainingUnitProduction=" + remainingUnitProduction);
// Place as many units as possible
final List<Unit> unitsThatCanBePlaced = new ArrayList<>(placeableUnits.getUnits());
final int landPlaceCount = Math.min(remainingUnitProduction, unitsThatCanBePlaced.size());
final List<Unit> unitsToPlace = unitsThatCanBePlaced.subList(0, landPlaceCount);
ProLogger.trace(t + ", placedUnits=" + unitsToPlace);
doPlace(t, unitsToPlace, placeDelegate);
}
}
private void addUnitsToPlaceTerritory(final ProPlaceTerritory placeTerritory, final List<Unit> unitsToPlace,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
// Add units to place territory
for (final Territory purchaseTerritory : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(purchaseTerritory).getCanPlaceTerritories()) {
// If place territory is equal to the current place territory and has remaining production
if (placeTerritory.equals(ppt) && purchaseTerritories.get(purchaseTerritory).getRemainingUnitProduction() > 0) {
// Place max number of units
final int numUnits =
Math.min(purchaseTerritories.get(purchaseTerritory).getRemainingUnitProduction(), unitsToPlace.size());
final List<Unit> units = unitsToPlace.subList(0, numUnits);
ppt.getPlaceUnits().addAll(units);
units.clear();
}
}
}
}
private void setCantHoldPlaceTerritory(final ProPlaceTerritory placeTerritory,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
// Add units to place territory
for (final Territory purchaseTerritory : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(purchaseTerritory).getCanPlaceTerritories()) {
// If place territory is equal to the current place territory
if (placeTerritory.equals(ppt)) {
ppt.setCanHold(false);
}
}
}
}
private List<ProPurchaseTerritory> getPurchaseTerritories(final ProPlaceTerritory placeTerritory,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
final List<ProPurchaseTerritory> territories = new ArrayList<>();
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(t).getCanPlaceTerritories()) {
if (placeTerritory.equals(ppt)) {
territories.add(purchaseTerritories.get(t));
}
}
}
return territories;
}
private void doPlace(final Territory t, final Collection<Unit> toPlace, final IAbstractPlaceDelegate del) {
for (final Unit unit : toPlace) {
final List<Unit> unitList = new ArrayList<>();
unitList.add(unit);
final String message = del.placeUnits(unitList, t, IAbstractPlaceDelegate.BidMode.NOT_BID);
if (message != null) {
ProLogger.warn(message);
ProLogger.warn("Attempt was at: " + t + " with: " + unit);
}
}
ProUtils.pause();
}
}