New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Transports To Load Default #647
Changes from all commits
0bdbeaa
30a6d2c
e998519
6516b75
76d0f91
5723389
be3d685
f6339b3
93c0e5d
a19dc84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -216,7 +216,7 @@ public boolean match(final Collection<Unit> units) { | |
final IntegerMap<Unit> capacityMap = new IntegerMap<Unit>(); | ||
for (final Unit transport : sortedTransports) { | ||
final Collection<Unit> transporting = TripleAUnit.get(transport).getTransporting(); | ||
capacityMap.add(transport, MoveValidator.getTransportCost(transporting)); | ||
capacityMap.add(transport, TransportUtils.getTransportCost(transporting)); | ||
} | ||
boolean hasChanged = false; | ||
final Comparator<Unit> increasingCapacityComparator = | ||
|
@@ -637,7 +637,7 @@ public boolean match(final Unit transport) { | |
capableTransports.removeAll(alliedTransports); | ||
// First, load capable transports | ||
final Map<Unit, Unit> unitsToCapableTransports = | ||
TransportUtils.mapTransports(route, availableUnits, capableTransports); | ||
TransportUtils.mapTransportsToLoadUsingMinTransports(availableUnits, capableTransports); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These 3 method updates are to hook up the new transport loading method that finds minimum number of needed transports. |
||
for (final Unit unit : unitsToCapableTransports.keySet()) { | ||
final Unit transport = unitsToCapableTransports.get(unit); | ||
final int unitCost = UnitAttachment.get(unit.getType()).getTransportCost(); | ||
|
@@ -647,7 +647,7 @@ public boolean match(final Unit transport) { | |
availableUnits.removeAll(unitsToCapableTransports.keySet()); | ||
// Next, load allied transports | ||
final Map<Unit, Unit> unitsToAlliedTransports = | ||
TransportUtils.mapTransports(route, availableUnits, alliedTransports); | ||
TransportUtils.mapTransportsToLoadUsingMinTransports(availableUnits, alliedTransports); | ||
for (final Unit unit : unitsToAlliedTransports.keySet()) { | ||
final Unit transport = unitsToAlliedTransports.get(unit); | ||
final int unitCost = UnitAttachment.get(unit.getType()).getTransportCost(); | ||
|
@@ -661,7 +661,7 @@ public boolean match(final Unit transport) { | |
// are selected, since it may not be obvious | ||
if (getSelectedEndpointTerritory() == null) { | ||
final Map<Unit, Unit> unitsToIncapableTransports = | ||
TransportUtils.mapTransports(route, availableUnits, incapableTransports); | ||
TransportUtils.mapTransportsToLoadUsingMinTransports(availableUnits, incapableTransports); | ||
for (final Unit unit : unitsToIncapableTransports.keySet()) { | ||
final Unit transport = unitsToIncapableTransports.get(unit); | ||
final int unitCost = UnitAttachment.get(unit.getType()).getTransportCost(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ | |
import games.strategy.triplea.delegate.TransportTracker; | ||
import games.strategy.util.IntegerMap; | ||
import games.strategy.util.Match; | ||
import games.strategy.util.Util; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
@@ -17,16 +16,13 @@ | |
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
|
||
public class TransportUtils { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The diff of this class is pretty much unreadable do to several methods being refactored and moved around. Suggest either looking over the final class or review commit by commit. |
||
|
||
/** | ||
* This method is static so it can be called from the client side. | ||
* | ||
* @return a map of unit -> transport (null if no mapping can be | ||
* done either because there is not sufficient transport capacity or because | ||
* a unit is not with its transport) | ||
* Returns a map of unit -> transport. | ||
*/ | ||
public static Map<Unit, Unit> mapTransports(final Route route, final Collection<Unit> units, | ||
final Collection<Unit> transportsToLoad) { | ||
|
@@ -40,125 +36,55 @@ public static Map<Unit, Unit> mapTransports(final Route route, final Collection< | |
} | ||
|
||
/** | ||
* Returns a map of unit -> transport. Unit must already be loaded in the | ||
* transport. If no units are loaded in the transports then an empty Map will | ||
* be returned. | ||
* Returns a map of unit -> transport. Tries to load units evenly across all transports. | ||
*/ | ||
private static Map<Unit, Unit> mapTransportsAlreadyLoaded(final Collection<Unit> units, | ||
public static Map<Unit, Unit> mapTransportsToLoad(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 List<Unit> canBeTransported = sortByTransportCostDescending(units); | ||
final List<Unit> canTransport = sortByTransportCapacityAscending(transports); | ||
|
||
// Add units to transports evenly | ||
final Map<Unit, Unit> mapping = new HashMap<Unit, Unit>(); | ||
final Iterator<Unit> land = canBeTransported.iterator(); | ||
while (land.hasNext()) { | ||
final Unit currentTransported = land.next(); | ||
final Unit transport = TransportTracker.transportedBy(currentTransported); | ||
final IntegerMap<Unit> addedLoad = new IntegerMap<Unit>(); | ||
for (final Unit unit : canBeTransported) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Complexity looks to be getting up there in this method. Can we push some of the lower level logic into helper methods? Java8 streams + filter operations might be another option, the inner most for loop looks like it can be simplified to a stream, filter, find_first operation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considered using streams but given the number of variables being tracked and needing to move the loaded transport to the end of the list, I couldn't think of a good way to do that. I'll take a look to see if a helper method makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ended up creating a helper method. I think it helps out a little though it isn't the best helper method as it has 4 parameters and modifies 2 of them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The helper method certainly helped |
||
final Optional<Unit> transport = loadUnitIntoFirstAvailableTransport(unit, canTransport, mapping, addedLoad); | ||
|
||
// already being transported, make sure it is in transports | ||
if (transport == null) { | ||
continue; | ||
// Move loaded transport to end of list | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of moving the loaded transport to the end of the list, could we check if it is full, and if so, remove it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I originally had that in and decided to remove it since performance wasn't really an issue. Figured its better to keep it as simple as possible. This is kind of the age old problem of having to edit a list you are looping through. Essentially after a unit is added, the transport needs to be moved to the end of the list. Any thoughts on a better way to move the loaded transport to the end of the list other than removing then adding it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO avoid the need to move it to the end. Alternatively have an index point where you start your iterations. This would mean converting your inner foreach loop into an old school style |
||
if (transport.isPresent()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect you can simplify, everything should be in memory, order nanosecond runtimes. This performance improvement strikes me each time. |
||
canTransport.remove(transport.get()); | ||
canTransport.add(transport.get()); | ||
} | ||
if (!canTransport.contains(transport)) { | ||
continue; | ||
} | ||
mapping.put(currentTransported, transport); | ||
} | ||
return mapping; | ||
} | ||
|
||
/** | ||
* Returns a map of unit -> transport. Tries to find transports to load all | ||
* units. If it can't succeed returns an empty Map. | ||
* Returns a map of unit -> transport. Tries load max units into each transport before moving to next. | ||
*/ | ||
public static Map<Unit, Unit> mapTransportsToLoad(final Collection<Unit> units, final Collection<Unit> transports) { | ||
final List<Unit> canBeTransported = Match.getMatches(units, Matches.UnitCanBeTransported); | ||
int transportIndex = 0; | ||
final Comparator<Unit> transportCostComparator = new Comparator<Unit>() { | ||
@Override | ||
public int compare(final Unit o1, final Unit o2) { | ||
final int cost1 = UnitAttachment.get((o1).getUnitType()).getTransportCost(); | ||
final int cost2 = UnitAttachment.get((o2).getUnitType()).getTransportCost(); | ||
return cost2 - cost1; | ||
} | ||
}; | ||
public static Map<Unit, Unit> mapTransportsToLoadUsingMinTransports(final Collection<Unit> units, | ||
final Collection<Unit> transports) { | ||
|
||
// fill the units with the highest cost first which allows easy loading of 2 infantry and 2 tanks on 2 transports in | ||
// WW2V2 rules | ||
Collections.sort(canBeTransported, transportCostComparator); | ||
final List<Unit> canTransport = Match.getMatches(transports, Matches.UnitCanTransport); | ||
final Comparator<Unit> transportCapacityComparator = new Comparator<Unit>() { | ||
@Override | ||
public int compare(final Unit o1, final Unit o2) { | ||
final int capacityLeft1 = TransportTracker.getAvailableCapacity(o1); | ||
final int capacityLeft2 = TransportTracker.getAvailableCapacity(o1); | ||
if (capacityLeft1 != capacityLeft2) { | ||
return capacityLeft1 - capacityLeft2; | ||
} | ||
final int capacity1 = UnitAttachment.get((o1).getUnitType()).getTransportCapacity(); | ||
final int capacity2 = UnitAttachment.get((o2).getUnitType()).getTransportCapacity(); | ||
return capacity1 - capacity2; | ||
} | ||
}; | ||
final List<Unit> canBeTransported = sortByTransportCostDescending(units); | ||
final List<Unit> canTransport = sortByTransportCapacityAscending(transports); | ||
|
||
// fill transports with the lowest capacity first | ||
Collections.sort(canTransport, transportCapacityComparator); | ||
// Add max units to each transport | ||
final Map<Unit, Unit> mapping = new HashMap<Unit, Unit>(); | ||
final IntegerMap<Unit> addedLoad = new IntegerMap<Unit>(); | ||
final Comparator<Unit> previouslyLoadedToLast = transportsThatPreviouslyUnloadedComeLast(); | ||
for (final Unit land : canBeTransported) { | ||
final UnitAttachment landUA = UnitAttachment.get(land.getType()); | ||
final int cost = landUA.getTransportCost(); | ||
boolean loaded = false; | ||
|
||
// we want to try to distribute units evenly to all the transports | ||
// if the user has 2 infantry, and selects two transports to load | ||
// we should put 1 infantry in each transport. | ||
// the algorithm below does not guarantee even distribution in all cases | ||
// but it solves most of the cases | ||
final List<Unit> shiftedToEnd = Util.shiftElementsToEnd(canTransport, transportIndex); | ||
|
||
// review the following loop in light of bug ticket 2827064- previously unloaded trns perhaps shouldn't be | ||
// included. | ||
Collections.sort(shiftedToEnd, previouslyLoadedToLast); | ||
final Iterator<Unit> transportIter = shiftedToEnd.iterator(); | ||
while (transportIter.hasNext() && !loaded) { | ||
transportIndex++; | ||
if (transportIndex >= canTransport.size()) { | ||
transportIndex = 0; | ||
} | ||
final Unit transport = transportIter.next(); | ||
int capacity = TransportTracker.getAvailableCapacity(transport); | ||
capacity -= addedLoad.getInt(transport); | ||
for (final Unit transport : canTransport) { | ||
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) { | ||
addedLoad.add(transport, cost); | ||
mapping.put(land, transport); | ||
loaded = true; | ||
capacity -= cost; | ||
mapping.put(unit, transport); | ||
it.remove(); | ||
} | ||
} | ||
} | ||
return mapping; | ||
} | ||
|
||
private static Comparator<Unit> transportsThatPreviouslyUnloadedComeLast() { | ||
return new Comparator<Unit>() { | ||
@Override | ||
public int compare(final Unit t1, final Unit t2) { | ||
if (t1 == t2 || t1.equals(t2)) { | ||
return 0; | ||
} | ||
final boolean t1previous = TransportTracker.hasTransportUnloadedInPreviousPhase(t1); | ||
final boolean t2previous = TransportTracker.hasTransportUnloadedInPreviousPhase(t2); | ||
if (t1previous == t2previous) { | ||
return 0; | ||
} | ||
if (t1previous == false) { | ||
return -1; | ||
} | ||
return 1; | ||
} | ||
}; | ||
} | ||
|
||
public static List<Unit> findUnitsToLoadOnAirTransports(final Collection<Unit> units, | ||
final Collection<Unit> transports) { | ||
final Collection<Unit> airTransports = Match.getMatches(transports, Matches.UnitIsAirTransport); | ||
|
@@ -194,4 +120,84 @@ public int compare(final Unit o1, final Unit o2) { | |
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; | ||
} | ||
|
||
/** | ||
* Returns a map of unit -> transport. Unit must already be loaded in the transport. | ||
*/ | ||
private 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<Unit, Unit>(); | ||
final Iterator<Unit> land = canBeTransported.iterator(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The complexity is not trivial in this method. Can you avoid using Iterator? Looks like a foreach or a Java8 stream+filter would both work to simplify things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually didn't touch this method. Just the mapTransportToLoad* ones. Leaving it alone for now unless I end up needing to update it for other transport enhancements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
while (land.hasNext()) { | ||
final Unit currentTransported = land.next(); | ||
final Unit transport = TransportTracker.transportedBy(currentTransported); | ||
|
||
// already being transported, make sure it is in transports | ||
if (transport == null) { | ||
continue; | ||
} | ||
if (!canTransport.contains(transport)) { | ||
continue; | ||
} | ||
mapping.put(currentTransported, transport); | ||
} | ||
return mapping; | ||
} | ||
|
||
private static List<Unit> sortByTransportCapacityAscending(final Collection<Unit> transports) { | ||
final Comparator<Unit> transportCapacityComparator = new Comparator<Unit>() { | ||
@Override | ||
public int compare(final Unit o1, final Unit o2) { | ||
final int capacityLeft1 = TransportTracker.getAvailableCapacity(o1); | ||
final int capacityLeft2 = TransportTracker.getAvailableCapacity(o2); | ||
return capacityLeft1 - capacityLeft2; | ||
} | ||
}; | ||
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 = new Comparator<Unit>() { | ||
@Override | ||
public int compare(final Unit o1, final Unit o2) { | ||
final int cost1 = UnitAttachment.get((o1).getUnitType()).getTransportCost(); | ||
final int cost2 = UnitAttachment.get((o2).getUnitType()).getTransportCost(); | ||
return cost2 - cost1; | ||
} | ||
}; | ||
final List<Unit> canBeTransported = Match.getMatches(units, Matches.UnitCanBeTransported); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RoiEXLab - example of Matching code |
||
Collections.sort(canBeTransported, transportCostComparator); | ||
return canBeTransported; | ||
} | ||
|
||
private static Optional<Unit> loadUnitIntoFirstAvailableTransport(final Unit unit, final List<Unit> canTransport, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i like the helper function 🌈 |
||
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(); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this method to TransportUtils.