Merge branch 'master' into feature/network_bandwidth

This commit is contained in:
magenoxx 2017-04-20 23:14:39 +03:00
commit 6461622204
150 changed files with 2609 additions and 355 deletions

View file

@ -289,6 +289,197 @@ public class CursesTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, 2);
assertPowerToughness(playerB, "Silvercoat Lion", 1, 1);
}
}
@Test
public void cruelRealityHasBothCreatureAndPwChoosePw() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
/*
Ugin, the Spirit Dragon {8}
Planeswalker Ugin
+2: Ugin, the Spirit Dragon deals 3 damage to target creature or player.
X: Exile each permanent with converted mana cost X or less that's one or more colors.
10: You gain 7 life, draw seven cards, then put up to seven permanent cards from your hand onto the battlefield.
*/
String ugin = "Ugin, the Spirit Dragon";
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, ugin);
addCard(Zone.BATTLEFIELD, playerB, memnite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setChoice(playerB, ugin);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, ugin, 1);
assertPermanentCount(playerB, memnite, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 20);
}
@Test
public void cruelRealityHasBothCreatureAndPwChooseCreature() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
/*
Ugin, the Spirit Dragon {8}
Planeswalker Ugin
+2: Ugin, the Spirit Dragon deals 3 damage to target creature or player.
X: Exile each permanent with converted mana cost X or less that's one or more colors.
10: You gain 7 life, draw seven cards, then put up to seven permanent cards from your hand onto the battlefield.
*/
String ugin = "Ugin, the Spirit Dragon";
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, ugin);
addCard(Zone.BATTLEFIELD, playerB, memnite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setChoice(playerB, memnite);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, memnite, 1);
assertPermanentCount(playerB, ugin, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 20);
}
@Test
public void cruelRealityOnlyHasCreatureNoChoiceMade() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, memnite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, memnite, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 20);
}
@Test
public void cruelRealityOnlyHasPwNoChoiceMade() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
/*
Ugin, the Spirit Dragon {8}
Planeswalker Ugin
+2: Ugin, the Spirit Dragon deals 3 damage to target creature or player.
X: Exile each permanent with converted mana cost X or less that's one or more colors.
10: You gain 7 life, draw seven cards, then put up to seven permanent cards from your hand onto the battlefield.
*/
String ugin = "Ugin, the Spirit Dragon";
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, ugin);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, ugin, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 20);
}
@Test
public void cruelRealityOnlyHasCreatureTryToChooseNotToSac() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, memnite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setChoice(playerB, "No");
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, memnite, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 20);
}
@Test
public void cruelRealityNoCreatureOrPwForcesLifeLoss() {
/*
Cruel Reality {5}{B}{B}
Enchantment - Aura Curse
Enchant player
At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, he or she loses 5 life.
*/
String cReality = "Cruel Reality";
String gPrison = "Ghostly Prison"; // {2}{W} enchantment - doesnt matter text for this
addCard(Zone.HAND, playerA, cReality);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerB, gPrison);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cReality, playerB);
setChoice(playerB, gPrison); // try to set choice to enchantment
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerB, gPrison, 1);
assertPermanentCount(playerA, cReality, 1);
assertLife(playerB, 15);
}
}

View file

@ -0,0 +1,91 @@
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author escplan9
*/
public class ExertTest extends CardTestPlayerBase {
@Test
public void exertUsedDoesNotUntapNextUntapStep() {
/*
Glory-Bound Initiate {1}{W}
Creature - Human Warrior 3/1
You may exert Glory-Bound Intiate as it attacks. When you do, it gets +1/+3 and gains lifelink until end of turn.
*/
String gbInitiate = "Glory-Bound Initiate";
addCard(Zone.BATTLEFIELD, playerA, gbInitiate);
attack(1, playerA, gbInitiate);
setChoice(playerA, "Yes");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertTapped(gbInitiate, true); // does not untap due to exert
assertLife(playerA, 24); // exerted 4/4 lifelink
assertLife(playerB, 16);
}
@Test
public void exertNotUsedRegularAttack() {
/*
Glory-Bound Initiate {1}{W}
Creature - Human Warrior 3/1
You may exert Glory-Bound Intiate as it attacks. When you do, it gets +1/+3 and gains lifelink until end of turn.
*/
String gbInitiate = "Glory-Bound Initiate";
addCard(Zone.BATTLEFIELD, playerA, gbInitiate);
attack(1, playerA, gbInitiate);
setChoice(playerA, "No");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertTapped(gbInitiate, false); // untaps as normal
assertLife(playerA, 20);
assertLife(playerB, 17);
}
/*
"If you gain control of another player's creature until end of turn and exert it, it will untap during that player's untap step."
See issue #3183
*/
@Test
public void stolenExertCreatureShouldUntapDuringOwnersUntapStep() {
/*
Glory-Bound Initiate {1}{W}
Creature - Human Warrior 3/1
You may exert Glory-Bound Intiate as it attacks. When you do, it gets +1/+3 and gains lifelink until end of turn.
*/
String gbInitiate = "Glory-Bound Initiate";
String aTreason = "Act of Treason"; // {2}{R} sorcery gain control target creature, untap, gains haste until end of turn
addCard(Zone.HAND, playerA, gbInitiate);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.HAND, playerB, aTreason);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, gbInitiate);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, aTreason, gbInitiate);
attack(2, playerB, gbInitiate);
setChoice(playerB, "Yes");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerB, aTreason, 1);
assertLife(playerA, 16);
assertLife(playerB, 24);
assertTapped(gbInitiate, false); // stolen creature exerted does untap during owner's untap step
}
}

