package games.strategy.triplea.ai.proAI.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.triplea.ai.AIUtils;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.ai.proAI.data.ProPurchaseOption;
import games.strategy.triplea.ai.proAI.data.ProTerritory;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.attachments.UnitSupportAttachment;
import games.strategy.triplea.delegate.AirMovementValidator;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.Match;
/**
* Pro AI transport utilities.
*/
public class ProTransportUtils {
public static int findMaxMovementForTransports(final List<ProPurchaseOption> seaTransportPurchaseOptions) {
int maxMovement = 2;
final int maxTransportEfficiency = 0;
for (final ProPurchaseOption ppo : seaTransportPurchaseOptions) {
if (ppo.getTransportEfficiency() > maxTransportEfficiency) {
maxMovement = ppo.getMovement();
}
}
return maxMovement;
}
public static int findNumUnitsThatCanBeTransported(final PlayerID player, final Territory t) {
final GameData data = ProData.getData();
int numUnitsToLoad = 0;
final Set<Territory> neighbors = data.getMap().getNeighbors(t, Matches.TerritoryIsLand);
for (final Territory neighbor : neighbors) {
numUnitsToLoad +=
Match.getMatches(neighbor.getUnits().getUnits(), ProMatches.unitIsOwnedTransportableUnit(player)).size();
}
return numUnitsToLoad;
}
public static List<Unit> getUnitsToTransportThatCantMoveToHigherValue(final PlayerID player, final Unit transport,
final Set<Territory> territoriesToLoadFrom, final List<Unit> unitsToIgnore,
final Map<Territory, ProTerritory> moveMap, final Map<Unit, Set<Territory>> unitMoveMap, final double value) {
final List<Unit> unitsToIgnoreOrHaveBetterLandMove = new ArrayList<>(unitsToIgnore);
if (!TransportTracker.isTransporting(transport)) {
// Get all units that can be transported
final List<Unit> units = new ArrayList<>();
for (final Territory loadFrom : territoriesToLoadFrom) {
units.addAll(
loadFrom.getUnits().getMatches(ProMatches.unitIsOwnedTransportableUnitAndCanBeLoaded(player, true)));
}
units.removeAll(unitsToIgnore);
// Check to see which have higher land move value
for (final Unit u : units) {
if (unitMoveMap.get(u) != null) {
for (final Territory t : unitMoveMap.get(u)) {
if (moveMap.get(t) != null && moveMap.get(t).getValue() > value) {
unitsToIgnoreOrHaveBetterLandMove.add(u);
break;
}
}
}
}
}
return getUnitsToTransportFromTerritories(player, transport, territoriesToLoadFrom,
unitsToIgnoreOrHaveBetterLandMove);
}
public static List<Unit> getUnitsToTransportFromTerritories(final PlayerID player, final Unit transport,
final Set<Territory> territoriesToLoadFrom, final List<Unit> unitsToIgnore) {
return getUnitsToTransportFromTerritories(player, transport, territoriesToLoadFrom, unitsToIgnore,
ProMatches.unitIsOwnedTransportableUnitAndCanBeLoaded(player, true));
}
// TODO: this needs fixed to consider whether a valid route exists to load all units
public static List<Unit> getUnitsToTransportFromTerritories(final PlayerID player, final Unit transport,
final Set<Territory> territoriesToLoadFrom, final List<Unit> unitsToIgnore, final Match<Unit> validUnitMatch) {
final List<Unit> selectedUnits = new ArrayList<>();
// Get units if transport already loaded
if (TransportTracker.isTransporting(transport)) {
selectedUnits.addAll(TransportTracker.transporting(transport));
} else {
// Get all units that can be transported
final List<Unit> units = new ArrayList<>();
for (final Territory loadFrom : territoriesToLoadFrom) {
units.addAll(loadFrom.getUnits().getMatches(validUnitMatch));
}
units.removeAll(unitsToIgnore);
// Sort units by attack
Collections.sort(units, (o1, o2) -> {
// Very rough way to add support power
final Set<UnitSupportAttachment> 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<UnitSupportAttachment> 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;
});
// Get best units that can be loaded
selectedUnits.addAll(selectUnitsToTransportFromList(transport, units));
}
return selectedUnits;
}
public static List<Unit> selectUnitsToTransportFromList(final Unit transport, final List<Unit> units) {
final List<Unit> selectedUnits = new ArrayList<>();
final int capacity = UnitAttachment.get(transport.getType()).getTransportCapacity();
int capacityCount = 0;
for (final Unit unit : units) {
final int cost = UnitAttachment.get(unit.getType()).getTransportCost();
if (cost <= (capacity - capacityCount)) {
selectedUnits.add(unit);
capacityCount += cost;
if (capacityCount >= capacity) {
break;
}
}
}
return selectedUnits;
}
public static int findUnitsTransportCost(final List<Unit> units) {
int transportCost = 0;
for (final Unit unit : units) {
transportCost += UnitAttachment.get(unit.getType()).getTransportCost();
}
return transportCost;
}
public static List<Unit> getAirThatCantLandOnCarrier(final PlayerID player, final Territory t,
final List<Unit> units) {
final GameData data = ProData.getData();
int capacity = AirMovementValidator.carrierCapacity(units, t);
final Collection<Unit> airUnits = Match.getMatches(units, ProMatches.unitIsAlliedAir(player, data));
final List<Unit> airThatCantLand = new ArrayList<>();
for (final Unit airUnit : airUnits) {
final UnitAttachment ua = UnitAttachment.get(airUnit.getType());
final int cost = ua.getCarrierCost();
if (cost != -1) {
if (cost <= capacity) {
capacity -= cost;
} else {
airThatCantLand.add(airUnit);
}
}
}
return airThatCantLand;
}
public static boolean validateCarrierCapacity(final PlayerID player, final Territory t,
final List<Unit> existingUnits, final Unit newUnit) {
final GameData data = ProData.getData();
int capacity = AirMovementValidator.carrierCapacity(existingUnits, t);
final Collection<Unit> airUnits = Match.getMatches(existingUnits, ProMatches.unitIsAlliedAir(player, data));
airUnits.add(newUnit);
for (final Unit airUnit : airUnits) {
final UnitAttachment ua = UnitAttachment.get(airUnit.getType());
final int cost = ua.getCarrierCost();
if (cost != -1) {
capacity -= cost;
}
}
return capacity >= 0;
}
public static int getUnusedLocalCarrierCapacity(final PlayerID player, final Territory t,
final List<Unit> unitsToPlace) {
final GameData data = ProData.getData();
// Find nearby carrier capacity
final Set<Territory> nearbyTerritories =
data.getMap().getNeighbors(t, 2, ProMatches.territoryCanMoveAirUnits(player, data, false));
nearbyTerritories.add(t);
final List<Unit> ownedNearbyUnits = new ArrayList<>();
int capacity = 0;
for (final Territory nearbyTerritory : nearbyTerritories) {
final List<Unit> units = nearbyTerritory.getUnits().getMatches(Matches.unitIsOwnedBy(player));
if (nearbyTerritory.equals(t)) {
units.addAll(unitsToPlace);
}
ownedNearbyUnits.addAll(units);
capacity += AirMovementValidator.carrierCapacity(units, t);
}
// Find nearby air unit carrier cost
final Collection<Unit> airUnits = Match.getMatches(ownedNearbyUnits, ProMatches.unitIsOwnedAir(player));
for (final Unit airUnit : airUnits) {
final UnitAttachment ua = UnitAttachment.get(airUnit.getType());
final int cost = ua.getCarrierCost();
if (cost != -1) {
capacity -= cost;
}
}
return capacity;
}
public static int getUnusedCarrierCapacity(final PlayerID player, final Territory t, final List<Unit> unitsToPlace) {
final List<Unit> units = new ArrayList<>(unitsToPlace);
units.addAll(t.getUnits().getUnits());
int capacity = AirMovementValidator.carrierCapacity(units, t);
final Collection<Unit> airUnits = Match.getMatches(units, ProMatches.unitIsOwnedAir(player));
for (final Unit airUnit : airUnits) {
final UnitAttachment ua = UnitAttachment.get(airUnit.getType());
final int cost = ua.getCarrierCost();
if (cost != -1) {
capacity -= cost;
}
}
return capacity;
}
public static List<Unit> InterleaveUnits_CarriersAndPlanes(final List<Unit> units,
final int planesThatDontNeedToLand) {
if (!(Match.someMatch(units, Matches.UnitIsCarrier) && Match.someMatch(units, Matches.UnitCanLandOnCarrier))) {
return units;
}
// Clone the current list
final ArrayList<Unit> result = new ArrayList<>(units);
Unit seekedCarrier = null;
int indexToPlaceCarrierAt = -1;
int spaceLeftOnSeekedCarrier = -1;
int processedPlaneCount = 0;
final List<Unit> filledCarriers = new ArrayList<>();
// Loop through all units, starting from the right, and rearrange units
for (int i = result.size() - 1; i >= 0; i--) {
final Unit unit = result.get(i);
final UnitAttachment ua = UnitAttachment.get(unit.getUnitType());
if (ua.getCarrierCost() > 0 || i == 0) { // If this is a plane or last unit
// If we haven't ignored enough trailing planes and not last unit
if (processedPlaneCount < planesThatDontNeedToLand && i > 0) {
processedPlaneCount++; // Increase number of trailing planes ignored
continue; // And skip any processing
}
// If this is the first carrier seek and not last unit
if (seekedCarrier == null && i > 0) {
final int seekedCarrierIndex = AIUtils.getIndexOfLastUnitMatching(result,
new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1);
if (seekedCarrierIndex == -1) {
break; // No carriers left
}
seekedCarrier = result.get(seekedCarrierIndex);
indexToPlaceCarrierAt = i + 1; // Tell the code to insert carrier to the right of this plane
spaceLeftOnSeekedCarrier = UnitAttachment.get(seekedCarrier.getUnitType()).getCarrierCapacity();
}
if (ua.getCarrierCost() > 0) {
spaceLeftOnSeekedCarrier -= ua.getCarrierCost();
}
// If the carrier has been filled or overflowed or last unit
if (indexToPlaceCarrierAt > 0 && (spaceLeftOnSeekedCarrier <= 0 || i == 0)) {
if (spaceLeftOnSeekedCarrier < 0) {
i++; // Increment current unit index, so we re-process this unit (since it can't fit on the current carrier)
}
// If the seeked carrier is earlier in the list
if (result.indexOf(seekedCarrier) < i) {
// Move the carrier up to the planes by: removing carrier, then reinserting it
// (index decreased cause removal of carrier reduced indexes)
result.remove(seekedCarrier);
result.add(indexToPlaceCarrierAt - 1, seekedCarrier);
i--; // We removed carrier in earlier part of list, so decrease index
filledCarriers.add(seekedCarrier);
// Find the next carrier
seekedCarrier = AIUtils.getLastUnitMatching(result,
new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1);
if (seekedCarrier == null) {
break; // No carriers left
}
// Place next carrier right before this plane (which just filled the old carrier that was just moved)
indexToPlaceCarrierAt = i;
spaceLeftOnSeekedCarrier = UnitAttachment.get(seekedCarrier.getUnitType()).getCarrierCapacity();
} else {
// If it's later in the list
final int oldIndex = result.indexOf(seekedCarrier);
int carrierPlaceLocation = indexToPlaceCarrierAt;
// Place carrier where it's supposed to go
result.remove(seekedCarrier);
if (oldIndex < indexToPlaceCarrierAt) {
carrierPlaceLocation--;
}
result.add(carrierPlaceLocation, seekedCarrier);
filledCarriers.add(seekedCarrier);
// Move the planes down to the carrier
final List<Unit> planesBetweenHereAndCarrier = new ArrayList<>();
for (int i2 = i; i2 < carrierPlaceLocation; i2++) {
final Unit unit2 = result.get(i2);
final UnitAttachment ua2 = UnitAttachment.get(unit2.getUnitType());
if (ua2.getCarrierCost() > 0) {
planesBetweenHereAndCarrier.add(unit2);
}
}
Collections.reverse(planesBetweenHereAndCarrier); // Invert list, so they are inserted in the same order
int planeMoveCount = 0;
for (final Unit plane : planesBetweenHereAndCarrier) {
result.remove(plane);
// Insert each plane right before carrier (index decreased cause removal of carrier reduced indexes)
result.add(carrierPlaceLocation - 1, plane);
planeMoveCount++;
}
// Find the next carrier
seekedCarrier = AIUtils.getLastUnitMatching(result,
new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1);
if (seekedCarrier == null) {
break; // No carriers left
}
// Since we only moved planes up, just reduce next carrier place index by plane move count
indexToPlaceCarrierAt = carrierPlaceLocation - planeMoveCount;
spaceLeftOnSeekedCarrier = UnitAttachment.get(seekedCarrier.getUnitType()).getCarrierCapacity();
}
}
}
}
return result;
}
}