diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/ProCombatMoveAi.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/ProCombatMoveAi.java index 53dfdcdea2c..017df73d6e6 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/ProCombatMoveAi.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/ProCombatMoveAi.java @@ -746,10 +746,10 @@ private void determineUnitsToAttackWith(final List prioritizedTerr // Re-sort attack options sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitNeededOptionsThenAttack(player, sortedUnitAttackOptions, attackMap, ProData.unitTerritoryMap, calc); + final List addedUnits = new ArrayList<>(); // Set air units in any territory with no AA (don't move planes to empty territories) - for (final Iterator it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); if (!isAirUnit) { continue; // skip non-air units @@ -783,19 +783,22 @@ private void determineUnitsToAttackWith(final List prioritizedTerr } } if (minWinTerritory != null) { - attackMap.get(minWinTerritory).addUnit(unit); attackMap.get(minWinTerritory).setBattleResult(null); - it.remove(); + attackMap.get(minWinTerritory).addUnit(unit); + addedUnits.add(unit); } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // 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 it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { + if (addedUnits.contains(unit)) { + continue; + } Territory minWinTerritory = null; for (final Territory t : sortedUnitAttackOptions.get(unit)) { final ProTerritory patd = attackMap.get(t); @@ -818,19 +821,20 @@ private void determineUnitsToAttackWith(final List prioritizedTerr } } if (minWinTerritory != null) { - attackMap.get(minWinTerritory).addUnit(unit); attackMap.get(minWinTerritory).setBattleResult(null); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, alreadyMovedUnits, attackMap); + attackMap.get(minWinTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // 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 it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isSeaUnit = UnitAttachment.get(unit.getType()).getIsSea(); if (!isSeaUnit) { continue; // skip non-sea units @@ -848,13 +852,14 @@ private void determineUnitsToAttackWith(final List prioritizedTerr 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(); + attackMap.get(t).addUnit(unit); + addedUnits.add(unit); break; } } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // Determine if all attacks are worth it final List usedUnits = new ArrayList<>(); @@ -1004,10 +1009,10 @@ private Map> tryToAttackTerritories(final List> sortedUnitAttackOptions = ProSortMoveOptionsUtils.sortUnitMoveOptions(unitAttackOptions); + final List addedUnits = new ArrayList<>(); // Try to set at least one destroyer in each sea territory with subs - for (final Iterator it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isDestroyerUnit = UnitAttachment.get(unit.getType()).getIsDestroyer(); if (!isDestroyerUnit) { continue; // skip non-destroyer units @@ -1019,17 +1024,17 @@ private Map> tryToAttackTerritories(final List it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); - if (isAirUnit) { + if (isAirUnit || addedUnits.contains(unit)) { continue; // skip air units } final TreeMap estimatesMap = new TreeMap<>(); @@ -1047,20 +1052,21 @@ private Map> tryToAttackTerritories(final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, alreadyMovedUnits, attackMap); + attackMap.get(minWinTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // 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 it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); - if (isAirUnit) { + if (isAirUnit || addedUnits.contains(unit)) { continue; // skip air units } Territory minWinTerritory = null; @@ -1081,19 +1087,20 @@ private Map> tryToAttackTerritories(final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, alreadyMovedUnits, attackMap); + attackMap.get(minWinTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // 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 it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); if (!isAirUnit) { continue; // skip non-air units @@ -1141,19 +1148,22 @@ private Map> tryToAttackTerritories(final List it = sortedUnitAttackOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitAttackOptions.keySet()) { + if (addedUnits.contains(unit)) { + continue; + } final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); Territory minWinTerritory = null; double minWinPercentage = ProData.winPercentage; @@ -1197,11 +1207,13 @@ private Map> tryToAttackTerritories(final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, alreadyMovedUnits, attackMap); + attackMap.get(minWinTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } + sortedUnitAttackOptions.keySet().removeAll(addedUnits); // Re-sort attack options sortedUnitAttackOptions = @@ -1292,14 +1304,9 @@ private Map> tryToAttackTerritories(final List 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 + final List alreadyAttackedWithUnits = + ProTransportUtils.getMovedUnits(alreadyMovedUnits, attackMap); for (final ProTransport proTransportData : transportMapList) { if (proTransportData.getTransport().equals(transport)) { diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java index 4a1b55ed053..1c2096e05e6 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/ProNonCombatMoveAi.java @@ -607,12 +607,12 @@ private void moveUnitsToDefendTerritories(final List prioritizedTe // Sort units by number of defend options and cost final Map> sortedUnitMoveOptions = ProSortMoveOptionsUtils.sortUnitMoveOptions(unitDefendOptions); + final List addedUnits = new ArrayList<>(); // Set enough units in territories to have at least a chance of winning - for (final Iterator it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitMoveOptions.keySet()) { final boolean isAirUnit = UnitAttachment.get(unit.getType()).getIsAir(); - if (isAirUnit || Matches.unitIsCarrier().test(unit)) { + if (isAirUnit || Matches.unitIsCarrier().test(unit) || addedUnits.contains(unit)) { continue; // skip air and carrier units } final TreeMap estimatesMap = new TreeMap<>(); @@ -628,15 +628,16 @@ private void moveUnitsToDefendTerritories(final List prioritizedTe } if (!estimatesMap.isEmpty() && estimatesMap.lastKey() > 60) { final Territory minWinTerritory = estimatesMap.lastEntry().getValue(); - moveMap.get(minWinTerritory).addTempUnit(unit); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, moveMap); + moveMap.get(minWinTerritory).addTempUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } + sortedUnitMoveOptions.keySet().removeAll(addedUnits); // Set non-air units in territories - for (final Iterator it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); - if (Matches.unitCanLandOnCarrier().test(unit)) { + for (final Unit unit : sortedUnitMoveOptions.keySet()) { + if (Matches.unitCanLandOnCarrier().test(unit) || addedUnits.contains(unit)) { continue; } Territory maxWinTerritory = null; @@ -662,9 +663,10 @@ private void moveUnitsToDefendTerritories(final List prioritizedTe } } if (maxWinTerritory != null) { - moveMap.get(maxWinTerritory).addTempUnit(unit); moveMap.get(maxWinTerritory).setBattleResult(null); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(unit, moveMap); + moveMap.get(maxWinTerritory).addTempUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); // If carrier has dependent allied fighters then move them too if (Matches.unitIsCarrier().test(unit)) { @@ -677,10 +679,10 @@ private void moveUnitsToDefendTerritories(final List prioritizedTe } } } + sortedUnitMoveOptions.keySet().removeAll(addedUnits); // Set air units in territories - for (final Iterator it = sortedUnitMoveOptions.keySet().iterator(); it.hasNext();) { - final Unit unit = it.next(); + for (final Unit unit : sortedUnitMoveOptions.keySet()) { Territory maxWinTerritory = null; double maxWinPercentage = -1; for (final Territory t : sortedUnitMoveOptions.get(unit)) { @@ -716,9 +718,10 @@ private void moveUnitsToDefendTerritories(final List prioritizedTe if (maxWinTerritory != null) { moveMap.get(maxWinTerritory).addTempUnit(unit); moveMap.get(maxWinTerritory).setBattleResult(null); - it.remove(); + addedUnits.add(unit); } } + sortedUnitMoveOptions.keySet().removeAll(addedUnits); // Loop through all my transports and see which territories they can defend from current list final List alreadyMovedTransports = new ArrayList<>(); @@ -1033,6 +1036,7 @@ private void moveUnitsToBestTerritories() { t.getTempAmphibAttackMap().clear(); t.setBattleResult(null); } + ProLogger.debug("Move amphib units"); // Transport amphib units to best territory @@ -1154,6 +1158,7 @@ private void moveUnitsToBestTerritories() { it.remove(); } } + ProLogger.debug("Move empty transports to best loading territory"); // Move remaining transports to best loading territory if safe @@ -1236,6 +1241,7 @@ private void moveUnitsToBestTerritories() { } } } + ProLogger.debug("Move remaining transports to safest territory"); // Move remaining transports to safest territory @@ -1327,6 +1333,7 @@ private void moveUnitsToBestTerritories() { } } } + ProLogger.debug("Move sea units"); // Move sea units to defend transports @@ -1558,13 +1565,14 @@ private void moveUnitsToBestTerritories() { t.getTempUnits().clear(); t.getTempAmphibAttackMap().clear(); } + ProLogger.info("Move land units"); // Move land units to territory with highest value and highest transport capacity // TODO: consider if territory ends up being safe - for (final Iterator it = unitMoveMap.keySet().iterator(); it.hasNext();) { - final Unit u = it.next(); - if (Matches.unitIsLand().test(u)) { + final List addedUnits = new ArrayList<>(); + for (final Unit u : unitMoveMap.keySet()) { + if (Matches.unitIsLand().test(u) && !addedUnits.contains(u)) { Territory maxValueTerritory = null; double maxValue = 0; int maxNeedAmphibUnitValue = Integer.MIN_VALUE; @@ -1630,19 +1638,20 @@ private void moveUnitsToBestTerritories() { if (maxValueTerritory != null) { ProLogger.trace(u + " moved to " + maxValueTerritory + " with value=" + maxValue + ", numNeededTransportUnits=" + maxNeedAmphibUnitValue); - moveMap.get(maxValueTerritory).addUnit(u); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(u, moveMap); + moveMap.get(maxValueTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } } + unitMoveMap.keySet().removeAll(addedUnits); // Move land units towards nearest factory that is adjacent to the sea final Set myFactoriesAdjacentToSea = new HashSet<>(CollectionUtils.getMatches(data.getMap().getTerritories(), ProMatches.territoryHasInfraFactoryAndIsOwnedLandAdjacentToSea(player, data))); - for (final Iterator it = unitMoveMap.keySet().iterator(); it.hasNext();) { - final Unit u = it.next(); - if (Matches.unitIsLand().test(u)) { + for (final Unit u : unitMoveMap.keySet()) { + if (Matches.unitIsLand().test(u) && !addedUnits.contains(u)) { int minDistance = Integer.MAX_VALUE; Territory minTerritory = null; for (final Territory t : unitMoveMap.get(u)) { @@ -1663,17 +1672,19 @@ private void moveUnitsToBestTerritories() { if (minTerritory != null) { ProLogger.trace( u.getType().getName() + " moved towards closest factory adjacent to sea at " + minTerritory.getName()); - moveMap.get(minTerritory).addUnit(u); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(u, moveMap); + moveMap.get(minTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } } + unitMoveMap.keySet().removeAll(addedUnits); + ProLogger.info("Move land units to safest territory"); // Move any remaining land units to safest territory (this is rarely used) - for (final Iterator it = unitMoveMap.keySet().iterator(); it.hasNext();) { - final Unit u = it.next(); - if (Matches.unitIsLand().test(u)) { + for (final Unit u : unitMoveMap.keySet()) { + if (Matches.unitIsLand().test(u) && !addedUnits.contains(u)) { // Get all units that have already moved final List alreadyMovedUnits = moveMap.values().stream() @@ -1698,11 +1709,14 @@ private void moveUnitsToBestTerritories() { if (minTerritory != null) { ProLogger.debug(u.getType().getName() + " moved to safest territory at " + minTerritory.getName() + " with strengthDifference=" + minStrengthDifference); - moveMap.get(minTerritory).addUnit(u); - it.remove(); + final List unitsToAdd = ProTransportUtils.getUnitsToAdd(u, moveMap); + moveMap.get(minTerritory).addUnits(unitsToAdd); + addedUnits.addAll(unitsToAdd); } } } + unitMoveMap.keySet().removeAll(addedUnits); + ProLogger.info("Move air units"); // Get list of territories that can't be held diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProPurchaseOption.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProPurchaseOption.java index 555d58c9b15..5a15f8b455b 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProPurchaseOption.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProPurchaseOption.java @@ -62,6 +62,8 @@ public class ProPurchaseOption { @Getter private final boolean isTransport; @Getter + private final boolean isLandTransport; + @Getter private final boolean isCarrier; @Getter private final int carrierCapacity; @@ -107,6 +109,7 @@ public class ProPurchaseOption { isSub = unitAttachment.getIsSub(); isDestroyer = unitAttachment.getIsDestroyer(); isTransport = unitAttachment.getTransportCapacity() > 0; + isLandTransport = unitAttachment.isLandTransport(); isCarrier = unitAttachment.getCarrierCapacity() > 0; carrierCapacity = unitAttachment.getCarrierCapacity() * quantity; transportEfficiency = (double) unitAttachment.getTransportCapacity() / cost; @@ -208,8 +211,9 @@ public double getAmphibEfficiency(final GameData data, final List ownedLoc private double calculateLandDistanceFactor(final int enemyDistance) { final double distance = Math.max(0, enemyDistance - 1.5); + final int moveValue = isLandTransport ? (movement + 1) : movement; // 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 moveFactor = 1 + 2 * (Math.pow(2, moveValue - 1) - 1) / Math.pow(2, moveValue - 1); return Math.pow(moveFactor, distance / 5); } diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritory.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritory.java index e9d04a32bed..ee0d3af2fd9 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritory.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritory.java @@ -174,6 +174,10 @@ void addMaxUnit(final Unit unit) { this.maxUnits.add(unit); } + void addMaxUnits(final List units) { + this.maxUnits.addAll(units); + } + public Territory getTerritory() { return territory; } diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java index a8909922cbf..3d274df61e7 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/data/ProTerritoryManager.java @@ -704,10 +704,13 @@ private static void findLandMoveOptions(final PlayerID player, final List unitsToAdd = ProTransportUtils.findBestUnitsToLandTransport(myLandUnit, startTerritory, + moveMap.get(potentialTerritory).getMaxUnits()); + moveMap.get(potentialTerritory).addMaxUnits(unitsToAdd); } else { final ProTerritory moveTerritoryData = new ProTerritory(potentialTerritory); - moveTerritoryData.addMaxUnit(myLandUnit); + final List unitsToAdd = ProTransportUtils.findBestUnitsToLandTransport(myLandUnit, startTerritory); + moveTerritoryData.addMaxUnits(unitsToAdd); moveMap.put(potentialTerritory, moveTerritoryData); } diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java index a8760bf294a..7bf12d13cb1 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMatches.java @@ -9,6 +9,7 @@ import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.triplea.Properties; +import games.strategy.triplea.TripleAUnit; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.AbstractMoveDelegate; import games.strategy.triplea.delegate.Matches; @@ -487,4 +488,9 @@ && unitIsOwnedTransportableUnit(player) .and(Matches.unitIsBeingTransported().negate()) .test(u); } + + public static Predicate unitHasLessMovementThan(final Unit unit) { + return u -> TripleAUnit.get(u).getMovementLeft() < TripleAUnit.get(unit).getMovementLeft(); + } + } diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java index a6d8e5083d7..25167df7cd6 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProMoveUtils.java @@ -22,6 +22,7 @@ import games.strategy.triplea.delegate.MoveValidator; import games.strategy.triplea.delegate.TransportTracker; import games.strategy.triplea.delegate.remote.IMoveDelegate; +import games.strategy.util.Tuple; /** * Pro AI move utilities. @@ -45,6 +46,7 @@ public static void calculateMoveRoutes(final PlayerID player, final List lastLandTransport = Tuple.of(null, null); for (final Unit u : attackMap.get(t).getUnits()) { // Skip amphib units @@ -62,6 +64,9 @@ public static void calculateMoveRoutes(final PlayerID player, final List unitList = new ArrayList<>(); unitList.add(u); moveUnits.add(unitList); + if (Matches.unitIsLandTransport().test(u)) { + lastLandTransport = Tuple.of(startTerritory, u); + } // If carrier has dependent allied fighters then move them too if (Matches.unitIsCarrier().test(u)) { @@ -84,6 +89,11 @@ public static void calculateMoveRoutes(final PlayerID player, final List())); + if (route == null && startTerritory.equals(lastLandTransport.getFirst())) { + route = + data.getMap().getRoute_IgnoreEnd(startTerritory, t, ProMatches.territoryCanMoveLandUnitsThrough(player, + data, lastLandTransport.getSecond(), startTerritory, isCombatMove, new ArrayList<>())); + } } else if (!unitList.isEmpty() && unitList.stream().allMatch(Matches.unitIsAir())) { // Air unit diff --git a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProTransportUtils.java b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProTransportUtils.java index 6e2e64fd3d1..a4c941e9986 100644 --- a/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProTransportUtils.java +++ b/game-core/src/main/java/games/strategy/triplea/ai/pro/util/ProTransportUtils.java @@ -3,19 +3,23 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; +import games.strategy.triplea.TripleAUnit; import games.strategy.triplea.ai.AiUtils; import games.strategy.triplea.ai.pro.ProData; import games.strategy.triplea.ai.pro.data.ProPurchaseOption; import games.strategy.triplea.ai.pro.data.ProTerritory; +import games.strategy.triplea.attachments.TechAttachment; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.attachments.UnitSupportAttachment; import games.strategy.triplea.delegate.AirMovementValidator; @@ -95,27 +99,7 @@ public static List getUnitsToTransportFromTerritories(final PlayerID playe units.removeAll(unitsToIgnore); // Sort units by attack - units.sort((o1, o2) -> { - - // Very rough way to add support power - final Set supportAttachments1 = UnitSupportAttachment.get(o1.getType()); - int maxSupport1 = 0; - for (final UnitSupportAttachment usa : supportAttachments1) { - if (usa.getAllied() && usa.getOffence() && usa.getBonus() > maxSupport1) { - maxSupport1 = usa.getBonus(); - } - } - final int attack1 = UnitAttachment.get(o1.getType()).getAttack(player) + maxSupport1; - final Set supportAttachments2 = UnitSupportAttachment.get(o2.getType()); - int maxSupport2 = 0; - for (final UnitSupportAttachment usa : supportAttachments2) { - if (usa.getAllied() && usa.getOffence() && usa.getBonus() > maxSupport2) { - maxSupport2 = usa.getBonus(); - } - } - final int attack2 = UnitAttachment.get(o2.getType()).getAttack(player) + maxSupport2; - return attack2 - attack1; - }); + units.sort(getDecreasingAttackComparator(player)); // Get best units that can be loaded selectedUnits.addAll(selectUnitsToTransportFromList(transport, units)); @@ -148,6 +132,83 @@ public static int findUnitsTransportCost(final List units) { return transportCost; } + public static List getUnitsToAdd(final Unit unit, final Map moveMap) { + return getUnitsToAdd(unit, new ArrayList<>(), moveMap); + } + + public static List getUnitsToAdd(final Unit unit, final List alreadyMovedUnits, + final Map moveMap) { + final List movedUnits = getMovedUnits(alreadyMovedUnits, moveMap); + return findBestUnitsToLandTransport(unit, ProData.unitTerritoryMap.get(unit), + movedUnits); + } + + public static List getMovedUnits(final List alreadyMovedUnits, + final Map attackMap) { + final List movedUnits = new ArrayList<>(alreadyMovedUnits); + movedUnits.addAll(attackMap.values().stream() + .map(ProTerritory::getAllDefenders) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + return movedUnits; + } + + public static List findBestUnitsToLandTransport(final Unit unit, final Territory t) { + return findBestUnitsToLandTransport(unit, t, new ArrayList<>()); + } + + /** + * Check if unit is can land transport and if there are any unused units that could be transported. + */ + public static List findBestUnitsToLandTransport(final Unit unit, final Territory t, + final List usedUnits) { + if (usedUnits.contains(unit)) { + return new ArrayList<>(); + } + final PlayerID player = unit.getOwner(); + final List units = t.getUnits().getMatches(Matches.unitIsOwnedBy(player) + .and(Matches.unitIsLandTransportable()).and(ProMatches.unitHasLessMovementThan(unit))); + units.removeAll(usedUnits); + if (Matches.unitIsLandTransport().negate().test(unit) || !TechAttachment.isMechanizedInfantry(player) + || units.isEmpty()) { + return Collections.singletonList(unit); + } + units.sort(Comparator.comparingInt(u -> TripleAUnit.get(u).getMovementLeft()) + .thenComparing(getDecreasingAttackComparator(player))); + final List results = new ArrayList(); + results.add(unit); + if (Matches.unitIsLandTransportWithoutCapacity().test(unit)) { + results.add(units.get(0)); + } else { + results.addAll(selectUnitsToTransportFromList(unit, units)); + } + return results; + } + + private static Comparator getDecreasingAttackComparator(final PlayerID player) { + return (o1, o2) -> { + + // Very rough way to add support power + final Set supportAttachments1 = UnitSupportAttachment.get(o1.getType()); + int maxSupport1 = 0; + for (final UnitSupportAttachment usa : supportAttachments1) { + if (usa.getAllied() && usa.getOffence() && usa.getBonus() > maxSupport1) { + maxSupport1 = usa.getBonus(); + } + } + final int attack1 = UnitAttachment.get(o1.getType()).getAttack(player) + maxSupport1; + final Set supportAttachments2 = UnitSupportAttachment.get(o2.getType()); + int maxSupport2 = 0; + for (final UnitSupportAttachment usa : supportAttachments2) { + if (usa.getAllied() && usa.getOffence() && usa.getBonus() > maxSupport2) { + maxSupport2 = usa.getBonus(); + } + } + final int attack2 = UnitAttachment.get(o2.getType()).getAttack(player) + maxSupport2; + return attack2 - attack1; + }; + } + public static List getAirThatCantLandOnCarrier(final PlayerID player, final Territory t, final List units) { final GameData data = ProData.getData(); diff --git a/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java b/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java index 9249a104bfe..34b5c89ce8e 100644 --- a/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java +++ b/game-core/src/main/java/games/strategy/triplea/delegate/Matches.java @@ -455,7 +455,7 @@ public static Predicate unitCanBlitz() { return unit -> UnitAttachment.get(unit.getType()).getCanBlitz(unit.getOwner()); } - static Predicate unitIsLandTransport() { + public static Predicate unitIsLandTransport() { return unit -> UnitAttachment.get(unit.getType()).getIsLandTransport(); } @@ -463,7 +463,7 @@ static Predicate unitIsLandTransportWithCapacity() { return unit -> unitIsLandTransport().and(unitCanTransport()).test(unit); } - static Predicate unitIsLandTransportWithoutCapacity() { + public static Predicate unitIsLandTransportWithoutCapacity() { return unit -> unitIsLandTransport().and(unitCanTransport().negate()).test(unit); }