use of games.strategy.triplea.ai.pro.data.ProOtherMoveOptions in project triplea by triplea-game.
the class ProCombatMoveAi method tryToAttackTerritories.
private Map<Unit, Set<Territory>> tryToAttackTerritories(final List<ProTerritory> prioritizedTerritories, final List<Unit> alreadyMovedUnits) {
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Unit, Set<Territory>> unitAttackMap = territoryManager.getAttackOptions().getUnitMoveMap();
final Map<Unit, Set<Territory>> transportAttackMap = territoryManager.getAttackOptions().getTransportMoveMap();
final Map<Unit, Set<Territory>> bombardMap = territoryManager.getAttackOptions().getBombardMap();
final List<ProTransport> transportMapList = territoryManager.getAttackOptions().getTransportList();
// Reset lists
for (final ProTerritory t : attackMap.values()) {
t.getUnits().clear();
t.getBombardTerritoryMap().clear();
t.getAmphibAttackMap().clear();
t.getTransportTerritoryMap().clear();
t.setBattleResult(null);
}
// Loop through all units and determine attack options
final Map<Unit, Set<Territory>> unitAttackOptions = new HashMap<>();
for (final Unit unit : unitAttackMap.keySet()) {
// Find number of attack options
final Set<Territory> canAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (unitAttackMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
// Add units with attack options to map
if (canAttackTerritories.size() >= 1) {
unitAttackOptions.put(unit, canAttackTerritories);
}
}
// Sort units by number of attack options and cost
Map<Unit, Set<Territory>> sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitMoveOptions(unitAttackOptions);
// Try to set at least one destroyer in each sea territory with subs
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isDestroyerUnit = UnitAttachment.get(unit.getType()).getIsDestroyer();
if (!isDestroyerUnit) {
// skip non-destroyer units
continue;
}
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
// Add destroyer if territory has subs and a destroyer has been already added
final List<Unit> defendingUnits = attackMap.get(t).getMaxEnemyDefenders(player, data);
if (defendingUnits.stream().anyMatch(Matches.unitIsSub()) && attackMap.get(t).getUnits().stream().noneMatch(Matches.unitIsDestroyer())) {
attackMap.get(t).addUnit(unit);
it.remove();
break;
}
}
}
// Set enough land and sea units in territories to have at least a chance of winning
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (isAirUnit) {
// skip air units
continue;
}
final TreeMap<Double, Territory> estimatesMap = new TreeMap<>();
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
if (t.isWater() && !attackMap.get(t).isCanHold()) {
// ignore sea territories that can't be held
continue;
}
final List<Unit> defendingUnits = attackMap.get(t).getMaxEnemyDefenders(player, data);
double estimate = ProBattleUtils.estimateStrengthDifference(t, attackMap.get(t).getUnits(), defendingUnits);
final boolean hasAa = defendingUnits.stream().anyMatch(Matches.unitIsAaForAnything());
if (hasAa) {
estimate -= 10;
}
estimatesMap.put(estimate, t);
}
if (!estimatesMap.isEmpty() && estimatesMap.firstKey() < 40) {
final Territory minWinTerritory = estimatesMap.entrySet().iterator().next().getValue();
attackMap.get(minWinTerritory).addUnit(unit);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Set non-air units in territories that can be held
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (isAirUnit) {
// skip air units
continue;
}
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!attackMap.get(t).isCurrentlyWins() && attackMap.get(t).isCanHold()) {
if (attackMap.get(t).getBattleResult() == null) {
attackMap.get(t).setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = attackMap.get(t).getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Set air units in territories that can't be held (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (!isAirUnit) {
// skip non-air units
continue;
}
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins() && !patd.isCanHold()) {
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isEnemyCapital = ProUtils.getLiveEnemyCapitals(data, player).contains(t);
final boolean isAdjacentToAlliedCapital = Matches.territoryHasNeighborMatching(data, Matches.territoryIsInList(ProUtils.getLiveAlliedCapitals(data, player))).test(t);
final int range = TripleAUnit.get(unit).getMovementLeft();
final int distance = data.getMap().getDistance_IgnoreEndForCondition(ProData.unitTerritoryMap.get(unit), t, ProMatches.territoryCanMoveAirUnitsAndNoAa(player, data, true));
final boolean usesMoreThanHalfOfRange = distance > range / 2;
if (isAirUnit && !isEnemyCapital && !isAdjacentToAlliedCapital && usesMoreThanHalfOfRange) {
continue;
}
// Check battle results
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasNoDefenders = defendingUnits.stream().noneMatch(ProMatches.unitIsEnemyAndNotInfa(player, data));
final boolean isOverwhelmingWin = ProBattleUtils.checkForOverwhelmingWin(t, patd.getUnits(), defendingUnits);
final boolean hasAa = defendingUnits.stream().anyMatch(Matches.unitIsAaForAnything());
if (!hasNoDefenders && !isOverwhelmingWin && (!hasAa || result.getWinPercentage() < minWinPercentage)) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
if (patd.isStrafing()) {
break;
}
}
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Set remaining units in any territory that needs it (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins()) {
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isAdjacentToAlliedFactory = Matches.territoryHasNeighborMatching(data, ProMatches.territoryHasInfraFactoryAndIsAlliedLand(player, data)).test(t);
final int range = TripleAUnit.get(unit).getMovementLeft();
final int distance = data.getMap().getDistance_IgnoreEndForCondition(ProData.unitTerritoryMap.get(unit), t, ProMatches.territoryCanMoveAirUnitsAndNoAa(player, data, true));
final boolean usesMoreThanHalfOfRange = distance > range / 2;
final boolean territoryValueIsLessThanUnitValue = patd.getValue() < ProData.unitValueMap.getInt(unit.getType());
if (isAirUnit && !isAdjacentToAlliedFactory && usesMoreThanHalfOfRange && (territoryValueIsLessThanUnitValue || (!t.isWater() && !patd.isCanHold()))) {
continue;
}
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasNoDefenders = defendingUnits.stream().noneMatch(ProMatches.unitIsEnemyAndNotInfa(player, data));
final boolean isOverwhelmingWin = ProBattleUtils.checkForOverwhelmingWin(t, patd.getUnits(), defendingUnits);
final boolean hasAa = defendingUnits.stream().anyMatch(Matches.unitIsAaForAnything());
if (!isAirUnit || (!hasNoDefenders && !isOverwhelmingWin && (!hasAa || result.getWinPercentage() < minWinPercentage))) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptions(player, sortedUnitAttackOptions, attackMap, calc);
// If transports can take casualties try placing in naval battles first
final List<Unit> alreadyAttackedWithTransports = new ArrayList<>();
if (!Properties.getTransportCasualtiesRestricted(data)) {
// Loop through all my transports and see which territories they can attack from current list
final Map<Unit, Set<Territory>> transportAttackOptions = new HashMap<>();
for (final Unit unit : transportAttackMap.keySet()) {
// Find number of attack options
final Set<Territory> canAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (transportAttackMap.get(unit).contains(attackTerritoryData.getTerritory())) {
canAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canAttackTerritories.isEmpty()) {
transportAttackOptions.put(unit, canAttackTerritories);
}
}
// Loop through transports with attack options and determine if any naval battle needs it
for (final Unit transport : transportAttackOptions.keySet()) {
// Find current naval battle that needs transport if it isn't transporting units
for (final Territory t : transportAttackOptions.get(transport)) {
final ProTerritory patd = attackMap.get(t);
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
if (!patd.isCurrentlyWins() && !TransportTracker.isTransporting(transport) && !defendingUnits.isEmpty()) {
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < ProData.winPercentage || !result.isHasLandUnitRemaining()) {
patd.addUnit(transport);
patd.setBattleResult(null);
alreadyAttackedWithTransports.add(transport);
ProLogger.trace("Adding attack transport to: " + t.getName());
break;
}
}
}
}
}
// Loop through all my transports and see which can make amphib attack
final Map<Unit, Set<Territory>> amphibAttackOptions = new HashMap<>();
for (final ProTransport proTransportData : transportMapList) {
// If already used to attack then ignore
if (alreadyAttackedWithTransports.contains(proTransportData.getTransport())) {
continue;
}
// Find number of attack options
final Set<Territory> canAmphibAttackTerritories = new HashSet<>();
for (final ProTerritory attackTerritoryData : prioritizedTerritories) {
if (proTransportData.getTransportMap().containsKey(attackTerritoryData.getTerritory())) {
canAmphibAttackTerritories.add(attackTerritoryData.getTerritory());
}
}
if (!canAmphibAttackTerritories.isEmpty()) {
amphibAttackOptions.put(proTransportData.getTransport(), canAmphibAttackTerritories);
}
}
// Loop through transports with amphib attack options and determine if any land battle needs it
for (final Unit transport : amphibAttackOptions.keySet()) {
// Find current land battle results for territories that unit can amphib attack
Territory minWinTerritory = null;
double minWinPercentage = ProData.winPercentage;
List<Unit> minAmphibUnitsToAdd = null;
Territory minUnloadFromTerritory = null;
for (final Territory t : amphibAttackOptions.get(transport)) {
final ProTerritory patd = attackMap.get(t);
if (!patd.isCurrentlyWins()) {
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
// Get all units that have already attacked
final List<Unit> alreadyAttackedWithUnits = new ArrayList<>(alreadyMovedUnits);
alreadyAttackedWithUnits.addAll(attackMap.values().stream().map(ProTerritory::getUnits).flatMap(Collection::stream).collect(Collectors.toList()));
// Find units that haven't attacked and can be transported
for (final ProTransport proTransportData : transportMapList) {
if (proTransportData.getTransport().equals(transport)) {
// Find units to load
final Set<Territory> territoriesCanLoadFrom = proTransportData.getTransportMap().get(t);
final List<Unit> amphibUnitsToAdd = ProTransportUtils.getUnitsToTransportFromTerritories(player, transport, territoriesCanLoadFrom, alreadyAttackedWithUnits);
if (amphibUnitsToAdd.isEmpty()) {
continue;
}
// Find best territory to move transport
double minStrengthDifference = Double.POSITIVE_INFINITY;
minUnloadFromTerritory = null;
final Set<Territory> territoriesToMoveTransport = data.getMap().getNeighbors(t, ProMatches.territoryCanMoveSeaUnits(player, data, false));
final Set<Territory> loadFromTerritories = new HashSet<>();
for (final Unit u : amphibUnitsToAdd) {
loadFromTerritories.add(ProData.unitTerritoryMap.get(u));
}
for (final Territory territoryToMoveTransport : territoriesToMoveTransport) {
if (proTransportData.getSeaTransportMap().containsKey(territoryToMoveTransport) && proTransportData.getSeaTransportMap().get(territoryToMoveTransport).containsAll(loadFromTerritories)) {
List<Unit> attackers = new ArrayList<>();
if (enemyAttackOptions.getMax(territoryToMoveTransport) != null) {
attackers = enemyAttackOptions.getMax(territoryToMoveTransport).getMaxUnits();
}
final List<Unit> defenders = territoryToMoveTransport.getUnits().getMatches(Matches.isUnitAllied(player, data));
defenders.add(transport);
final double strengthDifference = ProBattleUtils.estimateStrengthDifference(territoryToMoveTransport, attackers, defenders);
if (strengthDifference < minStrengthDifference) {
minStrengthDifference = strengthDifference;
minUnloadFromTerritory = territoryToMoveTransport;
}
}
}
minWinTerritory = t;
minWinPercentage = result.getWinPercentage();
minAmphibUnitsToAdd = amphibUnitsToAdd;
break;
}
}
}
}
}
if (minWinTerritory != null) {
if (minUnloadFromTerritory != null) {
attackMap.get(minWinTerritory).getTransportTerritoryMap().put(transport, minUnloadFromTerritory);
}
attackMap.get(minWinTerritory).addUnits(minAmphibUnitsToAdd);
attackMap.get(minWinTerritory).putAmphibAttackMap(transport, minAmphibUnitsToAdd);
attackMap.get(minWinTerritory).setBattleResult(null);
for (final Unit unit : minAmphibUnitsToAdd) {
sortedUnitAttackOptions.remove(unit);
}
ProLogger.trace("Adding amphibious attack to " + minWinTerritory + ", units=" + minAmphibUnitsToAdd.size() + ", unloadFrom=" + minUnloadFromTerritory);
}
}
// Get all units that have already moved
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final ProTerritory t : attackMap.values()) {
alreadyAttackedWithUnits.addAll(t.getUnits());
alreadyAttackedWithUnits.addAll(t.getAmphibAttackMap().keySet());
}
// Loop through all my bombard units and see which can bombard
final Map<Unit, Set<Territory>> bombardOptions = new HashMap<>();
for (final Unit u : bombardMap.keySet()) {
// If already used to attack then ignore
if (alreadyAttackedWithUnits.contains(u)) {
continue;
}
// Find number of bombard options
final Set<Territory> canBombardTerritories = new HashSet<>();
for (final ProTerritory patd : prioritizedTerritories) {
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean hasDefenders = defendingUnits.stream().anyMatch(Matches.unitIsInfrastructure().negate());
if (bombardMap.get(u).contains(patd.getTerritory()) && !patd.getTransportTerritoryMap().isEmpty() && hasDefenders && !TransportTracker.isTransporting(u)) {
canBombardTerritories.add(patd.getTerritory());
}
}
if (!canBombardTerritories.isEmpty()) {
bombardOptions.put(u, canBombardTerritories);
}
}
// Loop through bombard units to see if any amphib battles need
for (final Unit u : bombardOptions.keySet()) {
// Find current land battle results for territories that unit can bombard
Territory minWinTerritory = null;
double minWinPercentage = Double.MAX_VALUE;
Territory minBombardFromTerritory = null;
for (final Territory t : bombardOptions.get(u)) {
final ProTerritory patd = attackMap.get(t);
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
// Find territory to bombard from
Territory bombardFromTerritory = null;
for (final Territory unloadFromTerritory : patd.getTransportTerritoryMap().values()) {
if (patd.getBombardOptionsMap().get(u).contains(unloadFromTerritory)) {
bombardFromTerritory = unloadFromTerritory;
}
}
if (bombardFromTerritory != null) {
minWinTerritory = t;
minWinPercentage = result.getWinPercentage();
minBombardFromTerritory = bombardFromTerritory;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).getBombardTerritoryMap().put(u, minBombardFromTerritory);
attackMap.get(minWinTerritory).setBattleResult(null);
sortedUnitAttackOptions.remove(u);
ProLogger.trace("Adding bombard to " + minWinTerritory + ", units=" + u + ", bombardFrom=" + minBombardFromTerritory);
}
}
return sortedUnitAttackOptions;
}
use of games.strategy.triplea.ai.pro.data.ProOtherMoveOptions in project triplea by triplea-game.
the class ProCombatMoveAi method determineUnitsToAttackWith.
private void determineUnitsToAttackWith(final List<ProTerritory> prioritizedTerritories, final List<Unit> alreadyMovedUnits) {
ProLogger.info("Determine units to attack each territory with");
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Unit, Set<Territory>> unitAttackMap = territoryManager.getAttackOptions().getUnitMoveMap();
// Assign units to territories by prioritization
while (true) {
Map<Unit, Set<Territory>> sortedUnitAttackOptions = tryToAttackTerritories(prioritizedTerritories, alreadyMovedUnits);
// Clear bombers
for (final ProTerritory t : attackMap.values()) {
t.getBombers().clear();
}
// Get all units that have already moved
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final ProTerritory t : attackMap.values()) {
alreadyAttackedWithUnits.addAll(t.getUnits());
alreadyAttackedWithUnits.addAll(t.getAmphibAttackMap().keySet());
}
// Check to see if any territories can be bombed
final Map<Unit, Set<Territory>> bomberMoveMap = territoryManager.getAttackOptions().getBomberMoveMap();
for (final Unit unit : bomberMoveMap.keySet()) {
if (alreadyAttackedWithUnits.contains(unit)) {
continue;
}
Optional<Territory> maxBombingTerritory = Optional.empty();
int maxBombingScore = MIN_BOMBING_SCORE;
for (final Territory t : bomberMoveMap.get(unit)) {
final boolean territoryCanBeBombed = t.getUnits().anyMatch(Matches.unitCanProduceUnitsAndCanBeDamaged());
if (territoryCanBeBombed && canAirSafelyLandAfterAttack(unit, t)) {
final int noAaBombingDefense = t.getUnits().anyMatch(Matches.unitIsAaForBombingThisUnitOnly()) ? 0 : 1;
int maxDamage = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
maxDamage = ta.getProduction();
}
final int numExistingBombers = attackMap.get(t).getBombers().size();
final int remainingDamagePotential = maxDamage - 3 * numExistingBombers;
final int bombingScore = (1 + 9 * noAaBombingDefense) * remainingDamagePotential;
if (bombingScore >= maxBombingScore) {
maxBombingScore = bombingScore;
maxBombingTerritory = Optional.of(t);
}
}
}
if (maxBombingTerritory.isPresent()) {
final Territory t = maxBombingTerritory.get();
attackMap.get(t).getBombers().add(unit);
sortedUnitAttackOptions.remove(unit);
ProLogger.debug("Add bomber (" + unit + ") to " + t);
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Set air units in any territory with no AA (don't move planes to empty territories)
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir();
if (!isAirUnit) {
// skip non-air units
continue;
}
Territory minWinTerritory = null;
double minWinPercentage = Double.MAX_VALUE;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
// Check if air unit should avoid this territory due to no guaranteed safe landing location
final boolean isEnemyFactory = ProMatches.territoryHasInfraFactoryAndIsEnemyLand(player, data).test(t);
if (!isEnemyFactory && !canAirSafelyLandAfterAttack(unit, t)) {
continue;
}
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
if (result.getWinPercentage() < minWinPercentage || (!result.isHasLandUnitRemaining() && minWinTerritory == null)) {
final List<Unit> attackingUnits = patd.getUnits();
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean isOverwhelmingWin = ProBattleUtils.checkForOverwhelmingWin(t, attackingUnits, defendingUnits);
final boolean hasAa = defendingUnits.stream().anyMatch(Matches.unitIsAaForAnything());
if (!hasAa && !isOverwhelmingWin) {
minWinPercentage = result.getWinPercentage();
minWinTerritory = t;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Find territory that we can try to hold that needs unit
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
Territory minWinTerritory = null;
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (patd.isCanHold()) {
// Check if I already have enough attack units to win in 2 rounds
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
final List<Unit> attackingUnits = patd.getUnits();
final List<Unit> defendingUnits = patd.getMaxEnemyDefenders(player, data);
final boolean isOverwhelmingWin = ProBattleUtils.checkForOverwhelmingWin(t, attackingUnits, defendingUnits);
if (!isOverwhelmingWin && result.getBattleRounds() > 2) {
minWinTerritory = t;
break;
}
}
}
if (minWinTerritory != null) {
attackMap.get(minWinTerritory).addUnit(unit);
attackMap.get(minWinTerritory).setBattleResult(null);
it.remove();
}
}
// Re-sort attack options
sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc);
// Add sea units to any territory that significantly increases TUV gain
for (final Iterator<Unit> it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext(); ) {
final Unit unit = it.next();
final boolean isSeaUnit = UnitAttachment.get(unit.getType()).getIsSea();
if (!isSeaUnit) {
// skip non-sea units
continue;
}
for (final Territory t : sortedUnitAttackOptions.get(unit)) {
final ProTerritory patd = attackMap.get(t);
if (attackMap.get(t).getBattleResult() == null) {
attackMap.get(t).setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = attackMap.get(t).getBattleResult();
final List<Unit> attackers = new ArrayList<>(patd.getUnits());
attackers.add(unit);
final ProBattleResult result2 = calc.estimateAttackBattleResults(t, attackers, patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet());
final double unitValue = ProData.unitValueMap.getInt(unit.getType());
if ((result2.getTuvSwing() - unitValue / 3) > result.getTuvSwing()) {
attackMap.get(t).addUnit(unit);
attackMap.get(t).setBattleResult(null);
it.remove();
break;
}
}
}
// Determine if all attacks are worth it
final List<Unit> usedUnits = new ArrayList<>();
for (final ProTerritory patd : prioritizedTerritories) {
usedUnits.addAll(patd.getUnits());
}
ProTerritory territoryToRemove = null;
for (final ProTerritory patd : prioritizedTerritories) {
final Territory t = patd.getTerritory();
// Find battle result
if (patd.getBattleResult() == null) {
patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
}
final ProBattleResult result = patd.getBattleResult();
// Determine enemy counter attack results
boolean canHold = true;
double enemyCounterTuvSwing = 0;
if (enemyAttackOptions.getMax(t) != null && !ProMatches.territoryIsWaterAndAdjacentToOwnedFactory(player, data).test(t)) {
List<Unit> remainingUnitsToDefendWith = CollectionUtils.getMatches(result.getAverageAttackersRemaining(), Matches.unitIsAir().negate());
ProBattleResult result2 = calc.calculateBattleResults(t, patd.getMaxEnemyUnits(), remainingUnitsToDefendWith, patd.getMaxBombardUnits());
if (patd.isCanHold() && result2.getTuvSwing() > 0) {
final List<Unit> unusedUnits = new ArrayList<>(patd.getMaxUnits());
unusedUnits.addAll(patd.getMaxAmphibUnits());
unusedUnits.removeAll(usedUnits);
unusedUnits.addAll(remainingUnitsToDefendWith);
final ProBattleResult result3 = calc.calculateBattleResults(t, patd.getMaxEnemyUnits(), unusedUnits, patd.getMaxBombardUnits());
if (result3.getTuvSwing() < result2.getTuvSwing()) {
result2 = result3;
remainingUnitsToDefendWith = unusedUnits;
}
}
canHold = (!result2.isHasLandUnitRemaining() && !t.isWater()) || (result2.getTuvSwing() < 0) || (result2.getWinPercentage() < ProData.minWinPercentage);
if (result2.getTuvSwing() > 0) {
enemyCounterTuvSwing = result2.getTuvSwing();
}
ProLogger.trace("Territory=" + t.getName() + ", CanHold=" + canHold + ", MyDefenders=" + remainingUnitsToDefendWith.size() + ", EnemyAttackers=" + patd.getMaxEnemyUnits().size() + ", win%=" + result2.getWinPercentage() + ", EnemyTUVSwing=" + result2.getTuvSwing() + ", hasLandUnitRemaining=" + result2.isHasLandUnitRemaining());
}
// Find attack value
final boolean isNeutral = (!t.isWater() && t.getOwner().isNull());
final int isLand = !t.isWater() ? 1 : 0;
final int isCanHold = canHold ? 1 : 0;
final int isCantHoldAmphib = !canHold && !patd.getAmphibAttackMap().isEmpty() ? 1 : 0;
final int isFactory = ProMatches.territoryHasInfraFactoryAndIsLand().test(t) ? 1 : 0;
final int isFfa = ProUtils.isFfa(data, player) ? 1 : 0;
final int production = TerritoryAttachment.getProduction(t);
double capitalValue = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null && ta.isCapital()) {
capitalValue = ProUtils.getPlayerProduction(t.getOwner(), data);
}
final double territoryValue = (1 + isLand - isCantHoldAmphib + isFactory + isCanHold * (1 + 2 * isFfa + 2 * isFactory)) * production + capitalValue;
double tuvSwing = result.getTuvSwing();
if (isFfa == 1 && tuvSwing > 0) {
tuvSwing *= 0.5;
}
final double attackValue = tuvSwing + territoryValue * result.getWinPercentage() / 100 - enemyCounterTuvSwing * 2 / 3;
boolean allUnitsCanAttackOtherTerritory = true;
if (isNeutral && attackValue < 0) {
for (final Unit u : patd.getUnits()) {
boolean canAttackOtherTerritory = false;
for (final ProTerritory patd2 : prioritizedTerritories) {
if (!patd.equals(patd2) && unitAttackMap.get(u) != null && unitAttackMap.get(u).contains(patd2.getTerritory())) {
canAttackOtherTerritory = true;
break;
}
}
if (!canAttackOtherTerritory) {
allUnitsCanAttackOtherTerritory = false;
break;
}
}
}
// Determine whether to remove attack
if (!patd.isStrafing() && (result.getWinPercentage() < ProData.minWinPercentage || !result.isHasLandUnitRemaining() || (isNeutral && !canHold) || (attackValue < 0 && (!isNeutral || allUnitsCanAttackOtherTerritory || result.getBattleRounds() >= 4)))) {
territoryToRemove = patd;
}
ProLogger.debug(patd.getResultString() + ", attackValue=" + attackValue + ", territoryValue=" + territoryValue + ", allUnitsCanAttackOtherTerritory=" + allUnitsCanAttackOtherTerritory + " with attackers=" + patd.getUnits());
}
// Determine whether all attacks are successful or try to hold fewer territories
if (territoryToRemove == null) {
break;
}
prioritizedTerritories.remove(territoryToRemove);
ProLogger.debug("Removing " + territoryToRemove.getTerritory().getName());
}
}
use of games.strategy.triplea.ai.pro.data.ProOtherMoveOptions in project triplea by triplea-game.
the class ProCombatMoveAi method removeTerritoriesWhereTransportsAreExposed.
private void removeTerritoriesWhereTransportsAreExposed() {
ProLogger.info("Remove territories where transports are exposed");
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Find maximum defenders for each transport territory
final List<Territory> clearedTerritories = attackMap.entrySet().stream().filter(e -> !e.getValue().getUnits().isEmpty()).map(Map.Entry::getKey).collect(Collectors.toList());
territoryManager.populateDefenseOptions(clearedTerritories);
final Map<Territory, ProTerritory> defendMap = territoryManager.getDefendOptions().getTerritoryMap();
// Remove units that have already attacked
final Set<Unit> alreadyAttackedWithUnits = new HashSet<>();
for (final ProTerritory t : attackMap.values()) {
alreadyAttackedWithUnits.addAll(t.getUnits());
alreadyAttackedWithUnits.addAll(t.getAmphibAttackMap().keySet());
}
for (final ProTerritory t : defendMap.values()) {
t.getMaxUnits().removeAll(alreadyAttackedWithUnits);
}
// Loop through all prioritized territories
for (final Territory t : attackMap.keySet()) {
final ProTerritory patd = attackMap.get(t);
ProLogger.debug("Checking territory=" + patd.getTerritory().getName() + " with tranports size=" + patd.getTransportTerritoryMap().size());
if (!patd.getTerritory().isWater() && !patd.getTransportTerritoryMap().isEmpty()) {
// Find all transports for each unload territory
final Map<Territory, List<Unit>> territoryTransportAndBombardMap = new HashMap<>();
for (final Unit u : patd.getTransportTerritoryMap().keySet()) {
final Territory unloadTerritory = patd.getTransportTerritoryMap().get(u);
if (territoryTransportAndBombardMap.containsKey(unloadTerritory)) {
territoryTransportAndBombardMap.get(unloadTerritory).add(u);
} else {
final List<Unit> transports = new ArrayList<>();
transports.add(u);
territoryTransportAndBombardMap.put(unloadTerritory, transports);
}
}
// Find all bombard units for each unload territory
for (final Unit u : patd.getBombardTerritoryMap().keySet()) {
final Territory unloadTerritory = patd.getBombardTerritoryMap().get(u);
if (territoryTransportAndBombardMap.containsKey(unloadTerritory)) {
territoryTransportAndBombardMap.get(unloadTerritory).add(u);
} else {
final List<Unit> transports = new ArrayList<>();
transports.add(u);
territoryTransportAndBombardMap.put(unloadTerritory, transports);
}
}
// Determine counter attack results for each transport territory
double enemyTuvSwing = 0.0;
for (final Territory unloadTerritory : territoryTransportAndBombardMap.keySet()) {
if (enemyAttackOptions.getMax(unloadTerritory) != null) {
final List<Unit> enemyAttackers = enemyAttackOptions.getMax(unloadTerritory).getMaxUnits();
final Set<Unit> defenders = new HashSet<>(unloadTerritory.getUnits().getMatches(ProMatches.unitIsAlliedNotOwned(player, data)));
defenders.addAll(territoryTransportAndBombardMap.get(unloadTerritory));
if (defendMap.get(unloadTerritory) != null) {
defenders.addAll(defendMap.get(unloadTerritory).getMaxUnits());
}
final ProBattleResult result = calc.calculateBattleResults(unloadTerritory, enemyAttackOptions.getMax(unloadTerritory).getMaxUnits(), new ArrayList<>(defenders), new HashSet<>());
final ProBattleResult minResult = calc.calculateBattleResults(unloadTerritory, enemyAttackOptions.getMax(unloadTerritory).getMaxUnits(), territoryTransportAndBombardMap.get(unloadTerritory), new HashSet<>());
final double minTuvSwing = Math.min(result.getTuvSwing(), minResult.getTuvSwing());
if (minTuvSwing > 0) {
enemyTuvSwing += minTuvSwing;
}
ProLogger.trace(unloadTerritory + ", EnemyAttackers=" + enemyAttackers.size() + ", MaxDefenders=" + defenders.size() + ", MaxEnemyTUVSwing=" + result.getTuvSwing() + ", MinDefenders=" + territoryTransportAndBombardMap.get(unloadTerritory).size() + ", MinEnemyTUVSwing=" + minResult.getTuvSwing());
} else {
ProLogger.trace("Territory=" + unloadTerritory.getName() + " has no enemy attackers");
}
}
// Determine whether its worth attacking
final ProBattleResult result = calc.calculateBattleResults(t, patd.getUnits(), patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet());
int production = 0;
int isEnemyCapital = 0;
final TerritoryAttachment ta = TerritoryAttachment.get(t);
if (ta != null) {
production = ta.getProduction();
if (ta.isCapital()) {
isEnemyCapital = 1;
}
}
final double attackValue = result.getTuvSwing() + production * (1 + 3 * isEnemyCapital);
if (!patd.isStrafing() && (0.75 * enemyTuvSwing) > attackValue) {
ProLogger.debug("Removing amphib territory: " + patd.getTerritory() + ", enemyTUVSwing=" + enemyTuvSwing + ", attackValue=" + attackValue);
attackMap.get(t).getUnits().clear();
attackMap.get(t).getAmphibAttackMap().clear();
attackMap.get(t).getBombardTerritoryMap().clear();
} else {
ProLogger.debug("Keeping amphib territory: " + patd.getTerritory() + ", enemyTUVSwing=" + enemyTuvSwing + ", attackValue=" + attackValue);
}
}
}
}
use of games.strategy.triplea.ai.pro.data.ProOtherMoveOptions in project triplea by triplea-game.
the class ProCombatMoveAi method determineTerritoriesThatCanBeHeld.
private void determineTerritoriesThatCanBeHeld(final List<ProTerritory> prioritizedTerritories, final Map<Territory, Double> territoryValueMap) {
ProLogger.info("Check if we should try to hold attack territories");
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
final Map<Territory, ProTerritory> attackMap = territoryManager.getAttackOptions().getTerritoryMap();
// Determine which territories to try and hold
for (final ProTerritory patd : prioritizedTerritories) {
final Territory t = patd.getTerritory();
// If strafing then can't hold
if (patd.isStrafing()) {
patd.setCanHold(false);
ProLogger.debug(t + ", strafing so CanHold=false");
continue;
}
// Set max enemy attackers
if (enemyAttackOptions.getMax(t) != null) {
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
patd.setMaxEnemyUnits(new ArrayList<>(enemyAttackingUnits));
patd.setMaxEnemyBombardUnits(enemyAttackOptions.getMax(t).getMaxBombardUnits());
}
// Add strategic value for factories
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand().test(t)) {
isFactory = 1;
}
// Determine whether its worth trying to hold territory
double totalValue = 0.0;
final List<Unit> nonAirAttackers = CollectionUtils.getMatches(patd.getMaxUnits(), Matches.unitIsNotAir());
for (final Unit u : nonAirAttackers) {
totalValue += territoryValueMap.get(ProData.unitTerritoryMap.get(u));
}
final double averageValue = totalValue / nonAirAttackers.size() * 0.75;
final double territoryValue = territoryValueMap.get(t) * (1 + 4 * isFactory);
if (!t.isWater() && territoryValue < averageValue) {
attackMap.get(t).setCanHold(false);
ProLogger.debug(t + ", CanHold=false, value=" + territoryValueMap.get(t) + ", averageAttackFromValue=" + averageValue);
continue;
}
if (enemyAttackOptions.getMax(t) != null) {
// Find max remaining defenders
final Set<Unit> attackingUnits = new HashSet<>(patd.getMaxUnits());
attackingUnits.addAll(patd.getMaxAmphibUnits());
final ProBattleResult result = calc.estimateAttackBattleResults(t, new ArrayList<>(attackingUnits), patd.getMaxEnemyDefenders(player, data), patd.getMaxBombardUnits());
final List<Unit> remainingUnitsToDefendWith = CollectionUtils.getMatches(result.getAverageAttackersRemaining(), Matches.unitIsAir().negate());
ProLogger.debug(t + ", value=" + territoryValueMap.get(t) + ", averageAttackFromValue=" + averageValue + ", MyAttackers=" + attackingUnits.size() + ", RemainingUnits=" + remainingUnitsToDefendWith.size());
// Determine counter attack results to see if I can hold it
final ProBattleResult result2 = calc.calculateBattleResults(t, patd.getMaxEnemyUnits(), remainingUnitsToDefendWith, enemyAttackOptions.getMax(t).getMaxBombardUnits());
final boolean canHold = (!result2.isHasLandUnitRemaining() && !t.isWater()) || (result2.getTuvSwing() < 0) || (result2.getWinPercentage() < ProData.minWinPercentage);
patd.setCanHold(canHold);
ProLogger.debug(t + ", CanHold=" + canHold + ", MyDefenders=" + remainingUnitsToDefendWith.size() + ", EnemyAttackers=" + patd.getMaxEnemyUnits().size() + ", win%=" + result2.getWinPercentage() + ", EnemyTUVSwing=" + result2.getTuvSwing() + ", hasLandUnitRemaining=" + result2.isHasLandUnitRemaining());
} else {
attackMap.get(t).setCanHold(true);
ProLogger.debug(t + ", CanHold=true since no enemy counter attackers, value=" + territoryValueMap.get(t) + ", averageAttackFromValue=" + averageValue);
}
}
}
use of games.strategy.triplea.ai.pro.data.ProOtherMoveOptions in project triplea by triplea-game.
the class ProNonCombatMoveAi method determineIfMoveTerritoriesCanBeHeld.
private void determineIfMoveTerritoriesCanBeHeld() {
ProLogger.info("Find max enemy attackers and if territories can be held");
final Map<Territory, ProTerritory> moveMap = territoryManager.getDefendOptions().getTerritoryMap();
final ProOtherMoveOptions enemyAttackOptions = territoryManager.getEnemyAttackOptions();
// Determine which territories can possibly be held
for (final Territory t : moveMap.keySet()) {
final ProTerritory patd = moveMap.get(t);
// Check if no enemy attackers
if (enemyAttackOptions.getMax(t) == null) {
ProLogger.debug("Territory=" + t.getName() + ", CanHold=true since has no enemy attackers");
continue;
}
// Check if min defenders can hold it (not considering AA)
final Set<Unit> enemyAttackingUnits = new HashSet<>(enemyAttackOptions.getMax(t).getMaxUnits());
enemyAttackingUnits.addAll(enemyAttackOptions.getMax(t).getMaxAmphibUnits());
patd.setMaxEnemyUnits(new ArrayList<>(enemyAttackingUnits));
patd.setMaxEnemyBombardUnits(enemyAttackOptions.getMax(t).getMaxBombardUnits());
final List<Unit> minDefendingUnitsAndNotAa = CollectionUtils.getMatches(patd.getCantMoveUnits(), Matches.unitIsAaForAnything().negate());
final ProBattleResult minResult = calc.calculateBattleResults(t, new ArrayList<>(enemyAttackingUnits), minDefendingUnitsAndNotAa, enemyAttackOptions.getMax(t).getMaxBombardUnits());
patd.setMinBattleResult(minResult);
if (minResult.getTuvSwing() <= 0 && !minDefendingUnitsAndNotAa.isEmpty()) {
ProLogger.debug("Territory=" + t.getName() + ", CanHold=true" + ", MinDefenders=" + minDefendingUnitsAndNotAa.size() + ", EnemyAttackers=" + enemyAttackingUnits.size() + ", win%=" + minResult.getWinPercentage() + ", EnemyTUVSwing=" + minResult.getTuvSwing() + ", hasLandUnitRemaining=" + minResult.isHasLandUnitRemaining());
continue;
}
// Check if max defenders can hold it (not considering AA)
final Set<Unit> defendingUnits = new HashSet<>(patd.getMaxUnits());
defendingUnits.addAll(patd.getMaxAmphibUnits());
defendingUnits.addAll(patd.getCantMoveUnits());
final List<Unit> defendingUnitsAndNotAa = CollectionUtils.getMatches(defendingUnits, Matches.unitIsAaForAnything().negate());
final ProBattleResult result = calc.calculateBattleResults(t, new ArrayList<>(enemyAttackingUnits), defendingUnitsAndNotAa, enemyAttackOptions.getMax(t).getMaxBombardUnits());
int isFactory = 0;
if (ProMatches.territoryHasInfraFactoryAndIsLand().test(t)) {
isFactory = 1;
}
int isMyCapital = 0;
if (t.equals(ProData.myCapital)) {
isMyCapital = 1;
}
final List<Unit> extraUnits = new ArrayList<>(defendingUnitsAndNotAa);
extraUnits.removeAll(minDefendingUnitsAndNotAa);
final double extraUnitValue = TuvUtils.getTuv(extraUnits, ProData.unitValueMap);
final double holdValue = extraUnitValue / 8 * (1 + 0.5 * isFactory) * (1 + 2 * isMyCapital);
if (minDefendingUnitsAndNotAa.size() != defendingUnitsAndNotAa.size() && (result.getTuvSwing() - holdValue) < minResult.getTuvSwing()) {
ProLogger.debug("Territory=" + t.getName() + ", CanHold=true" + ", MaxDefenders=" + defendingUnitsAndNotAa.size() + ", EnemyAttackers=" + enemyAttackingUnits.size() + ", minTUVSwing=" + minResult.getTuvSwing() + ", win%=" + result.getWinPercentage() + ", EnemyTUVSwing=" + result.getTuvSwing() + ", hasLandUnitRemaining=" + result.isHasLandUnitRemaining() + ", holdValue=" + holdValue);
continue;
}
// Can't hold territory
patd.setCanHold(false);
ProLogger.debug("Can't hold Territory=" + t.getName() + ", MaxDefenders=" + defendingUnitsAndNotAa.size() + ", EnemyAttackers=" + enemyAttackingUnits.size() + ", minTUVSwing=" + minResult.getTuvSwing() + ", win%=" + result.getWinPercentage() + ", EnemyTUVSwing=" + result.getTuvSwing() + ", hasLandUnitRemaining=" + result.isHasLandUnitRemaining() + ", holdValue=" + holdValue);
}
}
Aggregations