package games.strategy.triplea.ai; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.ProductionFrontier; import games.strategy.engine.data.ProductionRule; import games.strategy.engine.data.Resource; import games.strategy.engine.data.Unit; import games.strategy.engine.data.UnitType; import games.strategy.triplea.Constants; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.Match; /** * Handy utility methods for the writers of an AI. */ public class AIUtils { /** * @return a comparator that sorts cheaper units before expensive ones. */ public static Comparator<Unit> getCostComparator() { return (o1, o2) -> getCost(o1.getType(), o1.getOwner(), o1.getData()) - getCost(o2.getType(), o2.getOwner(), o2.getData()); } /** * How many PU's does it cost the given player to produce the given unit type. * * <p> * If the player cannot produce the given unit, return Integer.MAX_VALUE * </p> */ static int getCost(final UnitType unitType, final PlayerID player, final GameData data) { final Resource PUs = data.getResourceList().getResource(Constants.PUS); final ProductionRule rule = getProductionRule(unitType, player); if (rule == null) { return Integer.MAX_VALUE; } else { return rule.getCosts().getInt(PUs); } } /** * Get the production rule for the given player, for the given unit type. * * <p> * If no such rule can be found, then return null. * </p> */ private static ProductionRule getProductionRule(final UnitType unitType, final PlayerID player) { final ProductionFrontier frontier = player.getProductionFrontier(); if (frontier == null) { return null; } for (final ProductionRule rule : frontier) { if (rule.getResults().getInt(unitType) == 1) { return rule; } } return null; } /** * Get a quick and dirty estimate of the strength of some units in a battle. * * @param units * - the units to measure * @param attacking * - are the units on attack or defense * @param sea * - calculate the strength of the units in a sea or land battle? */ public static float strength(final Collection<Unit> units, final boolean attacking, final boolean sea) { int strength = 0; for (final Unit u : units) { final UnitAttachment unitAttachment = UnitAttachment.get(u.getType()); if (unitAttachment.getIsInfrastructure()) { // nothing } else if (unitAttachment.getIsSea() == sea) { // 2 points since we can absorb a hit strength += 2; // two hit strength += 1.5 * unitAttachment.getHitPoints(); // the number of pips on the dice if (attacking) { strength += unitAttachment.getAttack(u.getOwner()); } else { strength += unitAttachment.getDefense(u.getOwner()); } if (attacking) { // a unit with attack of 0 isnt worth much // we dont want transports to try and gang up on subs if (unitAttachment.getAttack(u.getOwner()) == 0) { strength -= 1.2; } } } } if (attacking) { final int art = Match.countMatches(units, Matches.UnitIsArtillery); final int artSupport = Match.countMatches(units, Matches.UnitIsArtillerySupportable); strength += Math.min(art, artSupport); } return strength; } public static Unit getLastUnitMatching(final List<Unit> units, final Match<Unit> match, final int endIndex) { final int index = getIndexOfLastUnitMatching(units, match, endIndex); if (index == -1) { return null; } return units.get(index); } public static int getIndexOfLastUnitMatching(final List<Unit> units, final Match<Unit> match, final int endIndex) { for (int i = endIndex; i >= 0; i--) { final Unit unit = units.get(i); if (match.match(unit)) { return i; } } return -1; } public static List<Unit> interleaveCarriersAndPlanes(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 this is a plane if (ua.getCarrierCost() > 0) { // If we haven't ignored enough trailing planes if (processedPlaneCount < planesThatDontNeedToLand) { // Increase number of trailing planes ignored processedPlaneCount++; // And skip any processing continue; } // If this is the first carrier seek if (seekedCarrier == null) { final int seekedCarrierIndex = getIndexOfLastUnitMatching(result, new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1); if (seekedCarrierIndex == -1) { // No carriers left break; } seekedCarrier = result.get(seekedCarrierIndex); // Tell the code to insert carrier to the right of this plane indexToPlaceCarrierAt = i + 1; spaceLeftOnSeekedCarrier = UnitAttachment.get(seekedCarrier.getUnitType()).getCarrierCapacity(); } spaceLeftOnSeekedCarrier -= ua.getCarrierCost(); // If the carrier has been filled or overflowed if (spaceLeftOnSeekedCarrier <= 0) { if (spaceLeftOnSeekedCarrier < 0) { // Move current unit index up one, so we re-process this unit (since it can't fit on the current seeked // carrier) i++; } // 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); // We removed carrier in earlier part of list, so decrease index i--; filledCarriers.add(seekedCarrier); // Find the next carrier seekedCarrier = getLastUnitMatching(result, new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1); if (seekedCarrier == null) { // No carriers left break; } // 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); } } // Invert list, so they are inserted in the same order Collections.reverse(planesBetweenHereAndCarrier); 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 = getLastUnitMatching(result, new CompositeMatchAnd<>(Matches.UnitIsCarrier, Matches.isNotInList(filledCarriers)), result.size() - 1); if (seekedCarrier == null) { // No carriers left break; } // 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; } }