diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java index 64c863ba4af..38b1c2286c7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java @@ -52,6 +52,40 @@ public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps { assertPermanentCount(playerB, "Balduvian Bears", 5 - 3); } + @Test + public void test_AI_Attack() { + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + + // AI must attack + aiPlayStep(1, PhaseStep.DECLARE_ATTACKERS, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void test_AI_Block() { + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + + // AI must block + attack(1, playerA, "Balduvian Bears"); + aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 1); + assertGraveyardCount(playerB, 1); + assertLife(playerB, 20); + } + @Test @Ignore // AI can't play blade cause score system give priority for boost instead restriction effects like goad public void test_AI_GoadedByBloodthirstyBlade_Normal() { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BecomeBlockTriggersAITest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BecomeBlockTriggersAITest.java new file mode 100644 index 00000000000..3012f384191 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BecomeBlockTriggersAITest.java @@ -0,0 +1,93 @@ +package org.mage.test.cards.requirement; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class BecomeBlockTriggersAITest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_Manual_AutoBlock() { + removeAllCardsFromHand(playerA); + removeAllCardsFromHand(playerB); + + // All creatures able to block Nessian Boar do so. + // Whenever Nessian Boar becomes blocked by a creature, that creature’s controller draws a card. + addCard(Zone.BATTLEFIELD, playerA, "Nessian Boar", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + + // auto-block by requirement effect + attack(1, playerA, "Nessian Boar"); + //block(1, playerB, "Balduvian Bears", "Nessian Boar"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 1); + assertHandCount(playerA, 0); + assertHandCount(playerB, 1); + } + + @Test + public void test_Manual_CantBlockAgain() { + removeAllCardsFromHand(playerA); + removeAllCardsFromHand(playerB); + + // All creatures able to block Nessian Boar do so. + // Whenever Nessian Boar becomes blocked by a creature, that creature’s controller draws a card. + addCard(Zone.BATTLEFIELD, playerA, "Nessian Boar", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + + // auto-block by requirement effect + attack(1, playerA, "Nessian Boar"); + // try to block manually, but it must raise error + block(1, playerB, "Balduvian Bears", "Nessian Boar"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + try { + execute(); + Assert.fail("Expected exception, but not raise"); + } catch (UnsupportedOperationException ue) { + Assert.assertEquals("Balduvian Bears cannot block Nessian Boar it is already blocking the maximum amount of creatures.", ue.getMessage()); + } + //assertAllCommandsUsed(); // must have 1 missing command (block) + } + + @Test + public void test_AI_CantBlockAgain() { + removeAllCardsFromHand(playerA); + removeAllCardsFromHand(playerB); + + // All creatures able to block Nessian Boar do so. + // Whenever Nessian Boar becomes blocked by a creature, that creature’s controller draws a card. + addCard(Zone.BATTLEFIELD, playerA, "Nessian Boar", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + + // auto-block by requirement effect + attack(1, playerA, "Nessian Boar"); + // AI can't block same creature twice + aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 1); + assertHandCount(playerA, 0); + assertHandCount(playerB, 1); + } +} 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 8cdd2f194b8..ee778d4e57c 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 @@ -1,4 +1,3 @@ - package org.mage.test.cards.requirement; import mage.constants.PhaseStep; @@ -10,7 +9,6 @@ import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; /** - * * @author LevelX2, icetc */ public class BlockRequirementTest extends CardTestPlayerBase { @@ -91,7 +89,7 @@ public class BlockRequirementTest extends CardTestPlayerBase { /** * Elemental Uprising - "it must be blocked this turn if able", not working - * + *

* The bug just happened for me today as well - the problem is "must be * blocked" is not being enforced correctly. During opponent's main phase * they cast Elemental Uprising targeting an untapped land. They attacked @@ -186,7 +184,7 @@ public class BlockRequirementTest extends CardTestPlayerBase { attack(1, playerA, "Breaker of Armies"); // not allowed due to Breaker of Armies having menace - block(1, playerB, "Hill Giant", "Breaker of Armies"); + //block(1, playerB, "Hill Giant", "Breaker of Armies"); // auto-block from requrement effect must add blocker without command setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -197,7 +195,7 @@ public class BlockRequirementTest extends CardTestPlayerBase { assertEquals("Breaker of Armies is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); } } - + /* Reported bug: Slayer's Cleaver did not force Wretched Gryff (an eldrazi) to block */ @@ -225,7 +223,7 @@ public class BlockRequirementTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Dimensional Infiltrator", 1); assertGraveyardCount(playerB, "Llanowar Elves", 1); } - + /* Reported bug: Challenger Troll on field not enforcing block restrictions */ @@ -237,30 +235,30 @@ public class BlockRequirementTest extends CardTestPlayerBase { Each creature you control with power 4 or greater can’t be blocked by more than one creature. */ String cTroll = "Challenger Troll"; - + String bSable = "Bronze Sable"; // {2} 2/1 String hGiant = "Hill Giant"; // {3}{R} 3/3 - + addCard(Zone.BATTLEFIELD, playerA, cTroll); addCard(Zone.BATTLEFIELD, playerB, bSable); addCard(Zone.BATTLEFIELD, playerB, hGiant); - + attack(1, playerA, cTroll); // only 1 should be able to block it since Troll >=4 power block restriction block(1, playerB, bSable, cTroll); block(1, playerB, hGiant, cTroll); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - + try { execute(); fail("Expected exception not thrown"); } catch (UnsupportedOperationException e) { assertEquals("Challenger Troll is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); - } + } } - + /* Reported bug: Challenger Troll on field not enforcing block restrictions */ @@ -273,29 +271,29 @@ public class BlockRequirementTest extends CardTestPlayerBase { */ String cTroll = "Challenger Troll"; String bHulk = "Bloom Hulk"; // {3}{G} 4/4 ETB: proliferate - + String bSable = "Bronze Sable"; // {2} 2/1 String hGiant = "Hill Giant"; // {3}{R} 3/3 - + addCard(Zone.BATTLEFIELD, playerA, cTroll); addCard(Zone.BATTLEFIELD, playerA, bHulk); addCard(Zone.BATTLEFIELD, playerB, bSable); addCard(Zone.BATTLEFIELD, playerB, hGiant); - + attack(1, playerA, cTroll); attack(1, playerA, bHulk); // only 1 should be able to block Bloom Hulk since >=4 power and Troll on field block(1, playerB, bSable, bHulk); block(1, playerB, hGiant, bHulk); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - + try { execute(); fail("Expected exception not thrown"); } catch (UnsupportedOperationException e) { assertEquals("Bloom Hulk is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); - } + } } } 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 672140af986..08fcf3c4658 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 @@ -112,6 +112,12 @@ public class TestPlayer implements Player { computerPlayer.setTestPlayerLink(this); } + public TestPlayer(TestComputerPlayerMonteCarlo computerPlayer) { + this.computerPlayer = computerPlayer; + AIPlayer = false; + computerPlayer.setTestPlayerLink(this); + } + public TestPlayer(final TestPlayer testPlayer) { this.AIPlayer = testPlayer.AIPlayer; this.AICanChooseInStrictMode = testPlayer.AICanChooseInStrictMode; @@ -1411,6 +1417,16 @@ public class TestPlayer implements Player { boolean madeAttackByAction = false; for (Iterator it = actions.iterator(); it.hasNext(); ) { PlayerAction action = it.next(); + + // aiXXX commands + if (action.getTurnNum() == game.getTurnNum() && action.getAction().equals(AI_PREFIX + AI_COMMAND_PLAY_STEP)) { + mustAttackByAction = true; + madeAttackByAction = true; + this.computerPlayer.selectAttackers(game, attackingPlayerId); + it.remove(); + break; + } + if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) { mustAttackByAction = true; String command = action.getAction(); @@ -1473,7 +1489,7 @@ public class TestPlayer implements Player { this.chooseStrictModeFailed("attacker", game, "select attackers must use attack command but don't"); } - // AI play if no actions available + // AI FULL play if no actions available if (!mustAttackByAction && this.AIPlayer) { this.computerPlayer.selectAttackers(game, attackingPlayerId); } @@ -1486,14 +1502,22 @@ public class TestPlayer implements Player { @Override public void selectBlockers(Game game, UUID defendingPlayerId) { - List tempActions = new ArrayList<>(actions); UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); - // Map of Blocker reference -> list of creatures blocked - Map> blockedCreaturesByCreature = new HashMap<>(); + Map> blockedCreaturesList = getBlockedCreaturesByCreatureList(game); + boolean mustBlockByAction = false; for (PlayerAction action : tempActions) { + + // aiXXX commands + if (action.getTurnNum() == game.getTurnNum() && action.getAction().equals(AI_PREFIX + AI_COMMAND_PLAY_STEP)) { + mustBlockByAction = true; + this.computerPlayer.selectBlockers(game, defendingPlayerId); + actions.remove(action); + break; + } + if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) { mustBlockByAction = true; String command = action.getAction(); @@ -1510,7 +1534,8 @@ public class TestPlayer implements Player { String attackerName = groups[1]; Permanent attacker = findPermanent(new FilterAttackingCreature(), attackerName, opponentId, game); Permanent blocker = findPermanent(new FilterControlledPermanent(), blockerName, computerPlayer.getId(), game); - if (canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) { + + if (canBlockAnother(game, blocker, attacker, blockedCreaturesList)) { computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); actions.remove(action); } else { @@ -1518,40 +1543,74 @@ public class TestPlayer implements Player { } } } - checkMultipleBlockers(game, blockedCreaturesByCreature); + checkMultipleBlockers(game, blockedCreaturesList); - // AI play if no actions available + // AI FULL play if no actions available if (!mustBlockByAction && this.AIPlayer) { this.computerPlayer.selectBlockers(game, defendingPlayerId); } } - // Checks if a creature can block at least one more creature - private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map> blockedCreaturesByCreature) { + private Map> getBlockedCreaturesByCreatureList(Game game) { + // collect already declared blockers info (e.g. after auto-adding on block requirements) + // blocker -> blocked attackers + Map> blockedCreaturesByCreature = new HashMap<>(); + for (CombatGroup combatGroup : game.getCombat().getGroups()) { + for (UUID combatBlockerId : combatGroup.getBlockers()) { + Permanent blocker = game.getPermanent(combatBlockerId); + if (blocker != null) { + // collect all blocked attackers + List blocked = getBlockedAttackers(game, blocker, blockedCreaturesByCreature); + for (UUID combatAttackerId : combatGroup.getAttackers()) { + Permanent combatAttacker = game.getPermanent(combatAttackerId); + if (combatAttacker != null) { + blocked.add(new MageObjectReference(combatAttacker, game)); + } + } + } + } + } + return blockedCreaturesByCreature; + } + + private List getBlockedAttackers(Game game, Permanent blocker, Map> blockedCreaturesByCreature) { + // finds creatures list blocked by blocker permanent 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 + // already exist blockerRef = r; } } + + if (!blockedCreaturesByCreature.containsKey(blockerRef)) { + blockedCreaturesByCreature.put(blockerRef, new ArrayList<>()); + } List blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>()); + return blocked; + } + + private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map> blockedCreaturesByCreature) { + // check if blocker can block one more attacker and adds it + List blocked = getBlockedAttackers(game, blocker, blockedCreaturesByCreature); 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) { + // Check for Menace type abilities - if creatures can be blocked by >X or blockersForAttacker = new HashMap<>(); + // Calculate the number of blockers each attacker has for (List attackers : blockedCreaturesByCreature.values()) { for (MageObjectReference mr : attackers) { @@ -1559,6 +1618,7 @@ public class TestPlayer implements Player { 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); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index ef4695b34c1..8727880ee67 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -24,6 +24,7 @@ import mage.game.command.CommandObject; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.player.ai.ComputerPlayer7; +import mage.player.ai.ComputerPlayerMCTS; import mage.players.ManaPool; import mage.players.Player; import mage.util.CardUtil; @@ -1447,8 +1448,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } private void assertAiPlayAndGameCompatible(TestPlayer player) { - if (player.isAIPlayer() || !(player.getComputerPlayer() instanceof ComputerPlayer7)) { - Assert.fail("AI commands supported by CardTestPlayerBaseWithAIHelps only"); + boolean aiCompatible = (player.getComputerPlayer() instanceof ComputerPlayer7 || player.getComputerPlayer() instanceof ComputerPlayerMCTS); + if (player.isAIPlayer() || !aiCompatible) { + Assert.fail("AI commands supported by CardTestPlayerBaseWith***AIHelps only"); } }