From c1dfbbda6318e0203ccd129354bda3e2b4f7838e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 14 Dec 2020 03:00:38 +0400 Subject: [PATCH] Test framework: added support for flip coin tests (command: setFlipCoinResult); --- Mage.Sets/src/mage/cards/w/WireflyHive.java | 2 +- .../test/cards/copy/IdentityThiefTest.java | 1 + .../test/cards/flipcoin/FlipCoinTest.java | 69 +++++++++++++++++++ .../single/iko/OboshThePreypiercerTest.java | 12 ++-- .../mage/test/player/TestComputerPlayer.java | 25 +++++-- .../mage/test/player/TestComputerPlayer7.java | 12 ++-- .../player/TestComputerPlayerMonteCarlo.java | 12 ++-- .../java/org/mage/test/player/TestPlayer.java | 22 ++++++ .../base/impl/CardTestPlayerAPIImpl.java | 11 +++ .../java/org/mage/test/stub/PlayerStub.java | 5 ++ Mage/src/main/java/mage/players/Player.java | 2 + .../main/java/mage/players/PlayerImpl.java | 13 +++- 12 files changed, 162 insertions(+), 24 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/flipcoin/FlipCoinTest.java diff --git a/Mage.Sets/src/mage/cards/w/WireflyHive.java b/Mage.Sets/src/mage/cards/w/WireflyHive.java index 69a56320ac5..63715aed8bf 100644 --- a/Mage.Sets/src/mage/cards/w/WireflyHive.java +++ b/Mage.Sets/src/mage/cards/w/WireflyHive.java @@ -30,7 +30,7 @@ public final class WireflyHive extends CardImpl { public WireflyHive(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); - // {3}, {tap}: Flip a coin. If you win the flip, create a 2/2 colorless Insect artifact creature token with flying named Wirefly. + // {3}, {T}: Flip a coin. If you win the flip, create a 2/2 colorless Insect artifact creature token with flying named Wirefly. // If you lose the flip, destroy all permanents named Wirefly. Ability ability = new SimpleActivatedAbility(new FlipCoinEffect( new CreateTokenEffect(new WireflyToken()), new DestroyAllEffect(filter) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/IdentityThiefTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/IdentityThiefTest.java index 4b175739f1e..a549c9e747a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/IdentityThiefTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/IdentityThiefTest.java @@ -29,6 +29,7 @@ public class IdentityThiefTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Identity Thief"); // {2}{U}{U} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Molten Sentry"); + setFlipCoinResult(playerA, true); attack(2, playerB, "Identity Thief"); setChoice(playerB, "Yes"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/flipcoin/FlipCoinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/flipcoin/FlipCoinTest.java new file mode 100644 index 00000000000..6b183a428eb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/flipcoin/FlipCoinTest.java @@ -0,0 +1,69 @@ +package org.mage.test.cards.flipcoin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class FlipCoinTest extends CardTestPlayerBase { + + @Test + public void test_RandomResult() { + // {3}, {T}: Flip a coin. If you win the flip, create a 2/2 colorless Insect artifact creature token with flying named Wirefly. + // If you lose the flip, destroy all permanents named Wirefly. + addCard(Zone.BATTLEFIELD, playerA, "Wirefly Hive"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + + //setStrictChooseMode(true); // normal play without errors + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + } + + @Test(expected = AssertionError.class) + public void test_StrictMode_MustFailWithoutResultSetup() { + // {3}, {T}: Flip a coin. If you win the flip, create a 2/2 colorless Insect artifact creature token with flying named Wirefly. + // If you lose the flip, destroy all permanents named Wirefly. + addCard(Zone.BATTLEFIELD, playerA, "Wirefly Hive"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + + setStrictChooseMode(true); // no coinresult in choices, so it must fail + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_StrictMode_MustWorkWithResultSetup() { + // {3}, {T}: Flip a coin. If you win the flip, create a 2/2 colorless Insect artifact creature token with flying named Wirefly. + // If you lose the flip, destroy all permanents named Wirefly. + addCard(Zone.BATTLEFIELD, playerA, "Wirefly Hive"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + // activates 5 times with same flip coin result + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setFlipCoinResult(playerA, true); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setFlipCoinResult(playerA, true); + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setFlipCoinResult(playerA, true); + activateAbility(7, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setFlipCoinResult(playerA, true); + activateAbility(9, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setFlipCoinResult(playerA, true); + + setStrictChooseMode(true); + setStopAt(9, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Wirefly", 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java index 5f5b7d467d7..38f45e22587 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java @@ -8,13 +8,10 @@ package org.mage.test.cards.single.iko; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Assert; import org.junit.Test; - import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ @@ -29,11 +26,14 @@ public class OboshThePreypiercerTest extends CardTestPlayerBase { // Companion — Your starting deck contains only cards with odd converted mana costs and land cards. // If a source you control with an odd converted mana cost would deal damage to a permanent or player, it deals double that damage to that permanent or player instead. addCard(Zone.BATTLEFIELD, playerA, "Obosh, the Preypiercer"); - + + // lose the flip + setFlipCoinResult(playerA, false); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); assertAllCommandsUsed(); - - Assert.assertTrue("Life has to be 20 or 17 but was " + playerA.getLife() , playerA.getLife() == 17 || playerA.getLife() == 20); + + assertLife(playerA, 20 - 3); } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java index 87a712f6a87..e3e1269dd6d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer.java @@ -12,7 +12,19 @@ import java.util.UUID; * @author JayDi85 */ -// mock class to override to override AI logic for test +/** + * Mock class to override AI logic for test, cause PlayerImpl uses inner calls for other methods. If you + * want to override that methods for tests then call it here. + *

+ * It's a workaround and can be bugged (if you catch overflow error with new method then TestPlayer + * class must re-implement full method code without computerPlayer calls). + *

+ * Example 1: TestPlayer's code uses outer computerPlayer call to discard but discard's inner code must call choose from TestPlayer + * Example 2: TestPlayer's code uses outer computerPlayer call to flipCoin but flipCoin's inner code must call flipCoinResult from TestPlayer + *

+ * Don't forget to add new methods in another classes like TestComputerPlayer7 or TestComputerPlayerMonteCarlo + */ + public class TestComputerPlayer extends ComputerPlayer { private TestPlayer testPlayerLink; @@ -27,10 +39,13 @@ public class TestComputerPlayer extends ComputerPlayer { @Override public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { - // copy-paste for TestComputerXXX - - // workaround for discard spells - // reason: TestPlayer uses outer computerPlayer to discard but inner code uses choose return testPlayerLink.choose(outcome, target, sourceId, game); } + + @Override + public boolean flipCoinResult(Game game) { + return testPlayerLink.flipCoinResult(game); + } } + + diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java index dc92e509a27..cd313a54973 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayer7.java @@ -9,10 +9,11 @@ import mage.target.Target; import java.util.UUID; /** + * Copy paste methods from TestComputerPlayer, see docs in there + * * @author JayDi85 */ -// mock class to override AI logic in tests public class TestComputerPlayer7 extends ComputerPlayer7 { private TestPlayer testPlayerLink; @@ -27,10 +28,11 @@ public class TestComputerPlayer7 extends ComputerPlayer7 { @Override public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { - // copy-paste for TestComputerXXX - - // workaround for discard spells - // reason: TestPlayer uses outer computerPlayer to discard but inner code uses choose return testPlayerLink.choose(outcome, target, sourceId, game); } + + @Override + public boolean flipCoinResult(Game game) { + return testPlayerLink.flipCoinResult(game); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayerMonteCarlo.java b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayerMonteCarlo.java index 154dbfe450f..ee5d787f24b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayerMonteCarlo.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestComputerPlayerMonteCarlo.java @@ -9,10 +9,11 @@ import mage.target.Target; import java.util.UUID; /** + * Copy paste methods from TestComputerPlayer, see docs in there + * * @author JayDi85 */ -// mock class to override AI logic in tests public class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS { private TestPlayer testPlayerLink; @@ -27,10 +28,11 @@ public class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS { @Override public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { - // copy-paste for TestComputerXXX - - // workaround for discard spells - // reason: TestPlayer uses outer computerPlayer to discard but inner code uses choose return testPlayerLink.choose(outcome, target, sourceId, game); } + + @Override + public boolean flipCoinResult(Game game) { + return testPlayerLink.flipCoinResult(game); + } } 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 d025829913f..8ade40e88c9 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 @@ -54,6 +54,7 @@ import mage.players.net.UserData; import mage.target.*; import mage.target.common.*; import mage.util.CardUtil; +import mage.util.RandomUtil; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; @@ -83,6 +84,8 @@ public class TestPlayer implements Player { public static final String BLOCK_SKIP = "[block_skip]"; public static final String ATTACK_SKIP = "[attack_skip]"; public static final String NO_TARGET = "NO_TARGET"; // cast spell or activate ability without target defines + public static final String FLIPCOIN_RESULT_TRUE = "[flipcoin_true]"; + public static final String FLIPCOIN_RESULT_FALSE = "[flipcoin_false]"; private int maxCallsWithoutAction = 400; private int foundNoAction = 0; @@ -3304,6 +3307,25 @@ public class TestPlayer implements Player { return computerPlayer.flipCoin(source, game, true, appliedEffects); } + @Override + public boolean flipCoinResult(Game game) { + assertAliasSupportInChoices(false); + if (!choices.isEmpty()) { + String nextResult = choices.get(0); + if (nextResult.equals(FLIPCOIN_RESULT_TRUE)) { + choices.remove(0); + return true; + } else if (nextResult.equals(FLIPCOIN_RESULT_FALSE)) { + choices.remove(0); + return false; + } + } + this.chooseStrictModeFailed("flipcoin result", game, "Use setFlipCoinResult to setup it in unit tests"); + + // implementation from PlayerImpl: + return RandomUtil.nextBoolean(); + } + @Override public int rollDice(Ability source, Game game, int numSides) { return computerPlayer.rollDice(source, game, numSides); 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 61c2dc80dfa..10f196b1ded 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 @@ -1876,6 +1876,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement player.addModeChoice(choice); } + /** + * Set next result of next flipCoin's try (one flipCoin event can call multiple tries under some effects) + * TestPlayer/ComputerPlayer always selects Heads in good winable events + * + * @param player + * @param result + */ + public void setFlipCoinResult(TestPlayer player, boolean result) { + player.addChoice(result ? TestPlayer.FLIPCOIN_RESULT_TRUE : TestPlayer.FLIPCOIN_RESULT_FALSE); + } + /** * Set target permanents * diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 82e3aed0d92..3283b6d50ae 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -629,6 +629,11 @@ public class PlayerStub implements Player { return false; } + @Override + public boolean flipCoinResult(Game game) { + return false; + } + @Override public int rollDice(Ability source, Game game, int numSides) { return 1; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 6f25b849030..2b644b6b922 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -444,6 +444,8 @@ public interface Player extends MageItem, Copyable { boolean flipCoin(Ability source, Game game, boolean winnable, List appliedEffects); + boolean flipCoinResult(Game game); + int rollDice(Ability source, Game game, int numSides); int rollDice(Ability source, Game game, List appliedEffects, int numSides); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d9b58fa51e6..cc8030fe3c2 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2752,7 +2752,7 @@ public abstract class PlayerImpl implements Player, Serializable { chosen = this.chooseUse(Outcome.Benefit, "Heads or tails?", "", "Heads", "Tails", source, game); game.informPlayers(getLogName() + " chose " + CardUtil.booleanToFlipName(chosen)); } - boolean result = RandomUtil.nextBoolean(); + boolean result = this.flipCoinResult(game); FlipCoinEvent event = new FlipCoinEvent(playerId, source, result, chosen, winnable); event.addAppliedEffects(appliedEffects); game.replaceEvent(event); @@ -2762,7 +2762,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean canChooseHeads = event.getResult(); boolean canChooseTails = !event.getResult(); for (int i = 1; i < event.getFlipCount(); i++) { - boolean tempFlip = RandomUtil.nextBoolean(); + boolean tempFlip = this.flipCoinResult(game); canChooseHeads = canChooseHeads || tempFlip; canChooseTails = canChooseTails || !tempFlip; game.informPlayers(getLogName() + " flipped " + CardUtil.booleanToFlipName(tempFlip)); @@ -2789,6 +2789,15 @@ public abstract class PlayerImpl implements Player, Serializable { return event.getResult(); } + /** + * Return result for next flip coint try (can be contolled in tests) + * @return + */ + @Override + public boolean flipCoinResult(Game game) { + return RandomUtil.nextBoolean(); + } + @Override public int rollDice(Ability source, Game game, int numSides) { return this.rollDice(source, game, null, numSides);