Test framework improves:

* Added AI commands support to play attacker and blocker steps;
* Fixed double triggers of blocker declared event (if block command used with block requirement effect);
This commit is contained in:
Oleg Agafonov 2020-04-14 20:03:09 +04:00
parent 44adbae263
commit a7ac35a82d
5 changed files with 221 additions and 34 deletions

View file

@ -52,6 +52,40 @@ public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps {
assertPermanentCount(playerB, "Balduvian Bears", 5 - 3); 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 @Test
@Ignore // AI can't play blade cause score system give priority for boost instead restriction effects like goad @Ignore // AI can't play blade cause score system give priority for boost instead restriction effects like goad
public void test_AI_GoadedByBloodthirstyBlade_Normal() { public void test_AI_GoadedByBloodthirstyBlade_Normal() {

View file

@ -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 creatures 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 creatures 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 creatures 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);
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.requirement; package org.mage.test.cards.requirement;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -10,7 +9,6 @@ import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
*
* @author LevelX2, icetc * @author LevelX2, icetc
*/ */
public class BlockRequirementTest extends CardTestPlayerBase { 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 * Elemental Uprising - "it must be blocked this turn if able", not working
* * <p>
* The bug just happened for me today as well - the problem is "must be * 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 * blocked" is not being enforced correctly. During opponent's main phase
* they cast Elemental Uprising targeting an untapped land. They attacked * 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"); attack(1, playerA, "Breaker of Armies");
// not allowed due to Breaker of Armies having menace // 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); 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()); 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 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, "Dimensional Infiltrator", 1);
assertGraveyardCount(playerB, "Llanowar Elves", 1); assertGraveyardCount(playerB, "Llanowar Elves", 1);
} }
/* /*
Reported bug: Challenger Troll on field not enforcing block restrictions 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 cant be blocked by more than one creature. Each creature you control with power 4 or greater cant be blocked by more than one creature.
*/ */
String cTroll = "Challenger Troll"; String cTroll = "Challenger Troll";
String bSable = "Bronze Sable"; // {2} 2/1 String bSable = "Bronze Sable"; // {2} 2/1
String hGiant = "Hill Giant"; // {3}{R} 3/3 String hGiant = "Hill Giant"; // {3}{R} 3/3
addCard(Zone.BATTLEFIELD, playerA, cTroll); addCard(Zone.BATTLEFIELD, playerA, cTroll);
addCard(Zone.BATTLEFIELD, playerB, bSable); addCard(Zone.BATTLEFIELD, playerB, bSable);
addCard(Zone.BATTLEFIELD, playerB, hGiant); addCard(Zone.BATTLEFIELD, playerB, hGiant);
attack(1, playerA, cTroll); attack(1, playerA, cTroll);
// only 1 should be able to block it since Troll >=4 power block restriction // only 1 should be able to block it since Troll >=4 power block restriction
block(1, playerB, bSable, cTroll); block(1, playerB, bSable, cTroll);
block(1, playerB, hGiant, cTroll); block(1, playerB, hGiant, cTroll);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
try { try {
execute(); execute();
fail("Expected exception not thrown"); fail("Expected exception not thrown");
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
assertEquals("Challenger Troll is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); 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 Reported bug: Challenger Troll on field not enforcing block restrictions
*/ */
@ -273,29 +271,29 @@ public class BlockRequirementTest extends CardTestPlayerBase {
*/ */
String cTroll = "Challenger Troll"; String cTroll = "Challenger Troll";
String bHulk = "Bloom Hulk"; // {3}{G} 4/4 ETB: proliferate String bHulk = "Bloom Hulk"; // {3}{G} 4/4 ETB: proliferate
String bSable = "Bronze Sable"; // {2} 2/1 String bSable = "Bronze Sable"; // {2} 2/1
String hGiant = "Hill Giant"; // {3}{R} 3/3 String hGiant = "Hill Giant"; // {3}{R} 3/3
addCard(Zone.BATTLEFIELD, playerA, cTroll); addCard(Zone.BATTLEFIELD, playerA, cTroll);
addCard(Zone.BATTLEFIELD, playerA, bHulk); addCard(Zone.BATTLEFIELD, playerA, bHulk);
addCard(Zone.BATTLEFIELD, playerB, bSable); addCard(Zone.BATTLEFIELD, playerB, bSable);
addCard(Zone.BATTLEFIELD, playerB, hGiant); addCard(Zone.BATTLEFIELD, playerB, hGiant);
attack(1, playerA, cTroll); attack(1, playerA, cTroll);
attack(1, playerA, bHulk); attack(1, playerA, bHulk);
// only 1 should be able to block Bloom Hulk since >=4 power and Troll on field // only 1 should be able to block Bloom Hulk since >=4 power and Troll on field
block(1, playerB, bSable, bHulk); block(1, playerB, bSable, bHulk);
block(1, playerB, hGiant, bHulk); block(1, playerB, hGiant, bHulk);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
try { try {
execute(); execute();
fail("Expected exception not thrown"); fail("Expected exception not thrown");
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
assertEquals("Bloom Hulk is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); assertEquals("Bloom Hulk is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage());
} }
} }
} }

View file

@ -112,6 +112,12 @@ public class TestPlayer implements Player {
computerPlayer.setTestPlayerLink(this); computerPlayer.setTestPlayerLink(this);
} }
public TestPlayer(TestComputerPlayerMonteCarlo computerPlayer) {
this.computerPlayer = computerPlayer;
AIPlayer = false;
computerPlayer.setTestPlayerLink(this);
}
public TestPlayer(final TestPlayer testPlayer) { public TestPlayer(final TestPlayer testPlayer) {
this.AIPlayer = testPlayer.AIPlayer; this.AIPlayer = testPlayer.AIPlayer;
this.AICanChooseInStrictMode = testPlayer.AICanChooseInStrictMode; this.AICanChooseInStrictMode = testPlayer.AICanChooseInStrictMode;
@ -1411,6 +1417,16 @@ public class TestPlayer implements Player {
boolean madeAttackByAction = false; boolean madeAttackByAction = false;
for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext(); ) { for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext(); ) {
PlayerAction action = it.next(); 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:")) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) {
mustAttackByAction = true; mustAttackByAction = true;
String command = action.getAction(); 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"); 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) { if (!mustAttackByAction && this.AIPlayer) {
this.computerPlayer.selectAttackers(game, attackingPlayerId); this.computerPlayer.selectAttackers(game, attackingPlayerId);
} }
@ -1486,14 +1502,22 @@ public class TestPlayer implements Player {
@Override @Override
public void selectBlockers(Game game, UUID defendingPlayerId) { public void selectBlockers(Game game, UUID defendingPlayerId) {
List<PlayerAction> tempActions = new ArrayList<>(actions); List<PlayerAction> tempActions = new ArrayList<>(actions);
UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next();
// Map of Blocker reference -> list of creatures blocked Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesList = getBlockedCreaturesByCreatureList(game);
Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>();
boolean mustBlockByAction = false; boolean mustBlockByAction = false;
for (PlayerAction action : tempActions) { 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:")) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) {
mustBlockByAction = true; mustBlockByAction = true;
String command = action.getAction(); String command = action.getAction();
@ -1510,7 +1534,8 @@ public class TestPlayer implements Player {
String attackerName = groups[1]; String attackerName = groups[1];
Permanent attacker = findPermanent(new FilterAttackingCreature(), attackerName, opponentId, game); Permanent attacker = findPermanent(new FilterAttackingCreature(), attackerName, opponentId, game);
Permanent blocker = findPermanent(new FilterControlledPermanent(), blockerName, computerPlayer.getId(), 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); computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game);
actions.remove(action); actions.remove(action);
} else { } 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) { if (!mustBlockByAction && this.AIPlayer) {
this.computerPlayer.selectBlockers(game, defendingPlayerId); this.computerPlayer.selectBlockers(game, defendingPlayerId);
} }
} }
// Checks if a creature can block at least one more creature private Map<MageObjectReference, List<MageObjectReference>> getBlockedCreaturesByCreatureList(Game game) {
private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) { // collect already declared blockers info (e.g. after auto-adding on block requirements)
// blocker -> blocked attackers
Map<MageObjectReference, List<MageObjectReference>> 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<MageObjectReference> 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<MageObjectReference> getBlockedAttackers(Game game, Permanent blocker, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
// finds creatures list blocked by blocker permanent
MageObjectReference blockerRef = new MageObjectReference(blocker, game); MageObjectReference blockerRef = new MageObjectReference(blocker, game);
// See if we already reference this blocker
for (MageObjectReference r : blockedCreaturesByCreature.keySet()) { for (MageObjectReference r : blockedCreaturesByCreature.keySet()) {
if (r.equals(blockerRef)) { if (r.equals(blockerRef)) {
// Use the existing reference if we do // already exist
blockerRef = r; blockerRef = r;
} }
} }
if (!blockedCreaturesByCreature.containsKey(blockerRef)) {
blockedCreaturesByCreature.put(blockerRef, new ArrayList<>());
}
List<MageObjectReference> blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>()); List<MageObjectReference> blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>());
return blocked;
}
private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
// check if blocker can block one more attacker and adds it
List<MageObjectReference> blocked = getBlockedAttackers(game, blocker, blockedCreaturesByCreature);
int numBlocked = blocked.size(); int numBlocked = blocked.size();
// Can't block any more creatures // Can't block any more creatures
if (++numBlocked > blocker.getMaxBlocks()) { if (++numBlocked > blocker.getMaxBlocks()) {
return false; return false;
} }
// Add the attacker reference to the list of creatures this creature is blocking // Add the attacker reference to the list of creatures this creature is blocking
blocked.add(new MageObjectReference(attacker, game)); blocked.add(new MageObjectReference(attacker, game));
blockedCreaturesByCreature.put(blockerRef, blocked);
return true; return true;
} }
// Check for Menace type abilities - if creatures can be blocked by >X or <Y only
private void checkMultipleBlockers(Game game, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) { private void checkMultipleBlockers(Game game, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
// Check for Menace type abilities - if creatures can be blocked by >X or <Y only
// Stores the total number of blockers for each attacker // Stores the total number of blockers for each attacker
Map<MageObjectReference, Integer> blockersForAttacker = new HashMap<>(); Map<MageObjectReference, Integer> blockersForAttacker = new HashMap<>();
// Calculate the number of blockers each attacker has // Calculate the number of blockers each attacker has
for (List<MageObjectReference> attackers : blockedCreaturesByCreature.values()) { for (List<MageObjectReference> attackers : blockedCreaturesByCreature.values()) {
for (MageObjectReference mr : attackers) { for (MageObjectReference mr : attackers) {
@ -1559,6 +1618,7 @@ public class TestPlayer implements Player {
blockersForAttacker.put(mr, blockers + 1); blockersForAttacker.put(mr, blockers + 1);
} }
} }
// Check each attacker is blocked by an allowed amount of creatures // Check each attacker is blocked by an allowed amount of creatures
for (Map.Entry<MageObjectReference, Integer> entry : blockersForAttacker.entrySet()) { for (Map.Entry<MageObjectReference, Integer> entry : blockersForAttacker.entrySet()) {
Permanent attacker = entry.getKey().getPermanent(game); Permanent attacker = entry.getKey().getPermanent(game);

View file

@ -24,6 +24,7 @@ import mage.game.command.CommandObject;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentCard;
import mage.player.ai.ComputerPlayer7; import mage.player.ai.ComputerPlayer7;
import mage.player.ai.ComputerPlayerMCTS;
import mage.players.ManaPool; import mage.players.ManaPool;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -1447,8 +1448,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
} }
private void assertAiPlayAndGameCompatible(TestPlayer player) { private void assertAiPlayAndGameCompatible(TestPlayer player) {
if (player.isAIPlayer() || !(player.getComputerPlayer() instanceof ComputerPlayer7)) { boolean aiCompatible = (player.getComputerPlayer() instanceof ComputerPlayer7 || player.getComputerPlayer() instanceof ComputerPlayerMCTS);
Assert.fail("AI commands supported by CardTestPlayerBaseWithAIHelps only"); if (player.isAIPlayer() || !aiCompatible) {
Assert.fail("AI commands supported by CardTestPlayerBaseWith***AIHelps only");
} }
} }