package games.strategy.triplea.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import games.strategy.engine.data.Route;
import games.strategy.engine.data.Unit;
import games.strategy.triplea.TripleAUnit;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
public class TransportUtils {
/**
* Returns a map of unit -> transport.
*/
public static Map<Unit, Unit> mapTransports(final Route route, final Collection<Unit> units,
final Collection<Unit> transportsToLoad) {
if (route.isLoad()) {
return mapTransportsToLoad(units, transportsToLoad);
}
if (route.isUnload()) {
return mapTransportsAlreadyLoaded(units, route.getStart().getUnits().getUnits());
}
return mapTransportsAlreadyLoaded(units, units);
}
/**
* Returns a map of unit -> transport. Tries to load units evenly across all transports.
*/
public static Map<Unit, Unit> mapTransportsToLoad(final Collection<Unit> units,
final Collection<Unit> transports) {
final List<Unit> canBeTransported = sortByTransportCostDescending(units);
final List<Unit> canTransport = sortByTransportCapacityDescendingThenMovesDescending(transports);
// Add units to transports evenly
final Map<Unit, Unit> mapping = new HashMap<>();
final IntegerMap<Unit> addedLoad = new IntegerMap<>();
for (final Unit unit : canBeTransported) {
final Optional<Unit> transport = loadUnitIntoFirstAvailableTransport(unit, canTransport, mapping, addedLoad);
// Move loaded transport to end of list
if (transport.isPresent()) {
canTransport.remove(transport.get());
canTransport.add(transport.get());
}
}
return mapping;
}
/**
* Returns a map of unit -> transport. Tries load max units into each transport before moving to next.
*/
public static Map<Unit, Unit> mapTransportsToLoadUsingMinTransports(final Collection<Unit> units,
final Collection<Unit> transports) {
final List<Unit> canBeTransported = sortByTransportCostDescending(units);
final List<Unit> canTransport = sortByTransportCapacityDescendingThenMovesDescending(transports);
final Map<Unit, Unit> mapping = new HashMap<>();
Optional<Unit> finalTransport = Optional.empty();
for (final Unit currentTransport : canTransport) {
// Check if remaining units can all be loaded into 1 transport
final int capacity = TransportTracker.getAvailableCapacity(currentTransport);
final int remainingCost = getTransportCost(canBeTransported);
if (remainingCost <= capacity) {
if (!finalTransport.isPresent() || capacity < TransportTracker.getAvailableCapacity(finalTransport.get())) {
finalTransport = Optional.of(currentTransport);
}
continue; // Check all transports to find the one with the least remaining capacity that can fit all units
}
// Check if we've found the final transport to load remaining units
if (finalTransport.isPresent()) {
break;
}
loadMaxUnits(currentTransport, canBeTransported, mapping);
}
// Load remaining units in final transport
if (finalTransport.isPresent()) {
loadMaxUnits(finalTransport.get(), canBeTransported, mapping);
}
return mapping;
}
/**
* Returns a map of unit -> transport. Unit must already be loaded in the transport.
*/
public static Map<Unit, Unit> mapTransportsAlreadyLoaded(final Collection<Unit> units,
final Collection<Unit> transports) {
final Collection<Unit> canBeTransported = Match.getMatches(units, Matches.UnitCanBeTransported);
final Collection<Unit> canTransport = Match.getMatches(transports, Matches.UnitCanTransport);
final Map<Unit, Unit> mapping = new HashMap<>();
for (final Unit currentTransported : canBeTransported) {
final Unit transport = TransportTracker.transportedBy(currentTransported);
// Already being transported, make sure it is in transports
if (transport == null || !canTransport.contains(transport)) {
continue;
}
mapping.put(currentTransported, transport);
}
return mapping;
}
/**
* Returns list of transports. Transports must contain all units. Can swap units with equivalent state in order to
* minimize transports used to unload.
*/
public static Set<Unit> findMinTransportsToUnload(final Collection<Unit> units, final Collection<Unit> transports) {
final Set<Unit> result = new HashSet<>();
Map<Unit, List<Unit>> unitToPotentialTransports = findTransportsThatUnitsCouldUnloadFrom(units, transports);
while (!unitToPotentialTransports.isEmpty()) {
unitToPotentialTransports = sortByTransportOptionsAscending(unitToPotentialTransports);
final Unit currentUnit = unitToPotentialTransports.keySet().iterator().next();
final Unit selectedTransport = findOptimalTransportToUnloadFrom(currentUnit, unitToPotentialTransports);
unitToPotentialTransports = removeTransportAndLoadedUnits(selectedTransport, unitToPotentialTransports);
result.add(selectedTransport);
}
return result;
}
public static List<Unit> findUnitsToLoadOnAirTransports(final Collection<Unit> units,
final Collection<Unit> transports) {
final Collection<Unit> airTransports = Match.getMatches(transports, Matches.UnitIsAirTransport);
final List<Unit> canBeTransported = sortByTransportCostDescending(units);
// Define the max of all units that could be loaded
final List<Unit> totalLoad = new ArrayList<>();
// Get a list of the unit categories
final Collection<UnitCategory> unitTypes = UnitSeperator.categorize(canBeTransported, null, false, true);
final Collection<UnitCategory> transportTypes = UnitSeperator.categorize(airTransports, null, false, false);
for (final UnitCategory unitType : unitTypes) {
final int transportCost = unitType.getTransportCost();
for (final UnitCategory transportType : transportTypes) {
final int transportCapacity = UnitAttachment.get(transportType.getType()).getTransportCapacity();
if (transportCost > 0 && transportCapacity >= transportCost) {
final int transportCount = Match.countMatches(airTransports, Matches.unitIsOfType(transportType.getType()));
final int ttlTransportCapacity = transportCount * (int) Math.floor(transportCapacity / transportCost);
totalLoad.addAll(Match.getNMatches(canBeTransported, ttlTransportCapacity,
Matches.unitIsOfType(unitType.getType())));
}
}
}
return totalLoad;
}
public static int getTransportCost(final Collection<Unit> units) {
if (units == null) {
return 0;
}
int cost = 0;
final Iterator<Unit> iter = units.iterator();
while (iter.hasNext()) {
final Unit item = iter.next();
cost += UnitAttachment.get(item.getType()).getTransportCost();
}
return cost;
}
private static List<Unit> sortByTransportCapacityDescendingThenMovesDescending(final Collection<Unit> transports) {
final Comparator<Unit> transportCapacityComparator = (o1, o2) -> {
final int capacityLeft1 = TransportTracker.getAvailableCapacity(o1);
final int capacityLeft2 = TransportTracker.getAvailableCapacity(o2);
if (capacityLeft1 != capacityLeft2) {
return Integer.compare(capacityLeft2, capacityLeft1);
}
final int movementLeft1 = TripleAUnit.get(o1).getMovementLeft();
final int movementLeft2 = TripleAUnit.get(o2).getMovementLeft();
return Integer.compare(movementLeft2, movementLeft1);
};
final List<Unit> canTransport = Match.getMatches(transports, Matches.UnitCanTransport);
Collections.sort(canTransport, transportCapacityComparator);
return canTransport;
}
private static List<Unit> sortByTransportCostDescending(final Collection<Unit> units) {
final Comparator<Unit> transportCostComparator = (o1, o2) -> {
final int cost1 = UnitAttachment.get((o1).getUnitType()).getTransportCost();
final int cost2 = UnitAttachment.get((o2).getUnitType()).getTransportCost();
return Integer.compare(cost2, cost1);
};
final List<Unit> canBeTransported = Match.getMatches(units, Matches.UnitCanBeTransported);
Collections.sort(canBeTransported, transportCostComparator);
return canBeTransported;
}
private static Optional<Unit> loadUnitIntoFirstAvailableTransport(final Unit unit, final List<Unit> canTransport,
final Map<Unit, Unit> mapping, final IntegerMap<Unit> addedLoad) {
final int cost = UnitAttachment.get((unit).getType()).getTransportCost();
for (final Unit transport : canTransport) {
final int capacity = TransportTracker.getAvailableCapacity(transport) - addedLoad.getInt(transport);
if (capacity >= cost) {
addedLoad.add(transport, cost);
mapping.put(unit, transport);
return Optional.of(transport);
}
}
return Optional.empty();
}
private static void loadMaxUnits(final Unit transport, final List<Unit> canBeTransported,
final Map<Unit, Unit> mapping) {
int capacity = TransportTracker.getAvailableCapacity(transport);
for (final Iterator<Unit> it = canBeTransported.iterator(); it.hasNext();) {
final Unit unit = it.next();
final int cost = UnitAttachment.get((unit).getType()).getTransportCost();
if (capacity >= cost) {
capacity -= cost;
mapping.put(unit, transport);
it.remove();
}
}
}
private static Map<Unit, List<Unit>> findTransportsThatUnitsCouldUnloadFrom(final Collection<Unit> units,
final Collection<Unit> transports) {
final List<Unit> canBeTransported = Match.getMatches(units, Matches.UnitCanBeTransported);
final List<Unit> canTransport = Match.getMatches(transports, Matches.UnitCanTransport);
final Map<Unit, List<Unit>> result = new LinkedHashMap<>();
for (final Unit unit : canBeTransported) {
final List<Unit> transportOptions = new ArrayList<>();
for (final Unit transport : canTransport) {
if (containsEquivalentUnit(unit, TransportTracker.transporting(transport))) {
transportOptions.add(transport);
}
}
result.put(unit, transportOptions);
}
return result;
}
private static Map<Unit, List<Unit>> sortByTransportOptionsAscending(
final Map<Unit, List<Unit>> unitToPotentialTransports) {
final Map<Unit, List<Unit>> result = new LinkedHashMap<>();
unitToPotentialTransports.entrySet().stream()
.sorted((o1, o2) -> o1.getValue().size() - o2.getValue().size())
.forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
return result;
}
private static Unit findOptimalTransportToUnloadFrom(final Unit unit,
final Map<Unit, List<Unit>> unitToPotentialTransports) {
double minAverageTransportOptions = Integer.MAX_VALUE;
Unit selectedTransport = unitToPotentialTransports.get(unit).get(0);
for (final Unit transport : unitToPotentialTransports.get(unit)) {
int transportOptions = 0;
for (final Unit loadedUnit : TransportTracker.transporting(transport)) {
if (containsEquivalentUnit(loadedUnit, unitToPotentialTransports.keySet())) {
final Unit equivalentUnit = getEquivalentUnit(loadedUnit, unitToPotentialTransports.keySet());
transportOptions += unitToPotentialTransports.get(equivalentUnit).size();
} else {
transportOptions = Integer.MAX_VALUE;
break;
}
}
final double averageTransportOptions =
(double) transportOptions / TransportTracker.transporting(transport).size();
if (averageTransportOptions < minAverageTransportOptions) {
minAverageTransportOptions = averageTransportOptions;
selectedTransport = transport;
}
}
return selectedTransport;
}
private static Map<Unit, List<Unit>> removeTransportAndLoadedUnits(final Unit transport,
final Map<Unit, List<Unit>> unitToPotentialTransports) {
for (final Unit loadedUnit : TransportTracker.transporting(transport)) {
if (containsEquivalentUnit(loadedUnit, unitToPotentialTransports.keySet())) {
final Unit unit = getEquivalentUnit(loadedUnit, unitToPotentialTransports.keySet());
unitToPotentialTransports.remove(unit);
}
}
unitToPotentialTransports.values().stream().forEach(t -> t.remove(transport));
return unitToPotentialTransports;
}
private static boolean containsEquivalentUnit(final Unit unit, final Collection<Unit> units) {
return units.stream().anyMatch(u -> u.isEquivalent(unit));
}
private static Unit getEquivalentUnit(final Unit unit, final Collection<Unit> units) {
return units.stream().filter(u -> u.isEquivalent(unit)).findFirst().get();
}
}