View file

@ -73,18 +73,48 @@ public class TransmuteTest extends CardTestPlayerBase {
@Test
public void searchSplittedCardOneManaCmcSpell() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.HAND, playerA, "Dizzy Spell");
// Target creature gets -3/-0 until end of turn.
// Transmute {1}{U}{U} ({1}{U}{U}, Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Transmute only as a sorcery.)
addCard(Zone.HAND, playerA, "Dizzy Spell"); // Instant {U}
// Wear {1}{R}
// Destroy target artifact.
// Tear {W}
// Destroy target enchantment.
addCard(Zone.LIBRARY, playerA, "Wear // Tear");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Transmute {1}{U}{U}");
setChoice(playerA, "Wear // Tear");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Dizzy Spell", 1);
assertHandCount(playerA, "Wear", 1); // Filter search can only search for one side of a split card
assertHandCount(playerA, "Tear", 1); // Filter search can only search for one side of a split card
assertHandCount(playerA, "Wear // Tear", 0);
}
@Test
public void searchSplittedCardThreeManaCmcSpell() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
// Counter target spell unless its controller discards his or her hand.
// Transmute {1}{U}{B}
addCard(Zone.HAND, playerA, "Perplex"); // Instant {1}{U}{B}
// Wear {1}{R}
// Destroy target artifact.
// Tear {W}
// Destroy target enchantment.
addCard(Zone.LIBRARY, playerA, "Wear // Tear");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Transmute {1}{U}{B}");
setChoice(playerA, "Wear // Tear");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Perplex", 1);
assertHandCount(playerA, "Wear // Tear", 1);
}
}

View file

@ -5,6 +5,9 @@ import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.fail;
/**
* @author LevelX2
*/
@ -26,9 +29,47 @@ public class TwoHeadedSliverTest extends CardTestPlayerBase {
block(3, playerB, "Silvercoat Lion", "Two-Headed Sliver");
setStopAt(3, PhaseStep.END_TURN);
try {
execute();
fail("Expected exception not thrown");
} catch (UnsupportedOperationException e) {
assertEquals("Two-Headed Sliver is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage());
}
}
@Test
public void testCanBeBlockedByTwoEffectAbility() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
// Two-Headed Sliver {1}{R} 1/1
// All Sliver creatures have "This creature can't be blocked except by two or more creatures."
addCard(Zone.HAND, playerA, "Two-Headed Sliver");
// Silvercoat Lion {1}{W} 2/2
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
// Coral Barrier {2}{U} 1/3
addCard(Zone.BATTLEFIELD, playerB, "Coral Barrier");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Two-Headed Sliver");
attack(3, playerA, "Two-Headed Sliver");
// Two blocks will succeed
block(3, playerB, "Silvercoat Lion", "Two-Headed Sliver");
block(3, playerB, "Coral Barrier", "Two-Headed Sliver");
setStopAt(3, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Two-Headed Sliver", 1);
assertLife(playerB, 19);
assertLife(playerA, 20);
assertLife(playerB, 20);
// Two-Headed Sliver died from the block
assertPermanentCount(playerA, "Two-Headed Sliver", 0);
assertPermanentCount(playerB, "Silvercoat Lion", 1);
assertPermanentCount(playerB, "Coral Barrier", 1);
assertGraveyardCount(playerA, "Two-Headed Sliver", 1);
}
}
}

View file

