package games.strategy.triplea.ai.proAI.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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.Route;
import games.strategy.engine.data.Territory;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.MoveValidator;
import games.strategy.util.Match;
/**
* Pro AI battle utilities.
*/
public class ProTerritoryValueUtils {
private static int MIN_FACTORY_CHECK_DISTANCE = 9;
private static int MAX_FACTORY_CHECK_DISTANCE = 30;
public static double findTerritoryAttackValue(final PlayerID player, final Territory t) {
final GameData data = ProData.getData();
final int isEnemyFactory = ProMatches.territoryHasInfraFactoryAndIsEnemyLand(player, data).match(t) ? 1 : 0;
double value = 3 * TerritoryAttachment.getProduction(t) * (isEnemyFactory + 1);
if (!t.isWater() && t.getOwner().isNull()) {
final double strength =
ProBattleUtils.estimateStrength(t, new ArrayList<>(t.getUnits().getUnits()), new ArrayList<>(), false);
// Estimate TUV swing as number of casualties * cost
final double TUVSwing = -(strength / 8) * ProData.minCostPerHitPoint;
value += TUVSwing;
}
return value;
}
public static Map<Territory, Double> findTerritoryValues(final PlayerID player,
final List<Territory> territoriesThatCantBeHeld, final List<Territory> territoriesToAttack) {
return findTerritoryValues(player, territoriesThatCantBeHeld, territoriesToAttack,
new HashSet<>(ProData.getData().getMap().getTerritories()));
}
public static Map<Territory, Double> findTerritoryValues(final PlayerID player,
final List<Territory> territoriesThatCantBeHeld, final List<Territory> territoriesToAttack,
final Set<Territory> territoriesToCheck) {
final int maxLandMassSize = findMaxLandMassSize(player);
final Map<Territory, Double> enemyCapitalsAndFactoriesMap =
findEnemyCapitalsAndFactoriesValue(player, maxLandMassSize, territoriesThatCantBeHeld, territoriesToAttack);
final Map<Territory, Double> territoryValueMap = new HashMap<>();
for (final Territory t : territoriesToCheck) {
if (!t.isWater()) {
final double value = findLandValue(t, player, maxLandMassSize, enemyCapitalsAndFactoriesMap,
territoriesThatCantBeHeld, territoriesToAttack);
territoryValueMap.put(t, value);
}
}
for (final Territory t : territoriesToCheck) {
if (t.isWater()) {
final double value = findWaterValue(t, player, maxLandMassSize, enemyCapitalsAndFactoriesMap,
territoriesThatCantBeHeld, territoriesToAttack, territoryValueMap);
territoryValueMap.put(t, value);
}
}
return territoryValueMap;
}
public static Map<Territory, Double> findSeaTerritoryValues(final PlayerID player,
final List<Territory> territoriesThatCantBeHeld) {
// Determine value for water territories
final Map<Territory, Double> territoryValueMap = new HashMap<>();
final GameData data = ProData.getData();
for (final Territory t : data.getMap().getTerritories()) {
if (!territoriesThatCantBeHeld.contains(t) && t.isWater()
&& !data.getMap().getNeighbors(t, Matches.TerritoryIsWater).isEmpty()) {
// Determine sea value based on nearby convoy production
double nearbySeaProductionValue = 0;
final Set<Territory> nearbySeaTerritories =
data.getMap().getNeighbors(t, 4, ProMatches.territoryCanMoveSeaUnits(player, data, true));
final List<Territory> nearbyEnemySeaTerritories = Match.getMatches(nearbySeaTerritories,
ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld));
for (final Territory nearbyEnemySeaTerritory : nearbyEnemySeaTerritories) {
final Route route = data.getMap().getRoute_IgnoreEnd(t, nearbyEnemySeaTerritory,
ProMatches.territoryCanMoveSeaUnits(player, data, true));
if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) {
continue;
}
final int distance = route.numberOfSteps();
if (distance > 0) {
nearbySeaProductionValue +=
TerritoryAttachment.getProduction(nearbyEnemySeaTerritory) / Math.pow(2, distance);
}
}
// Determine sea value based on nearby enemy sea units
double nearbyEnemySeaUnitValue = 0;
final List<Territory> nearbyEnemySeaUnitTerritories =
Match.getMatches(nearbySeaTerritories, Matches.territoryHasEnemyUnits(player, data));
for (final Territory nearbyEnemySeaTerritory : nearbyEnemySeaUnitTerritories) {
final Route route = data.getMap().getRoute_IgnoreEnd(t, nearbyEnemySeaTerritory,
ProMatches.territoryCanMoveSeaUnits(player, data, true));
if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) {
continue;
}
final int distance = route.numberOfSteps();
if (distance > 0) {
nearbyEnemySeaUnitValue +=
nearbyEnemySeaTerritory.getUnits().countMatches(Matches.unitIsEnemyOf(data, player))
/ Math.pow(2, distance);
}
}
// Set final values
final double value = 100 * nearbySeaProductionValue + nearbyEnemySeaUnitValue;
territoryValueMap.put(t, value);
} else if (t.isWater()) {
territoryValueMap.put(t, 0.0);
}
}
return territoryValueMap;
}
private static int findMaxLandMassSize(final PlayerID player) {
int maxLandMassSize = 1;
final GameData data = ProData.getData();
for (final Territory t : data.getMap().getTerritories()) {
if (!t.isWater()) {
final int landMassSize = 1 + data.getMap()
.getNeighbors(t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)).size();
if (landMassSize > maxLandMassSize) {
maxLandMassSize = landMassSize;
}
}
}
return maxLandMassSize;
}
private static Map<Territory, Double> findEnemyCapitalsAndFactoriesValue(final PlayerID player,
final int maxLandMassSize, final List<Territory> territoriesThatCantBeHeld,
final List<Territory> territoriesToAttack) {
// Get all enemy factories and capitals (check if most territories have factories and if so remove them)
final Set<Territory> enemyCapitalsAndFactories = new HashSet<>();
final GameData data = ProData.getData();
final List<Territory> allTerritories = data.getMap().getTerritories();
enemyCapitalsAndFactories.addAll(
Match.getMatches(allTerritories, ProMatches.territoryHasInfraFactoryAndIsOwnedByPlayersOrCantBeHeld(player,
data, ProUtils.getPotentialEnemyPlayers(player), territoriesThatCantBeHeld)));
final int numPotentialEnemyTerritories =
Match.countMatches(allTerritories, Matches.isTerritoryOwnedBy(ProUtils.getPotentialEnemyPlayers(player)));
if (enemyCapitalsAndFactories.size() * 2 >= numPotentialEnemyTerritories) {
enemyCapitalsAndFactories.clear();
}
enemyCapitalsAndFactories.addAll(ProUtils.getLiveEnemyCapitals(data, player));
enemyCapitalsAndFactories.removeAll(territoriesToAttack);
// Find value for each enemy capital and factory
final Map<Territory, Double> enemyCapitalsAndFactoriesMap = new HashMap<>();
for (final Territory t : enemyCapitalsAndFactories) {
// Get factory production if factory
int factoryProduction = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
factoryProduction = TerritoryAttachment.getProduction(t);
}
// Get player production if capital
double playerProduction = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null && ta.isCapital()) {
playerProduction = ProUtils.getPlayerProduction(t.getOwner(), data);
}
// Calculate value
final int isNeutral = t.getOwner().isNull() ? 1 : 0;
final int landMassSize = 1 + data.getMap()
.getNeighbors(t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)).size();
final double value = Math.sqrt(factoryProduction + Math.sqrt(playerProduction)) * 32 / (1 + 3 * isNeutral)
* landMassSize / maxLandMassSize;
enemyCapitalsAndFactoriesMap.put(t, value);
}
return enemyCapitalsAndFactoriesMap;
}
private static double findLandValue(final Territory t, final PlayerID player, final int maxLandMassSize,
final Map<Territory, Double> enemyCapitalsAndFactoriesMap, final List<Territory> territoriesThatCantBeHeld,
final List<Territory> territoriesToAttack) {
if (territoriesThatCantBeHeld.contains(t)) {
return 0.0;
}
// Determine value based on enemy factory land distance
final List<Double> values = new ArrayList<>();
final GameData data = ProData.getData();
final Set<Territory> nearbyEnemyCapitalsAndFactories =
findNearbyEnemyCapitalsAndFactories(t, enemyCapitalsAndFactoriesMap);
for (final Territory enemyCapitalOrFactory : nearbyEnemyCapitalsAndFactories) {
final int distance = data.getMap().getDistance(t, enemyCapitalOrFactory,
ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true));
if (distance > 0) {
values.add(enemyCapitalsAndFactoriesMap.get(enemyCapitalOrFactory) / Math.pow(2, distance));
}
}
Collections.sort(values, Collections.reverseOrder());
double capitalOrFactoryValue = 0;
for (int i = 0; i < values.size(); i++) {
capitalOrFactoryValue += values.get(i) / Math.pow(2, i); // Decrease each additional factory value by half
}
// Determine value based on nearby territory production
double nearbyEnemyValue = 0;
final Set<Territory> nearbyTerritories =
data.getMap().getNeighbors(t, 2, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true));
final List<Territory> nearbyEnemyTerritories = Match.getMatches(nearbyTerritories,
ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld));
nearbyEnemyTerritories.removeAll(territoriesToAttack);
for (final Territory nearbyEnemyTerritory : nearbyEnemyTerritories) {
final int distance = data.getMap().getDistance(t, nearbyEnemyTerritory,
ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true));
if (distance > 0) {
double value = TerritoryAttachment.getProduction(nearbyEnemyTerritory);
if (nearbyEnemyTerritory.getOwner().isNull()) {
value = findTerritoryAttackValue(player, nearbyEnemyTerritory) / 3; // find neutral value
} else if (ProMatches.territoryIsAlliedLandAndHasNoEnemyNeighbors(player, data).match(nearbyEnemyTerritory)) {
value *= 0.1; // reduce value for can't hold amphib allied territories
}
if (value > 0) {
nearbyEnemyValue += (value / Math.pow(2, distance));
}
}
}
final int landMassSize = 1
+ data.getMap().getNeighbors(t, 6, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, true)).size();
double value = nearbyEnemyValue * landMassSize / maxLandMassSize + capitalOrFactoryValue;
if (ProMatches.territoryHasInfraFactoryAndIsLand(player).match(t)) {
value *= 1.1; // prefer territories with factories
}
return value;
}
private static double findWaterValue(final Territory t, final PlayerID player, final int maxLandMassSize,
final Map<Territory, Double> enemyCapitalsAndFactoriesMap, final List<Territory> territoriesThatCantBeHeld,
final List<Territory> territoriesToAttack, final Map<Territory, Double> territoryValueMap) {
final GameData data = ProData.getData();
if (territoriesThatCantBeHeld.contains(t) || data.getMap().getNeighbors(t, Matches.TerritoryIsWater).isEmpty()) {
return 0.0;
}
// Determine value based on enemy factory distance
final List<Double> values = new ArrayList<>();
final Set<Territory> nearbyEnemyCapitalsAndFactories =
findNearbyEnemyCapitalsAndFactories(t, enemyCapitalsAndFactoriesMap);
for (final Territory enemyCapitalOrFactory : nearbyEnemyCapitalsAndFactories) {
final Route route = data.getMap().getRoute_IgnoreEnd(t, enemyCapitalOrFactory,
ProMatches.territoryCanMoveSeaUnits(player, data, true));
if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) {
continue;
}
final int distance = route.numberOfSteps();
if (distance > 0) {
values.add(enemyCapitalsAndFactoriesMap.get(enemyCapitalOrFactory) / Math.pow(2, distance));
}
}
Collections.sort(values, Collections.reverseOrder());
double capitalOrFactoryValue = 0;
for (int i = 0; i < values.size(); i++) {
capitalOrFactoryValue += values.get(i) / Math.pow(2, i); // Decrease each additional factory value by half
}
// Determine value based on nearby territory production
double nearbyLandValue = 0;
final Set<Territory> nearbyTerritories = data.getMap().getNeighbors(t, 3);
final List<Territory> nearbyLandTerritories =
Match.getMatches(nearbyTerritories, ProMatches.territoryCanPotentiallyMoveLandUnits(player, data, false));
nearbyLandTerritories.removeAll(territoriesToAttack);
for (final Territory nearbyLandTerritory : nearbyLandTerritories) {
final Route route = data.getMap().getRoute_IgnoreEnd(t, nearbyLandTerritory,
ProMatches.territoryCanMoveSeaUnits(player, data, true));
if (route == null || MoveValidator.validateCanal(route, null, player, data) != null) {
continue;
}
final int distance = route.numberOfSteps();
if (distance > 0 && distance <= 3) {
if (ProMatches.territoryIsEnemyOrCantBeHeld(player, data, territoriesThatCantBeHeld)
.match(nearbyLandTerritory)) {
double value = TerritoryAttachment.getProduction(nearbyLandTerritory);
if (nearbyLandTerritory.getOwner().isNull()) {
value = findTerritoryAttackValue(player, nearbyLandTerritory);
}
nearbyLandValue += value;
}
if (!territoryValueMap.containsKey(nearbyLandTerritory)) {
final double value = findLandValue(nearbyLandTerritory, player, maxLandMassSize, enemyCapitalsAndFactoriesMap,
territoriesThatCantBeHeld, territoriesToAttack);
territoryValueMap.put(nearbyLandTerritory, value);
}
nearbyLandValue += territoryValueMap.get(nearbyLandTerritory);
}
}
final double value = capitalOrFactoryValue / 100 + nearbyLandValue / 10;
return value;
}
private static Set<Territory> findNearbyEnemyCapitalsAndFactories(final Territory t,
final Map<Territory, Double> enemyCapitalsAndFactoriesMap) {
Set<Territory> nearbyEnemyCapitalsAndFactories = new HashSet<>();
for (int i = MIN_FACTORY_CHECK_DISTANCE; i <= MAX_FACTORY_CHECK_DISTANCE; i++) {
nearbyEnemyCapitalsAndFactories = ProData.getData().getMap().getNeighbors(t, i);
nearbyEnemyCapitalsAndFactories.retainAll(enemyCapitalsAndFactoriesMap.keySet());
if (!nearbyEnemyCapitalsAndFactories.isEmpty()) {
break;
}
}
return nearbyEnemyCapitalsAndFactories;
}
}