Search in sources :

Example 1 with UnitType

use of games.strategy.engine.data.UnitType 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;
}
Also used : LinkedIntegerMap(games.strategy.util.LinkedIntegerMap) IntegerMap(games.strategy.util.IntegerMap) HashMap(java.util.HashMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) ArrayList(java.util.ArrayList) Unit(games.strategy.engine.data.Unit) UnitType(games.strategy.engine.data.UnitType) Tuple(games.strategy.util.Tuple) HashSet(java.util.HashSet)

Example 2 with UnitType

use of games.strategy.engine.data.UnitType 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;
}
Also used : GameData(games.strategy.engine.data.GameData) ArrayList(java.util.ArrayList) Unit(games.strategy.engine.data.Unit) ITripleAPlayer(games.strategy.triplea.player.ITripleAPlayer) UnitAttachment(games.strategy.triplea.attachments.UnitAttachment) WeakAi(games.strategy.triplea.ai.weak.WeakAi) CasualtyList(games.strategy.triplea.delegate.dataObjects.CasualtyList) UnitType(games.strategy.engine.data.UnitType) Collection(java.util.Collection) CasualtyDetails(games.strategy.triplea.delegate.dataObjects.CasualtyDetails) ArrayList(java.util.ArrayList) CasualtyList(games.strategy.triplea.delegate.dataObjects.CasualtyList) List(java.util.List)

Example 3 with UnitType

use of games.strategy.engine.data.UnitType in project triplea by triplea-game.

the class BattleDelegate method doKamikazeSuicideAttacks.

/**
 * KamikazeSuicideAttacks are attacks that are made during an Opponent's turn, using Resources that you own that have
 * been designated.
 * The resources are designated in PlayerAttachment, and hold information like the attack power of the resource.
 * KamikazeSuicideAttacks are done in any territory that is a kamikazeZone, and the attacks are done by the original
 * owner of that
 * territory.
 * The user has the option not to do any attacks, and they make target any number of units with any number of resource
 * tokens.
 * The units are then attacked individually by each resource token (meaning that casualties do not get selected
 * because the attacks are
 * targeted).
 * The enemies of current player should decide all their attacks before the attacks are rolled.
 */
private void doKamikazeSuicideAttacks() {
    final GameData data = getData();
    if (!Properties.getUseKamikazeSuicideAttacks(data)) {
        return;
    }
    // the current player is not the one who is doing these attacks, it is the all the enemies of this player who will
    // do attacks
    final Collection<PlayerID> enemies = CollectionUtils.getMatches(data.getPlayerList().getPlayers(), Matches.isAtWar(player, data));
    if (enemies.isEmpty()) {
        return;
    }
    final Predicate<Unit> canBeAttackedDefault = Matches.unitIsOwnedBy(player).and(Matches.unitIsSea()).and(Matches.unitIsNotTransportButCouldBeCombatTransport()).and(Matches.unitIsNotSub());
    final boolean onlyWhereThereAreBattlesOrAmphibious = Properties.getKamikazeSuicideAttacksOnlyWhereBattlesAre(data);
    final Collection<Territory> pendingBattles = battleTracker.getPendingBattleSites(false);
    // create a list of all kamikaze zones, listed by enemy
    final Map<PlayerID, Collection<Territory>> kamikazeZonesByEnemy = new HashMap<>();
    for (final Territory t : data.getMap().getTerritories()) {
        final TerritoryAttachment ta = TerritoryAttachment.get(t);
        if (ta == null || !ta.getKamikazeZone()) {
            continue;
        }
        final PlayerID owner = !Properties.getKamikazeSuicideAttacksDoneByCurrentTerritoryOwner(data) ? ta.getOriginalOwner() : t.getOwner();
        if (owner == null) {
            continue;
        }
        if (enemies.contains(owner)) {
            if (t.getUnits().getUnits().stream().noneMatch(Matches.unitIsOwnedBy(player))) {
                continue;
            }
            if (onlyWhereThereAreBattlesOrAmphibious) {
                // if no battle or amphibious from here, ignore it
                if (!pendingBattles.contains(t)) {
                    if (!Matches.territoryIsWater().test(t)) {
                        continue;
                    }
                    boolean amphib = false;
                    final Collection<Territory> landNeighbors = data.getMap().getNeighbors(t, Matches.territoryIsLand());
                    for (final Territory neighbor : landNeighbors) {
                        final IBattle battle = battleTracker.getPendingBattle(neighbor, false, BattleType.NORMAL);
                        if (battle == null) {
                            final Map<Territory, Collection<Unit>> whereFrom = battleTracker.getFinishedBattlesUnitAttackFromMap().get(neighbor);
                            if (whereFrom != null && whereFrom.containsKey(t)) {
                                amphib = true;
                                break;
                            }
                            continue;
                        }
                        if (battle.isAmphibious() && ((battle instanceof MustFightBattle && ((MustFightBattle) battle).getAmphibiousAttackTerritories().contains(t)) || (battle instanceof NonFightingBattle && ((NonFightingBattle) battle).getAmphibiousAttackTerritories().contains(t)))) {
                            amphib = true;
                            break;
                        }
                    }
                    if (!amphib) {
                        continue;
                    }
                }
            }
            final Collection<Territory> currentTerrs = kamikazeZonesByEnemy.getOrDefault(owner, new ArrayList<>());
            currentTerrs.add(t);
            kamikazeZonesByEnemy.put(owner, currentTerrs);
        }
    }
    if (kamikazeZonesByEnemy.isEmpty()) {
        return;
    }
    for (final Entry<PlayerID, Collection<Territory>> entry : kamikazeZonesByEnemy.entrySet()) {
        final PlayerID currentEnemy = entry.getKey();
        final PlayerAttachment pa = PlayerAttachment.get(currentEnemy);
        if (pa == null) {
            continue;
        }
        Predicate<Unit> canBeAttacked = canBeAttackedDefault;
        final Set<UnitType> suicideAttackTargets = pa.getSuicideAttackTargets();
        if (suicideAttackTargets != null) {
            canBeAttacked = Matches.unitIsOwnedBy(player).and(Matches.unitIsOfTypes(suicideAttackTargets));
        }
        // See if the player has any attack tokens
        final IntegerMap<Resource> resourcesAndAttackValues = pa.getSuicideAttackResources();
        if (resourcesAndAttackValues.size() <= 0) {
            continue;
        }
        final IntegerMap<Resource> playerResourceCollection = currentEnemy.getResources().getResourcesCopy();
        final IntegerMap<Resource> attackTokens = new IntegerMap<>();
        for (final Resource possible : resourcesAndAttackValues.keySet()) {
            final int amount = playerResourceCollection.getInt(possible);
            if (amount > 0) {
                attackTokens.put(possible, amount);
            }
        }
        if (attackTokens.size() <= 0) {
            continue;
        }
        // now let the enemy decide if they will do attacks
        final Collection<Territory> kamikazeZones = entry.getValue();
        final HashMap<Territory, Collection<Unit>> possibleUnitsToAttack = new HashMap<>();
        for (final Territory t : kamikazeZones) {
            final List<Unit> validTargets = t.getUnits().getMatches(canBeAttacked);
            if (!validTargets.isEmpty()) {
                possibleUnitsToAttack.put(t, validTargets);
            }
        }
        final Map<Territory, HashMap<Unit, IntegerMap<Resource>>> attacks = getRemotePlayer(currentEnemy).selectKamikazeSuicideAttacks(possibleUnitsToAttack);
        if (attacks == null || attacks.isEmpty()) {
            continue;
        }
        // now validate that we have the resources and those units are valid targets
        for (final Entry<Territory, HashMap<Unit, IntegerMap<Resource>>> territoryEntry : attacks.entrySet()) {
            final Territory t = territoryEntry.getKey();
            final Collection<Unit> possibleUnits = possibleUnitsToAttack.get(t);
            if (possibleUnits == null || !possibleUnits.containsAll(territoryEntry.getValue().keySet())) {
                throw new IllegalStateException("Player has chosen illegal units during Kamikaze Suicide Attacks");
            }
            for (final IntegerMap<Resource> resourceMap : territoryEntry.getValue().values()) {
                attackTokens.subtract(resourceMap);
            }
        }
        if (!attackTokens.isPositive()) {
            throw new IllegalStateException("Player has chosen illegal resource during Kamikaze Suicide Attacks");
        }
        for (final Entry<Territory, HashMap<Unit, IntegerMap<Resource>>> territoryEntry : attacks.entrySet()) {
            final Territory location = territoryEntry.getKey();
            for (final Entry<Unit, IntegerMap<Resource>> unitEntry : territoryEntry.getValue().entrySet()) {
                final Unit unitUnderFire = unitEntry.getKey();
                final IntegerMap<Resource> numberOfAttacks = unitEntry.getValue();
                if (numberOfAttacks != null && numberOfAttacks.size() > 0 && numberOfAttacks.totalValues() > 0) {
                    fireKamikazeSuicideAttacks(unitUnderFire, numberOfAttacks, resourcesAndAttackValues, currentEnemy, location);
                }
            }
        }
    }
}
Also used : PlayerID(games.strategy.engine.data.PlayerID) GameData(games.strategy.engine.data.GameData) HashMap(java.util.HashMap) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) PlayerAttachment(games.strategy.triplea.attachments.PlayerAttachment) UnitType(games.strategy.engine.data.UnitType) IntegerMap(games.strategy.util.IntegerMap) Territory(games.strategy.engine.data.Territory) TerritoryAttachment(games.strategy.triplea.attachments.TerritoryAttachment) Resource(games.strategy.engine.data.Resource) Collection(java.util.Collection) ResourceCollection(games.strategy.engine.data.ResourceCollection)

Example 4 with UnitType

use of games.strategy.engine.data.UnitType in project triplea by triplea-game.

the class BattleDelegate method whereCanAirLand.

private static Collection<Territory> whereCanAirLand(final Collection<Unit> strandedAir, final Territory currentTerr, final PlayerID alliedPlayer, final GameData data, final BattleTracker battleTracker, final int carrierCostForCurrentTerr, final int allowedMovement, final boolean useMaxScrambleDistance) {
    int maxDistance = allowedMovement;
    if ((maxDistance > 1) || useMaxScrambleDistance) {
        UnitType ut = null;
        for (final Unit u : strandedAir) {
            if (ut == null) {
                ut = u.getType();
            } else if (!ut.equals(u.getType())) {
                throw new IllegalStateException("whereCanAirLand can only accept 1 UnitType if byMovementCost or scrambled is true");
            }
        }
        if (useMaxScrambleDistance) {
            maxDistance = UnitAttachment.get(ut).getMaxScrambleDistance();
        }
    }
    if (maxDistance < 1 || strandedAir == null || strandedAir.isEmpty()) {
        return Collections.singletonList(currentTerr);
    }
    final boolean areNeutralsPassableByAir = (Properties.getNeutralFlyoverAllowed(data) && !Properties.getNeutralsImpassable(data));
    final HashSet<Territory> canNotLand = new HashSet<>();
    canNotLand.addAll(battleTracker.getPendingBattleSites(false));
    canNotLand.addAll(CollectionUtils.getMatches(data.getMap().getTerritories(), Matches.territoryHasEnemyUnits(alliedPlayer, data)));
    final Collection<Territory> possibleTerrs = new ArrayList<>(data.getMap().getNeighbors(currentTerr, maxDistance));
    if (maxDistance > 1) {
        final Iterator<Territory> possibleIter = possibleTerrs.iterator();
        while (possibleIter.hasNext()) {
            final Route route = data.getMap().getRoute(currentTerr, possibleIter.next(), Matches.airCanFlyOver(alliedPlayer, data, areNeutralsPassableByAir));
            if (route == null || route.getMovementCost(strandedAir.iterator().next()) > maxDistance) {
                possibleIter.remove();
            }
        }
    }
    possibleTerrs.add(currentTerr);
    final HashSet<Territory> availableLand = new HashSet<>(CollectionUtils.getMatches(possibleTerrs, Matches.isTerritoryAllied(alliedPlayer, data).and(Matches.territoryIsLand())));
    availableLand.removeAll(canNotLand);
    final HashSet<Territory> whereCanLand = new HashSet<>(availableLand);
    // now for carrier-air-landing validation
    if (!strandedAir.isEmpty() && strandedAir.stream().allMatch(Matches.unitCanLandOnCarrier())) {
        final HashSet<Territory> availableWater = new HashSet<>(CollectionUtils.getMatches(possibleTerrs, Matches.territoryHasUnitsThatMatch(Matches.unitIsAlliedCarrier(alliedPlayer, data)).and(Matches.territoryIsWater())));
        availableWater.removeAll(battleTracker.getPendingBattleSites(false));
        // a rather simple calculation, either we can take all the air, or we can't, nothing in the middle
        final int carrierCost = AirMovementValidator.carrierCost(strandedAir);
        final Iterator<Territory> waterIter = availableWater.iterator();
        while (waterIter.hasNext()) {
            final Territory t = waterIter.next();
            int carrierCapacity = AirMovementValidator.carrierCapacity(t.getUnits().getMatches(Matches.unitIsAlliedCarrier(alliedPlayer, data)), t);
            if (!t.equals(currentTerr)) {
                carrierCapacity -= AirMovementValidator.carrierCost(t.getUnits().getMatches(Matches.unitCanLandOnCarrier().and(Matches.alliedUnit(alliedPlayer, data))));
            } else {
                carrierCapacity -= carrierCostForCurrentTerr;
            }
            if (carrierCapacity < carrierCost) {
                waterIter.remove();
            }
        }
        whereCanLand.addAll(availableWater);
    }
    return whereCanLand;
}
Also used : Territory(games.strategy.engine.data.Territory) UnitType(games.strategy.engine.data.UnitType) ArrayList(java.util.ArrayList) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) Route(games.strategy.engine.data.Route) HashSet(java.util.HashSet)