@ -38,6 +38,8 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class FluctuatorTest extends CardTestPlayerBase {
/**
* NOTE: As of 4/19/2017 this test is failing due to a bug in code. See issue #3148
*
* Fluctuator makes 'Akroma's Vengeance' cyclic cost reduced to {1}
* Test it with one Plains on battlefield.
*/
@ -88,8 +90,9 @@ public class FluctuatorTest extends CardTestPlayerBase {
}
/**
* NOTE: As of 4/19/2017 this test is failing due to a bug in code. See issue #3148
*
* Test 2 Fluctuators reduce cycling cost up to 4.
*
*/
@Test
public void testTwoFluctuatorsReduceBy4() {

View file

@ -28,6 +28,8 @@ public class SplitCardCmcTest extends CardTestPlayerBase {
public void testSplitCardCmcInHand() {
// Total CMC of Failure // Comply is 3, so should be exiled by Transgress the Mind.
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
// Devoid
// Target player reveals his or her hand. You may choose a card from it with converted mana cost 3 or greater and exile that card.
addCard(Zone.HAND, playerA, "Transgress the Mind");
addCard(Zone.HAND, playerB, "Failure // Comply");
@ -42,11 +44,17 @@ public class SplitCardCmcTest extends CardTestPlayerBase {
public void testSplitCardCmcOnStack() {
// Counterbalance revealing Wear // Tear counters a spell with converted mana cost 3, but not 1 or 2.
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.HAND, playerA, "Typhoid Rats");
addCard(Zone.HAND, playerA, "Typhoid Rats"); // Creature 1/1 {B}
// Whenever an opponent casts a spell, you may reveal the top card of your library. If you do, counter that spell
// if it has the same converted mana cost as the revealed card.
addCard(Zone.BATTLEFIELD, playerB, "Counterbalance");
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
// Wear {1}{R}
// Destroy target artifact.
// Tear {W}
// Destroy target enchantment.
addCard(Zone.LIBRARY, playerB, "Wear // Tear"); // CMC now 3
skipInitShuffling(); // so the set to top card stays at top

View file

@ -14,7 +14,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class LilianaTest extends CardTestPlayerBase {
@Test
public void testMe() {
public void testCreatureGainsZombieAsAdditionalType() {
/*
Binding Mummy {1}{W}
Creature - Zombie 2/2

View file

@ -32,6 +32,9 @@ import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.fail;
/**
*
* @author LevelX2, icetc
@ -212,12 +215,13 @@ public class BlockRequirementTest extends CardTestPlayerBase {
block(1, playerB, "Hill Giant", "Breaker of Armies");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
// Hill giant is still alive
assertPermanentCount(playerB, "Hill Giant", 1);
// Player B was unable to block, so goes down to 10 life
assertLife(playerB, 8);
try {
execute();
fail("Expected exception not thrown");
} catch (UnsupportedOperationException e) {
assertEquals("Breaker of Armies is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage());
}
}
/*
@ -230,17 +234,17 @@ public class BlockRequirementTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Slayer's Cleaver");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Memnite"); // {1} 1/1
addCard(Zone.BATTLEFIELD, playerB, "Dimensional Infiltrator"); // 2/1 - Eldrazi
addCard(Zone.BATTLEFIELD, playerB, "Llanowar Elves"); // 1/1
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Memnite"); // pumps to 4/2
attack(1, playerA, "Memnite"); // must be blocked by Dimensional Infiltrator
block(1, playerB, "Llanowar Elves", "Memnite"); // should not be allowed as only blocker
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertPermanentCount(playerA, "Slayer's Cleaver", 1);
assertLife(playerB, 20);
assertGraveyardCount(playerA, "Memnite", 1);

View file

@ -65,12 +65,16 @@ public class DuskDawnTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Dusk // Dawn", 0);
}
// Fail to cast Dawn (Aftermath part) from hand
@Test
public void testCastDawnFail() {
// Fail to cast dawn from hand
// Dusk {2}{W}{W}
// Destroy all creatures with power 3 or greater.
// Dawn {3}{W}{W}
// Return all creature cards with power less than or equal to 2 from your graveyard to your hand.
addCard(Zone.HAND, playerA, "Dusk // Dawn");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.GRAVEYARD, playerA, "Devoted Hero");
addCard(Zone.GRAVEYARD, playerA, "Devoted Hero"); // Creature 1/2 {W}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dawn");
setStopAt(1, PhaseStep.END_TURN);

View file

@ -36,6 +36,9 @@ public class PermeatingMassTest extends CardTestPlayerBase {
assertPowerToughness(playerA, "Permeating Mass", 1, 3);
}
/*
* NOTE: As of 04/19/2017 this test is failing due to a bug in code. See issue #3167
*/
@Test
public void damagedCreatureWithVaryingPTbecomesCopyOfPermeatingMass() {
/*

View file

@ -29,7 +29,8 @@ package org.mage.test.cards.triggers;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import mage.counters.CounterType;
import mage.filter.Filter;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -45,9 +46,10 @@ public class TargetedTriggeredTest extends CardTestPlayerBase {
*
*/
@Test
@Ignore
// this does not currently work in test, because the target event will be fired earlier during tests,
//@Ignore
// this does not currently work in test (????), because the target event will be fired earlier during tests,
// so the zone change counter for the fixed target of the counterspell will not work
// UPDATE: seems to work fine now? 04/19/2017 escplan9
public void testKiraGreatGlassSpinnerFirstSpellTurn() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.HAND, playerA, "Lightning Bolt");
@ -89,4 +91,115 @@ public class TargetedTriggeredTest extends CardTestPlayerBase {
assertPowerToughness(playerB, "Ashenmoor Liege", 4, 1);
}
@Test
public void testGlyphKeeperCountersFirstSpell() {
/*
Glyph Keeper {3}{U}{U}
Creature - Sphinx
Flying 5/3
Whenever this creature becomes the target of a spell or ability for the first time in a turn, counter that spell or ability."
*/
String gKeeper = "Glyph Keeper";
String bolt = "Lightning Bolt"; // {R} instant deal 3 dmg
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.HAND, playerA, bolt);
addCard(Zone.BATTLEFIELD, playerB, gKeeper);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, gKeeper);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, bolt, 1);
assertPermanentCount(playerB, gKeeper, 1);
}
@Test
public void testGlyphKeeperCountersFirstSpellButNotSecondSpell() {
/*
Glyph Keeper {3}{U}{U}
Creature - Sphinx
Flying 5/3
Whenever this creature becomes the target of a spell or ability for the first time in a turn, counter that spell or ability."
*/
String gKeeper = "Glyph Keeper";
String bolt = "Lightning Bolt"; // {R} instant deal 3 dmg
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.HAND, playerA, bolt, 2);
addCard(Zone.BATTLEFIELD, playerB, gKeeper);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, gKeeper);
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, bolt, gKeeper);
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
execute();
assertGraveyardCount(playerA, bolt, 2);
assertPermanentCount(playerB, gKeeper, 0);
}
/*
NOTE: test is failing due to card bug as of 04/20/2017. See issue #3180
I had a Glyph Keeper on board (cloned with Vizier of many faces). -- note this test is a simplified version, next test will test on the Clone if needed
First I played a Soulstinger and targeted the Glyph Keeper, the ability was countered. Then on the same main phase I played a Cartouche of Strength targeting the Glyph Keeper, that was also countered.
Only the first should have been countered.
*/
@Test
public void testGlyphKeeperCountersFirstAbilityButNotSecondOne() {
/*
Glyph Keeper {3}{U}{U}
Creature - Sphinx
Flying 5/3
Whenever this creature becomes the target of a spell or ability for the first time in a turn, counter that spell or ability."
*/
String gKeeper = "Glyph Keeper";
/*
Soulstinger {3}{B}
Creature - Scorpion Demon 4/5
When Soulstinger enters the battlefield, put two -1/-1 counter on target creature you control.
When Soulstinger dies, you may put a -1/-1 counter on target creature for each -1/-1 counter on Soulstinger.
*/
String sStinger = "Soulstinger";
/*
Cartouche of Strength {2}{G}
Enchantment - Aura Cartouche
Enchant creature you control
When Cartouche of Strength enters the battlefield, you may have enchanted creature fight target creature an opponent controls.
Enchanted creature gets +1/+1 and has trample.
*/
String cStrength = "Cartouche of Strength";
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.BATTLEFIELD, playerA, gKeeper);
addCard(Zone.HAND, playerA, sStinger);
addCard(Zone.HAND, playerA, cStrength);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
addCard(Zone.BATTLEFIELD, playerB, memnite);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sStinger);
addTarget(playerA, gKeeper); // should be countered by Glyph Keeper clause as first ability targetting it
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cStrength, gKeeper); // should not be countered anymore
addTarget(playerA, memnite); // Cartouche of Strength fight
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, gKeeper, 1);
assertGraveyardCount(playerA, sStinger, 0); // countered
assertGraveyardCount(playerA, cStrength, 0); // should not be countered
assertPermanentCount(playerA, cStrength, 1);
assertGraveyardCount(playerB, memnite, 1); // dies from fight
assertPowerToughness(playerA, gKeeper, 5, 3, Filter.ComparisonScope.All); // Soul Stinger should never have given it two -1/-1 counters
assertCounterCount(playerA, gKeeper, CounterType.M1M1, 0);
}
}

View file

@ -2,8 +2,7 @@ package org.mage.test.cards.watchers;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -11,27 +10,28 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* Created by IGOUDT on 30-3-2017.
*/
public class KiraGreatGlassSpinnerTest extends CardTestPlayerBase {
private final String kira = "Kira, Great Glass-Spinner";
private final String shock = "Shock";
@Test
public void counterFirst(){
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
addCard(Zone.BATTLEFIELD, playerA, "Ugin, the Spirit Dragon"); // starts with 7 Loyality counters
public void counterFirst() {
String ugin = "Ugin, the Spirit Dragon";
addCard(Zone.BATTLEFIELD, playerA, ugin); // starts with 7 Loyality counters
/*
Kira, Great Glass-Spinner {1}{U}{U}
Legendary Creature - Spirit 2/2
Flying
Creatures you control have "Whenever this creature becomes the target of a spell or ability for the first time each turn, counter that spell or ability."
*/
addCard(Zone.BATTLEFIELD, playerA, kira);
addCard(Zone.HAND, playerA, shock);
addCard(Zone.HAND, playerA, shock);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2: {source} deals 3 damage to target creature or player.", kira);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2: {source} deals 3 damage to target creature or player.", kira); // Ugin ability
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
Permanent _kira = getPermanent(kira, playerA.getId());
Assert.assertNotNull(_kira);
assertPermanentCount(playerA, kira, 1);
assertCounterCount(playerA, ugin, CounterType.LOYALTY, 9);
}
}

View file

@ -8,6 +8,9 @@ import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Test restrictions for choosing attackers and blockers.
*
@ -38,7 +41,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase {
* Frost
*/
@Test
public void testWallofWrost() {
public void testWallofFrost() {
// Whenever Wall of Frost blocks a creature, that creature doesn't untap during its controller's next untap step.
addCard(Zone.BATTLEFIELD, playerA, "Wall of Frost"); // 0/7
addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm");
@ -338,36 +341,36 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase {
*/
@Test
public void testMustAttackButCannotAttackAlone()
{
{
/* Mogg Flunkies {1}{R} 3/3
Creature Goblin
Mogg Flunkies can't attack or block alone.
*/
String flunkies = "Mogg Flunkies";
/* Goblin Assault {2}{R}
* Enchantment
* Enchantment
At the beginning of your upkeep, create a 1/1 red Goblin creature token with haste.
Goblin creatures attack each turn if able.
*/
String gAssault = "Goblin Assault";
addCard(Zone.BATTLEFIELD, playerA, flunkies);
addCard(Zone.BATTLEFIELD, playerB, gAssault);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertTapped(flunkies, false);
assertLife(playerB, 20);
}
/*
Reported bug: Tromokratis is unable to be blocked.
*/
@Test
public void tromokratisBlockedByAll() {
/*
/*
Tromokratis {5}{U}{U}
Legendary Creature Kraken 8/8
Tromokratis has hexproof unless it's attacking or blocking.
@ -376,30 +379,30 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase {
String tromokratis = "Tromokratis";
String gBears = "Grizzly Bears"; // {1}{G} 2/2
String memnite = "Memnite"; // {0} 1/1
addCard(Zone.BATTLEFIELD, playerA, tromokratis);
addCard(Zone.BATTLEFIELD, playerB, gBears);
addCard(Zone.BATTLEFIELD, playerB, memnite);
attack(1, playerA, tromokratis);
block(1, playerB, gBears, tromokratis);
block(1, playerB, memnite, tromokratis);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 20);
assertGraveyardCount(playerB, gBears, 1);
assertGraveyardCount(playerB, memnite, 1);
assertTapped(tromokratis, true);
}
/*
Reported bug: Tromokratis is unable to be blocked.
*/
@Test
public void tromokratisNotBlockedByAll() {
/*
/*
Tromokratis {5}{U}{U}
Legendary Creature Kraken 8/8
Tromokratis has hexproof unless it's attacking or blocking.
@ -409,24 +412,138 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase {
String gBears = "Grizzly Bears"; // {1}{G} 2/2
String memnite = "Memnite"; // {0} 1/1
String hGiant = "Hill Giant"; // {3}{R} 3/3
addCard(Zone.BATTLEFIELD, playerA, tromokratis);
addCard(Zone.BATTLEFIELD, playerB, gBears);
addCard(Zone.BATTLEFIELD, playerB, memnite);
addCard(Zone.BATTLEFIELD, playerB, hGiant);
attack(2, playerB, hGiant); // forces a creature to be tapped so unable to block Tromokratis, which means it cannot be blocked at all
attack(3, playerA, tromokratis);
block(3, playerB, gBears, tromokratis);
block(3, playerB, memnite, tromokratis);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 12); // Hill Giant could not block it, so no other creature could block Tromokratis either
assertPermanentCount(playerB, gBears, 1);
assertPermanentCount(playerB, memnite, 1);
assertTapped(tromokratis, true);
assertTapped(hGiant, true);
}
@Test
public void underworldCerberusBlockedByOneTest() {
/* Underworld Cerberus {3}{B}{3} 6/6
* Underworld Cerberus can't be blocked except by three or more creatures.
* Cards in graveyards can't be the targets of spells or abilities.
* When Underworld Cerberus dies, exile it and each player returns all creature cards from his or her graveyard to his or her hand.
*/
addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus");
addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1
attack(3, playerA, "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
try {
execute();
fail("Expected exception not thrown");
} catch(UnsupportedOperationException e) {
assertEquals("Underworld Cerberus is blocked by 1 creature(s). It has to be blocked by 3 or more.", e.getMessage());
}
}
@Test
public void underworldCerberusBlockedByTwoTest() {
/* Underworld Cerberus {3}{B}{3} 6/6
* Underworld Cerberus can't be blocked except by three or more creatures.
* Cards in graveyards can't be the targets of spells or abilities.
* When Underworld Cerberus dies, exile it and each player returns all creature cards from his or her graveyard to his or her hand.
*/
addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus");
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 2); // 1/1
attack(3, playerA, "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
try {
execute();
fail("Expected exception not thrown");
} catch(UnsupportedOperationException e) {
assertEquals("Underworld Cerberus is blocked by 2 creature(s). It has to be blocked by 3 or more.", e.getMessage());
}
}
@Test
public void underworldCerberusBlockedByThreeTest() {
/* Underworld Cerberus {3}{B}{3} 6/6
* Underworld Cerberus can't be blocked except by three or more creatures.
* Cards in graveyards can't be the targets of spells or abilities.
* When Underworld Cerberus dies, exile it and each player returns all creature cards from his or her graveyard to his or her hand.
*/
addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus");
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
// Blocked by 3 creatures - this is acceptable
attack(3, playerA, "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
block(3, playerB, "Memnite", "Underworld Cerberus");
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Underworld Cerberus", 1);
assertPermanentCount(playerB, "Memnite", 0);
assertGraveyardCount(playerB, "Memnite", 3);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
@Test
public void underworldCerberusBlockedByTenTest() {
/* Underworld Cerberus {3}{B}{3} 6/6
* Underworld Cerberus can't be blocked except by three or more creatures.
* Cards in graveyards can't be the targets of spells or abilities.
* When Underworld Cerberus dies, exile it and each player returns all creature cards from his or her graveyard to his or her hand.
*/
addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus");
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1
// Blocked by 10 creatures - this is acceptable as it's >3
attack(3, playerA, "Underworld Cerberus");
for(int i = 0; i < 10; i++) {
block(3, playerB, "Memnite", "Underworld Cerberus");
}
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Underworld Cerberus", 0);
assertPermanentCount(playerB, "Memnite", 4);
// Actually exiled when it dies
assertGraveyardCount(playerA, "Underworld Cerberus", 0);
assertExileCount(playerA, "Underworld Cerberus", 1);
// Cards are returned to their owner's hand when Underworld Cerberus dies
assertGraveyardCount(playerB, "Memnite", 0);
assertHandCount(playerB, "Memnite", 6);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
}

View file

@ -1,119 +0,0 @@
/*
* 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.combat;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author jeffwadsworth
*/
public class CanBlockMultipleCreatures extends CardTestPlayerBase {
// test must be ignored until creature blocking multiple supported by test framework
@Ignore
@Test
public void testCombat() {
addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1);
addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6
addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium)
addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3
// Trample requirement for Kessig Dire Swine
addCard(Zone.GRAVEYARD, playerB, "Forest", 1);
addCard(Zone.GRAVEYARD, playerB, "Memnite", 1);
addCard(Zone.GRAVEYARD, playerB, "Flight", 1);
addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1);
// Attack with all 4 creatures and block all with the Watcher in the Web
attack(2, playerB, "Ulrich, Uncontested Alpha");
attack(2, playerB, "Kessig Dire Swine");
attack(2, playerB, "Howlpack Wolf");
attack(2, playerB, "Incorrigible Youths");
block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha");
block(2, playerA, "Watcher in the Web", "Kessig Dire Swine");
block(2, playerA, "Watcher in the Web", "Howlpack Wolf");
block(2, playerA, "Watcher in the Web", "Incorrigible Youths");
setStopAt(2, PhaseStep.COMBAT_DAMAGE);
execute();
assertLife(playerA, 19);
}
/*
* Reported bug: Night Market Guard was able to block a creature with Menace
*/
@Test
public void testNightMarketGuardShouldNotBlockCreatureWithMenace()
{
/*
Night Market Guard {3} 3/1
Artifact Creature Construct
Night Market Guard can block an additional creature each combat.
*/
String nMarketGuard = "Night Market Guard";
/*
Embraal Bruiser {1}{B}
Creature - Human Warrior
Embraal Bruiser enters the battlefield tapped.
Embraal Bruiser has menace as long as you control an artifact.
*/
String eBruiser = "Embraal Bruiser";
/*
{0} 1/1
* Artifact Creature Construct
*/
String memnite = "Memnite";
addCard(Zone.BATTLEFIELD, playerA, nMarketGuard);
addCard(Zone.BATTLEFIELD, playerB, eBruiser);
addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace
attack(4, playerB, eBruiser);
block(4, playerA, nMarketGuard, eBruiser);
setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertTapped(eBruiser, true);
assertLife(playerA, 17); // could not block, so 3 damage goes through
assertPermanentCount(playerA, nMarketGuard, 1);
assertPermanentCount(playerB, eBruiser, 1);
}
}

View file

@ -0,0 +1,226 @@
/*
* 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.combat;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
*
* @author jeffwadsworth
* @author Simown
*/
public class CanBlockMultipleCreaturesTest extends CardTestPlayerBase {
@Test
public void testMultipleBlockWithTrample() {
addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1);
addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6
addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium)
addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3
// Trample requirement for Kessig Dire Swine
addCard(Zone.GRAVEYARD, playerB, "Forest", 1);
addCard(Zone.GRAVEYARD, playerB, "Memnite", 1);
addCard(Zone.GRAVEYARD, playerB, "Flight", 1);
addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1);
// Attack with all 4 creatures and block all with the Watcher in the Web
attack(2, playerB, "Kessig Dire Swine");
attack(2, playerB, "Ulrich, Uncontested Alpha");
attack(2, playerB, "Howlpack Wolf");
attack(2, playerB, "Incorrigible Youths");
// BLOCKING ORDER MATTERS - the trampling creature must be selected to block first
// You can manually change the blocking order but it's easier to assign them in order
block(2, playerA, "Watcher in the Web", "Kessig Dire Swine");
block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha");
block(2, playerA, "Watcher in the Web", "Howlpack Wolf");
block(2, playerA, "Watcher in the Web", "Incorrigible Youths");
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 19);
}
@Test
public void testMultipleBlockWithTrample2() {
addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1);
addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6
addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium)
addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3
// Trample requirement for Kessig Dire Swine
addCard(Zone.GRAVEYARD, playerB, "Forest", 1);
addCard(Zone.GRAVEYARD, playerB, "Memnite", 1);
addCard(Zone.GRAVEYARD, playerB, "Flight", 1);
addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1);
// Attack with all 4 creatures and block all with the Watcher in the Web
attack(2, playerB, "Kessig Dire Swine");
attack(2, playerB, "Ulrich, Uncontested Alpha");
attack(2, playerB, "Howlpack Wolf");
attack(2, playerB, "Incorrigible Youths");
// BLOCKING ORDER MATTERS - the trampling creature must be selected to block first
block(2, playerA, "Watcher in the Web", "Kessig Dire Swine");
block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha");
block(2, playerA, "Watcher in the Web", "Howlpack Wolf");
// Don't block Incorrigible Youths
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
// Damage 1 from Kessig Dire Swine + 4 from Incorrigible Youths
assertLife(playerA, 15);
}
@Test
public void testCanOnlyBlockSingle() {
// Hundred-Handed One {2}{W}{W}
// Monstrosity 3. {3}{W}{W}{W} (If this creature isnt monstrous, put three +1/+1 counters on it and it becomes monstrous.)
//As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat.
addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1);
addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1
addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike
// Attack with all 4 creatures and try and block both with hundred-handed one
attack(2, playerB, "Bronze Sable");
attack(2, playerB, "Fabled Hero");
block(2, playerA, "Hundred-Handed One", "Bronze Sable");
block(2, playerA, "Hundred-Handed One", "Fabled Hero");
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
// Will fail on purpose - we are trying to block too many creatures!
try {
execute();
fail("Expected exception not thrown");
} catch(UnsupportedOperationException e) {
assertEquals("Hundred-Handed One cannot block Fabled Hero", e.getMessage());
}
}
@Test
public void testCanBlockMultiple() {
// Hundred-Handed One {2}{W}{W}
// Monstrosity 3. {3}{W}{W}{W} (If this creature isnt monstrous, put three +1/+1 counters on it and it becomes monstrous.)
// As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat.
addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1);
// For monstrosity
addCard(Zone.BATTLEFIELD, playerA, "Plains", 6);
addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1
addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike
// Make hundred-handed one monstrous
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{W}{W}{W}: Monstrosity 3.");
// Attack with all 4 creatures and try and block both with hundred-handed one
attack(2, playerB, "Bronze Sable");
attack(2, playerB, "Fabled Hero");
block(2, playerA, "Hundred-Handed One", "Bronze Sable");
block(2, playerA, "Hundred-Handed One", "Fabled Hero");
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
// Will not fail this time as hundred-handed one is monstrous and can block up to 100 creatures
execute();
// Was a 3/5 but monstrosity 3
assertPowerToughness(playerA, "Hundred-Handed One", 6, 8);
// No one has been hit
assertLife(playerA, 20);
assertLife(playerB, 20);
}
/*
* Reported bug: Night Market Guard was able to block a creature with Menace
*/
@Test
public void testNightMarketGuardShouldNotBlockCreatureWithMenace()
{
/*
Night Market Guard {3} 3/1
Artifact Creature Construct
Night Market Guard can block an additional creature each combat.
*/
String nMarketGuard = "Night Market Guard";
/*
Embraal Bruiser {1}{B}
Creature - Human Warrior
Embraal Bruiser enters the battlefield tapped.
Embraal Bruiser has menace as long as you control an artifact.
*/
String eBruiser = "Embraal Bruiser";
/*
{0} 1/1
* Artifact Creature Construct
*/
String memnite = "Memnite";
addCard(Zone.BATTLEFIELD, playerA, nMarketGuard);
addCard(Zone.BATTLEFIELD, playerB, eBruiser);
addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace
attack(4, playerB, eBruiser);
block(4, playerA, nMarketGuard, eBruiser);
setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
// Catch the illegal block
try {
execute();
fail("Expected exception not thrown");
} catch(UnsupportedOperationException e) {
assertEquals("Embraal Bruiser is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage());
}
}
}

