Search in sources :

Example 11 with CasualtyDetails

use of games.strategy.triplea.delegate.dataObjects.CasualtyDetails in project triplea by triplea-game.

the class Fire method selectCasualties.

private void selectCasualties(final IDelegateBridge bridge) {
    final int hitCount = m_dice.getHits();
    AbstractBattle.getDisplay(bridge).notifyDice(m_dice, m_stepName);
    final int countTransports = CollectionUtils.countMatches(m_attackableUnits, Matches.unitIsTransport().and(Matches.unitIsSea()));
    if (countTransports > 0 && isTransportCasualtiesRestricted(bridge.getData())) {
        final CasualtyDetails message;
        final Collection<Unit> nonTransports = CollectionUtils.getMatches(m_attackableUnits, Matches.unitIsNotTransportButCouldBeCombatTransport().or(Matches.unitIsNotSea()));
        final Collection<Unit> transportsOnly = CollectionUtils.getMatches(m_attackableUnits, Matches.unitIsTransportButNotCombatTransport().and(Matches.unitIsSea()));
        final int numPossibleHits = AbstractBattle.getMaxHits(nonTransports);
        // more hits than combat units
        if (hitCount > numPossibleHits) {
            int extraHits = hitCount - numPossibleHits;
            final Collection<PlayerID> alliedHitPlayer = new ArrayList<>();
            // find the players who have transports in the attackable pile
            for (final Unit unit : transportsOnly) {
                if (!alliedHitPlayer.contains(unit.getOwner())) {
                    alliedHitPlayer.add(unit.getOwner());
                }
            }
            // Leave enough transports for each defender for overflows so they can select who loses them.
            for (final PlayerID player : alliedHitPlayer) {
                final Predicate<Unit> match = Matches.unitIsTransportButNotCombatTransport().and(Matches.unitIsOwnedBy(player));
                final Collection<Unit> playerTransports = CollectionUtils.getMatches(transportsOnly, match);
                final int transportsToRemove = Math.max(0, playerTransports.size() - extraHits);
                transportsOnly.removeAll(CollectionUtils.getNMatches(playerTransports, transportsToRemove, Matches.unitIsTransportButNotCombatTransport()));
            }
            m_killed = nonTransports;
            m_damaged = Collections.emptyList();
            if (extraHits > transportsOnly.size()) {
                extraHits = transportsOnly.size();
            }
            message = BattleCalculator.selectCasualties(m_stepName, m_hitPlayer, transportsOnly, m_allEnemyUnitsNotIncludingWaitingToDie, m_firingPlayer, m_allFriendlyUnitsNotIncludingWaitingToDie, m_isAmphibious, m_amphibiousLandAttackers, m_battleSite, m_territoryEffects, bridge, m_text, m_dice, !m_defending, m_battleID, m_isHeadless, extraHits, true);
            m_killed.addAll(message.getKilled());
            m_confirmOwnCasualties = true;
        } else if (hitCount == numPossibleHits) {
            // exact number of combat units
            m_killed = nonTransports;
            m_damaged = Collections.emptyList();
            m_confirmOwnCasualties = true;
        } else {
            // less than possible number
            message = BattleCalculator.selectCasualties(m_stepName, m_hitPlayer, nonTransports, m_allEnemyUnitsNotIncludingWaitingToDie, m_firingPlayer, m_allFriendlyUnitsNotIncludingWaitingToDie, m_isAmphibious, m_amphibiousLandAttackers, m_battleSite, m_territoryEffects, bridge, m_text, m_dice, !m_defending, m_battleID, m_isHeadless, m_dice.getHits(), true);
            m_killed = message.getKilled();
            m_damaged = message.getDamaged();
            m_confirmOwnCasualties = message.getAutoCalculated();
        }
    } else {
        // they all die
        if (hitCount >= AbstractBattle.getMaxHits(m_attackableUnits)) {
            m_killed = m_attackableUnits;
            m_damaged = Collections.emptyList();
            // everything died, so we need to confirm
            m_confirmOwnCasualties = true;
        } else {
            // Choose casualties
            final CasualtyDetails message;
            message = BattleCalculator.selectCasualties(m_stepName, m_hitPlayer, m_attackableUnits, m_allEnemyUnitsNotIncludingWaitingToDie, m_firingPlayer, m_allFriendlyUnitsNotIncludingWaitingToDie, m_isAmphibious, m_amphibiousLandAttackers, m_battleSite, m_territoryEffects, bridge, m_text, m_dice, !m_defending, m_battleID, m_isHeadless, m_dice.getHits(), true);
            m_killed = message.getKilled();
            m_damaged = message.getDamaged();
            m_confirmOwnCasualties = message.getAutoCalculated();
        }
    }
}
Also used : PlayerID(games.strategy.engine.data.PlayerID) ArrayList(java.util.ArrayList) CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails) Unit(games.strategy.engine.data.Unit)

