From 30113522e0d0b02a318668ce7fd490d4fc7637dd Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Tue, 29 Mar 2022 19:39:59 +0200 Subject: [PATCH] Slight improvement to mustBlockAnAttacker --- .../main/java/forge/ai/AiBlockController.java | 1 + .../java/forge/game/combat/CombatUtil.java | 100 +++++++++++------- .../java/forge/game/phase/PhaseHandler.java | 2 +- .../game/staticability/StaticAbility.java | 2 +- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 07fa3d0766b..c132dc5c43a 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -1064,6 +1064,7 @@ public class AiBlockController { reinforceBlockersToKill(combat); } + // TODO could be made more accurate if this would be inside each blocker choosing loop instead lifeInDanger |= removeUnpayableBlocks(combat); // == 2. If the AI life would still be in danger make a safer approach == diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 932f3fbdf63..48b818d8df2 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -780,6 +780,7 @@ public class CombatUtil { if (getBlockCost(blocker.getGame(), blocker, cardToBeBlocked) != null) { continue; } + int additionalBlockers = getMinNumBlockersForAttacker(cardToBeBlocked, defending) -1; int potentialBlockers = 0; // if the attacker can only be blocked with multiple creatures check if that's possible @@ -809,6 +810,7 @@ public class CombatUtil { if (getBlockCost(blocker.getGame(), blocker, attacker) != null) { continue; } + if (canBlock(attacker, blocker, combat)) { boolean must = true; if (getMinNumBlockersForAttacker(attacker, defending) > 1) { @@ -877,48 +879,18 @@ public class CombatUtil { } final CardCollectionView attackers = combat.getAttackers(); - final CardCollection attackersWithLure = new CardCollection(); + + final CardCollection requirementCards = new CardCollection(); + final Player defender = blocker.getController(); for (final Card attacker : attackers) { if (getBlockCost(blocker.getGame(), blocker, attacker) != null) { continue; } - if (attacker.hasStartOfKeyword("All creatures able to block CARDNAME do so.") - || (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.") - && combat.getBlockers(attacker).isEmpty()) - || (attacker.hasStartOfKeyword("CARDNAME must be blocked by exactly one creature if able.") - && combat.getBlockers(attacker).size() != 1) - || (attacker.hasStartOfKeyword("CARDNAME must be blocked by two or more creatures if able.") - && combat.getBlockers(attacker).size() < 2)) { - attackersWithLure.add(attacker); - } else { - // TODO replace with Hidden Keyword or Static Ability - for (KeywordInterface inst : attacker.getKeywords()) { - String keyword = inst.getOriginal(); - // MustBeBlockedBy - if (keyword.startsWith("MustBeBlockedBy ")) { - final String valid = keyword.substring("MustBeBlockedBy ".length()); - if (blocker.isValid(valid, null, null, null) && - CardLists.getValidCardCount(combat.getBlockers(attacker), valid, null, null, null) == 0) { - attackersWithLure.add(attacker); - break; - } - } - // MustBeBlockedByAll: - if (keyword.startsWith("MustBeBlockedByAll")) { - final String valid = keyword.split(":")[1]; - if (blocker.isValid(valid, null, null, null)) { - attackersWithLure.add(attacker); - break; - } - } - } + if (attackerLureSatisfied(attacker, blocker, combat.getBlockers(attacker))) { + continue; } - } - final CardCollection requirementCards = new CardCollection(); - final Player defender = blocker.getController(); - for (final Card attacker : attackersWithLure) { if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)) { boolean canBe = true; Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker); @@ -963,12 +935,60 @@ public class CombatUtil { return false; } - boolean mustBlock = !combat.getAttackersBlockedBy(blocker).containsAll(requirementCards); - if (mustBlock && !canBlock(blocker, combat)) { - // the blocker can't block more but is he even part of another requirement? - mustBlock = Collections.disjoint(combat.getAttackersBlockedBy(blocker), requirementCards); + if (combat.getAttackersBlockedBy(blocker).containsAll(requirementCards)) { + return false; } - return mustBlock; + + if (!canBlock(blocker, combat)) { + // the blocker can't block more but is he even part of another requirement? + for (final Card attacker : attackers) { + final boolean requirementSatisfied = attackerLureSatisfied(attacker, blocker, combat.getBlockers(attacker)); + final CardCollection reducedBlockers = combat.getBlockers(attacker); + if (requirementSatisfied && reducedBlockers.contains(blocker)) { + reducedBlockers.remove(blocker); + if (!attackerLureSatisfied(attacker, blocker, reducedBlockers)) { + return false; + } + } + } + } + + return Collections.disjoint(combat.getAttackersBlockedBy(blocker), requirementCards); + } + + private static boolean attackerLureSatisfied(final Card attacker, final Card blocker, final CardCollection blockers) { + if (attacker.hasStartOfKeyword("All creatures able to block CARDNAME do so.") + || (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.") + && blockers.isEmpty()) + || (attacker.hasStartOfKeyword("CARDNAME must be blocked by exactly one creature if able.") + && blockers.size() != 1) + || (attacker.hasStartOfKeyword("CARDNAME must be blocked by two or more creatures if able.") + && blockers.size() < 2)) { + return false; + } + + // TODO replace with Hidden Keyword or Static Ability + for (KeywordInterface inst : attacker.getKeywords()) { + String keyword = inst.getOriginal(); + // MustBeBlockedBy + if (keyword.startsWith("MustBeBlockedBy ")) { + final String valid = keyword.substring("MustBeBlockedBy ".length()); + if (blocker.isValid(valid, null, null, null) && + CardLists.getValidCardCount(blockers, valid, null, null, null) == 0) { + return false; + + } + } + // MustBeBlockedByAll: + if (keyword.startsWith("MustBeBlockedByAll")) { + final String valid = keyword.split(":")[1]; + if (blocker.isValid(valid, null, null, null)) { + return false; + } + } + } + + return true; } // can a player block with one or more creatures at the moment? diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index b5d02e3094a..1cef9a25eea 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -578,7 +578,7 @@ public class PhaseHandler implements java.io.Serializable { } for (final Card attacker : combat.getAttackers()) { - // TODO currently doesn't refund (can really only happen if you cancel paying for a creature with an attacking requirement that could be satisfied without a tax) + // TODO currently doesn't refund previous attackers (can really only happen if you cancel paying for a creature with an attack requirement that could be satisfied without a tax) final boolean canAttack = CombatUtil.checkPropagandaEffects(game, attacker, combat); if (!canAttack) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index edd1fddc002..e2c6ded3115 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -642,4 +642,4 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone public List getTriggerRemembered() { return ImmutableList.of(); } -} // end class StaticAbility +}