From ddecc4226d343f8bd1ac4ecdb21206740b59a7e7 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Tue, 4 Jul 2023 23:26:40 +0200 Subject: [PATCH] Only return subset of potential attackers based on current declaration --- .../java/forge/ai/AiAttackController.java | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 1503adfd5bd..47b52d0798d 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -357,35 +357,41 @@ public class AiAttackController { } // this checks to make sure that the computer player doesn't lose when the human player attacks - public final List notNeededAsBlockers(final List attackers) { + public final List notNeededAsBlockers(final List currentAttackers, final List potentialAttackers) { //check for time walks if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) { - return attackers; + return potentialAttackers; } // no need to block (already holding mana to cast fog next turn) if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) { // Don't send the card that'll do the fog effect to attack, it's unsafe! List toRemove = Lists.newArrayList(); - for (Card c : attackers) { + for (Card c : potentialAttackers) { if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) { toRemove.add(c); } } - attackers.removeAll(toRemove); - return attackers; + potentialAttackers.removeAll(toRemove); + return potentialAttackers; } + if (ai.isCardInPlay("Masako the Humorless")) { + // "Tapped creatures you control can block as though they were untapped." + return potentialAttackers; + } + + final CardCollection notNeededAsBlockers = new CardCollection(potentialAttackers); + final List vigilantes = new ArrayList<>(); - for (final Card c : myList) { - if (c.getName().equals("Masako the Humorless")) { - // "Tapped creatures you control can block as though they were untapped." - return attackers; - } + for (final Card c : Iterables.concat(currentAttackers, potentialAttackers)) { // no need to block if an effect is in play which untaps all creatures // (pseudo-Vigilance akin to Awakening or Prophet of Kruphix) if (c.hasKeyword(Keyword.VIGILANCE) || ComputerUtilCard.willUntap(ai, c)) { vigilantes.add(c); + } else if (currentAttackers.contains(c)) { + // already attacking so can't block + notNeededAsBlockers.add(c); } } // reduce the search space @@ -399,10 +405,8 @@ public class AiAttackController { } }); - final CardCollection notNeededAsBlockers = new CardCollection(attackers); - // don't hold back creatures that can't block any of the human creatures - final List blockers = getPossibleBlockers(attackers, opponentsAttackers, true); + final List blockers = getPossibleBlockers(potentialAttackers, opponentsAttackers, true); if (!blockers.isEmpty()) { notNeededAsBlockers.removeAll(blockers); @@ -473,12 +477,15 @@ public class AiAttackController { // these creatures will be available to block anyway notNeededAsBlockers.addAll(vigilantes); + // remove those that were only included to ensure a full picture for the baseline + notNeededAsBlockers.removeAll(currentAttackers); + // Increase the total number of blockers needed by 1 if Finest Hour in play // (human will get an extra first attack with a creature that untaps) // In addition, if the computer guesses it needs no blockers, make sure // that it won't be surprised by Exalted final int humanExaltedBonus = defendingOpponent.countExaltedBonus(); - int blockersNeeded = attackers.size() - notNeededAsBlockers.size(); + int blockersNeeded = potentialAttackers.size() - notNeededAsBlockers.size(); if (humanExaltedBonus > 0) { final boolean finestHour = defendingOpponent.isCardInPlay("Finest Hour"); @@ -511,24 +518,28 @@ public class AiAttackController { public void reinforceWithBanding(final Player player, final Combat combat) { reinforceWithBanding(player, combat, null); } - public void reinforceWithBanding(final Player player, final Combat combat, final Card test) { + CardCollection attackers = combat.getAttackers(); + if (attackers.isEmpty()) { + return; + } + List bandsWithString = Arrays.asList("Bands with Other Legendary Creatures", "Bands with Other Creatures named Wolves of the Hunt", "Bands with Other Dinosaurs"); - List bandingCreatures = new CardCollection(); - + List bandingCreatures = null; if (test == null) { - bandingCreatures.addAll(notNeededAsBlockers(player.getCreaturesInPlay())); - bandingCreatures = CardLists.filter(bandingCreatures, card -> card.hasKeyword(Keyword.BANDING) || card.hasAnyKeyword(bandsWithString)); + bandingCreatures = CardLists.filter(myList, card -> card.hasKeyword(Keyword.BANDING) || card.hasAnyKeyword(bandsWithString)); // filter out anything that can't legally attack or is already declared as an attacker bandingCreatures = CardLists.filter(bandingCreatures, card -> !combat.isAttacking(card) && CombatUtil.canAttack(card)); + + bandingCreatures = notNeededAsBlockers(attackers, bandingCreatures); } else { // Test a specific creature for Banding if (test.hasKeyword(Keyword.BANDING) || test.hasAnyKeyword(bandsWithString)) { - bandingCreatures.add(test); + bandingCreatures = new CardCollection(test); } } @@ -539,12 +550,11 @@ public class AiAttackController { return; } - if (!bandingCreatures.isEmpty()) { + if (bandingCreatures != null) { List evasionKeywords = Arrays.asList("Flying", "Horsemanship", "Shadow", "Plainswalk", "Islandwalk", "Forestwalk", "Mountainwalk", "Swampwalk"); // TODO: Assign to band with the best attacker for now, but needs better logic. - CardCollection attackers = combat.getAttackers(); for (Card c : bandingCreatures) { Card bestBand; @@ -1275,7 +1285,7 @@ public class AiAttackController { if ( LOG_AI_ATTACKS ) System.out.println("Normal attack"); - attackersLeft = notNeededAsBlockers(attackersLeft); + attackersLeft = notNeededAsBlockers(combat.getAttackers(), attackersLeft); attackersLeft = sortAttackers(attackersLeft); if ( LOG_AI_ATTACKS )