From c767d19534b292c95d952f46d4bd83e5796a9def Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 29 Jan 2020 07:42:20 +0400 Subject: [PATCH] Test framework: added AI support in basic card tests: * added new command to play one best action on priority by AI (aiPlayPriority); * added new command to play all best actions on step by AI (aiPlayStep); * now you can setup battlefield by standard commands and run AI simulations for single priority/step; * CardTestPlayerBase - old default class for card tests, it uses simple AI for choosing, but do not play/simulate it; * CardTestPlayerBaseAI - old default class for AI game tests, auto-playable playerA, you can control only playerB; * CardTestPlayerBaseWithAIHelps - new improved class for card tests, it uses controllable and real AI with game simulations; --- .../AI/basic/TestFrameworkCanPlayAITest.java | 77 +++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 40 +++++++++- .../base/CardTestPlayerBaseWithAIHelps.java | 22 ++++++ .../base/impl/CardTestPlayerAPIImpl.java | 29 +++++++ 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseWithAIHelps.java 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 new file mode 100644 index 0000000000..64c863ba4a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TestFrameworkCanPlayAITest.java @@ -0,0 +1,77 @@ +package org.mage.test.AI.basic; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_AI_PlayOnePriorityAction() { + addCard(Zone.HAND, playerA, "Lightning Bolt", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 5); + + // AI must play one time + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertPermanentCount(playerB, "Balduvian Bears", 5 - 1); + } + + @Test + public void test_AI_PlayManyActionsInOneStep() { + addCard(Zone.HAND, playerA, "Lightning Bolt", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 5); + + // AI must play all step actions + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Lightning Bolt", 3); + assertPermanentCount(playerB, "Balduvian Bears", 5 - 3); + } + + @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() { + // Equipped creature gets +2/+0 and is goaded + // {1}: Attach Bloodthirsty Blade to target creature an opponent controls. Activate this ability only any time you could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerA, "Bloodthirsty Blade", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + + // AI must play + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + Assert.assertEquals(1, currentGame.getBattlefield().getAllActivePermanents(CardType.CREATURE).size()); + Permanent permanent = currentGame.getBattlefield().getAllActivePermanents(CardType.CREATURE).get(0); + Assert.assertEquals(1, permanent.getAttachments().size()); + } +} 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 97094b56a7..1c2a68c241 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 @@ -84,6 +84,7 @@ public class TestPlayer implements Player { private int foundNoAction = 0; private boolean AIPlayer; private final List actions = new ArrayList<>(); + private final Map actionsToRemovesLater = new HashMap<>(); // remove actions later, on next step (e.g. for AI commands) private final List choices = new ArrayList<>(); // choices stack for choice private final List targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command) private final Map aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use) @@ -499,6 +500,18 @@ public class TestPlayer implements Player { @Override public boolean priority(Game game) { + // later remove actions (ai commands related) + if (actionsToRemovesLater.size() > 0) { + List removed = new ArrayList<>(); + actionsToRemovesLater.forEach((action, step) -> { + if (game.getStep().getType() != step) { + actions.remove(action); + removed.add(action); + } + }); + removed.forEach(actionsToRemovesLater::remove); + } + int numberOfActions = actions.size(); List tempActions = new ArrayList<>(); tempActions.addAll(actions); @@ -622,6 +635,25 @@ public class TestPlayer implements Player { actions.remove(action); } } + } else if (action.getAction().startsWith(AI_PREFIX)) { + String command = action.getAction(); + command = command.substring(command.indexOf(AI_PREFIX) + AI_PREFIX.length()); + + // play priority + if (command.equals(AI_COMMAND_PLAY_PRIORITY)) { + computerPlayer.priority(game); + actions.remove(action); + return true; + } + + // play step + if (command.equals(AI_COMMAND_PLAY_STEP)) { + actionsToRemovesLater.put(action, game.getStep().getType()); + computerPlayer.priority(game); + return true; + } + + Assert.fail("Unknow ai command: " + command); } else if (action.getAction().startsWith(CHECK_PREFIX)) { String command = action.getAction(); command = command.substring(command.indexOf(CHECK_PREFIX) + CHECK_PREFIX.length()); @@ -752,9 +784,9 @@ public class TestPlayer implements Player { if (!wasProccessed) { Assert.fail("Unknow check command or params: " + command); } - } else if (action.getAction().startsWith("show:")) { + } else if (action.getAction().startsWith(SHOW_PREFIX)) { String command = action.getAction(); - command = command.substring(command.indexOf("show:") + "show:".length()); + command = command.substring(command.indexOf(SHOW_PREFIX) + SHOW_PREFIX.length()); String[] params = command.split(CHECK_PARAM_DELIMETER); boolean wasProccessed = false; @@ -3640,4 +3672,8 @@ public class TestPlayer implements Player { this.chooseStrictModeFailed("choice", game, getInfo(card) + " - can't select ability to cast.\n" + "Card's abilities:\n" + allInfo); return computerPlayer.chooseAbilityForCast(card, game, noMana); } + + public ComputerPlayer getComputerPlayer() { + return computerPlayer; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseWithAIHelps.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseWithAIHelps.java new file mode 100644 index 0000000000..47091ce582 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseWithAIHelps.java @@ -0,0 +1,22 @@ +package org.mage.test.serverside.base; + +import mage.constants.RangeOfInfluence; +import org.mage.test.player.TestComputerPlayer7; +import org.mage.test.player.TestPlayer; + +/** + * Base class but with latest computer player to test single AI commands (it's different from full AI simulation from CardTestPlayerBaseAI): + * 1. AI don't play normal priorities (you must use ai*** commands to play it); + * 2. AI will choose in non strict mode (it's simulated ComputerPlayer7, not simple ComputerPlayer from basic tests) + * + * @author JayDi85 + */ +public abstract class CardTestPlayerBaseWithAIHelps extends CardTestPlayerBase { + + @Override + protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { + TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7(name, RangeOfInfluence.ONE, 6)); + testPlayer.setAIPlayer(false); // AI can't play it by itself, use AI commands + return testPlayer; + } +} 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 b9aad30df3..62f0f603df 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 @@ -22,6 +22,7 @@ import mage.game.GameOptions; import mage.game.command.CommandObject; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.player.ai.ComputerPlayer7; import mage.players.ManaPool; import mage.players.Player; import mage.util.CardUtil; @@ -53,6 +54,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String ALIAS_PREFIX = "@"; // don't change -- it uses in user's tests public static final String CHECK_PARAM_DELIMETER = "#"; public static final String CHECK_PREFIX = "check:"; // prefix for all check commands + public static final String SHOW_PREFIX = "show:"; // prefix for all show commands + public static final String AI_PREFIX = "ai:"; // prefix for all ai commands static { // aliases can be used in check commands, so all prefixes and delimeters must be unique @@ -67,6 +70,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public static final String ACTIVATE_PLAY = "activate:Play "; public static final String ACTIVATE_CAST = "activate:Cast "; + // commands for AI + public static final String AI_COMMAND_PLAY_PRIORITY = "play priority"; + public static final String AI_COMMAND_PLAY_STEP = "play step"; + static { // cards can be played/casted by activate ability command too Assert.assertTrue("musts contains activate ability part", ACTIVATE_PLAY.startsWith(ACTIVATE_ABILITY)); @@ -1391,6 +1398,28 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool); } + /** + * AI play one PRIORITY with multi game simulations (calcs and play ONE best action, can be called with stack) + */ + public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player) { + assertAiPlayAndGameCompatible(player); + player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_PRIORITY); + } + + /** + * AI play STEP to the end with multi game simulations (calcs and play best actions until step ends, can be called in the middle of the step) + */ + public void aiPlayStep(int turnNum, PhaseStep step, TestPlayer player) { + assertAiPlayAndGameCompatible(player); + player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_STEP); + } + + private void assertAiPlayAndGameCompatible(TestPlayer player) { + if (player.isAIPlayer() || !(player.getComputerPlayer() instanceof ComputerPlayer7)) { + Assert.fail("AI commands supported by CardTestPlayerBaseWithAIHelps only"); + } + } + public void waitStackResolved(int turnNum, PhaseStep step, TestPlayer player) { player.addAction(turnNum, step, "waitStackResolved"); }