test framework: added commands to check declared attackers and blockers creatures (useful for AI tests, see checkAttackers and checkBlockers)

This commit is contained in:
Oleg Agafonov 2024-12-30 15:09:02 +04:00
parent 6b9532febd
commit 60112c6be5
5 changed files with 146 additions and 7 deletions

View file

@ -6,6 +6,9 @@ import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author JayDi85
*/
@ -21,6 +24,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
// 2 x 2/2 vs 0 - can't lose any attackers
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
checkAttackers("x2 attack", 1, playerA, "Balduvian Bears", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -35,6 +40,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 1); // 1/1
checkAttackers("x2 attack", 1, playerA, "Balduvian Bears", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -49,6 +56,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 2); // 1/1
checkAttackers("x1 attack", 1, playerA, "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -63,6 +72,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 2); // 1/1
checkAttackers("x2 attack", 1, playerA, "Balduvian Bears", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -75,6 +86,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
public void test_Attack_1_small_vs_0() {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
checkAttackers("x1 attack", 1, playerA, "Arbor Elf");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -88,6 +101,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
checkAttackers("no attack", 1, playerA, "");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -101,6 +116,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 2); // 1/1
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
checkAttackers("no attack", 1, playerA, "");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@ -114,6 +131,11 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 15); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
String needAttackers = IntStream.rangeClosed(1, 15)
.mapToObj(x -> "Balduvian Bears")
.collect(Collectors.joining("^"));
checkAttackers("x15 attack", 1, playerA, needAttackers);
block(1, playerB, "Ancient Brontodon", "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
@ -131,6 +153,10 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Balduvian Bears");
String needAttackers = IntStream.rangeClosed(1, 10)
.mapToObj(x -> "Balduvian Bears")
.collect(Collectors.joining("^"));
checkAttackers("x10 attack", 1, playerA, needAttackers);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -146,6 +172,7 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Ancient Brontodon", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Goblin Brigand");
checkAttackers("forced x1 attack", 1, playerA, "Goblin Brigand");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -163,6 +190,7 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
block(1, playerB, "Ancient Brontodon:0", "Goblin Brigand:0");
block(1, playerB, "Ancient Brontodon:1", "Goblin Brigand:1");
checkAttackers("forced x2 attack", 1, playerA, "Goblin Brigand", "Goblin Brigand");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -183,6 +211,7 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Trove of Temptation", 1); // 9/9
block(1, playerB, "Ancient Brontodon", "Arbor Elf");
checkAttackers("forced x1 attack", 1, playerA, "Arbor Elf");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -201,6 +230,7 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Seeker of Slaanesh", 1); // 3/3
block(1, playerB, "Seeker of Slaanesh", "Arbor Elf");
checkAttackers("forced x1 attack", 1, playerA, "Arbor Elf");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -217,6 +247,8 @@ public class AttackAndBlockByAITest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Chainbreaker", 1); // 3/3, but with 2x -1/-1 counters
checkAttackers("x1 attack", 1, playerA, "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();

View file

@ -19,6 +19,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must block
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 blocker", 1, playerB, "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -37,8 +38,9 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
attack(1, playerA, "Arbor Elf");
// ai must block
// ai must block by optimal blocker
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 optimal blocker", 1, playerB, "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -58,6 +60,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must block
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 blocker", 1, playerB, "Arbor Elf");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -78,6 +81,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must not block
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("no blockers", 1, playerB, "");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -100,6 +104,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must not block
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("no blockers", 1, playerB, "");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -123,6 +128,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must block bigger attacker and survive (6/6 must block 5/5)
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 optimal blocker", 1, playerB, "Colossal Dreadmaw");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -151,6 +157,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must block bigger attacker and survive (3/3 must block 2/2)
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 optimal blocker", 1, playerB, "Spectral Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -175,6 +182,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
// ai must use smaller blocker and survive (3/3 must block 2/2)
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
checkBlockers("x1 optimal blocker", 1, playerB, "Spectral Bears");
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);

View file

@ -28,10 +28,7 @@ import mage.filter.common.*;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.SummoningSicknessPredicate;
import mage.game.Game;
import mage.game.GameImpl;
import mage.game.Graveyard;
import mage.game.Table;
import mage.game.*;
import mage.game.combat.CombatGroup;
import mage.game.command.CommandObject;
import mage.game.draft.Draft;
@ -52,6 +49,7 @@ import mage.target.common.*;
import mage.util.CardUtil;
import mage.util.MultiAmountMessage;
import mage.util.RandomUtil;
import mage.watchers.common.AttackedOrBlockedThisCombatWatcher;
import org.apache.log4j.Logger;
import org.junit.Assert;
@ -839,6 +837,20 @@ public class TestPlayer implements Player {
wasProccessed = true;
}
// check attacking: attackers list
if (params[0].equals(CHECK_COMMAND_ATTACKERS) && params.length == 2) {
assertAttackers(action, game, computerPlayer, params[1]);
actions.remove(action);
wasProccessed = true;
}
// check attacking: attackers list
if (params[0].equals(CHECK_COMMAND_BLOCKERS) && params.length == 2) {
assertBlockers(action, game, computerPlayer, params[1]);
actions.remove(action);
wasProccessed = true;
}
// check playable ability: ability text, must have
if (params[0].equals(CHECK_COMMAND_PLAYABLE_ABILITY) && params.length == 3) {
assertPlayableAbility(action, game, computerPlayer, params[1], Boolean.parseBoolean(params[2]));
@ -1418,6 +1430,64 @@ public class TestPlayer implements Player {
}
}
private void assertAttackers(PlayerAction action, Game game, Player player, String attackers) {
AttackedOrBlockedThisCombatWatcher watcher = game.getState().getWatcher(AttackedOrBlockedThisCombatWatcher.class);
Assert.assertNotNull(watcher);
List<String> actualAttackers = watcher.getAttackedThisTurnCreatures().stream()
.map(mor -> game.getObject(mor.getSourceId()))// no needs in zcc/lki
.filter(Objects::nonNull)
.filter(o -> o instanceof ControllableOrOwnerable)
.map(o -> (ControllableOrOwnerable) o)
.filter(o -> o.getControllerOrOwnerId().equals(player.getId()))
.map(o -> ((MageObject) o).getName())
.sorted()
.collect(Collectors.toList());
List<String> needAttackers = Arrays.stream(attackers.split("\\^"))
.filter(s -> !s.equals(TestPlayer.ATTACK_SKIP))
.sorted()
.collect(Collectors.toList());
if (!actualAttackers.equals(needAttackers)) {
printStart(game, action.getActionName());
System.out.println(String.format("Need attackers: %d", needAttackers.size()));
needAttackers.forEach(s -> System.out.println(" - " + s));
System.out.println(String.format("Actual attackers: %d", actualAttackers.size()));
actualAttackers.forEach(s -> System.out.println(" - " + s));
printEnd();
Assert.fail("Found wrong attackers");
}
}
private void assertBlockers(PlayerAction action, Game game, Player player, String blockers) {
AttackedOrBlockedThisCombatWatcher watcher = game.getState().getWatcher(AttackedOrBlockedThisCombatWatcher.class);
Assert.assertNotNull(watcher);
List<String> actualBlockers = watcher.getBlockedThisTurnCreatures().stream()
.map(mor -> game.getObject(mor.getSourceId()))// no needs in zcc/lki
.filter(Objects::nonNull)
.filter(o -> o instanceof ControllableOrOwnerable)
.map(o -> (ControllableOrOwnerable) o)
.filter(o -> o.getControllerOrOwnerId().equals(player.getId()))
.map(o -> ((MageObject) o).getName())
.sorted()
.collect(Collectors.toList());
List<String> needBlockers = Arrays.stream(blockers.split("\\^"))
.filter(s -> !s.equals(TestPlayer.BLOCK_SKIP))
.sorted()
.collect(Collectors.toList());
if (!actualBlockers.equals(needBlockers)) {
printStart(game, action.getActionName());
System.out.println(String.format("Need blockers: %d", needBlockers.size()));
needBlockers.forEach(s -> System.out.println(" - " + s));
System.out.println(String.format("Actual blockers: %d", actualBlockers.size()));
actualBlockers.forEach(s -> System.out.println(" - " + s));
printEnd();
Assert.fail("Found wrong blockers");
}
}
private void assertMayAttackDefender(PlayerAction action, Game game, Player controller, String permanentName, Player defender, boolean expectedMayAttack) {
Permanent attackingPermanent = findPermanentWithAssert(action, game, controller, permanentName);

View file

@ -87,6 +87,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String CHECK_COMMAND_LIFE = "LIFE";
public static final String CHECK_COMMAND_ABILITY = "ABILITY";
public static final String CHECK_COMMAND_PLAYABLE_ABILITY = "PLAYABLE_ABILITY";
public static final String CHECK_COMMAND_ATTACKERS = "ATTACKERS";
public static final String CHECK_COMMAND_BLOCKERS = "BLOCKERS";
public static final String CHECK_COMMAND_MAY_ATTACK_DEFENDER = "MAY_ATTACK_DEFENDER";
public static final String CHECK_COMMAND_PERMANENT_COUNT = "PERMANENT_COUNT";
public static final String CHECK_COMMAND_PERMANENT_TAPPED = "PERMANENT_TAPPED";
@ -428,7 +430,33 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
}
/**
* Checks whether or not a creature can attack on a given turn a defender (player only for now, could be extended to permanents)
* Make sure in last declared attackers
*
* @param attackers in any order, use empty string or params for no attackers check
*/
public void checkAttackers(String checkName, int turnNum, TestPlayer player, String... attackers) {
String list = String.join("^", attackers);
if (list.isEmpty()) {
list = TestPlayer.ATTACK_SKIP;
}
check(checkName, turnNum, PhaseStep.DECLARE_ATTACKERS, player, CHECK_COMMAND_ATTACKERS, list);
}
/**
* Make sure in last declared blockers
*
* @param blockers in any order, use empty string or params for no blockers check
*/
public void checkBlockers(String checkName, int turnNum, TestPlayer player, String... blockers) {
String list = String.join("^", blockers);
if (list.isEmpty()) {
list = TestPlayer.BLOCK_SKIP;
}
check(checkName, turnNum, PhaseStep.DECLARE_BLOCKERS, player, CHECK_COMMAND_BLOCKERS, list);
}
/**
* Checks whether a creature can attack on a given turn a defender (player only for now, could be extended to permanents)
*
* @param checkName String to show up if the check fails, for display purposes only.
* @param turnNum The turn number to check on.