package games.strategy.triplea.ai.proAI.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.ProductionFrontier;
import games.strategy.engine.data.ProductionRule;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.triplea.Constants;
import games.strategy.triplea.TripleAUnit;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.ai.proAI.data.ProPlaceTerritory;
import games.strategy.triplea.ai.proAI.data.ProPurchaseOption;
import games.strategy.triplea.ai.proAI.data.ProPurchaseTerritory;
import games.strategy.triplea.ai.proAI.data.ProResourceTracker;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.ai.proAI.simulate.ProDummyDelegateBridge;
import games.strategy.triplea.attachments.RulesAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.delegate.AbstractPlaceDelegate;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.OriginalOwnerTracker;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.util.CompositeMatch;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.Match;
/**
* Pro AI purchase utilities.
*/
public class ProPurchaseUtils {
public static List<ProPurchaseOption> findPurchaseOptionsForTerritory(final PlayerID player,
final List<ProPurchaseOption> purchaseOptions, final Territory t) {
final List<ProPurchaseOption> result = new ArrayList<>();
for (final ProPurchaseOption ppo : purchaseOptions) {
if (canTerritoryUsePurchaseOption(player, ppo, t)) {
result.add(ppo);
}
}
return result;
}
public static boolean canTerritoryUsePurchaseOption(final PlayerID player, final ProPurchaseOption ppo,
final Territory t) {
if (ppo == null) {
return false;
}
final List<Unit> units = ppo.getUnitType().create(ppo.getQuantity(), player, true);
return canUnitsBePlaced(units, player, t);
}
public static boolean canUnitsBePlaced(final List<Unit> units, final PlayerID player, final Territory t) {
final GameData data = ProData.getData();
final AbstractPlaceDelegate placeDelegate = (AbstractPlaceDelegate) data.getDelegateList().getDelegate("place");
final IDelegateBridge bridge = new ProDummyDelegateBridge(ProData.getProAI(), player, data);
placeDelegate.setDelegateBridgeAndPlayer(bridge);
final String s = placeDelegate.canUnitsBePlaced(t, units, player);
return s == null;
}
public static List<ProPurchaseOption> removeInvalidPurchaseOptions(final PlayerID player, final GameData data,
final List<ProPurchaseOption> purchaseOptions, final ProResourceTracker resourceTracker,
final int remainingUnitProduction, final List<Unit> unitsToPlace,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
for (final Iterator<ProPurchaseOption> it = purchaseOptions.iterator(); it.hasNext();) {
final ProPurchaseOption purchaseOption = it.next();
// Check PU cost and production
if (!resourceTracker.hasEnough(purchaseOption) || purchaseOption.getQuantity() > remainingUnitProduction) {
it.remove();
continue;
}
// Check max unit limits (-1 is unlimited)
final int maxBuilt = purchaseOption.getMaxBuiltPerPlayer();
final UnitType type = purchaseOption.getUnitType();
if (maxBuilt == 0) {
it.remove();
} else if (maxBuilt > 0) {
// Find number of unit type that are already built and about to be placed
int currentlyBuilt = 0;
final CompositeMatch<Unit> unitTypeOwnedBy =
new CompositeMatchAnd<>(Matches.unitIsOfType(type), Matches.unitIsOwnedBy(player));
final List<Territory> allTerritories = data.getMap().getTerritories();
for (final Territory t : allTerritories) {
currentlyBuilt += t.getUnits().countMatches(unitTypeOwnedBy);
}
currentlyBuilt += Match.countMatches(unitsToPlace, unitTypeOwnedBy);
for (final Territory t : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory placeTerritory : purchaseTerritories.get(t).getCanPlaceTerritories()) {
currentlyBuilt += Match.countMatches(placeTerritory.getPlaceUnits(), unitTypeOwnedBy);
}
}
final int allowedBuild = maxBuilt - currentlyBuilt;
if (allowedBuild - purchaseOption.getQuantity() < 0) {
it.remove();
}
}
}
return purchaseOptions;
}
public static ProPurchaseOption randomizePurchaseOption(final Map<ProPurchaseOption, Double> purchaseEfficiencies,
final String type) {
ProLogger.trace("Select purchase option for " + type);
double totalEfficiency = 0;
for (final Double efficiency : purchaseEfficiencies.values()) {
totalEfficiency += efficiency;
}
final Map<ProPurchaseOption, Double> purchasePercentages = new LinkedHashMap<>();
double upperBound = 0.0;
for (final ProPurchaseOption ppo : purchaseEfficiencies.keySet()) {
final double chance = purchaseEfficiencies.get(ppo) / totalEfficiency * 100;
upperBound += chance;
purchasePercentages.put(ppo, upperBound);
ProLogger.trace(ppo.getUnitType().getName() + ", probability=" + chance + ", upperBound=" + upperBound);
}
final double randomNumber = Math.random() * 100;
ProLogger.trace("Random number: " + randomNumber);
for (final ProPurchaseOption ppo : purchasePercentages.keySet()) {
if (randomNumber <= purchasePercentages.get(ppo)) {
return ppo;
}
}
return purchasePercentages.keySet().iterator().next();
}
public static List<Unit> findMaxPurchaseDefenders(final PlayerID player, final Territory t,
final List<ProPurchaseOption> landPurchaseOptions) {
ProLogger.info("Find max purchase defenders for " + t.getName());
final GameData data = ProData.getData();
// Determine most cost efficient defender that can be produced in this territory
final Resource PUs = data.getResourceList().getResource(Constants.PUS);
final int PUsRemaining = player.getResources().getQuantity(PUs);
final List<ProPurchaseOption> purchaseOptionsForTerritory =
findPurchaseOptionsForTerritory(player, landPurchaseOptions, t);
ProPurchaseOption bestDefenseOption = null;
double maxDefenseEfficiency = 0;
for (final ProPurchaseOption ppo : purchaseOptionsForTerritory) {
if (ppo.getDefenseEfficiency() > maxDefenseEfficiency && ppo.getCost() <= PUsRemaining) {
bestDefenseOption = ppo;
maxDefenseEfficiency = ppo.getDefenseEfficiency();
}
}
// Determine number of defenders I can purchase
final List<Unit> placeUnits = new ArrayList<>();
if (bestDefenseOption != null) {
ProLogger.debug("Best defense option: " + bestDefenseOption.getUnitType().getName());
int remainingUnitProduction = getUnitProduction(t, data, player);
int PUsSpent = 0;
while (true) {
// If out of PUs or production then break
if (bestDefenseOption.getCost() > (PUsRemaining - PUsSpent)
|| remainingUnitProduction < bestDefenseOption.getQuantity()) {
break;
}
// Create new temp defenders
PUsSpent += bestDefenseOption.getCost();
remainingUnitProduction -= bestDefenseOption.getQuantity();
placeUnits.addAll(bestDefenseOption.getUnitType().create(bestDefenseOption.getQuantity(), player, true));
}
ProLogger.debug("Potential purchased defenders: " + placeUnits);
}
return placeUnits;
}
public static Map<Territory, ProPurchaseTerritory> findPurchaseTerritories(final PlayerID player) {
ProLogger.info("Find all purchase territories");
final GameData data = ProData.getData();
// Find all territories that I can place units on
final RulesAttachment ra = player.getRulesAttachment();
List<Territory> ownedAndNotConqueredFactoryTerritories = new ArrayList<>();
if (ra != null && ra.getPlacementAnyTerritory()) {
ownedAndNotConqueredFactoryTerritories = data.getMap().getTerritoriesOwnedBy(player);
} else {
ownedAndNotConqueredFactoryTerritories = Match.getMatches(data.getMap().getTerritories(),
ProMatches.territoryHasInfraFactoryAndIsNotConqueredOwnedLand(player, data));
}
ownedAndNotConqueredFactoryTerritories = Match.getMatches(ownedAndNotConqueredFactoryTerritories,
ProMatches.territoryCanMoveLandUnits(player, data, false));
// Create purchase territory holder for each factory territory
final Map<Territory, ProPurchaseTerritory> purchaseTerritories = new HashMap<>();
for (final Territory t : ownedAndNotConqueredFactoryTerritories) {
final int unitProduction = getUnitProduction(t, data, player);
final ProPurchaseTerritory ppt = new ProPurchaseTerritory(t, data, player, unitProduction);
purchaseTerritories.put(t, ppt);
ProLogger.debug(ppt.toString());
}
return purchaseTerritories;
}
public static int getUnitProduction(final Territory territory, final GameData data, final PlayerID player) {
final CompositeMatchAnd<Unit> factoryMatch = new CompositeMatchAnd<>(
Matches.UnitIsOwnedAndIsFactoryOrCanProduceUnits(player), Matches.unitIsBeingTransported().invert());
if (territory.isWater()) {
factoryMatch.add(Matches.UnitIsLand.invert());
} else {
factoryMatch.add(Matches.UnitIsSea.invert());
}
final Collection<Unit> factoryUnits = territory.getUnits().getMatches(factoryMatch);
final TerritoryAttachment ta = TerritoryAttachment.get(territory);
final boolean originalFactory = (ta != null && ta.getOriginalFactory());
final boolean playerIsOriginalOwner =
factoryUnits.size() > 0 && player.equals(getOriginalFactoryOwner(territory, player));
final RulesAttachment ra = player.getRulesAttachment();
if (originalFactory && playerIsOriginalOwner) {
if (ra != null && ra.getMaxPlacePerTerritory() != -1) {
return Math.max(0, ra.getMaxPlacePerTerritory());
}
return Integer.MAX_VALUE;
}
if (ra != null && ra.getPlacementAnyTerritory()) {
return Integer.MAX_VALUE;
}
return TripleAUnit.getProductionPotentialOfTerritory(territory.getUnits().getUnits(), territory, player, data, true,
true);
}
private static PlayerID getOriginalFactoryOwner(final Territory territory, final PlayerID player) {
final Collection<Unit> factoryUnits = territory.getUnits().getMatches(Matches.UnitCanProduceUnits);
if (factoryUnits.size() == 0) {
throw new IllegalStateException("No factory in territory:" + territory);
}
final Iterator<Unit> iter = factoryUnits.iterator();
while (iter.hasNext()) {
final Unit factory2 = iter.next();
if (player.equals(OriginalOwnerTracker.getOriginalOwner(factory2))) {
return OriginalOwnerTracker.getOriginalOwner(factory2);
}
}
final Unit factory = factoryUnits.iterator().next();
return OriginalOwnerTracker.getOriginalOwner(factory);
}
/**
* Comparator that sorts cheaper units before expensive ones.
*/
public static Comparator<Unit> getCostComparator() {
return (o1, o2) -> Double.compare(getCost(o1), getCost(o2));
}
/**
* How many PU's does it cost the given player to produce the given unit including any dependents.
*/
public static double getCost(final Unit unit) {
final Resource PUs = unit.getData().getResourceList().getResource(Constants.PUS);
final Collection<Unit> units = TransportTracker.transportingAndUnloaded(unit);
units.add(unit);
double cost = 0.0;
for (final Unit u : units) {
final ProductionRule rule = getProductionRule(u.getType(), u.getOwner());
if (rule == null) {
cost += ProData.unitValueMap.getInt(u.getType());
} else {
cost += ((double) rule.getCosts().getInt(PUs)) / rule.getResults().totalValues();
}
}
return cost;
}
/**
* Get the production rule for the given player, for the given unit type.
*
* <p>
* If no such rule can be found, then return null.
* </p>
*/
private static ProductionRule getProductionRule(final UnitType unitType, final PlayerID player) {
final ProductionFrontier frontier = player.getProductionFrontier();
if (frontier == null) {
return null;
}
for (final ProductionRule rule : frontier) {
if (rule.getResults().getInt(unitType) > 0) {
return rule;
}
}
return null;
}
public static List<Unit> getPlaceUnits(final Territory t,
final Map<Territory, ProPurchaseTerritory> purchaseTerritories) {
final List<Unit> placeUnits = new ArrayList<>();
if (purchaseTerritories == null) {
return placeUnits;
}
for (final Territory purchaseTerritory : purchaseTerritories.keySet()) {
for (final ProPlaceTerritory ppt : purchaseTerritories.get(purchaseTerritory).getCanPlaceTerritories()) {
if (t.equals(ppt.getTerritory())) {
placeUnits.addAll(ppt.getPlaceUnits());
}
}
}
return placeUnits;
}
}