From 1e969082ba000933affa67087f9afd2c5d9eb550 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Wed, 5 Jul 2023 14:00:43 -0400 Subject: [PATCH] Fix stalemate logic to end stratBomber vs. transport fights. --- .../delegate/battle/MustFightBattle.java | 4 ++ .../steps/change/CheckGeneralBattleEnd.java | 69 ++++++++++++++----- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java index bc549d713e1..a02f7dd5396 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/MustFightBattle.java @@ -1301,6 +1301,10 @@ public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { throw new IllegalStateException( "Round 10,000 reached in a battle. Something must be wrong." + " Please report this to TripleA.\n" + + " Territory: " + + battleSite + + " Attacker: " + + attacker.getName() + " Attacking unit types: " + attackingUnits.stream() .map(Unit::getType) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/CheckGeneralBattleEnd.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/CheckGeneralBattleEnd.java index 96c789b1f69..b6bb391edfb 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/CheckGeneralBattleEnd.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/battle/steps/change/CheckGeneralBattleEnd.java @@ -4,6 +4,7 @@ import static games.strategy.triplea.delegate.battle.BattleState.Side.OFFENSE; import static games.strategy.triplea.delegate.battle.BattleState.UnitBattleFilter.ALIVE; +import com.google.common.collect.Iterables; import games.strategy.engine.data.Unit; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.triplea.Properties; @@ -15,14 +16,19 @@ import games.strategy.triplea.delegate.battle.IBattle; import games.strategy.triplea.delegate.battle.steps.BattleStep; import games.strategy.triplea.delegate.battle.steps.RetreatChecks; +import games.strategy.triplea.delegate.battle.steps.fire.FiringGroup; import games.strategy.triplea.delegate.battle.steps.fire.general.FiringGroupSplitterGeneral; import games.strategy.triplea.delegate.power.calculator.CombatValueBuilder; import games.strategy.triplea.delegate.power.calculator.PowerStrengthAndRolls; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import lombok.AllArgsConstructor; +import org.triplea.java.collections.CollectionUtils; @AllArgsConstructor public class CheckGeneralBattleEnd implements BattleStep { @@ -54,10 +60,8 @@ public Order getOrder() { public void execute(final ExecutionStack stack, final IDelegateBridge bridge) { if (hasSideLost(OFFENSE)) { battleActions.endBattle(IBattle.WhoWon.DEFENDER, bridge); - } else if (hasSideLost(DEFENSE)) { battleActions.endBattle(IBattle.WhoWon.ATTACKER, bridge); - } else if (isStalemate() && !canAttackerRetreatInStalemate()) { battleActions.endBattle(IBattle.WhoWon.DRAW, bridge); } @@ -68,18 +72,43 @@ protected boolean hasSideLost(final BattleState.Side side) { .noneMatch(Matches.unitIsNotInfrastructure()); } + private Predicate inAnyFiringGroup(Iterable firingGroups) { + return u -> + StreamSupport.stream(firingGroups.spliterator(), false) + .anyMatch(fg -> fg.getFiringUnits().contains(u)); + } + protected boolean isStalemate() { + if (battleState.getStatus().isLastRound()) { + return true; + } + final Iterable attackerFiringGroups = getAllFiringGroups(OFFENSE); + // Filter attackers to only units that are in firing groups, to eliminate units + // that can technically roll dice in abstract, but not against any current enemies. + final Collection attackers = + CollectionUtils.getMatches( + battleState.filterUnits(ALIVE, OFFENSE), inAnyFiringGroup(attackerFiringGroups)); + final Iterable defendersFiringGroups = getAllFiringGroups(DEFENSE); + // Filter defenders to only units that are in firing groups, to eliminate units + // that can technically roll dice in abstract, but not against any current enemies. + final Collection defenders = + CollectionUtils.getMatches( + battleState.filterUnits(ALIVE, DEFENSE), inAnyFiringGroup(defendersFiringGroups)); return battleState.getStatus().isLastRound() - || (hasNoStrengthOrRolls(OFFENSE) && hasNoStrengthOrRolls(DEFENSE)) - || (hasNoTargets(OFFENSE) && hasNoTargets(DEFENSE)); + || (hasNoStrengthOrRolls(OFFENSE, attackers, defenders) + && hasNoStrengthOrRolls(DEFENSE, defenders, attackers)) + || (hasNoTargets(attackerFiringGroups) && hasNoTargets(defendersFiringGroups)); } - private boolean hasNoStrengthOrRolls(final BattleState.Side side) { + private boolean hasNoStrengthOrRolls( + final BattleState.Side side, + final Collection myUnits, + final Collection enemyUnits) { return !PowerStrengthAndRolls.buildWithPreSortedUnits( - battleState.filterUnits(ALIVE, side), + myUnits, CombatValueBuilder.mainCombatValue() - .enemyUnits(battleState.filterUnits(ALIVE, side.getOpposite())) - .friendlyUnits(battleState.filterUnits(ALIVE, side)) + .enemyUnits(enemyUnits) + .friendlyUnits(myUnits) .side(side) .gameSequence(battleState.getGameData().getSequence()) .supportAttachments(battleState.getGameData().getUnitTypeList().getSupportRules()) @@ -87,18 +116,22 @@ private boolean hasNoStrengthOrRolls(final BattleState.Side side) { Properties.getLhtrHeavyBombers(battleState.getGameData().getProperties())) .gameDiceSides(battleState.getGameData().getDiceSides()) .territoryEffects(battleState.getTerritoryEffects()) - .build()) - .hasStrengthOrRolls(); + .build()).hasStrengthOrRolls(); + } + + private Iterable getAllFiringGroups(final BattleState.Side side) { + return Iterables.concat( + getFiringGroup(side, FiringGroupSplitterGeneral.Type.NORMAL), + getFiringGroup(side, FiringGroupSplitterGeneral.Type.FIRST_STRIKE)); + } + + private List getFiringGroup( + final BattleState.Side side, final FiringGroupSplitterGeneral.Type type) { + return FiringGroupSplitterGeneral.of(side, type, "stalemate").apply(battleState); } - private boolean hasNoTargets(final BattleState.Side side) { - return FiringGroupSplitterGeneral.of(side, FiringGroupSplitterGeneral.Type.NORMAL, "stalemate") - .apply(battleState) - .isEmpty() - && FiringGroupSplitterGeneral.of( - side, FiringGroupSplitterGeneral.Type.FIRST_STRIKE, "stalemate") - .apply(battleState) - .isEmpty(); + private boolean hasNoTargets(Iterable firingGroups) { + return Iterables.isEmpty(firingGroups); } protected boolean canAttackerRetreatInStalemate() {