package games.strategy.triplea.ai.proAI.data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
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.Resource;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.triplea.Constants;
import games.strategy.triplea.Properties;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.attachments.UnitSupportAttachment;
import games.strategy.triplea.delegate.DiceRoll;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TechTracker;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
public class ProPurchaseOption {
private final ProductionRule productionRule;
private final UnitType unitType;
private final PlayerID player;
private final int cost;
private final IntegerMap<Resource> costs;
private final int movement;
private final int quantity;
private int hitPoints;
private final double attack;
private final double amphibAttack;
private final double defense;
private final int transportCost;
private final int carrierCost;
private final boolean isAir;
private final boolean isSub;
private final boolean isDestroyer;
private final boolean isTransport;
private final boolean isCarrier;
private final int transportCapacity;
private final int carrierCapacity;
private final double transportEfficiency;
private final double carrierEfficiency;
private double costPerHitPoint;
private final double hitPointEfficiency;
private final double attackEfficiency;
private final double defenseEfficiency;
private final int maxBuiltPerPlayer;
private final Set<UnitSupportAttachment> unitSupportAttachments;
private boolean isAttackSupport;
private boolean isDefenseSupport;
public ProPurchaseOption(final ProductionRule productionRule, final UnitType unitType, final PlayerID player,
final GameData data) {
this.productionRule = productionRule;
this.unitType = unitType;
this.player = player;
final UnitAttachment unitAttachment = UnitAttachment.get(unitType);
final Resource PUs = data.getResourceList().getResource(Constants.PUS);
cost = productionRule.getCosts().getInt(PUs);
costs = productionRule.getCosts();
movement = unitAttachment.getMovement(player);
quantity = productionRule.getResults().totalValues();
final boolean isInfra = unitAttachment.getIsInfrastructure();
hitPoints = unitAttachment.getHitPoints() * quantity;
if (isInfra) {
hitPoints = 0;
}
attack = unitAttachment.getAttack(player) * quantity;
amphibAttack = attack + 0.5 * unitAttachment.getIsMarine() * quantity;
defense = unitAttachment.getDefense(player) * quantity;
transportCost = unitAttachment.getTransportCost() * quantity;
carrierCost = unitAttachment.getCarrierCost() * quantity;
isAir = unitAttachment.getIsAir();
isSub = unitAttachment.getIsSub();
isDestroyer = unitAttachment.getIsDestroyer();
isTransport = unitAttachment.getTransportCapacity() > 0;
isCarrier = unitAttachment.getCarrierCapacity() > 0;
transportCapacity = unitAttachment.getTransportCapacity() * quantity;
carrierCapacity = unitAttachment.getCarrierCapacity() * quantity;
transportEfficiency = (double) unitAttachment.getTransportCapacity() / cost;
carrierEfficiency = (double) unitAttachment.getCarrierCapacity() / cost;
if (hitPoints == 0) {
costPerHitPoint = Double.POSITIVE_INFINITY;
} else {
costPerHitPoint = ((double) cost) / hitPoints;
}
hitPointEfficiency =
(hitPoints + 0.1 * attack * 6 / data.getDiceSides() + 0.2 * defense * 6 / data.getDiceSides()) / cost;
attackEfficiency = (1 + hitPoints)
* (hitPoints + attack * 6 / data.getDiceSides() + 0.5 * defense * 6 / data.getDiceSides()) / cost;
defenseEfficiency = (1 + hitPoints)
* (hitPoints + 0.5 * attack * 6 / data.getDiceSides() + defense * 6 / data.getDiceSides()) / cost;
maxBuiltPerPlayer = unitAttachment.getMaxBuiltPerPlayer();
// Support fields
unitSupportAttachments = UnitSupportAttachment.get(unitType);
isAttackSupport = false;
isDefenseSupport = false;
for (final UnitSupportAttachment usa : unitSupportAttachments) {
if (usa.getOffence()) {
isAttackSupport = true;
}
if (usa.getDefence()) {
isDefenseSupport = true;
}
}
}
@Override
public String toString() {
return productionRule + " | cost=" + cost + " | moves=" + movement + " | quantity=" + quantity
+ " | hitPointEfficiency=" + hitPointEfficiency + " | attackEfficiency=" + attackEfficiency
+ " | defenseEfficiency=" + defenseEfficiency + " | isSub=" + isSub + " | isTransport=" + isTransport
+ " | isCarrier=" + isCarrier;
}
public ProductionRule getProductionRule() {
return productionRule;
}
public int getCost() {
return cost;
}
public IntegerMap<Resource> getCosts() {
return costs;
}
public int getMovement() {
return movement;
}
public int getQuantity() {
return quantity;
}
public int getHitPoints() {
return hitPoints;
}
public double getAttack() {
return attack;
}
public double getDefense() {
return defense;
}
public boolean isSub() {
return isSub;
}
public boolean isDestroyer() {
return isDestroyer;
}
public boolean isTransport() {
return isTransport;
}
public boolean isCarrier() {
return isCarrier;
}
public double getTransportEfficiency() {
return transportEfficiency;
}
public double getCarrierEfficiency() {
return carrierEfficiency;
}
public double getHitPointEfficiency() {
return hitPointEfficiency;
}
public double getAttackEfficiency() {
return attackEfficiency;
}
public double getDefenseEfficiency() {
return defenseEfficiency;
}
public UnitType getUnitType() {
return unitType;
}
public int getTransportCapacity() {
return transportCapacity;
}
public int getCarrierCapacity() {
return carrierCapacity;
}
public int getTransportCost() {
return transportCost;
}
public int getCarrierCost() {
return carrierCost;
}
public boolean isAir() {
return isAir;
}
public void setCostPerHitPoint(final double costPerHitPoint) {
this.costPerHitPoint = costPerHitPoint;
}
public double getCostPerHitPoint() {
return costPerHitPoint;
}
public int getMaxBuiltPerPlayer() {
return maxBuiltPerPlayer;
}
public boolean isAttackSupport() {
return isAttackSupport;
}
public boolean isDefenseSupport() {
return isDefenseSupport;
}
public double getFodderEfficiency(final int enemyDistance, final GameData data, final List<Unit> ownedLocalUnits,
final List<Unit> unitsToPlace) {
final double supportAttackFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, false);
final double supportDefenseFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, true);
final double distanceFactor = Math.sqrt(calculateLandDistanceFactor(enemyDistance));
return calculateEfficiency(0.25, 0.25, supportAttackFactor, supportDefenseFactor, distanceFactor, data);
}
public double getAttackEfficiency2(final int enemyDistance, final GameData data, final List<Unit> ownedLocalUnits,
final List<Unit> unitsToPlace) {
final double supportAttackFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, false);
final double supportDefenseFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, true);
final double distanceFactor = calculateLandDistanceFactor(enemyDistance);
return calculateEfficiency(1.25, 0.75, supportAttackFactor, supportDefenseFactor, distanceFactor, data);
}
public double getDefenseEfficiency2(final int enemyDistance, final GameData data, final List<Unit> ownedLocalUnits,
final List<Unit> unitsToPlace) {
final double supportAttackFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, false);
final double supportDefenseFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, true);
final double distanceFactor = calculateLandDistanceFactor(enemyDistance);
return calculateEfficiency(0.75, 1.25, supportAttackFactor, supportDefenseFactor, distanceFactor, data);
}
public double getSeaDefenseEfficiency(final GameData data, final List<Unit> ownedLocalUnits,
final List<Unit> unitsToPlace, final boolean needDestroyer, final int unusedCarrierCapacity,
final int unusedLocalCarrierCapacity) {
if (isAir && (carrierCost <= 0 || carrierCost > unusedCarrierCapacity
|| !Properties.getProduceFightersOnCarriers(data))) {
return 0;
}
final double supportAttackFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, false);
final double supportDefenseFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, true);
double seaFactor = 1;
if (needDestroyer && isDestroyer) {
seaFactor = 8;
}
if (isAir) {
seaFactor = 4;
} else if (carrierCapacity > 0 && unusedLocalCarrierCapacity <= 0) {
seaFactor = 4;
}
return calculateEfficiency(0.75, 1, supportAttackFactor, supportDefenseFactor, movement, seaFactor, data);
}
public double getAmphibEfficiency(final GameData data, final List<Unit> ownedLocalUnits,
final List<Unit> unitsToPlace) {
final double supportAttackFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, false);
final double supportDefenseFactor = calculateSupportFactor(ownedLocalUnits, unitsToPlace, data, true);
final double hitPointPerUnitFactor = (3 + hitPoints / quantity);
final double transportCostFactor = Math.pow(1.0 / transportCost, .2);
final double hitPointValue = 2 * hitPoints;
final double attackValue = (amphibAttack + supportAttackFactor * quantity) * 6 / data.getDiceSides();
final double defenseValue = (defense + supportDefenseFactor * quantity) * 6 / data.getDiceSides();
return Math.pow((hitPointValue + attackValue + defenseValue) * hitPointPerUnitFactor * transportCostFactor / cost,
30) / quantity;
}
public double getTransportEfficiency(final GameData data) {
return Math.pow(transportEfficiency, 30) / quantity;
}
private double calculateLandDistanceFactor(final int enemyDistance) {
final double distance = Math.max(0, enemyDistance - 1.5);
// 1, 2, 2.5, 2.75, etc
final double moveFactor = 1 + 2 * (Math.pow(2, movement - 1) - 1) / Math.pow(2, movement - 1);
final double distanceFactor = Math.pow(moveFactor, distance / 5);
return distanceFactor;
}
// TODO: doesn't consider enemy support
private double calculateSupportFactor(final List<Unit> ownedLocalUnits, final List<Unit> unitsToPlace,
final GameData data, final boolean defense) {
if ((!isAttackSupport && !defense) || (!isDefenseSupport && defense)) {
return 0;
}
final List<Unit> units = new ArrayList<>(ownedLocalUnits);
units.addAll(unitsToPlace);
units.addAll(unitType.create(1, player, true));
final Set<List<UnitSupportAttachment>> supportsAvailable = new HashSet<>();
final IntegerMap<UnitSupportAttachment> supportLeft = new IntegerMap<>();
DiceRoll.getSupport(units, supportsAvailable, supportLeft, new HashMap<>(), data, defense, true);
double totalSupportFactor = 0;
for (final UnitSupportAttachment usa : unitSupportAttachments) {
for (final List<UnitSupportAttachment> bonusType : supportsAvailable) {
if (!bonusType.contains(usa)) {
continue;
}
// Find number of support provided and supportable units
int numAddedSupport = usa.getNumber();
if (usa.getImpArtTech() && TechTracker.hasImprovedArtillerySupport(player)) {
numAddedSupport *= 2;
}
int numSupportProvided = -numAddedSupport;
final Set<Unit> supportableUnits = new HashSet<>();
for (final UnitSupportAttachment usa2 : bonusType) {
numSupportProvided += supportLeft.getInt(usa2);
supportableUnits.addAll(Match.getMatches(units, Matches.unitIsOfTypes(usa2.getUnitType())));
}
final int numSupportableUnits = supportableUnits.size();
// Find ratio of supportable to support units (optimal 2 to 1)
final int numExtraSupportableUnits = Math.max(0, numSupportableUnits - numSupportProvided);
// Ranges from 0 to 1
final double ratio = Math.min(1, 2.0 * numExtraSupportableUnits / (numSupportableUnits + numAddedSupport));
// Find approximate strength bonus provided
double bonus = 0;
if (usa.getStrength()) {
bonus += usa.getBonus();
}
if (usa.getRoll()) {
bonus += (usa.getBonus() * data.getDiceSides() * 0.75);
}
// Find support factor value
final double supportFactor = Math.pow(numAddedSupport * 0.9, 0.9) * bonus * ratio;
totalSupportFactor += supportFactor;
ProLogger.trace(unitType.getName() + ", bonusType=" + usa.getBonusType() + ", supportFactor=" + supportFactor
+ ", numSupportProvided=" + numSupportProvided + ", numSupportableUnits=" + numSupportableUnits
+ ", numAddedSupport=" + numAddedSupport + ", ratio=" + ratio + ", bonus=" + bonus);
}
}
ProLogger.debug(unitType.getName() + ", defense=" + defense + ", totalSupportFactor=" + totalSupportFactor);
return totalSupportFactor;
}
private double calculateEfficiency(final double attackFactor, final double defenseFactor,
final double supportAttackFactor, final double supportDefenseFactor, final double distanceFactor,
final GameData data) {
return calculateEfficiency(attackFactor, defenseFactor, supportAttackFactor, supportDefenseFactor, distanceFactor,
1, data);
}
private double calculateEfficiency(final double attackFactor, final double defenseFactor,
final double supportAttackFactor, final double supportDefenseFactor, final double distanceFactor,
final double seaFactor, final GameData data) {
final double hitPointPerUnitFactor = (3 + hitPoints / quantity);
final double hitPointValue = 2 * hitPoints;
final double attackValue = attackFactor * (attack + supportAttackFactor * quantity) * 6 / data.getDiceSides();
final double defenseValue = defenseFactor * (defense + supportDefenseFactor * quantity) * 6 / data.getDiceSides();
return Math.pow(
(hitPointValue + attackValue + defenseValue) * hitPointPerUnitFactor * distanceFactor * seaFactor / cost, 30)
/ quantity;
}
}