use of games.strategy.engine.data.Unit in project triplea by triplea-game.
the class BattleCalculator method categorizeLowLuckAirUnits.
/**
* http://triplea.sourceforge.net/mywiki/Forum#nabble-td4658925%7Ca4658925
* returns two lists, the first list is the air units that can be evenly divided into groups of 3 or 6 (depending on
* radar)
* the second list is all the air units that do not fit in the first list
*/
private static Tuple<List<List<Unit>>, List<Unit>> categorizeLowLuckAirUnits(final Collection<Unit> units, final int groupSize) {
final Collection<UnitCategory> categorizedAir = UnitSeperator.categorize(units, null, false, true);
final List<List<Unit>> groupsOfSize = new ArrayList<>();
final List<Unit> toRoll = new ArrayList<>();
for (final UnitCategory uc : categorizedAir) {
final int remainder = uc.getUnits().size() % groupSize;
final int splitPosition = uc.getUnits().size() - remainder;
final List<Unit> group = new ArrayList<>(uc.getUnits().subList(0, splitPosition));
if (!group.isEmpty()) {
for (int i = 0; i < splitPosition; i += groupSize) {
final List<Unit> miniGroup = new ArrayList<>(uc.getUnits().subList(i, i + groupSize));
if (!miniGroup.isEmpty()) {
groupsOfSize.add(miniGroup);
}
}
}
toRoll.addAll(uc.getUnits().subList(splitPosition, uc.getUnits().size()));
}
return Tuple.of(groupsOfSize, toRoll);
}
use of games.strategy.engine.data.Unit in project triplea by triplea-game.
the class BattleCalculator method getTotalHitpointsLeft.
public static int getTotalHitpointsLeft(final Collection<Unit> units) {
if (units == null || units.isEmpty()) {
return 0;
}
int totalHitPoints = 0;
for (final Unit u : units) {
final UnitAttachment ua = UnitAttachment.get(u.getType());
totalHitPoints += ua.getHitPoints();
totalHitPoints -= u.getHits();
}
return totalHitPoints;
}
use of games.strategy.engine.data.Unit in project triplea by triplea-game.
the class BattleCalculator method sortUnitsForCasualtiesWithSupport.
/**
* The purpose of this is to return a list in the PERFECT order of which units should be selected to die first,
* And that means that certain units MUST BE INTERLEAVED.
* This list assumes that you have already taken any extra hit points away from any 2 hitpoint units.
* Example: You have a 1 attack Artillery unit that supports, and a 1 attack infantry unit that can receive support.
* The best selection of units to die is first to take whichever unit has excess, then cut that down til they are both
* the same size,
* then to take 1 artillery followed by 1 infantry, followed by 1 artillery, then 1 inf, etc, until everyone is dead.
* If you just return all infantry followed by all artillery, or the other way around, you will be missing out on some
* important support
* provided.
* (Veqryn)
*/
private static List<Unit> sortUnitsForCasualtiesWithSupport(final Collection<Unit> targetsToPickFrom, final boolean defending, final PlayerID player, final Collection<Unit> enemyUnits, final boolean amphibious, final Collection<Unit> amphibiousLandAttackers, final Territory battlesite, final IntegerMap<UnitType> costs, final Collection<TerritoryEffect> territoryEffects, final GameData data, final boolean bonus) {
// Convert unit lists to unit type lists
final List<UnitType> targetTypes = new ArrayList<>();
for (final Unit u : targetsToPickFrom) {
targetTypes.add(u.getType());
}
final List<UnitType> amphibTypes = new ArrayList<>();
if (amphibiousLandAttackers != null) {
for (final Unit u : amphibiousLandAttackers) {
amphibTypes.add(u.getType());
}
}
// Calculate hashes and cache key
int targetsHashCode = 1;
for (final UnitType ut : targetTypes) {
targetsHashCode += ut.hashCode();
}
targetsHashCode *= 31;
int amphibHashCode = 1;
for (final UnitType ut : amphibTypes) {
amphibHashCode += ut.hashCode();
}
amphibHashCode *= 31;
String key = player.getName() + "|" + battlesite.getName() + "|" + defending + "|" + amphibious + "|" + targetsHashCode + "|" + amphibHashCode;
// Check OOL cache
final List<UnitType> stored = oolCache.get(key);
if (stored != null) {
// System.out.println("Hit with cacheSize=" + oolCache.size() + ", key=" + key);
final List<Unit> result = new ArrayList<>();
final List<Unit> selectFrom = new ArrayList<>(targetsToPickFrom);
for (final UnitType ut : stored) {
for (final Iterator<Unit> it = selectFrom.iterator(); it.hasNext(); ) {
final Unit u = it.next();
if (ut.equals(u.getType())) {
result.add(u);
it.remove();
}
}
}
return result;
}
// System.out.println("Miss with cacheSize=" + oolCache.size() + ", key=" + key);
// Sort enough units to kill off
final List<Unit> sortedUnitsList = new ArrayList<>(targetsToPickFrom);
sortedUnitsList.sort(new UnitBattleComparator(defending, costs, territoryEffects, data, bonus, false));
// Sort units starting with strongest so that support gets added to them first
Collections.reverse(sortedUnitsList);
final UnitBattleComparator unitComparatorWithoutPrimaryPower = new UnitBattleComparator(defending, costs, territoryEffects, data, bonus, true);
final Map<Unit, IntegerMap<Unit>> unitSupportPowerMap = new HashMap<>();
final Map<Unit, IntegerMap<Unit>> unitSupportRollsMap = new HashMap<>();
final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRollsMap = DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList, new ArrayList<>(enemyUnits), defending, false, data, battlesite, territoryEffects, amphibious, amphibiousLandAttackers, unitSupportPowerMap, unitSupportRollsMap);
// Sort units starting with weakest for finding the worst units
Collections.reverse(sortedUnitsList);
final List<Unit> sortedWellEnoughUnitsList = new ArrayList<>();
for (int i = 0; i < sortedUnitsList.size(); ++i) {
// Loop through all target units to find the best unit to take as casualty
Unit worstUnit = null;
int minPower = Integer.MAX_VALUE;
final Set<UnitType> unitTypes = new HashSet<>();
for (final Unit u : sortedUnitsList) {
if (unitTypes.contains(u.getType())) {
continue;
}
unitTypes.add(u.getType());
// Find unit power
final Map<Unit, Tuple<Integer, Integer>> currentUnitMap = new HashMap<>();
currentUnitMap.put(u, unitPowerAndRollsMap.get(u));
int power = DiceRoll.getTotalPower(currentUnitMap, data);
// Add any support power that it provides to other units
final IntegerMap<Unit> unitSupportPowerMapForUnit = unitSupportPowerMap.get(u);
if (unitSupportPowerMapForUnit != null) {
for (final Unit supportedUnit : unitSupportPowerMapForUnit.keySet()) {
Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
// Remove any rolls provided by this support so they aren't counted twice
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(u);
if (unitSupportRollsMapForUnit != null) {
strengthAndRolls = Tuple.of(strengthAndRolls.getFirst(), strengthAndRolls.getSecond() - unitSupportRollsMapForUnit.getInt(supportedUnit));
}
// If one roll then just add the power
if (strengthAndRolls.getSecond() == 1) {
power += unitSupportPowerMapForUnit.getInt(supportedUnit);
continue;
}
// Find supported unit power with support
final Map<Unit, Tuple<Integer, Integer>> supportedUnitMap = new HashMap<>();
supportedUnitMap.put(supportedUnit, strengthAndRolls);
final int powerWithSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Find supported unit power without support
final int strengthWithoutSupport = strengthAndRolls.getFirst() - unitSupportPowerMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthWithoutSupport, strengthAndRolls.getSecond());
supportedUnitMap.put(supportedUnit, strengthAndRollsWithoutSupport);
final int powerWithoutSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Add the actual power provided by the support
final int addedPower = powerWithSupport - powerWithoutSupport;
power += addedPower;
}
}
// Add any power from support rolls that it provides to other units
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(u);
if (unitSupportRollsMapForUnit != null) {
for (final Unit supportedUnit : unitSupportRollsMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
// Find supported unit power with support
final Map<Unit, Tuple<Integer, Integer>> supportedUnitMap = new HashMap<>();
supportedUnitMap.put(supportedUnit, strengthAndRolls);
final int powerWithSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Find supported unit power without support
final int rollsWithoutSupport = strengthAndRolls.getSecond() - unitSupportRollsMap.get(u).getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthAndRolls.getFirst(), rollsWithoutSupport);
supportedUnitMap.put(supportedUnit, strengthAndRollsWithoutSupport);
final int powerWithoutSupport = DiceRoll.getTotalPower(supportedUnitMap, data);
// Add the actual power provided by the support
final int addedPower = powerWithSupport - powerWithoutSupport;
power += addedPower;
}
}
// Check if unit has lower power
if (power < minPower || (power == minPower && unitComparatorWithoutPrimaryPower.compare(u, worstUnit) < 0)) {
worstUnit = u;
minPower = power;
}
}
// Add worst unit to sorted list, update any units it supported, and remove from other collections
final IntegerMap<Unit> unitSupportPowerMapForUnit = unitSupportPowerMap.get(worstUnit);
if (unitSupportPowerMapForUnit != null) {
for (final Unit supportedUnit : unitSupportPowerMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
final int strengthWithoutSupport = strengthAndRolls.getFirst() - unitSupportPowerMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthWithoutSupport, strengthAndRolls.getSecond());
unitPowerAndRollsMap.put(supportedUnit, strengthAndRollsWithoutSupport);
sortedUnitsList.remove(supportedUnit);
sortedUnitsList.add(0, supportedUnit);
}
}
final IntegerMap<Unit> unitSupportRollsMapForUnit = unitSupportRollsMap.get(worstUnit);
if (unitSupportRollsMapForUnit != null) {
for (final Unit supportedUnit : unitSupportRollsMapForUnit.keySet()) {
final Tuple<Integer, Integer> strengthAndRolls = unitPowerAndRollsMap.get(supportedUnit);
if (strengthAndRolls == null) {
continue;
}
final int rollsWithoutSupport = strengthAndRolls.getSecond() - unitSupportRollsMapForUnit.getInt(supportedUnit);
final Tuple<Integer, Integer> strengthAndRollsWithoutSupport = Tuple.of(strengthAndRolls.getFirst(), rollsWithoutSupport);
unitPowerAndRollsMap.put(supportedUnit, strengthAndRollsWithoutSupport);
sortedUnitsList.remove(supportedUnit);
sortedUnitsList.add(0, supportedUnit);
}
}
sortedWellEnoughUnitsList.add(worstUnit);
sortedUnitsList.remove(worstUnit);
unitPowerAndRollsMap.remove(worstUnit);
unitSupportPowerMap.remove(worstUnit);
unitSupportRollsMap.remove(worstUnit);
}
sortedWellEnoughUnitsList.addAll(sortedUnitsList);
// Cache result and all subsets of the result
final List<UnitType> unitTypes = new ArrayList<>();
for (final Unit u : sortedWellEnoughUnitsList) {
unitTypes.add(u.getType());
}
for (final Iterator<UnitType> it = unitTypes.iterator(); it.hasNext(); ) {
oolCache.put(key, new ArrayList<>(unitTypes));
final UnitType unitTypeToRemove = it.next();
targetTypes.remove(unitTypeToRemove);
if (Collections.frequency(targetTypes, unitTypeToRemove) < Collections.frequency(amphibTypes, unitTypeToRemove)) {
amphibTypes.remove(unitTypeToRemove);
}
targetsHashCode = 1;
for (final UnitType ut : targetTypes) {
targetsHashCode += ut.hashCode();
}
targetsHashCode *= 31;
amphibHashCode = 1;
for (final UnitType ut : amphibTypes) {
amphibHashCode += ut.hashCode();
}
amphibHashCode *= 31;
key = player.getName() + "|" + battlesite.getName() + "|" + defending + "|" + amphibious + "|" + targetsHashCode + "|" + amphibHashCode;
it.remove();
}
return sortedWellEnoughUnitsList;
}
use of games.strategy.engine.data.Unit in project triplea by triplea-game.
the class BattleCalculator method selectCasualties.
/**
* @param battleId
* may be null if we are not in a battle (eg, if this is an aa fire due to moving).
*/
public static CasualtyDetails selectCasualties(final String step, final PlayerID player, final Collection<Unit> targetsToPickFrom, final Collection<Unit> friendlyUnits, final PlayerID enemyPlayer, final Collection<Unit> enemyUnits, final boolean amphibious, final Collection<Unit> amphibiousLandAttackers, final Territory battlesite, final Collection<TerritoryEffect> territoryEffects, final IDelegateBridge bridge, final String text, final DiceRoll dice, final boolean defending, final GUID battleId, final boolean headLess, final int extraHits, final boolean allowMultipleHitsPerUnit) {
if (targetsToPickFrom.isEmpty()) {
return new CasualtyDetails();
}
if (!friendlyUnits.containsAll(targetsToPickFrom)) {
throw new IllegalStateException("friendlyUnits should but does not contain all units from targetsToPickFrom");
}
final GameData data = bridge.getData();
final boolean isEditMode = BaseEditDelegate.getEditMode(data);
final ITripleAPlayer tripleaPlayer = player.isNull() ? new WeakAi(player.getName(), TripleA.WEAK_COMPUTER_PLAYER_TYPE) : (ITripleAPlayer) bridge.getRemotePlayer(player);
final Map<Unit, Collection<Unit>> dependents = headLess ? Collections.emptyMap() : getDependents(targetsToPickFrom);
if (isEditMode && !headLess) {
final CasualtyDetails editSelection = tripleaPlayer.selectCasualties(targetsToPickFrom, dependents, 0, text, dice, player, friendlyUnits, enemyPlayer, enemyUnits, amphibious, amphibiousLandAttackers, new CasualtyList(), battleId, battlesite, allowMultipleHitsPerUnit);
final List<Unit> killed = editSelection.getKilled();
// if partial retreat is possible, kill amphibious units first
if (isPartialAmphibiousRetreat(data)) {
killAmphibiousFirst(killed, targetsToPickFrom);
}
return editSelection;
}
if (dice.getHits() == 0) {
return new CasualtyDetails(Collections.emptyList(), Collections.emptyList(), true);
}
int hitsRemaining = dice.getHits();
if (isTransportCasualtiesRestricted(data)) {
hitsRemaining = extraHits;
}
if (!isEditMode && allTargetsOneTypeOneHitPoint(targetsToPickFrom, dependents)) {
final List<Unit> killed = new ArrayList<>();
final Iterator<Unit> iter = targetsToPickFrom.iterator();
for (int i = 0; i < hitsRemaining; i++) {
if (i >= targetsToPickFrom.size()) {
break;
}
killed.add(iter.next());
}
return new CasualtyDetails(killed, Collections.emptyList(), true);
}
// Create production cost map, Maybe should do this elsewhere, but in case prices change, we do it here.
final IntegerMap<UnitType> costs = TuvUtils.getCostsForTuv(player, data);
final Tuple<CasualtyList, List<Unit>> defaultCasualtiesAndSortedTargets = getDefaultCasualties(targetsToPickFrom, hitsRemaining, defending, player, enemyUnits, amphibious, amphibiousLandAttackers, battlesite, costs, territoryEffects, data, allowMultipleHitsPerUnit, true);
final CasualtyList defaultCasualties = defaultCasualtiesAndSortedTargets.getFirst();
final List<Unit> sortedTargetsToPickFrom = defaultCasualtiesAndSortedTargets.getSecond();
if (sortedTargetsToPickFrom.size() != targetsToPickFrom.size() || !targetsToPickFrom.containsAll(sortedTargetsToPickFrom) || !sortedTargetsToPickFrom.containsAll(targetsToPickFrom)) {
throw new IllegalStateException("sortedTargetsToPickFrom must contain the same units as targetsToPickFrom list");
}
final int totalHitpoints = (allowMultipleHitsPerUnit ? getTotalHitpointsLeft(sortedTargetsToPickFrom) : sortedTargetsToPickFrom.size());
final CasualtyDetails casualtySelection;
if (hitsRemaining >= totalHitpoints) {
casualtySelection = new CasualtyDetails(defaultCasualties, true);
} else {
casualtySelection = tripleaPlayer.selectCasualties(sortedTargetsToPickFrom, dependents, hitsRemaining, text, dice, player, friendlyUnits, enemyPlayer, enemyUnits, amphibious, amphibiousLandAttackers, defaultCasualties, battleId, battlesite, allowMultipleHitsPerUnit);
}
List<Unit> killed = casualtySelection.getKilled();
// if partial retreat is possible, kill amphibious units first
if (isPartialAmphibiousRetreat(data)) {
killed = killAmphibiousFirst(killed, sortedTargetsToPickFrom);
}
final List<Unit> damaged = casualtySelection.getDamaged();
int numhits = killed.size();
if (!allowMultipleHitsPerUnit) {
damaged.clear();
} else {
for (final Unit unit : killed) {
final UnitAttachment ua = UnitAttachment.get(unit.getType());
final int damageToUnit = Collections.frequency(damaged, unit);
// allowed damage
numhits += Math.max(0, Math.min(damageToUnit, (ua.getHitPoints() - (1 + unit.getHits()))));
// remove from damaged list, since they will die
damaged.removeIf(unit::equals);
}
}
// check right number
if (!isEditMode && !(numhits + damaged.size() == (hitsRemaining > totalHitpoints ? totalHitpoints : hitsRemaining))) {
tripleaPlayer.reportError("Wrong number of casualties selected");
if (headLess) {
System.err.println("Possible Infinite Loop: Wrong number of casualties selected: number of hits on units " + (numhits + damaged.size()) + " != number of hits to take " + (hitsRemaining > totalHitpoints ? totalHitpoints : hitsRemaining) + ", for " + casualtySelection.toString());
}
return selectCasualties(step, player, sortedTargetsToPickFrom, friendlyUnits, enemyPlayer, enemyUnits, amphibious, amphibiousLandAttackers, battlesite, territoryEffects, bridge, text, dice, defending, battleId, headLess, extraHits, allowMultipleHitsPerUnit);
}
// check we have enough of each type
if (!sortedTargetsToPickFrom.containsAll(killed) || !sortedTargetsToPickFrom.containsAll(damaged)) {
tripleaPlayer.reportError("Cannot remove enough units of those types");
if (headLess) {
System.err.println("Possible Infinite Loop: Cannot remove enough units of those types: targets " + MyFormatter.unitsToTextNoOwner(sortedTargetsToPickFrom) + ", for " + casualtySelection.toString());
}
return selectCasualties(step, player, sortedTargetsToPickFrom, friendlyUnits, enemyPlayer, enemyUnits, amphibious, amphibiousLandAttackers, battlesite, territoryEffects, bridge, text, dice, defending, battleId, headLess, extraHits, allowMultipleHitsPerUnit);
}
return casualtySelection;
}
use of games.strategy.engine.data.Unit in project triplea by triplea-game.
the class BattleCalculator method randomAaCasualties.
/**
* Choose plane casualties randomly.
*/
public static CasualtyDetails randomAaCasualties(final Collection<Unit> planes, final DiceRoll dice, final IDelegateBridge bridge, final boolean allowMultipleHitsPerUnit) {
{
final Set<Unit> duplicatesCheckSet1 = new HashSet<>(planes);
if (planes.size() != duplicatesCheckSet1.size()) {
throw new IllegalStateException("Duplicate Units Detected: Original List:" + planes + " HashSet:" + duplicatesCheckSet1);
}
}
final int hitsLeft = dice.getHits();
if (hitsLeft <= 0) {
return new CasualtyDetails();
}
final CasualtyDetails finalCasualtyDetails = new CasualtyDetails();
// normal behavior is instant kill, which means planes.size()
final int planeHitPoints = (allowMultipleHitsPerUnit ? getTotalHitpointsLeft(planes) : planes.size());
final List<Unit> planesList = new ArrayList<>();
for (final Unit plane : planes) {
final int hpLeft = allowMultipleHitsPerUnit ? (UnitAttachment.get(plane.getType()).getHitPoints() - plane.getHits()) : (Math.min(1, UnitAttachment.get(plane.getType()).getHitPoints() - plane.getHits()));
for (int hp = 0; hp < hpLeft; ++hp) {
// if allowMultipleHitsPerUnit, then because the number of rolls exactly equals the hitpoints of all units,
// we roll multiple times for any unit with multiple hitpoints
planesList.add(plane);
}
}
// We need to choose which planes die randomly
if (hitsLeft < planeHitPoints) {
// roll all at once to prevent frequent random calls, important for pbem games
final int[] hitRandom = bridge.getRandom(planeHitPoints, hitsLeft, null, DiceType.ENGINE, "Deciding which planes should die due to AA fire");
int pos = 0;
for (final int element : hitRandom) {
pos += element;
final Unit unitHit = planesList.remove(pos % planesList.size());
if (allowMultipleHitsPerUnit && (Collections.frequency(finalCasualtyDetails.getDamaged(), unitHit) < (getTotalHitpointsLeft(unitHit) - 1))) {
finalCasualtyDetails.addToDamaged(unitHit);
} else {
finalCasualtyDetails.addToKilled(unitHit);
}
}
} else {
for (final Unit plane : planesList) {
if (finalCasualtyDetails.getKilled().contains(plane)) {
finalCasualtyDetails.addToDamaged(plane);
} else {
finalCasualtyDetails.addToKilled(plane);
}
}
}
return finalCasualtyDetails;
}
Aggregations