mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 19:41:59 -08:00
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:
parent
44adbae263
commit
a7ac35a82d
5 changed files with 221 additions and 34 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<org.mage.test.player.PlayerAction> 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<PlayerAction> tempActions = new ArrayList<>(actions);
|
||||
|
||||
UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next();
|
||||
// Map of Blocker reference -> list of creatures blocked
|
||||
Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>();
|
||||
Map<MageObjectReference, List<MageObjectReference>> 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<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
|
||||
private Map<MageObjectReference, List<MageObjectReference>> getBlockedCreaturesByCreatureList(Game game) {
|
||||
// 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);
|
||||
// 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<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();
|
||||
|
||||
// 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 <Y only
|
||||
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
|
||||
Map<MageObjectReference, Integer> blockersForAttacker = new HashMap<>();
|
||||
|
||||
// Calculate the number of blockers each attacker has
|
||||
for (List<MageObjectReference> 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<MageObjectReference, Integer> entry : blockersForAttacker.entrySet()) {
|
||||
Permanent attacker = entry.getKey().getPermanent(game);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue