use of games.strategy.util.Tuple in project triplea by triplea-game.
the class DiceRoll method rollDiceLowLuck.
/**
* Roll dice for units using low luck rules. Low luck rules based on rules in DAAK.
*/
private static DiceRoll rollDiceLowLuck(final List<Unit> unitsList, final boolean defending, final PlayerID player, final IDelegateBridge bridge, final IBattle battle, final String annotation, final Collection<TerritoryEffect> territoryEffects, final List<Unit> allEnemyUnitsAliveOrWaitingToDie) {
final List<Unit> units = new ArrayList<>(unitsList);
{
final Set<Unit> duplicatesCheckSet = new HashSet<>(unitsList);
if (units.size() != duplicatesCheckSet.size()) {
throw new IllegalStateException("Duplicate Units Detected: Original List:" + units + " HashSet:" + duplicatesCheckSet);
}
}
final GameData data = bridge.getData();
final Territory location = battle.getTerritory();
final boolean isAmphibiousBattle = battle.isAmphibious();
final Collection<Unit> amphibiousLandAttackers = battle.getAmphibiousLandAttackers();
final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRollsMap = DiceRoll.getUnitPowerAndRollsForNormalBattles(units, allEnemyUnitsAliveOrWaitingToDie, defending, false, data, location, territoryEffects, isAmphibiousBattle, amphibiousLandAttackers);
final int power = getTotalPower(unitPowerAndRollsMap, data);
if (power == 0) {
return new DiceRoll(new ArrayList<>(0), 0, 0);
}
int hitCount = power / data.getDiceSides();
final List<Die> dice = new ArrayList<>();
// We need to roll dice for the fractional part of the dice.
final int rollFor = power % data.getDiceSides();
final int[] random;
if (rollFor == 0) {
random = new int[0];
} else {
random = bridge.getRandom(data.getDiceSides(), 1, player, DiceType.COMBAT, annotation);
// zero based
final boolean hit = rollFor > random[0];
if (hit) {
hitCount++;
}
dice.add(new Die(random[0], rollFor, hit ? DieType.HIT : DieType.MISS));
}
// Create DiceRoll object
final double expectedHits = ((double) power) / data.getDiceSides();
final DiceRoll diceRoll = new DiceRoll(dice, hitCount, expectedHits);
bridge.getHistoryWriter().addChildToEvent(annotation + " : " + MyFormatter.asDice(random), diceRoll);
return diceRoll;
}
use of games.strategy.util.Tuple in project triplea by triplea-game.
the class DiceRoll method rollDiceNormal.
/**
* Roll dice for units per normal rules.
*/
private static DiceRoll rollDiceNormal(final List<Unit> unitsList, final boolean defending, final PlayerID player, final IDelegateBridge bridge, final IBattle battle, final String annotation, final Collection<TerritoryEffect> territoryEffects, final List<Unit> allEnemyUnitsAliveOrWaitingToDie) {
final List<Unit> units = new ArrayList<>(unitsList);
{
final Set<Unit> duplicatesCheckSet = new HashSet<>(unitsList);
if (units.size() != duplicatesCheckSet.size()) {
throw new IllegalStateException("Duplicate Units Detected: Original List:" + units + " HashSet:" + duplicatesCheckSet);
}
}
final GameData data = bridge.getData();
sortByStrength(units, defending);
final Territory location = battle.getTerritory();
final boolean isAmphibiousBattle = battle.isAmphibious();
final Collection<Unit> amphibiousLandAttackers = battle.getAmphibiousLandAttackers();
final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRollsMap = DiceRoll.getUnitPowerAndRollsForNormalBattles(units, allEnemyUnitsAliveOrWaitingToDie, defending, false, data, location, territoryEffects, isAmphibiousBattle, amphibiousLandAttackers);
final Tuple<Integer, Integer> totalPowerAndRolls = getTotalPowerAndRolls(unitPowerAndRollsMap, data);
final int totalPower = totalPowerAndRolls.getFirst();
final int rollCount = totalPowerAndRolls.getSecond();
if (rollCount == 0) {
return new DiceRoll(new ArrayList<>(), 0, 0);
}
final int[] random = bridge.getRandom(data.getDiceSides(), rollCount, player, DiceType.COMBAT, annotation);
final boolean lhtrBombers = Properties.getLhtrHeavyBombers(data);
final List<Die> dice = new ArrayList<>();
int hitCount = 0;
int diceIndex = 0;
for (final Unit current : units) {
final UnitAttachment ua = UnitAttachment.get(current.getType());
final Tuple<Integer, Integer> powerAndRolls = unitPowerAndRollsMap.get(current);
final int strength = powerAndRolls.getFirst();
final int rolls = powerAndRolls.getSecond();
// lhtr heavy bombers take best of n dice for both attack and defense
if (rolls <= 0 || strength <= 0) {
continue;
}
if (rolls > 1 && (lhtrBombers || ua.getChooseBestRoll())) {
int smallestDieIndex = 0;
int smallestDie = data.getDiceSides();
for (int i = 0; i < rolls; i++) {
if (random[diceIndex + i] < smallestDie) {
smallestDie = random[diceIndex + i];
smallestDieIndex = i;
}
}
// zero based
final boolean hit = strength > random[diceIndex + smallestDieIndex];
dice.add(new Die(random[diceIndex + smallestDieIndex], strength, hit ? DieType.HIT : DieType.MISS));
for (int i = 0; i < rolls; i++) {
if (i != smallestDieIndex) {
dice.add(new Die(random[diceIndex + i], strength, DieType.IGNORED));
}
}
if (hit) {
hitCount++;
}
diceIndex += rolls;
} else {
for (int i = 0; i < rolls; i++) {
// zero based
final boolean hit = strength > random[diceIndex];
dice.add(new Die(random[diceIndex], strength, hit ? DieType.HIT : DieType.MISS));
if (hit) {
hitCount++;
}
diceIndex++;
}
}
}
final double expectedHits = ((double) totalPower) / data.getDiceSides();
final DiceRoll diceRoll = new DiceRoll(dice, hitCount, expectedHits);
bridge.getHistoryWriter().addChildToEvent(annotation + " : " + MyFormatter.asDice(random), diceRoll);
return diceRoll;
}
use of games.strategy.util.Tuple in project triplea by triplea-game.
the class DiceRoll method getUnitPowerAndRollsForNormalBattles.
/**
* @param unitsGettingPowerFor
* should be sorted from weakest to strongest, before the method is called, for the actual battle.
*/
protected static Map<Unit, Tuple<Integer, Integer>> getUnitPowerAndRollsForNormalBattles(final List<Unit> unitsGettingPowerFor, final List<Unit> allEnemyUnitsAliveOrWaitingToDie, final boolean defending, final boolean bombing, final GameData data, final Territory location, final Collection<TerritoryEffect> territoryEffects, final boolean isAmphibiousBattle, final Collection<Unit> amphibiousLandAttackers, final Map<Unit, IntegerMap<Unit>> unitSupportPowerMap, final Map<Unit, IntegerMap<Unit>> unitSupportRollsMap) {
final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRolls = new HashMap<>();
if (unitsGettingPowerFor == null || unitsGettingPowerFor.isEmpty()) {
return unitPowerAndRolls;
}
// get all supports, friendly and enemy
final Set<List<UnitSupportAttachment>> supportRulesFriendly = new HashSet<>();
final IntegerMap<UnitSupportAttachment> supportLeftFriendly = new IntegerMap<>();
final Map<UnitSupportAttachment, LinkedIntegerMap<Unit>> supportUnitsLeftFriendly = new HashMap<>();
getSupport(unitsGettingPowerFor, supportRulesFriendly, supportLeftFriendly, supportUnitsLeftFriendly, data, defending, true);
final Set<List<UnitSupportAttachment>> supportRulesEnemy = new HashSet<>();
final IntegerMap<UnitSupportAttachment> supportLeftEnemy = new IntegerMap<>();
final Map<UnitSupportAttachment, LinkedIntegerMap<Unit>> supportUnitsLeftEnemy = new HashMap<>();
getSupport(allEnemyUnitsAliveOrWaitingToDie, supportRulesEnemy, supportLeftEnemy, supportUnitsLeftEnemy, data, !defending, false);
// copy for rolls
final IntegerMap<UnitSupportAttachment> supportLeftFriendlyRolls = new IntegerMap<>(supportLeftFriendly);
final IntegerMap<UnitSupportAttachment> supportLeftEnemyRolls = new IntegerMap<>(supportLeftEnemy);
final Map<UnitSupportAttachment, LinkedIntegerMap<Unit>> supportUnitsLeftFriendlyRolls = new HashMap<>();
for (final UnitSupportAttachment usa : supportUnitsLeftFriendly.keySet()) {
supportUnitsLeftFriendlyRolls.put(usa, new LinkedIntegerMap<>(supportUnitsLeftFriendly.get(usa)));
}
final Map<UnitSupportAttachment, LinkedIntegerMap<Unit>> supportUnitsLeftEnemyRolls = new HashMap<>();
for (final UnitSupportAttachment usa : supportUnitsLeftEnemy.keySet()) {
supportUnitsLeftEnemyRolls.put(usa, new LinkedIntegerMap<>(supportUnitsLeftEnemy.get(usa)));
}
final int diceSides = data.getDiceSides();
for (final Unit current : unitsGettingPowerFor) {
// find our initial strength
int strength;
final UnitAttachment ua = UnitAttachment.get(current.getType());
if (defending) {
strength = ua.getDefense(current.getOwner());
if (isFirstTurnLimitedRoll(current.getOwner(), data)) {
strength = Math.min(1, strength);
} else {
strength += getSupport(current, supportRulesFriendly, supportLeftFriendly, supportUnitsLeftFriendly, unitSupportPowerMap, true, false);
}
strength += getSupport(current, supportRulesEnemy, supportLeftEnemy, supportUnitsLeftEnemy, unitSupportPowerMap, true, false);
} else {
strength = ua.getAttack(current.getOwner());
if (ua.getIsMarine() != 0 && isAmphibiousBattle) {
if (amphibiousLandAttackers.contains(current)) {
strength += ua.getIsMarine();
}
}
if (ua.getIsSea() && isAmphibiousBattle && Matches.territoryIsLand().test(location)) {
// change the strength to be bombard, not attack/defense, because this is a
strength = ua.getBombard();
// bombarding naval unit
}
strength += getSupport(current, supportRulesFriendly, supportLeftFriendly, supportUnitsLeftFriendly, unitSupportPowerMap, true, false);
strength += getSupport(current, supportRulesEnemy, supportLeftEnemy, supportUnitsLeftEnemy, unitSupportPowerMap, true, false);
}
strength += TerritoryEffectHelper.getTerritoryCombatBonus(current.getType(), territoryEffects, defending);
strength = Math.min(Math.max(strength, 0), diceSides);
// now determine our rolls
int rolls;
if (!bombing && strength == 0) {
rolls = 0;
} else {
if (defending) {
rolls = ua.getDefenseRolls(current.getOwner());
} else {
rolls = ua.getAttackRolls(current.getOwner());
}
rolls += getSupport(current, supportRulesFriendly, supportLeftFriendlyRolls, supportUnitsLeftFriendlyRolls, unitSupportRollsMap, false, true);
rolls += getSupport(current, supportRulesEnemy, supportLeftEnemyRolls, supportUnitsLeftEnemyRolls, unitSupportRollsMap, false, true);
rolls = Math.max(0, rolls);
if (rolls == 0) {
strength = 0;
}
}
unitPowerAndRolls.put(current, Tuple.of(strength, rolls));
}
return unitPowerAndRolls;
}
use of games.strategy.util.Tuple in project triplea by triplea-game.
the class DiceRoll method getTotalPowerAndRolls.
private static Tuple<Integer, Integer> getTotalPowerAndRolls(final Map<Unit, Tuple<Integer, Integer>> unitPowerAndRollsMap, final GameData data) {
final int diceSides = data.getDiceSides();
final boolean lhtrBombers = Properties.getLhtrHeavyBombers(data);
// bonus is normally 1 for most games
final int extraRollBonus = Math.max(1, data.getDiceSides() / 6);
int totalPower = 0;
int totalRolls = 0;
for (final Entry<Unit, Tuple<Integer, Integer>> entry : unitPowerAndRollsMap.entrySet()) {
int unitStrength = Math.min(Math.max(0, entry.getValue().getFirst()), diceSides);
final int unitRolls = entry.getValue().getSecond();
if (unitStrength <= 0 || unitRolls <= 0) {
continue;
}
if (unitRolls == 1) {
totalPower += unitStrength;
totalRolls += unitRolls;
} else {
final UnitAttachment ua = UnitAttachment.get(entry.getKey().getType());
if (lhtrBombers || ua.getChooseBestRoll()) {
// LHTR means pick the best dice roll, which doesn't really make sense in LL. So instead, we will just add
// +1 onto the power to
// simulate the gains of having the best die picked.
unitStrength += extraRollBonus * (unitRolls - 1);
totalPower += Math.min(unitStrength, diceSides);
totalRolls += unitRolls;
} else {
totalPower += unitRolls * unitStrength;
totalRolls += unitRolls;
}
}
}
return Tuple.of(totalPower, totalRolls);
}
use of games.strategy.util.Tuple in project triplea by triplea-game.
the class AbstractPlaceDelegate method freePlacementCapacity.
/**
* frees the requested amount of capacity for the given producer by trying to hand over already made placements to
* other territories.
* This only works if one of the placements is done for another territory, more specific for a sea zone.
* If such placements exists it will be tried to let them be done by other adjacent territories.
*
* @param producer
* territory that needs more placement capacity
* @param freeSize
* amount of capacity that is requested
*/
protected void freePlacementCapacity(final Territory producer, final int freeSize, final Collection<Unit> unitsLeftToPlace, final Territory at, final PlayerID player) {
// placements of the producer that could be redone by other territories
final List<UndoablePlacement> redoPlacements = new ArrayList<>();
// territories the producer produced for (but not itself) and the amount of units it produced
final HashMap<Territory, Integer> redoPlacementsCount = new HashMap<>();
// find map place territory -> possible free space for producer
for (final UndoablePlacement placement : placements) {
// find placement move of producer that can be taken over
if (placement.getProducerTerritory().equals(producer)) {
final Territory placeTerritory = placement.getPlaceTerritory();
// units with requiresUnits are too difficult to mess with logically, so do not move them around at all
if (placeTerritory.isWater() && !placeTerritory.equals(producer) && (!isUnitPlacementRestrictions() || placement.getUnits().stream().noneMatch(Matches.unitRequiresUnitsOnCreation()))) {
// found placement move of producer that can be taken over
// remember move and amount of placements in that territory
redoPlacements.add(placement);
redoPlacementsCount.merge(placeTerritory, placement.getUnits().size(), (a, b) -> a + b);
}
}
}
// let other producers take over placements of producer
// remember placement move and new territory if a placement has to be split up
final Collection<Tuple<UndoablePlacement, Territory>> splitPlacements = new ArrayList<>();
int foundSpaceTotal = 0;
for (final Entry<Territory, Integer> entry : redoPlacementsCount.entrySet()) {
final Territory placeTerritory = entry.getKey();
final int maxProductionThatCanBeTakenOverFromThisPlacement = entry.getValue();
// find other producers that could produce for the placeTerritory
final List<Territory> potentialNewProducers = getAllProducers(placeTerritory, player, unitsLeftToPlace);
potentialNewProducers.remove(producer);
potentialNewProducers.sort(getBestProducerComparator(placeTerritory, unitsLeftToPlace, player));
// we can just free a certain amount or still need a certain amount of space
final int maxSpaceToBeFree = Math.min(maxProductionThatCanBeTakenOverFromThisPlacement, freeSize - foundSpaceTotal);
// space that got free this on this placeTerritory
int spaceAlreadyFree = 0;
for (final Territory potentialNewProducerTerritory : potentialNewProducers) {
int leftToPlace = getMaxUnitsToBePlacedFrom(potentialNewProducerTerritory, unitsPlacedInTerritorySoFar(placeTerritory), placeTerritory, player);
if (leftToPlace == -1) {
leftToPlace = maxProductionThatCanBeTakenOverFromThisPlacement;
}
// find placements of the producer the potentialNewProducerTerritory can take over
for (final UndoablePlacement placement : redoPlacements) {
if (!placement.getPlaceTerritory().equals(placeTerritory)) {
continue;
}
final Collection<Unit> placedUnits = placement.getUnits();
final int placementSize = placedUnits.size();
// System.out.println("UndoPlacement: " + placement.getMoveLabel());
if (placementSize <= leftToPlace) {
// potentialNewProducerTerritory can take over complete production
placement.setProducerTerritory(potentialNewProducerTerritory);
removeFromProducedMap(producer, placedUnits);
updateProducedMap(potentialNewProducerTerritory, placedUnits);
spaceAlreadyFree += placementSize;
} else {
// potentialNewProducerTerritory can take over ONLY parts of the production
// remember placement and potentialNewProducerTerritory but try to avoid splitting a placement
splitPlacements.add(Tuple.of(placement, potentialNewProducerTerritory));
}
if (spaceAlreadyFree >= maxSpaceToBeFree) {
break;
}
}
if (spaceAlreadyFree >= maxSpaceToBeFree) {
break;
}
}
foundSpaceTotal += spaceAlreadyFree;
if (foundSpaceTotal >= freeSize) {
break;
}
}
// we had a bug where we tried to split the same undoable placement twice (it can only be undone once!)
boolean unusedSplitPlacments = false;
if (foundSpaceTotal < freeSize) {
// we need to split some placement moves
final Collection<UndoablePlacement> usedUnoablePlacements = new ArrayList<>();
for (final Tuple<UndoablePlacement, Territory> tuple : splitPlacements) {
final UndoablePlacement placement = tuple.getFirst();
if (usedUnoablePlacements.contains(placement)) {
unusedSplitPlacments = true;
continue;
}
final Territory newProducer = tuple.getSecond();
int leftToPlace = getMaxUnitsToBePlacedFrom(newProducer, unitsLeftToPlace, at, player);
foundSpaceTotal += leftToPlace;
// divide set of units that get placed
final Collection<Unit> unitsForOldProducer = new ArrayList<>(placement.getUnits());
final Collection<Unit> unitsForNewProducer = new ArrayList<>();
for (final Unit unit : unitsForOldProducer) {
if (leftToPlace == 0) {
break;
}
unitsForNewProducer.add(unit);
--leftToPlace;
}
unitsForOldProducer.removeAll(unitsForNewProducer);
// split move, by undo and creating two new ones
if (!unitsForNewProducer.isEmpty()) {
// there is a chance we have 2 or more splitPlacements that are using the same placement (trying to split the
// same placement).
// So we must make sure that after we undo it the first time, it can never be undone again.
usedUnoablePlacements.add(placement);
undoMove(placement.getIndex());
performPlaceFrom(newProducer, unitsForNewProducer, placement.getPlaceTerritory(), player);
performPlaceFrom(producer, unitsForOldProducer, placement.getPlaceTerritory(), player);
}
}
}
if (foundSpaceTotal < freeSize && unusedSplitPlacments) {
freePlacementCapacity(producer, (freeSize - foundSpaceTotal), unitsLeftToPlace, at, player);
}
}
Aggregations