diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java index 98f1e49237..ba4a71c7e5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java @@ -5,6 +5,8 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static junit.framework.TestCase.assertEquals; + /** * @author LevelX2 */ @@ -26,9 +28,12 @@ public class TwoHeadedSliverTest extends CardTestPlayerBase { block(3, playerB, "Silvercoat Lion", "Two-Headed Sliver"); setStopAt(3, PhaseStep.END_TURN); - execute(); - assertPermanentCount(playerA, "Two-Headed Sliver", 1); - assertLife(playerB, 19); + try { + execute(); + } catch (UnsupportedOperationException e) { + assertEquals("Two-Headed Sliver is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index 4305b94d51..27f1108e07 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -32,6 +32,8 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static junit.framework.TestCase.assertEquals; + /** * * @author LevelX2, icetc @@ -212,12 +214,12 @@ public class BlockRequirementTest extends CardTestPlayerBase { block(1, playerB, "Hill Giant", "Breaker of Armies"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - // Hill giant is still alive - assertPermanentCount(playerB, "Hill Giant", 1); - // Player B was unable to block, so goes down to 10 life - assertLife(playerB, 8); + try { + execute(); + } catch (UnsupportedOperationException e) { + assertEquals("Breaker of Armies is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } } /* diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java deleted file mode 100644 index abb44c24a2..0000000000 --- a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package org.mage.test.combat; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Ignore; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author jeffwadsworth - */ -public class CanBlockMultipleCreatures extends CardTestPlayerBase { - - // test must be ignored until creature blocking multiple supported by test framework - @Ignore - @Test - public void testCombat() { - addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); - - addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 - addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) - addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 - addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 - - // Trample requirement for Kessig Dire Swine - addCard(Zone.GRAVEYARD, playerB, "Forest", 1); - addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); - addCard(Zone.GRAVEYARD, playerB, "Flight", 1); - addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); - - // Attack with all 4 creatures and block all with the Watcher in the Web - attack(2, playerB, "Ulrich, Uncontested Alpha"); - attack(2, playerB, "Kessig Dire Swine"); - attack(2, playerB, "Howlpack Wolf"); - attack(2, playerB, "Incorrigible Youths"); - - block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); - block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); - block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); - block(2, playerA, "Watcher in the Web", "Incorrigible Youths"); - - setStopAt(2, PhaseStep.COMBAT_DAMAGE); - execute(); - - assertLife(playerA, 19); - - } - - /* - * Reported bug: Night Market Guard was able to block a creature with Menace - */ - @Test - public void testNightMarketGuardShouldNotBlockCreatureWithMenace() - { - /* - Night Market Guard {3} 3/1 - Artifact Creature — Construct - Night Market Guard can block an additional creature each combat. - */ - String nMarketGuard = "Night Market Guard"; - - /* - Embraal Bruiser {1}{B} - Creature - Human Warrior - Embraal Bruiser enters the battlefield tapped. - Embraal Bruiser has menace as long as you control an artifact. - */ - String eBruiser = "Embraal Bruiser"; - - /* - {0} 1/1 - * Artifact Creature — Construct - */ - String memnite = "Memnite"; - - addCard(Zone.BATTLEFIELD, playerA, nMarketGuard); - addCard(Zone.BATTLEFIELD, playerB, eBruiser); - addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace - - attack(4, playerB, eBruiser); - block(4, playerA, nMarketGuard, eBruiser); - - setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertTapped(eBruiser, true); - assertLife(playerA, 17); // could not block, so 3 damage goes through - assertPermanentCount(playerA, nMarketGuard, 1); - assertPermanentCount(playerB, eBruiser, 1); - } -} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java new file mode 100644 index 0000000000..c488cb35f3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.combat; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; + +/** + * + * @author jeffwadsworth + * @author Simown + */ +public class CanBlockMultipleCreaturesTest extends CardTestPlayerBase { + + @Test + public void testMultipleBlockWithTrample() { + + addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 + addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) + addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 + + // Trample requirement for Kessig Dire Swine + addCard(Zone.GRAVEYARD, playerB, "Forest", 1); + addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); + addCard(Zone.GRAVEYARD, playerB, "Flight", 1); + addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); + + // Attack with all 4 creatures and block all with the Watcher in the Web + attack(2, playerB, "Kessig Dire Swine"); + attack(2, playerB, "Ulrich, Uncontested Alpha"); + attack(2, playerB, "Howlpack Wolf"); + attack(2, playerB, "Incorrigible Youths"); + + // BLOCKING ORDER MATTERS - the trampling creature must be selected to block first + // You can manually change the blocking order but it's easier to assign them in order + block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); + block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); + block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); + block(2, playerA, "Watcher in the Web", "Incorrigible Youths"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 19); + + } + + @Test + public void testMultipleBlockWithTrample2() { + + addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 + addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) + addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 + + // Trample requirement for Kessig Dire Swine + addCard(Zone.GRAVEYARD, playerB, "Forest", 1); + addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); + addCard(Zone.GRAVEYARD, playerB, "Flight", 1); + addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); + + // Attack with all 4 creatures and block all with the Watcher in the Web + attack(2, playerB, "Kessig Dire Swine"); + attack(2, playerB, "Ulrich, Uncontested Alpha"); + attack(2, playerB, "Howlpack Wolf"); + attack(2, playerB, "Incorrigible Youths"); + + // BLOCKING ORDER MATTERS - the trampling creature must be selected to block first + block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); + block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); + block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); + // Don't block Incorrigible Youths + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + // Damage 1 from Kessig Dire Swine + 4 from Incorrigible Youths + assertLife(playerA, 15); + } + + @Test + public void testCanOnlyBlockSingle() { + + // Hundred-Handed One {2}{W}{W} + // Monstrosity 3. {3}{W}{W}{W} (If this creature isn’t monstrous, put three +1/+1 counters on it and it becomes monstrous.) + //As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat. + addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike + + // Attack with all 4 creatures and try and block both with hundred-handed one + attack(2, playerB, "Bronze Sable"); + attack(2, playerB, "Fabled Hero"); + + block(2, playerA, "Hundred-Handed One", "Bronze Sable"); + block(2, playerA, "Hundred-Handed One", "Fabled Hero"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + + // Will fail on purpose - we are trying to block too many creatures! + try { + execute(); + } catch(UnsupportedOperationException e) { + assertEquals("Hundred-Handed One cannot block Fabled Hero", e.getMessage()); + } + } + + @Test + public void testCanBlockMultiple() { + + // Hundred-Handed One {2}{W}{W} + // Monstrosity 3. {3}{W}{W}{W} (If this creature isn’t monstrous, put three +1/+1 counters on it and it becomes monstrous.) + // As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat. + addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1); + // For monstrosity + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike + + // Make hundred-handed one monstrous + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{W}{W}{W}: Monstrosity 3."); + + // Attack with all 4 creatures and try and block both with hundred-handed one + attack(2, playerB, "Bronze Sable"); + attack(2, playerB, "Fabled Hero"); + + block(2, playerA, "Hundred-Handed One", "Bronze Sable"); + block(2, playerA, "Hundred-Handed One", "Fabled Hero"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + + // Will not fail this time as hundred-handed one is monstrous and can block up to 100 creatures + execute(); + + // Was a 3/5 but monstrosity 3 + assertPowerToughness(playerA, "Hundred-Handed One", 6, 8); + // No one has been hit + assertLife(playerA, 20); + assertLife(playerB, 20); + } + + /* + * Reported bug: Night Market Guard was able to block a creature with Menace + */ + @Test + public void testNightMarketGuardShouldNotBlockCreatureWithMenace() + { + /* + Night Market Guard {3} 3/1 + Artifact Creature — Construct + Night Market Guard can block an additional creature each combat. + */ + String nMarketGuard = "Night Market Guard"; + + /* + Embraal Bruiser {1}{B} + Creature - Human Warrior + Embraal Bruiser enters the battlefield tapped. + Embraal Bruiser has menace as long as you control an artifact. + */ + String eBruiser = "Embraal Bruiser"; + + /* + {0} 1/1 + * Artifact Creature — Construct + */ + String memnite = "Memnite"; + + addCard(Zone.BATTLEFIELD, playerA, nMarketGuard); + addCard(Zone.BATTLEFIELD, playerB, eBruiser); + addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace + + attack(4, playerB, eBruiser); + block(4, playerA, nMarketGuard, eBruiser); + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + + // Catch the illegal block + try { + execute(); + } catch(UnsupportedOperationException e) { + assertEquals("Embraal Bruiser is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } + + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index e58a4f17cb..7bf5099618 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -28,13 +28,10 @@ package org.mage.test.player; import java.io.Serializable; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; + import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -65,15 +62,10 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.filter.Filter; import mage.filter.FilterPermanent; -import mage.filter.common.FilterAttackingCreature; -import mage.filter.common.FilterCreatureForCombat; -import mage.filter.common.FilterCreatureForCombatBlock; -import mage.filter.common.FilterCreatureOrPlayer; -import mage.filter.common.FilterPlaneswalkerPermanent; +import mage.filter.common.*; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.AttackingPredicate; -import mage.filter.predicate.permanent.BlockingPredicate; import mage.filter.predicate.permanent.SummoningSicknessPredicate; import mage.game.Game; import mage.game.Graveyard; @@ -181,6 +173,11 @@ public class TestPlayer implements Player { return null; } + // Gets all permanents that match the filter + protected List findPermanents(FilterPermanent filter, UUID controllerId, Game game) { + return game.getBattlefield().getAllActivePermanents(filter, controllerId, game); + } + private boolean checkExecuteCondition(String[] groups, Game game) { if (groups[2].startsWith("spellOnStack=")) { String spellOnStack = groups[2].substring(13); @@ -289,7 +286,7 @@ public class TestPlayer implements Player { int index = 0; int targetsSet = 0; for (String targetName : targetList) { - Mode selectedMode = null; + Mode selectedMode; if (targetName.startsWith("mode=")) { int modeNr = Integer.parseInt(targetName.substring(5, 6)); if (modeNr == 0 || modeNr > (ability.getModes().isEachModeMoreThanOnce() ? ability.getModes().getSelectedModes().size() : ability.getModes().size())) { @@ -561,27 +558,88 @@ public class TestPlayer implements Player { @Override public void selectBlockers(Game game, UUID defendingPlayerId) { UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); + // Map of Blocker reference -> list of creatures blocked + Map> blockedCreaturesByCreature = new HashMap<>(); for (PlayerAction action : actions) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) { String command = action.getAction(); command = command.substring(command.indexOf("block:") + 6); String[] groups = command.split("\\$"); - FilterCreatureForCombatBlock filterBlocker = new FilterCreatureForCombatBlock(); - filterBlocker.add(new NamePredicate(groups[0])); - filterBlocker.add(Predicates.not(new BlockingPredicate())); - Permanent blocker = findPermanent(filterBlocker, computerPlayer.getId(), game); - if (blocker != null) { - FilterAttackingCreature filterAttacker = new FilterAttackingCreature(); - filterAttacker.add(new NamePredicate(groups[1])); - Permanent attacker = findPermanent(filterAttacker, opponentId, game); - if (attacker != null) { - computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); + FilterAttackingCreature filterAttacker = new FilterAttackingCreature(); + filterAttacker.add(new NamePredicate(groups[1])); + Permanent attacker = findPermanent(filterAttacker, opponentId, game); + FilterControlledPermanent filterPermanent = new FilterControlledPermanent(); + filterPermanent.add(new NamePredicate(groups[0])); + // Get all possible blockers - those with the same name on the battlefield + List possibleBlockers = findPermanents(filterPermanent, computerPlayer.getId(), game); + if (!possibleBlockers.isEmpty() && attacker != null) { + boolean blockerFound = false; + for(Permanent blocker: possibleBlockers) { + // See if it can block this creature + if(canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) { + computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); + blockerFound = true; + break; + } + } + // If we haven't found a blocker then an illegal block has been made in the test + if(!blockerFound) { + throw new UnsupportedOperationException(groups[0] + " cannot block " + groups[1]); } } } + checkMultipleBlockers(game, blockedCreaturesByCreature); } } + // Checks if a creature can block at least one more creature + private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map> blockedCreaturesByCreature) { + MageObjectReference blockerRef = new MageObjectReference(blocker, game); + // See if we already reference this blocker + for(MageObjectReference r: blockedCreaturesByCreature.keySet()) { + if(r.equals(blockerRef)) { + // Use the existing reference if we do + blockerRef = r; + } + } + List blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>()); + int numBlocked = blocked.size(); + // Can't block any more creatures + if(++numBlocked > blocker.getMaxBlocks()) { + return false; + } + // Add the attacker reference to the list of creatures this creature is blocking + blocked.add(new MageObjectReference(attacker, game)); + blockedCreaturesByCreature.put(blockerRef, blocked); + return true; + } + + // Check for Menace type abilities - if creatures can be blocked by >X or > blockedCreaturesByCreature) { + // Stores the total number of blockers for each attacker + Map blockersForAttacker = new HashMap<>(); + // Calculate the number of blockers each attacker has + for(List attackers : blockedCreaturesByCreature.values()) { + for(MageObjectReference mr: attackers) { + Integer blockers = blockersForAttacker.getOrDefault(mr, 0); + blockersForAttacker.put(mr, blockers+1); + } + } + // Check each attacker is blocked by an allowed amount of creatures + for(Map.Entry entry: blockersForAttacker.entrySet()) { + Permanent attacker = entry.getKey().getPermanent(game); + Integer blockers = entry.getValue(); + // If getMaxBlockedBy() == 0 it means any number of creatures can block this creature + if(attacker.getMaxBlockedBy() != 0 && blockers > attacker.getMaxBlockedBy()) { + throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It can only be blocked by " + attacker.getMaxBlockedBy() + " or less."); + } + else if(blockers < attacker.getMinBlockedBy()) { + throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It has to be blocked by " + attacker.getMinBlockedBy() + " or more."); + } + } + // No errors raised - all the blockers pass the test! + } + @Override public Mode chooseMode(Modes modes, Ability source, Game game) { if (!modesSet.isEmpty() && modes.getMaxModes() > modes.getSelectedModes().size()) {