Example 5 with UnitType

use of games.strategy.engine.data.UnitType in project triplea by triplea-game.

the class BattleTracker method captureOrDestroyUnits.

/**
 * Called when a territory is conquered to determine if remaining enemy units should be
 * captured, destroyed, or take damage.
 */
public static void captureOrDestroyUnits(final Territory territory, final PlayerID id, final PlayerID newOwner, final IDelegateBridge bridge, final UndoableMove changeTracker) {
    final GameData data = bridge.getData();
    // destroy any units that should be destroyed on capture
    if (Properties.getUnitsCanBeDestroyedInsteadOfCaptured(data)) {
        final Predicate<Unit> enemyToBeDestroyed = Matches.enemyUnit(id, data).and(Matches.unitDestroyedWhenCapturedByOrFrom(id));
        final Collection<Unit> destroyed = territory.getUnits().getMatches(enemyToBeDestroyed);
        if (!destroyed.isEmpty()) {
            final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
            bridge.getHistoryWriter().addChildToEvent("Some non-combat units are destroyed: ", destroyed);
            bridge.addChange(destroyUnits);
            if (changeTracker != null) {
                changeTracker.addChange(destroyUnits);
            }
        }
    }
    // destroy any capture on entering units, IF the property to destroy them instead of capture is turned on
    if (Properties.getOnEnteringUnitsDestroyedInsteadOfCaptured(data)) {
        final Collection<Unit> destroyed = territory.getUnits().getMatches(Matches.unitCanBeCapturedOnEnteringToInThisTerritory(id, territory, data));
        if (!destroyed.isEmpty()) {
            final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
            bridge.getHistoryWriter().addChildToEvent(id.getName() + " destroys some units instead of capturing them", destroyed);
            bridge.addChange(destroyUnits);
            if (changeTracker != null) {
                changeTracker.addChange(destroyUnits);
            }
        }
    }
    // destroy any disabled units owned by the enemy that are NOT infrastructure or factories
    final Predicate<Unit> enemyToBeDestroyed = Matches.enemyUnit(id, data).and(Matches.unitIsDisabled()).and(Matches.unitIsInfrastructure().negate());
    final Collection<Unit> destroyed = territory.getUnits().getMatches(enemyToBeDestroyed);
    if (!destroyed.isEmpty()) {
        final Change destroyUnits = ChangeFactory.removeUnits(territory, destroyed);
        bridge.getHistoryWriter().addChildToEvent(id.getName() + " destroys some disabled combat units", destroyed);
        bridge.addChange(destroyUnits);
        if (changeTracker != null) {
            changeTracker.addChange(destroyUnits);
        }
    }
    // take over non combatants
    final Predicate<Unit> enemyNonCom = Matches.enemyUnit(id, data).and(Matches.unitIsInfrastructure());
    final Predicate<Unit> willBeCaptured = enemyNonCom.or(Matches.unitCanBeCapturedOnEnteringToInThisTerritory(id, territory, data));
    final Collection<Unit> nonCom = territory.getUnits().getMatches(willBeCaptured);
    // change any units that change unit types on capture
    if (Properties.getUnitsCanBeChangedOnCapture(data)) {
        final Collection<Unit> toReplace = CollectionUtils.getMatches(nonCom, Matches.unitWhenCapturedChangesIntoDifferentUnitType());
        for (final Unit u : toReplace) {
            final Map<String, Tuple<String, IntegerMap<UnitType>>> map = UnitAttachment.get(u.getType()).getWhenCapturedChangesInto();
            final PlayerID currentOwner = u.getOwner();
            for (final String value : map.keySet()) {
                final String[] s = value.split(":");
                if (!(s[0].equals("any") || data.getPlayerList().getPlayerId(s[0]).equals(currentOwner))) {
                    continue;
                }
                // we could use "id" or "newOwner" here... not sure which to use
                if (!(s[1].equals("any") || data.getPlayerList().getPlayerId(s[1]).equals(id))) {
                    continue;
                }
                final CompositeChange changes = new CompositeChange();
                final Collection<Unit> toAdd = new ArrayList<>();
                final Tuple<String, IntegerMap<UnitType>> toCreate = map.get(value);
                final boolean translateAttributes = toCreate.getFirst().equalsIgnoreCase("true");
                for (final UnitType ut : toCreate.getSecond().keySet()) {
                    toAdd.addAll(ut.create(toCreate.getSecond().getInt(ut), newOwner));
                }
                if (!toAdd.isEmpty()) {
                    if (translateAttributes) {
                        final Change translate = TripleAUnit.translateAttributesToOtherUnits(u, toAdd, territory);
                        if (!translate.isEmpty()) {
                            changes.add(translate);
                        }
                    }
                    changes.add(ChangeFactory.removeUnits(territory, Collections.singleton(u)));
                    changes.add(ChangeFactory.addUnits(territory, toAdd));
                    changes.add(ChangeFactory.markNoMovementChange(toAdd));
                    bridge.getHistoryWriter().addChildToEvent(id.getName() + " converts " + u.toStringNoOwner() + " into different units", toAdd);
                    bridge.addChange(changes);
                    if (changeTracker != null) {
                        changeTracker.addChange(changes);
                    }
                    // don't forget to remove this unit from the list
                    nonCom.remove(u);
                    break;
                }
            }
        }
    }
    if (!nonCom.isEmpty()) {
        // FYI: a dummy delegate will not do anything with this change,
        // meaning that the battle calculator will think this unit lived,
        // even though it died or was captured, etc!
        final Change capture = ChangeFactory.changeOwner(nonCom, newOwner, territory);
        bridge.addChange(capture);
        if (changeTracker != null) {
            changeTracker.addChange(capture);
        }
        final Change noMovementChange = ChangeFactory.markNoMovementChange(nonCom);
        bridge.addChange(noMovementChange);
        if (changeTracker != null) {
            changeTracker.addChange(noMovementChange);
        }
        final IntegerMap<Unit> damageMap = new IntegerMap<>();
        for (final Unit unit : CollectionUtils.getMatches(nonCom, Matches.unitWhenCapturedSustainsDamage())) {
            final TripleAUnit taUnit = (TripleAUnit) unit;
            final int damageLimit = taUnit.getHowMuchMoreDamageCanThisUnitTake(unit, territory);
            final int sustainedDamage = UnitAttachment.get(unit.getType()).getWhenCapturedSustainsDamage();
            final int actualDamage = Math.max(0, Math.min(sustainedDamage, damageLimit));
            final int totalDamage = taUnit.getUnitDamage() + actualDamage;
            damageMap.put(unit, totalDamage);
        }
        if (!damageMap.isEmpty()) {
            final Change damageChange = ChangeFactory.bombingUnitDamage(damageMap);
            bridge.addChange(damageChange);
            if (changeTracker != null) {
                changeTracker.addChange(damageChange);
            }
            // Kill any units that can die if they have reached max damage
            if (damageMap.keySet().stream().anyMatch(Matches.unitCanDieFromReachingMaxDamage())) {
                final List<Unit> unitsCanDie = CollectionUtils.getMatches(damageMap.keySet(), Matches.unitCanDieFromReachingMaxDamage());
                unitsCanDie.retainAll(CollectionUtils.getMatches(unitsCanDie, Matches.unitIsAtMaxDamageOrNotCanBeDamaged(territory)));
                if (!unitsCanDie.isEmpty()) {
                    final Change removeDead = ChangeFactory.removeUnits(territory, unitsCanDie);
                    bridge.addChange(removeDead);
                    if (changeTracker != null) {
                        changeTracker.addChange(removeDead);
                    }
                }
            }
        }
    }
}
Also used : IntegerMap(games.strategy.util.IntegerMap) PlayerID(games.strategy.engine.data.PlayerID) GameData(games.strategy.engine.data.GameData) ArrayList(java.util.ArrayList) CompositeChange(games.strategy.engine.data.CompositeChange) Change(games.strategy.engine.data.Change) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) TripleAUnit(games.strategy.triplea.TripleAUnit) UnitType(games.strategy.engine.data.UnitType) CompositeChange(games.strategy.engine.data.CompositeChange) Tuple(games.strategy.util.Tuple)

Aggregations

UnitType (games.strategy.engine.data.UnitType)211 Test (org.junit.jupiter.api.Test)108 IntegerMap (games.strategy.util.IntegerMap)86 Route (games.strategy.engine.data.Route)76 Unit (games.strategy.engine.data.Unit)76 PlayerID (games.strategy.engine.data.PlayerID)64 Territory (games.strategy.engine.data.Territory)58 TripleAUnit (games.strategy.triplea.TripleAUnit)49 ArrayList (java.util.ArrayList)44 ITestDelegateBridge (games.strategy.engine.data.ITestDelegateBridge)36 GameParseException (games.strategy.engine.data.GameParseException)29 ScriptedRandomSource (games.strategy.engine.random.ScriptedRandomSource)23 UnitAttachment (games.strategy.triplea.attachments.UnitAttachment)21 HashSet (java.util.HashSet)17 GameData (games.strategy.engine.data.GameData)15 Change (games.strategy.engine.data.Change)14 CompositeChange (games.strategy.engine.data.CompositeChange)14 Resource (games.strategy.engine.data.Resource)13 NamedAttachable (games.strategy.engine.data.NamedAttachable)11 ProductionRule (games.strategy.engine.data.ProductionRule)11