Example 12 with CasualtyDetails

use of games.strategy.triplea.delegate.dataObjects.CasualtyDetails in project triplea by triplea-game.

the class BattleCalculator method getLowLuckAaCasualties.

private static CasualtyDetails getLowLuckAaCasualties(final boolean defending, final Collection<Unit> planes, final Collection<Unit> defendingAa, 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 Set<Unit> duplicatesCheckSet2 = new HashSet<>(defendingAa);
        if (defendingAa.size() != duplicatesCheckSet2.size()) {
            throw new IllegalStateException("Duplicate Units Detected: Original List:" + defendingAa + "  HashSet:" + duplicatesCheckSet2);
        }
    }
    int hitsLeft = dice.getHits();
    if (hitsLeft <= 0) {
        return new CasualtyDetails();
    }
    // if we can damage units, do it now
    final CasualtyDetails finalCasualtyDetails = new CasualtyDetails();
    final GameData data = bridge.getData();
    final Tuple<Integer, Integer> attackThenDiceSides = DiceRoll.getAAattackAndMaxDiceSides(defendingAa, data, !defending);
    final int highestAttack = attackThenDiceSides.getFirst();
    if (highestAttack < 1) {
        return new CasualtyDetails();
    }
    final int chosenDiceSize = attackThenDiceSides.getSecond();
    final Triple<Integer, Integer, Boolean> triple = DiceRoll.getTotalAaPowerThenHitsAndFillSortedDiceThenIfAllUseSameAttack(null, null, !defending, defendingAa, planes, data, false);
    final boolean allSameAttackPower = triple.getThird();
    // multiple HP units need to be counted multiple times:
    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);
        }
    }
    // killing the air by groups does not work if the the attack power is different for some of the rolls
    // also, killing by groups does not work if some of the aa guns have 'MayOverStackAA' and we have more hits than the
    // total number of
    // groups (including the remainder group)
    // (when i mean, 'does not work', i mean that it is no longer a mathematically fair way to find casualties)
    // find group size (if no groups, do dice sides)
    final int groupSize;
    if (allSameAttackPower) {
        groupSize = chosenDiceSize / highestAttack;
    } else {
        groupSize = chosenDiceSize;
    }
    final int numberOfGroupsByDiceSides = (int) Math.ceil((double) planesList.size() / (double) groupSize);
    final boolean tooManyHitsToDoGroups = hitsLeft > numberOfGroupsByDiceSides;
    if (!allSameAttackPower || tooManyHitsToDoGroups || chosenDiceSize % highestAttack != 0) {
        // we have too many hits, so just pick randomly
        return randomAaCasualties(planes, dice, bridge, allowMultipleHitsPerUnit);
    }
    // if we have a group of 6 fighters and 2 bombers, and dicesides is 6, and attack was 1, then we would want 1
    // fighter to die for sure.
    // this is what groupsize is for.
    // if the attack is greater than 1 though, and all use the same attack power, then the group size can be smaller
    // (ie: attack is 2, and
    // we have 3 fighters and 2 bombers, we would want 1 fighter to die for sure).
    // categorize with groupSize
    final Tuple<List<List<Unit>>, List<Unit>> airSplit = categorizeLowLuckAirUnits(planesList, groupSize);
    // because we might have missed the dice roll to hit the remainder)
    if (hitsLeft < (airSplit.getFirst().size() + ((int) Math.ceil((double) airSplit.getSecond().size() / (double) groupSize)))) {
        // fewer hits than groups.
        final List<Unit> tempPossibleHitUnits = new ArrayList<>();
        for (final List<Unit> group : airSplit.getFirst()) {
            tempPossibleHitUnits.add(group.get(0));
        }
        if (airSplit.getSecond().size() > 0) {
            // if we have a remainder group, we need to add some of them into the mix
            // but we have to do so randomly.
            final List<Unit> remainders = new ArrayList<>(airSplit.getSecond());
            if (remainders.size() == 1) {
                tempPossibleHitUnits.add(remainders.remove(0));
            } else {
                final int numberOfRemainderGroups = (int) Math.ceil((double) remainders.size() / (double) groupSize);
                final int[] randomRemainder = bridge.getRandom(remainders.size(), numberOfRemainderGroups, null, DiceType.ENGINE, "Deciding which planes should die due to AA fire");
                int pos2 = 0;
                for (final int element : randomRemainder) {
                    pos2 += element;
                    tempPossibleHitUnits.add(remainders.remove(pos2 % remainders.size()));
                }
            }
        }
        final int[] hitRandom = bridge.getRandom(tempPossibleHitUnits.size(), hitsLeft, null, DiceType.ENGINE, "Deciding which planes should die due to AA fire");
        // now we find the
        int pos = 0;
        for (final int element : hitRandom) {
            pos += element;
            final Unit unitHit = tempPossibleHitUnits.remove(pos % tempPossibleHitUnits.size());
            if (allowMultipleHitsPerUnit && (Collections.frequency(finalCasualtyDetails.getDamaged(), unitHit) < (getTotalHitpointsLeft(unitHit) - 1))) {
                finalCasualtyDetails.addToDamaged(unitHit);
            } else {
                finalCasualtyDetails.addToKilled(unitHit);
            }
        }
    } else {
        // kill one in every group
        for (final List<Unit> group : airSplit.getFirst()) {
            final Unit unitHit = group.get(0);
            if (allowMultipleHitsPerUnit && (Collections.frequency(finalCasualtyDetails.getDamaged(), unitHit) < (getTotalHitpointsLeft(unitHit) - 1))) {
                finalCasualtyDetails.addToDamaged(unitHit);
            } else {
                finalCasualtyDetails.addToKilled(unitHit);
            }
            hitsLeft--;
        }
        // for any hits left over...
        if (hitsLeft == airSplit.getSecond().size()) {
            for (final Unit unitHit : airSplit.getSecond()) {
                if (allowMultipleHitsPerUnit && (Collections.frequency(finalCasualtyDetails.getDamaged(), unitHit) < (getTotalHitpointsLeft(unitHit) - 1))) {
                    finalCasualtyDetails.addToDamaged(unitHit);
                } else {
                    finalCasualtyDetails.addToKilled(unitHit);
                }
            }
        } else if (hitsLeft != 0) {
            // the remainder
            // roll all at once to prevent frequent random calls, important for pbem games
            final int[] hitRandom = bridge.getRandom(airSplit.getSecond().size(), 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 = airSplit.getSecond().remove(pos % airSplit.getSecond().size());
                if (allowMultipleHitsPerUnit && (Collections.frequency(finalCasualtyDetails.getDamaged(), unitHit) < (getTotalHitpointsLeft(unitHit) - 1))) {
                    finalCasualtyDetails.addToDamaged(unitHit);
                } else {
                    finalCasualtyDetails.addToKilled(unitHit);
                }
            }
        }
    }
    // double check
    if (finalCasualtyDetails.size() != dice.getHits()) {
        throw new IllegalStateException("wrong number of casulaties, expected:" + dice + " but got: " + finalCasualtyDetails);
    }
    return finalCasualtyDetails;
}
Also used : HashSet(java.util.HashSet) Set(java.util.Set) GameData(games.strategy.engine.data.GameData) ArrayList(java.util.ArrayList) Unit(games.strategy.engine.data.Unit) CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails) ArrayList(java.util.ArrayList) CasualtyList(games.strategy.triplea.delegate.dataObjects.CasualtyList) List(java.util.List)

