From 085ed5a7a784ec147a9a137d9adb5dcd3eb34000 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 11 May 2024 11:39:02 +0400 Subject: [PATCH] tests: improved tests for Double Season (new tests and docs); --- .../cards/replacement/DoublingSeasonTest.java | 254 +++++++++++++----- 1 file changed, 185 insertions(+), 69 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java index 427d9ef8661..4d83309eedd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java @@ -7,14 +7,18 @@ import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.stream.IntStream; + /** - * Doubling Season: If an effect would put one or more tokens onto the + * Doubling Season: + * If an effect would put one or more tokens onto the * battlefield under your control, it puts twice that many of those tokens onto - * the battlefield instead. If an effect would place one or more counters on a + * the battlefield instead. + * If an effect would place one or more counters on a * permanent you control, it places twice that many of those counters on that * permanent instead. * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class DoublingSeasonTest extends CardTestPlayerBase { @@ -23,39 +27,77 @@ public class DoublingSeasonTest extends CardTestPlayerBase { * added to Pallid Mycoderm if Doubling Season is on the battlefield. */ @Test - public void testDoubleSporeCounter() { + public void test_Counters_ByTrigger() { addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // At the beginning of your upkeep, put a spore counter on Pallid Mycoderm. addCard(Zone.HAND, playerA, "Pallid Mycoderm"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pallid Mycoderm"); + setStrictChooseMode(true); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); assertCounterCount("Pallid Mycoderm", CounterType.SPORE, 2); } + /** + * Tests that 2 Saproling tokens are created instead of one if Doubling + * Season is on the battlefield. + */ + @Test + public void test_Tokens_ByActivate() { + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + + // At the beginning of your upkeep, put a spore counter on Pallid Mycoderm. + // Remove three spore counters from Pallid Mycoderm: Create a 1/1 green Saproling creature token. + addCard(Zone.HAND, playerA, "Pallid Mycoderm"); + + // prepare pallid and wait upkeeps to collect spore counters + // turn 3 - +2 counters + // turn 5 - +2 counters + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pallid Mycoderm"); + + // create token and double it + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove three spore counters from {this}: Create"); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerA, "Saproling Token", 1 + 1); + assertCounterCount("Pallid Mycoderm", CounterType.SPORE, 2 + 2 - 3); + } + /** * Tests if 3 damage are prevented with Test of Faith and Doubling Season is * on the battlefield, that 6 +1/+1 counters are added to the target * creature. */ @Test - public void testDoubleP1P1Counter() { + public void test_Counters_ByPreventEffect() { addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Prevent the next 3 damage that would be dealt to target creature this turn. + // For each 1 damage prevented this way, put a +1/+1 counter on that creature. addCard(Zone.HAND, playerA, "Test of Faith"); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerB, "Plains", 1); + // Lightning Helix deals 3 damage to any target and you gain 3 life. addCard(Zone.HAND, playerB, "Lightning Helix"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Test of Faith", "Silvercoat Lion"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Helix", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -65,33 +107,24 @@ public class DoublingSeasonTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Silvercoat Lion", 1); assertCounterCount("Silvercoat Lion", CounterType.P1P1, 6); assertPowerToughness(playerA, "Silvercoat Lion", 8, 8); - } - /** - * Tests that 2 Saproling tokens are created instead of one if Doubling - * Season is on the battlefield. - */ @Test - public void testDoubleTokens() { + public void test_Tokens_ByPlaneswalkerActivate() { + // +1: Create a 2/2 black Zombie creature token. + addCard(Zone.BATTLEFIELD, playerA, "Liliana, Dreadhorde General"); + // + // If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. + // If an effect would put one or more counters on a permanent you control, it puts twice that many of those counters on that permanent instead. addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); - addCard(Zone.HAND, playerA, "Pallid Mycoderm"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pallid Mycoderm"); - - activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove three spore counters from {this}: Create a 1/1 green Saproling creature token."); - - setStopAt(5, PhaseStep.END_TURN); + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertPermanentCount(playerA, "Saproling Token", 2); - assertCounterCount("Pallid Mycoderm", CounterType.SPORE, 1); - + assertPermanentCount(playerA, "Zombie Token", 2); } /** @@ -106,7 +139,7 @@ public class DoublingSeasonTest extends CardTestPlayerBase { * at the same time and not related to the specific cards. */ @Test - public void testDoubleRiteOfReplication() { + public void test_RiteOfReplication_Tokens_ByMultipleEffects() { /** * If an effect would put one or more tokens onto the battlefield under * your control, it puts twice that many of those tokens onto the @@ -118,80 +151,93 @@ public class DoublingSeasonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); addCard(Zone.BATTLEFIELD, playerA, "Island", 9); - // Create a tokenthat's a copy of target creature onto the battlefield. If Rite of Replication was kicked, put five of those tokens onto the battlefield instead. + // Create a token that's a copy of target creature onto the battlefield. + // If Rite of Replication was kicked, put five of those tokens onto the battlefield instead. addCard(Zone.HAND, playerA, "Rite of Replication"); // When Venerable Monk enters the battlefield, you gain 2 life. addCard(Zone.BATTLEFIELD, playerB, "Venerable Monk", 1); + // create 5 * 2 copied tokens castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rite of Replication", "Venerable Monk"); - setChoice(playerA, true); + setChoice(playerA, true); // use kicker + IntStream.rangeClosed(1, 9).forEach(value -> { + setChoice(playerA, "When {this} enters"); // x10 triggers order + }); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertLife(playerA, 40); + assertLife(playerA, 20 + 10 * 2); // x10 etb x2 life assertPermanentCount(playerA, "Venerable Monk", 10); - } /** * Doubling Season doesn't create two tokens from opponent's Rite of Raging Storm */ @Test - public void testDoubleRiteOfRagingStorm() { + public void test_RiteOfReplication_TokensFromController_CanAttack() { // At the beginning of each player's upkeep, that player creates a 5/1 red Elemental creature token named Lightning Rager. // It has trample, haste, and "At the beginning of the end step, sacrifice this creature." addCard(Zone.HAND, playerA, "Rite of the Raging Storm");// {3}{R}{R} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // // If an effect would put one or more tokens onto the battlefield under your control, it puts twice that many of those tokens onto the battlefield instead. // If an effect would place one or more counters on a permanent you control, it places twice that many of those counters on that permanent instead. addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); + // prepare x2 tokens on turn 2 and turn 3 castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rite of the Raging Storm"); + // controller's token can attack opponent attack(3, playerA, "Lightning Rager"); attack(3, playerA, "Lightning Rager"); + setStrictChooseMode(true); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); execute(); assertPermanentCount(playerA, "Rite of the Raging Storm", 1); - assertPermanentCount(playerA, "Lightning Rager", 2); + assertTappedCount("Lightning Rager", true, 2); assertLife(playerB, 10); assertLife(playerA, 20); } @Test - public void testDoubleRiteOfRagingStormOpponent() { + public void test_RiteOfReplication_TokensFromOpponent_CanNotAttack() { // At the beginning of each player's upkeep, that player creates a 5/1 red Elemental creature token named Lightning Rager. // It has trample, haste, and "At the beginning of the end step, sacrifice this creature." addCard(Zone.HAND, playerA, "Rite of the Raging Storm");// {3}{R}{R} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // // If an effect would put one or more tokens onto the battlefield under your control, it puts twice that many of those tokens onto the battlefield instead. // If an effect would place one or more counters on a permanent you control, it places twice that many of those counters on that permanent instead. addCard(Zone.BATTLEFIELD, playerB, "Doubling Season"); + // prepare x2 tokens on turn 2 castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rite of the Raging Storm"); - attack(2, playerB, "Lightning Rager:0"); // Can't attack - attack(2, playerB, "Lightning Rager:1"); // Can't attack + // opponent's token can not attack + attack(2, playerB, "Lightning Rager"); // can't attack + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); try { execute(); - + Assert.fail("must throw exception on execute"); } catch (Throwable e) { - if (!e.getMessage().contains("Player PlayerB must have 0 actions but found 2")) { + if (!e.getMessage().contains("Can't find available command - attack:Lightning Rager")) { Assert.fail("Should have thrown error about cannot attack, but got:\n" + e.getMessage()); } } assertPermanentCount(playerA, "Rite of the Raging Storm", 1); assertPermanentCount(playerB, "Lightning Rager", 2); + assertTappedCount("Lightning Rager", true, 0); } /** @@ -202,21 +248,58 @@ public class DoublingSeasonTest extends CardTestPlayerBase { * on as a cost, not as an effect. */ @Test - public void testPlaneswalkerLoyalty() { + public void test_LoyaltyCounters_DoubleSeason() { + // planeswalker, 2 starting loyalty + // +1: Draw a card, then discard a card at random. + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); addCard(Zone.BATTLEFIELD, playerA, "Tibalt, the Fiend-Blooded"); - addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); + // 2 starting loyalty + // on etb: x2 from double season + int onEtbCount = 2 * 2; + checkPermanentCounters("etb counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tibalt, the Fiend-Blooded", CounterType.LOYALTY, onEtbCount); + // on +1 cost: nothing from double season + int onCostCount = 1; activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Draw a card, then discard a card at random."); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); assertLife(playerA, 20); assertLife(playerB, 20); - //Should not be doubled - assertCounterCount("Tibalt, the Fiend-Blooded", CounterType.LOYALTY, 3); + assertCounterCount("Tibalt, the Fiend-Blooded", CounterType.LOYALTY, onEtbCount + onCostCount); + } + + @Test + public void test_LoyaltyCounters_Pir() { + // If one or more counters would be put on a permanent your team controls, that many plus one of each of those + // kinds of counters are put on that permanent instead. + addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); + // + // planeswalker, 2 starting loyalty + // +1: Draw a card, then discard a card at random. + addCard(Zone.BATTLEFIELD, playerA, "Tibalt, the Fiend-Blooded"); + + // 2 starting loyalty + // on etb: +1 from pir + int onEtbCount = 2 + 1; + checkPermanentCounters("etb counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tibalt, the Fiend-Blooded", CounterType.LOYALTY, onEtbCount); + + // on +1 cost: +1 from pir + int onCostCount = 1 + 1; + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Draw a card, then discard a card at random."); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertCounterCount("Tibalt, the Fiend-Blooded", CounterType.LOYALTY, onEtbCount + onCostCount); } /** @@ -225,51 +308,84 @@ public class DoublingSeasonTest extends CardTestPlayerBase { * https://github.com/magefree/mage/issues/5802 */ @Test - public void testPlaneswalkerWithoutReplacementEffect() { - //addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); - addCard(Zone.BATTLEFIELD, playerA, "Chandra, Fire Artisan"); + public void test_LoyaltyCounters_SeasonAndPir_SeasonFirst() { + /* + details explain from https://www.reddit.com/r/mtgrules/comments/aql1it/doubling_season_and_pir_imaginative_rascal/ + + When you activate Tezzeret's ability to make Thopters, you'll put on 4. + In this case, Doubling Season can't apply initially because it's a cost and not an effect, + but Pir can apply making it 2 and then that lets Season in the door to make it 4. + + Related rules: + 616.1 + If two or more replacement and/or prevention effects are attempting to modify the way an event affects an + object or player, the affected object’s controller (or its owner if it has no controller) or the affected + player chooses one to apply, following the steps listed below [..]. + 616.1e + Once the chosen effect has been applied, this process is repeated (taking into account only replacement or + prevention effects that would now be applicable) until there are no more left to apply. + 614.16 + Some replacement effects apply “if an effect would create one or more tokens” or “if an effect would + put one or more counters on a permanent.” These replacement effects apply if the effect of a resolving + spell or ability creates a token or puts a counter on a permanent, and they also apply if another replacement + or prevention effect does so, even if the original event being modified wasn’t itself an effect. + */ + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, 4 + 1); - } - - @Test - public void testPlaneswalkerWithReplacementEffect() { + // + // If one or more counters would be put on a permanent your team controls, that many plus one of each of those + // kinds of counters are put on that permanent instead. + addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); + // planeswalker, 4 starting loyalty + // +1: Exile the top card of your library. You may play it this turn. addCard(Zone.BATTLEFIELD, playerA, "Chandra, Fire Artisan"); - addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); // x2 counters - addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); // +1 counter + // choose etb replacement effects (from season and pir) + setChoice(playerA, "Doubling Season"); + + // 4 starting loyalty + // on etb: x2 from double season, +1 from pir + int onEtbCount = 4 * 2 + 1; + checkPermanentCounters("etb counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, onEtbCount); + + // +1 cost (no double effect on cost, +1 effect from pir, double effect after pir's effect) + int onCostCount = (1 + 1) * 2; activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, 4 + (1 + 1) * 2); + assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, onEtbCount + onCostCount); } @Test - public void testPlaneswalkerCreatesToken() { - setStrictChooseMode(true); + public void test_LoyaltyCounters_SeasonAndPir_PirFirst() { + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); + // + // If one or more counters would be put on a permanent your team controls, that many plus one of each of those + // kinds of counters are put on that permanent instead. + addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); + // planeswalker, 4 starting loyalty + // +1: Exile the top card of your library. You may play it this turn. + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Fire Artisan"); - // +1: Create a 2/2 black Zombie creature token. - // -4: Each player sacrifices two creatures. - // -9: Each opponent chooses a permanent they control of each permanent type and sacrifices the rest. - addCard(Zone.BATTLEFIELD, playerA, "Liliana, Dreadhorde General"); - // If an effect would create one or more tokens under your control, it creates twice that many of those tokens instead. - // If an effect would put one or more counters on a permanent you control, it puts twice that many of those counters on that permanent instead. - addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); // + // choose etb replacement effects (from season and pir) + setChoice(playerA, "Pir, Imaginative Rascal"); + // 4 starting loyalty + // on etb: +1 from pir, x2 from double season + int onEtbCount = (4 + 1) * 2; + checkPermanentCounters("etb counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, onEtbCount); + + // +1 cost (no double effect on cost, +1 effect from pir, double effect after pir's effect) + int onCostCount = (1 + 1) * 2; activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, "Zombie Token", 2); + assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, onEtbCount + onCostCount); } - }