package games.strategy.triplea.ai.proAI.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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.engine.data.Unit; import games.strategy.triplea.TripleAUnit; import games.strategy.triplea.ai.proAI.ProData; import games.strategy.triplea.ai.proAI.data.ProTerritory; import games.strategy.triplea.ai.proAI.logging.ProLogger; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.MoveValidator; import games.strategy.triplea.delegate.TransportTracker; import games.strategy.triplea.delegate.remote.IMoveDelegate; import games.strategy.util.Match; /** * Pro AI move utilities. */ public class ProMoveUtils { public static void calculateMoveRoutes(final PlayerID player, final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final Map<Territory, ProTerritory> attackMap, final boolean isCombatMove) { final GameData data = ProData.getData(); // Find all amphib units final Set<Unit> amphibUnits = new HashSet<>(); for (final Territory t : attackMap.keySet()) { amphibUnits.addAll(attackMap.get(t).getAmphibAttackMap().keySet()); for (final Unit transport : attackMap.get(t).getAmphibAttackMap().keySet()) { amphibUnits.addAll(attackMap.get(t).getAmphibAttackMap().get(transport)); } } // Loop through all territories to attack for (final Territory t : attackMap.keySet()) { // Loop through each unit that is attacking the current territory for (final Unit u : attackMap.get(t).getUnits()) { // Skip amphib units if (amphibUnits.contains(u)) { continue; } // Skip if unit is already in move to territory final Territory startTerritory = ProData.unitTerritoryMap.get(u); if (startTerritory == null || startTerritory.equals(t)) { continue; } // Add unit to move list final List<Unit> unitList = new ArrayList<>(); unitList.add(u); moveUnits.add(unitList); // If carrier has dependent allied fighters then move them too if (Matches.UnitIsCarrier.match(u)) { final Map<Unit, Collection<Unit>> carrierMustMoveWith = MoveValidator.carrierMustMoveWith(startTerritory.getUnits().getUnits(), startTerritory, data, player); if (carrierMustMoveWith.containsKey(u)) { unitList.addAll(carrierMustMoveWith.get(u)); } } // Determine route and add to move list Route route = null; if (Match.someMatch(unitList, Matches.UnitIsSea)) { // Sea unit (including carriers with planes) route = data.getMap().getRoute_IgnoreEnd(startTerritory, t, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); } else if (Match.allMatch(unitList, Matches.UnitIsLand)) { // Land unit route = data.getMap().getRoute_IgnoreEnd(startTerritory, t, ProMatches .territoryCanMoveLandUnitsThrough(player, data, u, startTerritory, isCombatMove, new ArrayList<>())); } else if (Match.allMatch(unitList, Matches.UnitIsAir)) { // Air unit route = data.getMap().getRoute_IgnoreEnd(startTerritory, t, ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, isCombatMove)); } if (route == null) { ProLogger.warn(data.getSequence().getRound() + "-" + data.getSequence().getStep().getName() + ": route is null " + startTerritory + " to " + t + ", units=" + unitList); } moveRoutes.add(route); } } } public static void calculateAmphibRoutes(final PlayerID player, final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final List<Collection<Unit>> transportsToLoad, final Map<Territory, ProTerritory> attackMap, final boolean isCombatMove) { final GameData data = ProData.getData(); // Loop through all territories to attack for (final Territory t : attackMap.keySet()) { // Loop through each amphib attack map final Map<Unit, List<Unit>> amphibAttackMap = attackMap.get(t).getAmphibAttackMap(); for (final Unit transport : amphibAttackMap.keySet()) { int movesLeft = TripleAUnit.get(transport).getMovementLeft(); Territory transportTerritory = ProData.unitTerritoryMap.get(transport); // Check if units are already loaded or not final List<Unit> loadedUnits = new ArrayList<>(); final List<Unit> remainingUnitsToLoad = new ArrayList<>(); if (TransportTracker.isTransporting(transport)) { loadedUnits.addAll(amphibAttackMap.get(transport)); } else { remainingUnitsToLoad.addAll(amphibAttackMap.get(transport)); } // Load units and move transport while (movesLeft >= 0) { // Load adjacent units if no enemies present in transport territory if (Matches.territoryHasEnemyUnits(player, data).invert().match(transportTerritory)) { final List<Unit> unitsToRemove = new ArrayList<>(); for (final Unit amphibUnit : remainingUnitsToLoad) { if (data.getMap().getDistance(transportTerritory, ProData.unitTerritoryMap.get(amphibUnit)) == 1) { moveUnits.add(Collections.singletonList(amphibUnit)); transportsToLoad.add(Collections.singletonList(transport)); final Route route = new Route(ProData.unitTerritoryMap.get(amphibUnit), transportTerritory); moveRoutes.add(route); unitsToRemove.add(amphibUnit); loadedUnits.add(amphibUnit); } } for (final Unit u : unitsToRemove) { remainingUnitsToLoad.remove(u); } } // Move transport if I'm not already at the end or out of moves final Territory unloadTerritory = attackMap.get(t).getTransportTerritoryMap().get(transport); int distanceFromEnd = data.getMap().getDistance(transportTerritory, t); if (t.isWater()) { distanceFromEnd++; } if (movesLeft > 0 && (distanceFromEnd > 1 || !remainingUnitsToLoad.isEmpty() || (unloadTerritory != null && !unloadTerritory.equals(transportTerritory)))) { final Set<Territory> neighbors = data.getMap().getNeighbors(transportTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); Territory territoryToMoveTo = null; int minUnitDistance = Integer.MAX_VALUE; int maxDistanceFromEnd = Integer.MIN_VALUE; // Used to move to farthest away loading territory first for (final Territory neighbor : neighbors) { if (MoveValidator.validateCanal(new Route(transportTerritory, neighbor), Collections.singletonList(transport), player, data) != null) { continue; } int distanceFromUnloadTerritory = 0; if (unloadTerritory != null) { distanceFromUnloadTerritory = data.getMap().getDistance_IgnoreEndForCondition(neighbor, unloadTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); } int neighborDistanceFromEnd = data.getMap().getDistance_IgnoreEndForCondition(neighbor, t, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, isCombatMove)); if (t.isWater()) { neighborDistanceFromEnd++; } int maxUnitDistance = 0; for (final Unit u : remainingUnitsToLoad) { final int distance = data.getMap().getDistance(neighbor, ProData.unitTerritoryMap.get(u)); if (distance > maxUnitDistance) { maxUnitDistance = distance; } } if (neighborDistanceFromEnd <= movesLeft && maxUnitDistance <= minUnitDistance && distanceFromUnloadTerritory < movesLeft && (maxUnitDistance < minUnitDistance || (maxUnitDistance > 1 && neighborDistanceFromEnd > maxDistanceFromEnd) || (maxUnitDistance <= 1 && neighborDistanceFromEnd < maxDistanceFromEnd))) { territoryToMoveTo = neighbor; minUnitDistance = maxUnitDistance; if (neighborDistanceFromEnd > maxDistanceFromEnd) { maxDistanceFromEnd = neighborDistanceFromEnd; } } } if (territoryToMoveTo != null) { final List<Unit> unitsToMove = new ArrayList<>(); unitsToMove.add(transport); unitsToMove.addAll(loadedUnits); moveUnits.add(unitsToMove); transportsToLoad.add(null); final Route route = new Route(transportTerritory, territoryToMoveTo); moveRoutes.add(route); transportTerritory = territoryToMoveTo; } } movesLeft--; } if (!remainingUnitsToLoad.isEmpty()) { ProLogger.warn(data.getSequence().getRound() + "-" + data.getSequence().getStep().getName() + ": " + t + ", remainingUnitsToLoad=" + remainingUnitsToLoad); } // Set territory transport is moving to attackMap.get(t).getTransportTerritoryMap().put(transport, transportTerritory); // Unload transport if (!loadedUnits.isEmpty() && !t.isWater()) { moveUnits.add(loadedUnits); transportsToLoad.add(null); final Route route = new Route(transportTerritory, t); moveRoutes.add(route); } } } } public static void calculateBombardMoveRoutes(final PlayerID player, final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final Map<Territory, ProTerritory> attackMap) { final GameData data = ProData.getData(); // Loop through all territories to attack for (final Territory t : attackMap.keySet()) { // Loop through each unit that is attacking the current territory for (final Unit u : attackMap.get(t).getBombardTerritoryMap().keySet()) { final Territory bombardFromTerritory = attackMap.get(t).getBombardTerritoryMap().get(u); // Skip if unit is already in move to territory final Territory startTerritory = ProData.unitTerritoryMap.get(u); if (startTerritory.equals(bombardFromTerritory)) { continue; } // Add unit to move list final List<Unit> unitList = new ArrayList<>(); unitList.add(u); moveUnits.add(unitList); // Determine route and add to move list Route route = null; if (Match.allMatch(unitList, ProMatches.unitCanBeMovedAndIsOwnedSea(player, true))) { // Naval unit route = data.getMap().getRoute_IgnoreEnd(startTerritory, bombardFromTerritory, ProMatches.territoryCanMoveSeaUnitsThrough(player, data, true)); } moveRoutes.add(route); } } } public static void calculateBombingRoutes(final PlayerID player, final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final Map<Territory, ProTerritory> attackMap) { final GameData data = ProData.getData(); // Loop through all territories to attack for (final Territory t : attackMap.keySet()) { // Loop through each unit that is attacking the current territory for (final Unit u : attackMap.get(t).getBombers()) { // Skip if unit is already in move to territory final Territory startTerritory = ProData.unitTerritoryMap.get(u); if (startTerritory == null || startTerritory.equals(t)) { continue; } // Add unit to move list final List<Unit> unitList = new ArrayList<>(); unitList.add(u); moveUnits.add(unitList); // Determine route and add to move list Route route = null; if (Match.allMatch(unitList, Matches.UnitIsAir)) { route = data.getMap().getRoute_IgnoreEnd(startTerritory, t, ProMatches.territoryCanMoveAirUnitsAndNoAA(player, data, true)); } moveRoutes.add(route); } } } public static void doMove(final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final IMoveDelegate moveDel) { doMove(moveUnits, moveRoutes, null, moveDel); } public static void doMove(final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final List<Collection<Unit>> transportsToLoad, final IMoveDelegate moveDel) { final GameData data = ProData.getData(); // Group non-amphib units of the same type moving on the same route if (transportsToLoad == null) { for (int i = 0; i < moveRoutes.size(); i++) { final Route r = moveRoutes.get(i); for (int j = i + 1; j < moveRoutes.size(); j++) { final Route r2 = moveRoutes.get(j); if (r.equals(r2)) { moveUnits.get(j).addAll(moveUnits.get(i)); moveUnits.remove(i); moveRoutes.remove(i); i--; break; } } } } // Move units for (int i = 0; i < moveRoutes.size(); i++) { if (!ProData.isSimulation) { ProUtils.pause(); } if (moveRoutes.get(i) == null || moveRoutes.get(i).getEnd() == null || moveRoutes.get(i).getStart() == null) { ProLogger.warn(data.getSequence().getRound() + "-" + data.getSequence().getStep().getName() + ": route not valid " + moveRoutes.get(i) + " units: " + moveUnits.get(i)); continue; } String result; if (transportsToLoad == null || transportsToLoad.get(i) == null) { result = moveDel.move(moveUnits.get(i), moveRoutes.get(i)); } else { result = moveDel.move(moveUnits.get(i), moveRoutes.get(i), transportsToLoad.get(i)); } if (result != null) { ProLogger.warn(data.getSequence().getRound() + "-" + data.getSequence().getStep().getName() + ": could not move " + moveUnits.get(i) + " over " + moveRoutes.get(i) + " because: " + result); } } } }