Example 13 with CasualtyDetails

use of games.strategy.triplea.delegate.dataObjects.CasualtyDetails in project triplea by triplea-game.

the class AAInMoveUtil method selectCasualties.

/**
 * hits are removed from units. Note that units are removed in the order
 * that the iterator will move through them.
 */
private void selectCasualties(final DiceRoll dice, final Collection<Unit> allFriendlyUnits, final Collection<Unit> validTargetedUnitsForThisRoll, final Collection<Unit> defendingAa, final Collection<Unit> allEnemyUnits, final Territory territory, final String currentTypeAa) {
    final CasualtyDetails casualties = BattleCalculator.getAaCasualties(false, validTargetedUnitsForThisRoll, allFriendlyUnits, defendingAa, allEnemyUnits, dice, bridge, territory.getOwner(), player, null, territory, TerritoryEffectHelper.getEffects(territory), false, new ArrayList<>());
    getRemotePlayer().reportMessage(casualties.size() + " " + currentTypeAa + " hits in " + territory.getName(), casualties.size() + " " + currentTypeAa + " hits in " + territory.getName());
    BattleDelegate.markDamaged(new ArrayList<>(casualties.getDamaged()), bridge);
    bridge.getHistoryWriter().addChildToEvent(MyFormatter.unitsToTextNoOwner(casualties.getKilled()) + " lost in " + territory.getName(), new ArrayList<>(casualties.getKilled()));
    allFriendlyUnits.removeAll(casualties.getKilled());
    if (m_casualties == null) {
        m_casualties = new ArrayList<>(casualties.getKilled());
    } else {
        m_casualties.addAll(casualties.getKilled());
    }
}
Also used : CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails)

Example 14 with CasualtyDetails

use of games.strategy.triplea.delegate.dataObjects.CasualtyDetails in project triplea by triplea-game.

the class WW2V3Year41Test method setUp.

