Test framework: added support for flip coin tests (command: setFlipCoinResult);

This commit is contained in:
Oleg Agafonov 2020-12-14 03:00:38 +04:00
parent fde24f349f
commit c1dfbbda63
12 changed files with 162 additions and 24 deletions

View file

@ -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)

View file

@ -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");

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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.
* <p>
* 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).
* <p>
* 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
* <p>
* 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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
*

View file

@ -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;

View file

@ -444,6 +444,8 @@ public interface Player extends MageItem, Copyable<Player> {
boolean flipCoin(Ability source, Game game, boolean winnable, List<UUID> appliedEffects);
boolean flipCoinResult(Game game);
int rollDice(Ability source, Game game, int numSides);
int rollDice(Ability source, Game game, List<UUID> appliedEffects, int numSides);

View file

@ -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);