use of games.strategy.engine.data.Territory in project triplea by triplea-game.
the class WeakAi method purchase.
@Override
public void purchase(final boolean purchaseForBid, final int pusToSpend, final IPurchaseDelegate purchaseDelegate, final GameData data, final PlayerID player) {
if (purchaseForBid) {
// bid will only buy land units, due to weak ai placement for bid not being able to handle sea units
final Resource pus = data.getResourceList().getResource(Constants.PUS);
int leftToSpend = pusToSpend;
final List<ProductionRule> rules = player.getProductionFrontier().getRules();
final IntegerMap<ProductionRule> purchase = new IntegerMap<>();
int minCost = Integer.MAX_VALUE;
int i = 0;
while ((minCost == Integer.MAX_VALUE || leftToSpend >= minCost) && i < 100000) {
i++;
for (final ProductionRule rule : rules) {
final NamedAttachable resourceOrUnit = rule.getResults().keySet().iterator().next();
if (!(resourceOrUnit instanceof UnitType)) {
continue;
}
final UnitType results = (UnitType) resourceOrUnit;
if (Matches.unitTypeIsSea().test(results) || Matches.unitTypeIsAir().test(results) || Matches.unitTypeIsInfrastructure().test(results) || Matches.unitTypeIsAaForAnything().test(results) || Matches.unitTypeHasMaxBuildRestrictions().test(results) || Matches.unitTypeConsumesUnitsOnCreation().test(results) || Matches.unitTypeIsStatic(player).test(results)) {
continue;
}
final int cost = rule.getCosts().getInt(pus);
if (cost < 1) {
continue;
}
if (minCost == Integer.MAX_VALUE) {
minCost = cost;
}
if (minCost > cost) {
minCost = cost;
}
// give a preference to cheap units
if (Math.random() * cost < 2) {
if (cost <= leftToSpend) {
leftToSpend -= cost;
purchase.add(rule, 1);
}
}
}
}
purchaseDelegate.purchase(purchase);
pause();
return;
}
final boolean isAmphib = isAmphibAttack(player, data);
final Route amphibRoute = getAmphibRoute(player, data);
final int transportCount = countTransports(data, player);
final int landUnitCount = countLandUnits(data, player);
int defUnitsAtAmpibRoute = 0;
if (isAmphib && amphibRoute != null) {
defUnitsAtAmpibRoute = amphibRoute.getEnd().getUnits().getUnitCount();
}
final Resource pus = data.getResourceList().getResource(Constants.PUS);
final int totalPu = player.getResources().getQuantity(pus);
int leftToSpend = totalPu;
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
final List<ProductionRule> rules = player.getProductionFrontier().getRules();
final IntegerMap<ProductionRule> purchase = new IntegerMap<>();
final List<RepairRule> repairRules;
final Predicate<Unit> ourFactories = Matches.unitIsOwnedBy(player).and(Matches.unitCanProduceUnits());
final List<Territory> repairFactories = CollectionUtils.getMatches(Utils.findUnitTerr(data, ourFactories), Matches.isTerritoryOwnedBy(player));
// figure out if anything needs to be repaired
if (player.getRepairFrontier() != null && Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) {
repairRules = player.getRepairFrontier().getRules();
final IntegerMap<RepairRule> repairMap = new IntegerMap<>();
final HashMap<Unit, IntegerMap<RepairRule>> repair = new HashMap<>();
final Map<Unit, Territory> unitsThatCanProduceNeedingRepair = new HashMap<>();
final int minimumUnitPrice = 3;
int diff;
int capProduction = 0;
Unit capUnit = null;
Territory capUnitTerritory = null;
int currentProduction = 0;
// we should sort this
Collections.shuffle(repairFactories);
for (final Territory fixTerr : repairFactories) {
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.unitCanProduceUnitsAndCanBeDamaged()).test(fixTerr)) {
continue;
}
final Unit possibleFactoryNeedingRepair = TripleAUnit.getBiggestProducer(CollectionUtils.getMatches(fixTerr.getUnits().getUnits(), ourFactories), fixTerr, player, data, false);
if (Matches.unitHasTakenSomeBombingUnitDamage().test(possibleFactoryNeedingRepair)) {
unitsThatCanProduceNeedingRepair.put(possibleFactoryNeedingRepair, fixTerr);
}
if (fixTerr == capitol) {
capProduction = TripleAUnit.getHowMuchCanUnitProduce(possibleFactoryNeedingRepair, fixTerr, player, data, true, true);
capUnit = possibleFactoryNeedingRepair;
capUnitTerritory = fixTerr;
}
currentProduction += TripleAUnit.getHowMuchCanUnitProduce(possibleFactoryNeedingRepair, fixTerr, player, data, true, true);
}
repairFactories.remove(capitol);
unitsThatCanProduceNeedingRepair.remove(capUnit);
// assume minimum unit price is 3, and that we are buying only that... if we over repair, oh well, that is better
// than under-repairing
// goal is to be able to produce all our units, and at least half of that production in the capitol
//
// if capitol is super safe, we don't have to do this. and if capitol is under siege, we should repair enough to
// place all our units here
int maxUnits = (totalPu - 1) / minimumUnitPrice;
if ((capProduction <= maxUnits / 2 || repairFactories.isEmpty()) && capUnit != null) {
for (final RepairRule rrule : repairRules) {
if (!capUnit.getType().equals(rrule.getResults().keySet().iterator().next())) {
continue;
}
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.unitCanProduceUnitsAndCanBeDamaged()).test(capitol)) {
continue;
}
final TripleAUnit taUnit = (TripleAUnit) capUnit;
diff = taUnit.getUnitDamage();
final int unitProductionAllowNegative = TripleAUnit.getHowMuchCanUnitProduce(capUnit, capUnitTerritory, player, data, false, true) - diff;
if (!repairFactories.isEmpty()) {
diff = Math.min(diff, (maxUnits / 2 - unitProductionAllowNegative) + 1);
} else {
diff = Math.min(diff, (maxUnits - unitProductionAllowNegative));
}
diff = Math.min(diff, leftToSpend - minimumUnitPrice);
if (diff > 0) {
if (unitProductionAllowNegative >= 0) {
currentProduction += diff;
} else {
currentProduction += diff + unitProductionAllowNegative;
}
repairMap.add(rrule, diff);
repair.put(capUnit, repairMap);
leftToSpend -= diff;
purchaseDelegate.purchaseRepair(repair);
repair.clear();
repairMap.clear();
// ideally we would adjust this after each single PU spent, then re-evaluate
// everything.
maxUnits = (leftToSpend - 1) / minimumUnitPrice;
}
}
}
int i = 0;
while (currentProduction < maxUnits && i < 2) {
for (final RepairRule rrule : repairRules) {
for (final Unit fixUnit : unitsThatCanProduceNeedingRepair.keySet()) {
if (fixUnit == null || !fixUnit.getType().equals(rrule.getResults().keySet().iterator().next())) {
continue;
}
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.unitCanProduceUnitsAndCanBeDamaged()).test(unitsThatCanProduceNeedingRepair.get(fixUnit))) {
continue;
}
// territories
if (currentProduction >= maxUnits) {
continue;
}
final TripleAUnit taUnit = (TripleAUnit) fixUnit;
diff = taUnit.getUnitDamage();
final int unitProductionAllowNegative = TripleAUnit.getHowMuchCanUnitProduce(fixUnit, unitsThatCanProduceNeedingRepair.get(fixUnit), player, data, false, true) - diff;
if (i == 0) {
if (unitProductionAllowNegative < 0) {
diff = Math.min(diff, (maxUnits - currentProduction) - unitProductionAllowNegative);
} else {
diff = Math.min(diff, (maxUnits - currentProduction));
}
}
diff = Math.min(diff, leftToSpend - minimumUnitPrice);
if (diff > 0) {
if (unitProductionAllowNegative >= 0) {
currentProduction += diff;
} else {
currentProduction += diff + unitProductionAllowNegative;
}
repairMap.add(rrule, diff);
repair.put(fixUnit, repairMap);
leftToSpend -= diff;
purchaseDelegate.purchaseRepair(repair);
repair.clear();
repairMap.clear();
// ideally we would adjust this after each single PU spent, then re-evaluate
// everything.
maxUnits = (leftToSpend - 1) / minimumUnitPrice;
}
}
}
repairFactories.add(capitol);
if (capUnit != null) {
unitsThatCanProduceNeedingRepair.put(capUnit, capUnitTerritory);
}
i++;
}
}
int minCost = Integer.MAX_VALUE;
int i = 0;
while ((minCost == Integer.MAX_VALUE || leftToSpend >= minCost) && i < 100000) {
i++;
for (final ProductionRule rule : rules) {
final NamedAttachable resourceOrUnit = rule.getResults().keySet().iterator().next();
if (!(resourceOrUnit instanceof UnitType)) {
continue;
}
final UnitType results = (UnitType) resourceOrUnit;
if (Matches.unitTypeIsAir().test(results) || Matches.unitTypeIsInfrastructure().test(results) || Matches.unitTypeIsAaForAnything().test(results) || Matches.unitTypeHasMaxBuildRestrictions().test(results) || Matches.unitTypeConsumesUnitsOnCreation().test(results) || Matches.unitTypeIsStatic(player).test(results)) {
continue;
}
final int transportCapacity = UnitAttachment.get(results).getTransportCapacity();
// buy transports if we can be amphibious
if (Matches.unitTypeIsSea().test(results)) {
if (!isAmphib || transportCapacity <= 0) {
continue;
}
}
final int cost = rule.getCosts().getInt(pus);
if (cost < 1) {
continue;
}
if (minCost == Integer.MAX_VALUE) {
minCost = cost;
}
if (minCost > cost) {
minCost = cost;
}
// give a preferene to cheap units, and to transports
// but dont go overboard with buying transports
int goodNumberOfTransports = 0;
final boolean isTransport = transportCapacity > 0;
if (amphibRoute != null) {
// 25% transports - can be more if frontier is far away
goodNumberOfTransports = (landUnitCount / 4);
// boost for transport production
if (isTransport && defUnitsAtAmpibRoute > goodNumberOfTransports && landUnitCount > defUnitsAtAmpibRoute && defUnitsAtAmpibRoute > transportCount) {
final int transports = (leftToSpend / cost);
leftToSpend -= cost * transports;
purchase.add(rule, transports);
continue;
}
}
final boolean buyBecauseTransport = (Math.random() < 0.7 && transportCount < goodNumberOfTransports) || Math.random() < 0.10;
final boolean dontBuyBecauseTooManyTransports = transportCount > 2 * goodNumberOfTransports;
if ((!isTransport && Math.random() * cost < 2) || (isTransport && buyBecauseTransport && !dontBuyBecauseTooManyTransports)) {
if (cost <= leftToSpend) {
leftToSpend -= cost;
purchase.add(rule, 1);
}
}
}
}
purchaseDelegate.purchase(purchase);
pause();
}
use of games.strategy.engine.data.Territory in project triplea by triplea-game.
the class ProPurchaseAi method prioritizeTerritoriesToDefend.
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(t, new ArrayList<>(enemyAttackingUnits), placeTerritory.getDefendingUnits(), enemyAttackOptions.getMax(t).getMaxBombardUnits());
placeTerritory.setMinBattleResult(result);
double holdValue = 0;
if (t.isWater()) {
final double unitValue = TuvUtils.getTuv(CollectionUtils.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() && !enemyAttackingUnits.isEmpty() && enemyAttackingUnits.stream().allMatch(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).test(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 = TuvUtils.getTuv(placeTerritory.getDefendingUnits(), ProData.unitValueMap);
if (t.isWater() && placeTerritory.getDefendingUnits().stream().noneMatch(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
needToDefendTerritories.removeIf(ppt -> ppt.getDefenseValue() <= 0);
// Sort territories by value
final List<ProPlaceTerritory> sortedTerritories = new ArrayList<>(needToDefendTerritories);
sortedTerritories.sort(Comparator.comparingDouble(ProPlaceTerritory::getDefenseValue));
for (final ProPlaceTerritory placeTerritory : sortedTerritories) {
ProLogger.debug(placeTerritory.toString() + " defenseValue=" + placeTerritory.getDefenseValue());
}
return sortedTerritories;
}
use of games.strategy.engine.data.Territory in project triplea by triplea-game.
the class ProPurchaseAi method bid.
/**
* Default settings for bidding:
* 1) Limit one bid unit in a territory or sea zone (until set in all territories then 2, etc).
* 2) The nation placing a unit in a territory or sea zone must have started with a unit in said territory or sea zone
* prior to placing the bid.
*/
Map<Territory, ProPurchaseTerritory> bid(final int pus, final IPurchaseDelegate purchaseDelegate, final GameData startOfTurnData) {
// Current data fields
data = ProData.getData();
this.startOfTurnData = startOfTurnData;
player = ProData.getPlayer();
resourceTracker = new ProResourceTracker(pus, data);
territoryManager = new ProTerritoryManager(calc);
isBid = true;
final ProPurchaseOptionMap purchaseOptions = ProData.purchaseOptions;
ProLogger.info("Starting bid phase with resources: " + resourceTracker);
if (!player.getUnits().getUnits().isEmpty()) {
ProLogger.info("Starting bid phase with unplaced units=" + player.getUnits().getUnits());
}
// Find all purchase/place territories
final Map<Territory, ProPurchaseTerritory> purchaseTerritories = ProPurchaseUtils.findBidTerritories(player);
int previousNumUnits = 0;
while (true) {
// Determine max enemy attack units and current allied defenders
territoryManager.populateEnemyAttackOptions(new ArrayList<>(), new ArrayList<>(purchaseTerritories.keySet()));
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 ProPurchaseTerritory t : purchaseTerritories.values()) {
for (final ProPlaceTerritory ppt : 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);
// 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);
// Check if no remaining PUs or no unit built this iteration
final int numUnits = purchaseTerritories.values().stream().map(ProPurchaseTerritory::getCanPlaceTerritories).map(t -> t.get(0)).map(ProPlaceTerritory::getPlaceUnits).mapToInt(List::size).sum();
if (resourceTracker.isEmpty() || numUnits == previousNumUnits) {
break;
}
previousNumUnits = numUnits;
ProPurchaseUtils.incrementUnitProductionForBidTerritories(purchaseTerritories);
}
// 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;
}
use of games.strategy.engine.data.Territory in project triplea by triplea-game.
the class ProPurchaseAi method upgradeUnitsWithRemainingPUs.
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)
prioritizedLandTerritories.sort(Comparator.comparingDouble(ProPlaceTerritory::getStrategicValue));
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, isBid);
// 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);
}
}
}
}
}
use of games.strategy.engine.data.Territory in project triplea by triplea-game.
the class ProPurchaseAi method purchaseSeaAndAmphibUnits.
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<>());
ProLogger.trace(t + ", unusedCarrierCapacity=" + unusedCarrierCapacity + ", unusedLocalCarrierCapacity=" + unusedLocalCarrierCapacity);
// If any enemy attackers then purchase sea defenders until it can be held
boolean needDestroyer = false;
if (enemyAttackOptions.getMax(t) != null) {
// Determine if need destroyer
if (enemyAttackOptions.getMax(t).getMaxUnits().stream().anyMatch(Matches.unitIsSub()) && t.getUnits().getMatches(Matches.unitIsOwnedBy(player)).stream().noneMatch(Matches.unitIsDestroyer())) {
needDestroyer = true;
}
ProLogger.trace(t + ", needDestroyer=" + needDestroyer + ", checking defense since has enemy attackers: " + enemyAttackOptions.getMax(t).getMaxUnits());
final List<Unit> initialDefendingUnits = new ArrayList<>(placeTerritory.getDefendingUnits());
initialDefendingUnits.addAll(ProPurchaseUtils.getPlaceUnits(t, purchaseTerritories));
ProBattleResult result = calc.calculateBattleResults(t, enemyAttackOptions.getMax(t).getMaxUnits(), initialDefendingUnits, enemyAttackOptions.getMax(t).getMaxBombardUnits());
boolean hasOnlyRetreatingSubs = Properties.getSubRetreatBeforeBattle(data) && !initialDefendingUnits.isEmpty() && initialDefendingUnits.stream().allMatch(Matches.unitIsSub()) && enemyAttackOptions.getMax(t).getMaxUnits().stream().noneMatch(Matches.unitIsDestroyer());
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, isBid);
seaPurchaseOptionsForTerritory.addAll(purchaseOptions.getAirOptions());
// Purchase enough sea defenders to hold territory
while (true) {
// If it can be held then break
if (!hasOnlyRetreatingSubs && (result.getTuvSwing() < -1 || result.getWinPercentage() < ProData.winPercentage)) {
break;
}
// Select purchase option
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, seaPurchaseOptionsForTerritory, resourceTracker, remainingUnitProduction, unitsToPlace, purchaseTerritories);
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaPurchaseOptionsForTerritory) {
defenseEfficiencies.put(ppo, ppo.getSeaDefenseEfficiency(data, ownedLocalUnits, unitsToPlace, needDestroyer, unusedCarrierCapacity, unusedLocalCarrierCapacity));
}
final Optional<ProPurchaseOption> optionalSelectedOption = ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Sea Defense");
if (!optionalSelectedOption.isPresent()) {
break;
}
final ProPurchaseOption selectedOption = optionalSelectedOption.get();
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(t, enemyAttackOptions.getMax(t).getMaxUnits(), defendingUnits, enemyAttackOptions.getMax(t).getMaxBombardUnits());
hasOnlyRetreatingSubs = Properties.getSubRetreatBeforeBattle(data) && !defendingUnits.isEmpty() && defendingUnits.stream().allMatch(Matches.unitIsSub()) && enemyAttackOptions.getMax(t).getMaxUnits().stream().noneMatch(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 Set<Territory> nearbyTerritories = data.getMap().getNeighbors(t, enemyDistance, ProMatches.territoryCanMoveAirUnits(player, data, false));
final List<Territory> nearbyLandTerritories = CollectionUtils.getMatches(nearbyTerritories, Matches.territoryIsLand());
final Set<Territory> nearbyEnemySeaTerritories = data.getMap().getNeighbors(t, enemyDistance, Matches.territoryIsWater());
nearbyEnemySeaTerritories.add(t);
final int alliedDistance = (enemyDistance + 1) / 2;
final Set<Territory> nearbyAlliedSeaTerritories = data.getMap().getNeighbors(t, alliedDistance, Matches.territoryIsWater());
nearbyAlliedSeaTerritories.add(t);
final List<Unit> enemyUnitsInLandTerritories = new ArrayList<>();
for (final Territory nearbyLandTerritory : nearbyLandTerritories) {
enemyUnitsInLandTerritories.addAll(nearbyLandTerritory.getUnits().getMatches(ProMatches.unitIsEnemyAir(player, data)));
}
final List<Unit> enemyUnitsInSeaTerritories = new ArrayList<>();
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);
}
}
final List<Unit> myUnitsInSeaTerritories = new ArrayList<>();
for (final Territory nearbySeaTerritory : nearbyAlliedSeaTerritories) {
myUnitsInSeaTerritories.addAll(nearbySeaTerritory.getUnits().getMatches(ProMatches.unitIsOwnedNotLand(player)));
myUnitsInSeaTerritories.addAll(ProPurchaseUtils.getPlaceUnits(nearbySeaTerritory, purchaseTerritories));
}
// Check if destroyer is needed
final int numEnemySubs = CollectionUtils.countMatches(enemyUnitsInSeaTerritories, Matches.unitIsSub());
final int numMyDestroyers = CollectionUtils.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, isBid);
seaPurchaseOptionsForTerritory.addAll(purchaseOptions.getAirOptions());
while (true) {
// If I have naval attack/defense superiority then break
if (ProBattleUtils.territoryHasLocalNavalSuperiority(t, player, purchaseTerritories, unitsToPlace)) {
break;
}
// Select purchase option
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, seaPurchaseOptionsForTerritory, resourceTracker, remainingUnitProduction, unitsToPlace, purchaseTerritories);
final Map<ProPurchaseOption, Double> defenseEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaPurchaseOptionsForTerritory) {
defenseEfficiencies.put(ppo, ppo.getSeaDefenseEfficiency(data, ownedLocalUnits, unitsToPlace, needDestroyer, unusedCarrierCapacity, unusedLocalCarrierCapacity));
}
final Optional<ProPurchaseOption> optionalSelectedOption = ProPurchaseUtils.randomizePurchaseOption(defenseEfficiencies, "Sea Defense");
if (!optionalSelectedOption.isPresent()) {
break;
}
final ProPurchaseOption selectedOption = optionalSelectedOption.get();
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, isBid);
final List<ProPurchaseOption> amphibPurchaseOptionsForTerritory = ProPurchaseUtils.findPurchaseOptionsForTerritory(player, purchaseOptions.getLandOptions(), landTerritory, isBid);
// 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 = CollectionUtils.getMatches(unitsInTerritory, ProMatches.unitIsOwnedTransport(player));
for (final Unit transport : transports) {
transportsThatNeedUnits.add(transport);
final Set<Territory> territoriesToLoadFrom = new HashSet<>(data.getMap().getNeighbors(seaTerritory, distance));
territoriesToLoadFrom.removeIf(potentialTerritory -> potentialTerritory.isWater() || territoryValueMap.get(potentialTerritory) > 0.25);
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(CollectionUtils.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) {
// Select amphib purchase option and add units
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, amphibPurchaseOptionsForTerritory, resourceTracker, remainingUnitProduction, amphibUnitsToPlace, purchaseTerritories);
final Map<ProPurchaseOption, Double> amphibEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : amphibPurchaseOptionsForTerritory) {
if (ppo.getTransportCost() <= transportCapacity) {
amphibEfficiencies.put(ppo, ppo.getAmphibEfficiency(data, ownedLocalAmphibUnits, amphibUnitsToPlace));
}
}
final Optional<ProPurchaseOption> optionalSelectedOption = ProPurchaseUtils.randomizePurchaseOption(amphibEfficiencies, "Amphib");
if (!optionalSelectedOption.isPresent()) {
break;
}
final ProPurchaseOption ppo = optionalSelectedOption.get();
// Add amphib unit
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 {
// Select purchase option
ProPurchaseUtils.removeInvalidPurchaseOptions(player, startOfTurnData, seaTransportPurchaseOptionsForTerritory, resourceTracker, remainingUnitProduction, transportUnitsToPlace, purchaseTerritories);
final Map<ProPurchaseOption, Double> transportEfficiencies = new HashMap<>();
for (final ProPurchaseOption ppo : seaTransportPurchaseOptionsForTerritory) {
transportEfficiencies.put(ppo, ppo.getTransportEfficiencyRatio());
}
final Optional<ProPurchaseOption> optionalSelectedOption = ProPurchaseUtils.randomizePurchaseOption(transportEfficiencies, "Sea Transport");
if (!optionalSelectedOption.isPresent()) {
break;
}
final ProPurchaseOption ppo = optionalSelectedOption.get();
// 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);
}
}
}
Aggregations