diff --git a/Mage.Tests/CommanderAnafenza_WBG.dck b/Mage.Tests/CommanderAnafenza_WBG.dck new file mode 100644 index 0000000000..0c8db1eb73 --- /dev/null +++ b/Mage.Tests/CommanderAnafenza_WBG.dck @@ -0,0 +1,88 @@ +1 [RTR:71] Necropolis Regent +1 [FUT:171] Llanowar Reborn +1 [RAV:158] Doubling Season +1 [RAV:33] Twilight Drover +1 [RAV:278] Golgari Rot Farm +1 [8ED:247] Fecundity +1 [M11:221] Whispersilk Cloak +1 [6ED:274] Ashnod's Altar +1 [ZEN:182] Scute Mob +1 [RTR:152] Corpsejack Menace +1 [ALA:140] Mycoloth +1 [RTR:150] Collective Blessing +1 [RTR:194] Selesnya Charm +1 [M13:170] Farseek +1 [BNG:148] Karametra, God of Harvests +7 [BFZ:270] Forest +1 [FUT:138] Sprout Swarm +1 [DKA:76] Tragic Slip +1 [DGM:149] Golgari Guildgate +1 [GTC:123] Gyre Sage +1 [DGM:93] Putrefy +1 [GTC:242] Godless Shrine +1 [AVR:10] Cathars' Crusade +1 [AVR:171] Champion of Lambholt +1 [M14:1] Ajani, Caller of the Pride +1 [THS:153] Bow of Nylea +1 [INV:233] Aura Shards +1 [RTR:63] Desecration Demon +1 [MOR:72] Oona's Blackguard +1 [KTK:215] Abzan Banner +1 [KTK:79] Mer-Ek Nightblade +1 [KTK:179] Ivorytusk Fortress +1 [M15:177] Hornet Nest +1 [DGM:153] Orzhov Guildgate +1 [KTK:133] Hardened Scales +1 [KTK:210] Utter End +1 [DGM:155] Selesnya Guildgate +1 [RTR:176] Korozda Guildmage +1 [VMA:255] Magister of Worth +1 [ISD:199] Parallel Lives +4 [BFZ:260] Swamp +1 [M12:190] Rampant Growth +1 [KTK:85] Retribution of the Ancients +1 [DGM:63] Deadbridge Chant +1 [CHK:239] Sakura-Tribe Elder +1 [ISD:239] Gavony Township +1 [DGM:114] Voice of Resurgence +1 [VMA:250] Deathreap Ritual +1 [CHK:225] Kodama's Reach +1 [M13:229] Sunpetal Grove +1 [M13:227] Reliquary Tower +1 [SCG:25] Wing Shards +1 [KTK:1] Abzan Battle Priest +1 [M11:120] Viscera Seer +1 [THS:227] Temple of Silence +1 [RTR:174] Jarad, Golgari Lich Lord +1 [RAV:251] Privileged Position +1 [KTK:3] Ainok Bond-Kin +1 [NPH:73] Sheoldred, Whispering One +1 [KTK:2] Abzan Falconer +1 [GTC:182] Obzedat, Ghost Council +1 [KTK:241] Sandsteppe Citadel +1 [RTR:121] Death's Presence +4 [BFZ:250] Plains +1 [KTK:161] Abzan Charm +1 [ISD:242] Isolated Chapel +1 [RTR:248] Temple Garden +1 [KTK:160] Abzan Ascendancy +1 [JOU:65] Dictate of Erebos +1 [TMP:36] Living Death +1 [M11:11] Condemn +1 [ORI:246] Evolving Wilds +1 [RAV:218] Phytohydra +1 [ISD:249] Woodland Cemetery +1 [M15:93] Cruel Sadist +1 [CSP:130] Juniper Order Ranger +1 [WWK:132] Bojuka Bog +1 [FUT:169] Dakmor Salvage +1 [CMD:200] Ghave, Guru of Spores +1 [DIS:95] Sprouting Phytohydra +1 [RAV:281] Selesnya Sanctuary +1 [KTK:156] Tuskguard Captain +1 [DKA:156] Grim Backwoods +1 [GPT:122] Mortify +1 [GPT:161] Orzhov Basilica +1 [C13:281] Command Tower +1 [RAV:207] Glare of Subdual +SB: 1 [KTK:163] Anafenza, the Foremost diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java index b2f712686d..b113e20ba6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java @@ -382,4 +382,39 @@ public class ZoneChangeReplacementTest extends CardTestPlayerBase { } + /** + * I was using Anafenza, the Foremost as Commander. She attacked and traded + * with two creatures. I moved Anafenza to the Command Zone, but the + * opponent's creatures "when {this} dies" abilities triggered. Since + * Anafenza and those creatures all received lethal damage at the same time, + * the creatures should have been exiled due to Anafenza's replacement + * effect, but I guess since the logic asks if you want to use the Command + * Zone replacement effect first, that it doesn't see her leaving the + * battlefield at the same time as the other creatures. + * + * http://blogs.magicjudges.org/rulestips/2015/05/anafenza-vs-deathmist-raptor/ + */ + @Test + public void testAnafenzaExileInCombat() { + // Whenever Anafenza, the Foremost attacks, put a +1/+1 counter on another target tapped creature you control. + // If a creature card would be put into an opponent's graveyard from anywhere, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Anafenza, the Foremost"); // 4/4 + + // Reach (This creature can block creatures with flying.) + addCard(Zone.BATTLEFIELD, playerB, "Skyraker Giant"); // 4/3 + + attack(2, playerB, "Skyraker Giant"); + block(2, playerA, "Anafenza, the Foremost", "Skyraker Giant"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertExileCount("Skyraker Giant", 1); + assertPermanentCount(playerA, "Anafenza, the Foremost", 0); + assertGraveyardCount(playerA, "Anafenza, the Foremost", 1); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java new file mode 100644 index 0000000000..aa0eda13c9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/AnafenzaTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.commander.duel; + +import java.io.FileNotFoundException; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.GameException; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +/** + * + * @author LevelX2 + */ +public class AnafenzaTest extends CardTestCommanderDuelBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + setDecknamePlayerA("CommanderAnafenza_WBG.dck"); // Commander = Anafenza, the Foremost + return super.createNewGameAndPlayers(); + } + + /** + * I was using Anafenza, the Foremost as Commander. She attacked and traded + * with two creatures. I moved Anafenza to the Command Zone, but the + * opponent's creatures "when {this} dies" abilities triggered. Since + * Anafenza and those creatures all received lethal damage at the same time, + * the creatures should have been exiled due to Anafenza's replacement + * effect, but I guess since the logic asks if you want to use the Command + * Zone replacement effect first, that it doesn't see her leaving the + * battlefield at the same time as the other creatures. + * + * http://blogs.magicjudges.org/rulestips/2015/05/anafenza-vs-deathmist-raptor/ + */ + @Test + public void testAnafenzaExileInCombat() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + + // When Runed Servitor dies, each player draws a card. + addCard(Zone.BATTLEFIELD, playerB, "Runed Servitor", 2); + + // Whenever Anafenza, the Foremost attacks, put a +1/+1 counter on another target tapped creature you control. + // If a creature card would be put into an opponent's graveyard from anywhere, exile it instead. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Anafenza, the Foremost"); + + attack(3, playerA, "Anafenza, the Foremost"); + block(3, playerB, "Runed Servitor", "Anafenza, the Foremost"); + block(3, playerB, "Runed Servitor", "Anafenza, the Foremost"); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 40); + assertLife(playerB, 40); + + assertExileCount("Runed Servitor", 2); + + assertCommandZoneCount(playerA, "Anafenza, the Foremost", 1); + assertGraveyardCount(playerA, "Anafenza, the Foremost", 0); + + assertHandCount(playerA, 1); // turn 3 draw + assertHandCount(playerB, 1); // turn 2 draw + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java index 67cf6484b5..08b16ef886 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java @@ -8,8 +8,6 @@ import mage.filter.Filter; import mage.players.Player; import org.mage.test.player.TestPlayer; - - /** * Interface for all test initialization and assertion operations. */ @@ -19,21 +17,23 @@ public interface CardTestAPI { * Types of game result. */ public enum GameResult { + WON, LOST, DRAW } //******* INITIALIZATION METHODS *******/ - /** - * Default game initialization params for red player (that plays with Mountains) + * Default game initialization params for red player (that plays with + * Mountains) */ void useRedDefault(); /** - * Removes all cards from player's library from the game. - * Usually this should be used once before initialization to form the library in certain order. + * Removes all cards from player's library from the game. Usually this + * should be used once before initialization to form the library in certain + * order. * * @param player {@link Player} to remove all library cards from. */ @@ -43,7 +43,8 @@ public interface CardTestAPI { * Add a card to specified zone of specified player. * * @param gameZone {@link mage.constants.Zone} to add cards to. - * @param player {@link Player} to add cards for. Use either playerA or playerB. + * @param player {@link Player} to add cards for. Use either playerA or + * playerB. * @param cardName Card name in string format. */ void addCard(Zone gameZone, TestPlayer player, String cardName); @@ -52,9 +53,10 @@ public interface CardTestAPI { * Add any amount of cards to specified zone of specified player. * * @param gameZone {@link mage.constants.Zone} to add cards to. - * @param player {@link Player} to add cards for. Use either playerA or playerB. + * @param player {@link Player} to add cards for. Use either playerA or + * playerB. * @param cardName Card name in string format. - * @param count Amount of cards to be added. + * @param count Amount of cards to be added. */ void addCard(Zone gameZone, TestPlayer player, String cardName, int count); @@ -62,11 +64,13 @@ public interface CardTestAPI { * Add any amount of cards to specified zone of specified player. * * @param gameZone {@link mage.constants.Zone} to add cards to. - * @param player {@link Player} to add cards for. Use either playerA or playerB. + * @param player {@link Player} to add cards for. Use either playerA or + * playerB. * @param cardName Card name in string format. - * @param count Amount of cards to be added. - * @param tapped In case gameZone is Battlefield, determines whether permanent should be tapped. - * In case gameZone is other than Battlefield, {@link IllegalArgumentException} is thrown + * @param count Amount of cards to be added. + * @param tapped In case gameZone is Battlefield, determines whether + * permanent should be tapped. In case gameZone is other than Battlefield, + * {@link IllegalArgumentException} is thrown */ void addCard(Zone gameZone, TestPlayer player, String cardName, int count, boolean tapped); @@ -74,27 +78,27 @@ public interface CardTestAPI { * Set player's initial life count. * * @param player {@link Player} to set life count for. - * @param life Life count to set. + * @param life Life count to set. */ void setLife(TestPlayer player, int life); //******* GAME OPTIONS *******/ - /** * Define turn number to stop the game on. + * * @param turn */ void setStopOnTurn(int turn); /** * Define the turn number and step to stop the game on. + * * @param turn * @param step */ void setStopAt(int turn, PhaseStep step); //******* ASSERT METHODS *******/ - /** * Assert turn number after test execution. * @@ -114,26 +118,26 @@ public interface CardTestAPI { * Assert player's life count after test execution. * * @param player {@link Player} to get life for comparison. - * @param life Expected player's life to compare with. + * @param life Expected player's life to compare with. */ void assertLife(Player player, int life) throws AssertionError; /** * Assert creature's power and toughness by card name. *
- * Throws {@link AssertionError} in the following cases: - * 1. no such player - * 2. no such creature under player's control - * 3. depending on comparison scope: - * 3a. any: no creature under player's control with the specified p\t params - * 3b. all: there is at least one creature with the cardName with the different p\t params + * Throws {@link AssertionError} in the following cases: 1. no such player + * 2. no such creature under player's control 3. depending on comparison + * scope: 3a. any: no creature under player's control with the specified p\t + * params 3b. all: there is at least one creature with the cardName with the + * different p\t params * - * @param player {@link Player} to get creatures for comparison. - * @param cardName Card name to compare with. - * @param power Expected power to compare with. + * @param player {@link Player} to get creatures for comparison. + * @param cardName Card name to compare with. + * @param power Expected power to compare with. * @param toughness Expected toughness to compare with. - * @param scope {@link Filter.ComparisonScope} Use ANY, if you want "at least one creature with given name should have specified p\t" - * Use ALL, if you want "all creature with gived name should have specified p\t" + * @param scope {@link Filter.ComparisonScope} Use ANY, if you want "at + * least one creature with given name should have specified p\t" Use ALL, if + * you want "all creature with gived name should have specified p\t" */ void assertPowerToughness(Player player, String cardName, int power, int toughness, Filter.ComparisonScope scope) throws AssertionError; @@ -141,13 +145,12 @@ public interface CardTestAPI { /** * Assert creature's abilities. * - * Throws {@link AssertionError} in the following cases: - * 1. no such player - * 2. no such creature under player's control - * 3. there is more than one creature with such name + * Throws {@link AssertionError} in the following cases: 1. no such player + * 2. no such creature under player's control 3. there is more than one + * creature with such name * - * @param player {@link Player} to get creatures for comparison. - * @param cardName Card name to compare with. + * @param player {@link Player} to get creatures for comparison. + * @param cardName Card name to compare with. * @param abilities Expected abilities */ void assertAbilities(Player player, String cardName, List