@BeforeEach
public void setUp() throws Exception {
    when(dummyPlayer.selectCasualties(any(), any(), anyInt(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any(), any(), anyBoolean())).thenAnswer(new Answer<CasualtyDetails>() {

        @Override
        public CasualtyDetails answer(final InvocationOnMock invocation) {
            final CasualtyList defaultCasualties = invocation.getArgument(11);
            if (defaultCasualties != null) {
                return new CasualtyDetails(defaultCasualties.getKilled(), defaultCasualties.getDamaged(), true);
            }
            return null;
        }
    });
    gameData = TestMapGameData.WW2V3_1941.getGameData();
}
Also used : CasualtyList(games.strategy.triplea.delegate.dataObjects.CasualtyList) InvocationOnMock(org.mockito.invocation.InvocationOnMock) CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails) BeforeEach(org.junit.jupiter.api.BeforeEach)

Example 15 with CasualtyDetails

use of games.strategy.triplea.delegate.dataObjects.CasualtyDetails in project triplea by triplea-game.

the class BattleCalculatorTest method testAaCasualtiesLowLuckMixedWithChooseAaCasualtiesRoll.

@Test
public void testAaCasualtiesLowLuckMixedWithChooseAaCasualtiesRoll() {
    final GameData data = bridge.getData();
    makeGameLowLuck(data);
    setSelectAaCasualties(data, true);
    // 7 bombers and 7 fighters
    final Collection<Unit> planes = bomber(data).create(7, british(data));
    planes.addAll(fighter(data).create(7, british(data)));
    final Collection<Unit> defendingAa = territory("Germany", data).getUnits().getMatches(Matches.unitIsAaForAnything());
    when(dummyPlayer.selectCasualties(any(), any(), anyInt(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any(), any(), anyBoolean())).thenAnswer(new Answer<CasualtyDetails>() {

        @Override
        public CasualtyDetails answer(final InvocationOnMock invocation) {
            final Collection<Unit> selectFrom = invocation.getArgument(0);
            final int count = invocation.getArgument(2);
            final List<Unit> selected = CollectionUtils.getNMatches(selectFrom, count, Matches.unitIsStrategicBomber());
            return new CasualtyDetails(selected, new ArrayList<>(), false);
        }
    });
    bridge.setRemote(dummyPlayer);
    // only 1 roll, a hit
    bridge.setRandomSource(new ScriptedRandomSource(new int[] { 0, ScriptedRandomSource.ERROR }));
    final DiceRoll roll = DiceRoll.rollAa(CollectionUtils.getMatches(planes, Matches.unitIsOfTypes(UnitAttachment.get(defendingAa.iterator().next().getType()).getTargetsAa(data))), defendingAa, bridge, territory("Germany", data), true);
    final Collection<Unit> casualties = BattleCalculator.getAaCasualties(false, planes, planes, defendingAa, defendingAa, roll, bridge, germans(data), british(data), null, territory("Germany", data), null, false, null).getKilled();
    assertEquals(3, casualties.size());
    // we selected all bombers
    assertEquals(3, CollectionUtils.countMatches(casualties, Matches.unitIsStrategicBomber()));
    assertEquals(0, CollectionUtils.countMatches(casualties, Matches.unitIsStrategicBomber().negate()));
}
Also used : TestMapGameData(games.strategy.triplea.xml.TestMapGameData) GameData(games.strategy.engine.data.GameData) InvocationOnMock(org.mockito.invocation.InvocationOnMock) ArrayList(java.util.ArrayList) Collection(java.util.Collection) CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails) ArrayList(java.util.ArrayList) List(java.util.List) ScriptedRandomSource(games.strategy.engine.random.ScriptedRandomSource) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) Test(org.junit.jupiter.api.Test)

Aggregations

CasualtyDetails (games.strategy.triplea.delegate.dataObjects.CasualtyDetails)17 ArrayList (java.util.ArrayList)11 Unit (games.strategy.engine.data.Unit)10 GameData (games.strategy.engine.data.GameData)6 CasualtyList (games.strategy.triplea.delegate.dataObjects.CasualtyList)6 List (java.util.List)6 InvocationOnMock (org.mockito.invocation.InvocationOnMock)5 ScriptedRandomSource (games.strategy.engine.random.ScriptedRandomSource)4 Collection (java.util.Collection)4 Test (org.junit.jupiter.api.Test)4 TripleAUnit (games.strategy.triplea.TripleAUnit)3 ITestDelegateBridge (games.strategy.engine.data.ITestDelegateBridge)2 PlayerID (games.strategy.engine.data.PlayerID)2 Route (games.strategy.engine.data.Route)2 Territory (games.strategy.engine.data.Territory)2 TestMapGameData (games.strategy.triplea.xml.TestMapGameData)2 HashSet (java.util.HashSet)2 Set (java.util.Set)2 BeforeEach (org.junit.jupiter.api.BeforeEach)2 UnitType (games.strategy.engine.data.UnitType)1