From a20bca1b21499f556c18891a181d481812bedfc7 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 12 Mar 2020 02:05:06 +0400 Subject: [PATCH] Test framework: fixed ai play commands, added more tests --- .../java/mage/player/ai/ComputerPlayer.java | 16 +- .../org/mage/test/AI/basic/CopyAITest.java | 208 ++++++++++++++++++ .../org/mage/test/player/PlayerAction.java | 10 +- .../java/org/mage/test/player/TestPlayer.java | 33 +-- .../base/impl/CardTestPlayerAPIImpl.java | 14 +- .../src/main/java/mage/constants/Outcome.java | 2 +- 6 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 296fe78496..4070da886f 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -490,13 +490,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { required = false; } - // temp lists List goodList = new ArrayList<>(); List badList = new ArrayList<>(); List allList = new ArrayList<>(); - List goodList2 = new ArrayList<>(); - List badList2 = new ArrayList<>(); - List allList2 = new ArrayList<>(); // TODO: improve to process multiple opponents instead random UUID randomOpponentId; @@ -2541,6 +2537,18 @@ public class ComputerPlayer extends PlayerImpl implements Player { allList.addAll(goodList); allList.addAll(badList); + + // "can target all mode" don't need your/opponent lists -- all targets goes with same value + if (outcome.isCanTargetAll()) { + allList.sort(comparator); // bad sort + if (outcome.isGood()) { + Collections.reverse(allList); // good sort + } + goodList.clear(); + goodList.addAll(allList); + badList.clear(); + badList.addAll(allList); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java new file mode 100644 index 0000000000..3e10c293b6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java @@ -0,0 +1,208 @@ +package org.mage.test.AI.basic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class CopyAITest extends CardTestPlayerBaseWithAIHelps { + + // AI makes decisions by two different modes: + // 1. Simulation: If it searching playable spells then it play it in FULL SIMULATION (abilities + all possible targets) + // 2. Response: If it searching response on dialog then it use simple target search (without simulation) + + @Test + public void test_CloneChoose_Manual() { + // You may have Clone enter the battlefield as a copy of any creature on the battlefield. + addCard(Zone.HAND, playerA, "Clone", 1); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + // + addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3 + + // clone + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Spectral Bears"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CloneChoose_AI_Simulation_MostValueableFromOwn() { + // You may have Clone enter the battlefield as a copy of any creature on the battlefield. + addCard(Zone.HAND, playerA, "Clone", 1); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + // + addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Spectral Bears", 1); // 3/3 + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + + // clone (AI must choose most valueable permanent - own) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 2); + assertPermanentCount(playerB, "Spectral Bears", 0); + } + + @Test + public void test_CloneChoose_AI_Simulation_MostValueableFromOpponent() { + // You may have Clone enter the battlefield as a copy of any creature on the battlefield. + addCard(Zone.HAND, playerA, "Clone", 1); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + // + addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1 + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3 + + // clone (AI must choose most valueable permanent - opponent) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CopyTarget_Manual() { + // Exile target creature card from a graveyard. Dimir Doppelganger becomes a copy of that card, except it has this ability. + addCard(Zone.BATTLEFIELD, playerA, "Dimir Doppelganger", 1); // {1}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Arbor Elf", 1); // 1/1 + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.GRAVEYARD, playerB, "Spectral Bears", 1); // 3/3 + + // copy + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}{B}: Exile target"); + addTarget(playerA, "Spectral Bears"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertExileCount("Spectral Bears", 1); + } + + @Test + public void test_CopyTarget_AI_Simulation_MostValueableFromOwn() { + // Exile target creature card from a graveyard. Dimir Doppelganger becomes a copy of that card, except it has this ability. + addCard(Zone.BATTLEFIELD, playerA, "Dimir Doppelganger", 1); // {1}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Arbor Elf", 1); // 1/1 + addCard(Zone.GRAVEYARD, playerA, "Spectral Bears", 1); // 3/3 + // + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 + + // copy (AI must choose most valueable permanent - own) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertExileCount("Spectral Bears", 1); + } + + @Test + public void test_CopyTarget_AI_Simulation_MostValueableFromOpponent() { + // Exile target creature card from a graveyard. Dimir Doppelganger becomes a copy of that card, except it has this ability. + addCard(Zone.BATTLEFIELD, playerA, "Dimir Doppelganger", 1); // {1}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Arbor Elf", 1); // 1/1 + // + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.GRAVEYARD, playerB, "Spectral Bears", 1); // 3/3 + + // copy (AI must choose most valueable permanent - opponent) + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertExileCount("Spectral Bears", 1); + } + + @Test + public void test_CopyTarget_AI_Response_MostValueableFromOwn() { + // Exile target creature card from a graveyard. Dimir Doppelganger becomes a copy of that card, except it has this ability. + addCard(Zone.BATTLEFIELD, playerA, "Dimir Doppelganger", 1); // {1}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Arbor Elf", 1); // 1/1 + addCard(Zone.GRAVEYARD, playerA, "Spectral Bears", 1); // 3/3 + // + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 + + // copy (AI must choose most valueable permanent - own) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}{B}: Exile target"); + //addTarget(playerA, "Spectral Bears"); // AI must choose + + setStopAt(1, PhaseStep.END_TURN); + //setStrictChooseMode(true); // AI must choose + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertExileCount("Spectral Bears", 1); + } + + @Test + public void test_CopyTarget_AI_Response_MostValueableFromOpponent() { + // Exile target creature card from a graveyard. Dimir Doppelganger becomes a copy of that card, except it has this ability. + addCard(Zone.BATTLEFIELD, playerA, "Dimir Doppelganger", 1); // {1}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Arbor Elf", 1); // 1/1 + // + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.GRAVEYARD, playerB, "Spectral Bears", 1); // 3/3 + + // copy (AI must choose most valueable permanent - opponent) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}{B}: Exile target"); + //addTarget(playerA, "Spectral Bears"); // AI must choose + + setStopAt(1, PhaseStep.END_TURN); + //setStrictChooseMode(true); // AI must choose + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Spectral Bears", 1); + assertExileCount("Spectral Bears", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/PlayerAction.java b/Mage.Tests/src/test/java/org/mage/test/player/PlayerAction.java index 3662666b04..62ffe0fa4c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/PlayerAction.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/PlayerAction.java @@ -1,8 +1,7 @@ - - package org.mage.test.player; import mage.constants.PhaseStep; +import mage.game.Game; /** * @author BetaSteward_at_googlemail.com @@ -36,4 +35,11 @@ public class PlayerAction { public String getActionName() { return this.actionName; } + + /** + * Calls after action removed from commands queue later (for multi steps action, e.g. AI related) + */ + public void onActionRemovedLater(Game game, TestPlayer player) { + // + } } 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 36526dfbd9..672140af98 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 @@ -176,6 +176,10 @@ public class TestPlayer implements Player { actions.add(new PlayerAction(actionName, turnNum, step, action)); } + public void addAction(PlayerAction playerAction) { + actions.add(playerAction); + } + public List getActions() { return actions; } @@ -552,6 +556,7 @@ public class TestPlayer implements Player { List removed = new ArrayList<>(); actionsToRemovesLater.forEach((action, step) -> { if (game.getStep().getType() != step) { + action.onActionRemovedLater(game, this); actions.remove(action); removed.add(action); } @@ -689,26 +694,18 @@ public class TestPlayer implements Player { // play priority if (command.equals(AI_COMMAND_PLAY_PRIORITY)) { - AICanChooseInStrictMode = true; - try { - computerPlayer.priority(game); - actions.remove(action); - return true; - } finally { - AICanChooseInStrictMode = false; - } + AICanChooseInStrictMode = true; // disable on action's remove + computerPlayer.priority(game); + actions.remove(action); + return true; } // play step if (command.equals(AI_COMMAND_PLAY_STEP)) { - AICanChooseInStrictMode = true; - try { - actionsToRemovesLater.put(action, game.getStep().getType()); - computerPlayer.priority(game); - return true; - } finally { - AICanChooseInStrictMode = false; - } + AICanChooseInStrictMode = true; // disable on action's remove + actionsToRemovesLater.put(action, game.getStep().getType()); + computerPlayer.priority(game); + return true; } Assert.fail("Unknow ai command: " + command); @@ -3771,4 +3768,8 @@ public class TestPlayer implements Player { public ComputerPlayer getComputerPlayer() { return computerPlayer; } + + public void setAICanChooseInStrictMode(boolean AICanChooseInStrictMode) { + this.AICanChooseInStrictMode = AICanChooseInStrictMode; + } } 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 31a343f540..ef4695b34c 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 @@ -1424,7 +1424,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player) { assertAiPlayAndGameCompatible(player); - player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_PRIORITY); + player.addAction(createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_PRIORITY)); } /** @@ -1433,7 +1433,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ public void aiPlayStep(int turnNum, PhaseStep step, TestPlayer player) { assertAiPlayAndGameCompatible(player); - player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_STEP); + player.addAction(createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_STEP)); + } + + public PlayerAction createAIPlayerAction(int turnNum, PhaseStep step, String aiCommand) { + // AI actions must disable and enable strict mode + return new PlayerAction("", turnNum, step, AI_PREFIX + aiCommand) { + @Override + public void onActionRemovedLater(Game game, TestPlayer player) { + player.setAICanChooseInStrictMode(false); + } + }; } private void assertAiPlayAndGameCompatible(TestPlayer player) { diff --git a/Mage/src/main/java/mage/constants/Outcome.java b/Mage/src/main/java/mage/constants/Outcome.java index e6de55a778..2fdba3d1ce 100644 --- a/Mage/src/main/java/mage/constants/Outcome.java +++ b/Mage/src/main/java/mage/constants/Outcome.java @@ -46,7 +46,7 @@ public enum Outcome { // AI sorting targets by priorities (own or opponents) and selects most valueable or weakest private final boolean good; - // no different between own or opponent targets (example: copy must choose from all permanents) // TODO: copy must choose most valueable from opponent too + // no different between own or opponent targets (example: copy must choose from all permanents) private boolean canTargetAll; Outcome(boolean good) {