View file

@ -28,13 +28,10 @@
package org.mage.test.player;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
@ -65,15 +62,10 @@ import mage.counters.Counter;
import mage.counters.Counters;
import mage.filter.Filter;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterAttackingCreature;
import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.common.FilterCreatureOrPlayer;
import mage.filter.common.FilterPlaneswalkerPermanent;
import mage.filter.common.*;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.BlockingPredicate;
import mage.filter.predicate.permanent.SummoningSicknessPredicate;
import mage.game.Game;
import mage.game.Graveyard;
@ -181,6 +173,11 @@ public class TestPlayer implements Player {
return null;
}
// Gets all permanents that match the filter
protected List<Permanent> findPermanents(FilterPermanent filter, UUID controllerId, Game game) {
return game.getBattlefield().getAllActivePermanents(filter, controllerId, game);
}
private boolean checkExecuteCondition(String[] groups, Game game) {
if (groups[2].startsWith("spellOnStack=")) {
String spellOnStack = groups[2].substring(13);
@ -289,7 +286,7 @@ public class TestPlayer implements Player {
int index = 0;
int targetsSet = 0;
for (String targetName : targetList) {
Mode selectedMode = null;
Mode selectedMode;
if (targetName.startsWith("mode=")) {
int modeNr = Integer.parseInt(targetName.substring(5, 6));
if (modeNr == 0 || modeNr > (ability.getModes().isEachModeMoreThanOnce() ? ability.getModes().getSelectedModes().size() : ability.getModes().size())) {
@ -561,25 +558,86 @@ public class TestPlayer implements Player {
@Override
public void selectBlockers(Game game, UUID defendingPlayerId) {
UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next();
// Map of Blocker reference -> list of creatures blocked
Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>();
for (PlayerAction action : actions) {
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) {
String command = action.getAction();
command = command.substring(command.indexOf("block:") + 6);
String[] groups = command.split("\\$");
FilterCreatureForCombatBlock filterBlocker = new FilterCreatureForCombatBlock();
filterBlocker.add(new NamePredicate(groups[0]));
filterBlocker.add(Predicates.not(new BlockingPredicate()));
Permanent blocker = findPermanent(filterBlocker, computerPlayer.getId(), game);
if (blocker != null) {
FilterAttackingCreature filterAttacker = new FilterAttackingCreature();
filterAttacker.add(new NamePredicate(groups[1]));
Permanent attacker = findPermanent(filterAttacker, opponentId, game);
if (attacker != null) {
computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game);
FilterAttackingCreature filterAttacker = new FilterAttackingCreature();
filterAttacker.add(new NamePredicate(groups[1]));
Permanent attacker = findPermanent(filterAttacker, opponentId, game);
FilterControlledPermanent filterPermanent = new FilterControlledPermanent();
filterPermanent.add(new NamePredicate(groups[0]));
// Get all possible blockers - those with the same name on the battlefield
List<Permanent> possibleBlockers = findPermanents(filterPermanent, computerPlayer.getId(), game);
if (!possibleBlockers.isEmpty() && attacker != null) {
boolean blockerFound = false;
for(Permanent blocker: possibleBlockers) {
// See if it can block this creature
if(canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) {
computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game);
blockerFound = true;
break;
}
}
// If we haven't found a blocker then an illegal block has been made in the test
if(!blockerFound) {
throw new UnsupportedOperationException(groups[0] + " cannot block " + groups[1]);
}
}
}
}
checkMultipleBlockers(game, blockedCreaturesByCreature);
}
// Checks if a creature can block at least one more creature
private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
MageObjectReference blockerRef = new MageObjectReference(blocker, game);
// See if we already reference this blocker
for(MageObjectReference r: blockedCreaturesByCreature.keySet()) {
if(r.equals(blockerRef)) {
// Use the existing reference if we do
blockerRef = r;
}
}
List<MageObjectReference> blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>());
int numBlocked = blocked.size();
// Can't block any more creatures
if(++numBlocked > blocker.getMaxBlocks()) {
return false;
}
// Add the attacker reference to the list of creatures this creature is blocking
blocked.add(new MageObjectReference(attacker, game));
blockedCreaturesByCreature.put(blockerRef, blocked);
return true;
}
// Check for Menace type abilities - if creatures can be blocked by >X or <Y only
private void checkMultipleBlockers(Game game, Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature) {
// Stores the total number of blockers for each attacker
Map<MageObjectReference, Integer> blockersForAttacker = new HashMap<>();
// Calculate the number of blockers each attacker has
for(List<MageObjectReference> attackers : blockedCreaturesByCreature.values()) {
for(MageObjectReference mr: attackers) {
Integer blockers = blockersForAttacker.getOrDefault(mr, 0);
blockersForAttacker.put(mr, blockers+1);
}
}
// Check each attacker is blocked by an allowed amount of creatures
for(Map.Entry<MageObjectReference, Integer> entry: blockersForAttacker.entrySet()) {
Permanent attacker = entry.getKey().getPermanent(game);
Integer blockers = entry.getValue();
// If getMaxBlockedBy() == 0 it means any number of creatures can block this creature
if(attacker.getMaxBlockedBy() != 0 && blockers > attacker.getMaxBlockedBy()) {
throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It can only be blocked by " + attacker.getMaxBlockedBy() + " or less.");
}
else if(blockers < attacker.getMinBlockedBy()) {
throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It has to be blocked by " + attacker.getMinBlockedBy() + " or more.");
}
}
// No errors raised - all the blockers pass the test!
}
@Override

View file

@ -805,9 +805,19 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param count Expected count.
*/
public void assertHandCount(Player player, String cardName, int count) throws AssertionError {
FilterCard filter = new FilterCard();
filter.add(new NamePredicate(cardName));
int actual = currentGame.getPlayer(player.getId()).getHand().count(filter, player.getId(), currentGame);
int actual;
if (cardName.contains("//")) { // special logic for cheched split cards, because in game logic of card name filtering is different than for test
actual = 0;
for (Card card : currentGame.getPlayer(player.getId()).getHand().getCards(currentGame)) {
if (card.getName().equals(cardName)) {
actual++;
}
}
} else {
FilterCard filter = new FilterCard();
filter.add(new NamePredicate(cardName));
actual = currentGame.getPlayer(player.getId()).getHand().count(filter, player.getId(), currentGame);
}
Assert.assertEquals("(Hand) Card counts for card " + cardName + " for " + player.getName() + " are not equal ", count, actual);
}