diff --git a/Mage.Sets/src/mage/sets/shadowmoor/PaintersServant.java b/Mage.Sets/src/mage/sets/shadowmoor/PaintersServant.java index 1df814857f2..23b1b3f46c8 100644 --- a/Mage.Sets/src/mage/sets/shadowmoor/PaintersServant.java +++ b/Mage.Sets/src/mage/sets/shadowmoor/PaintersServant.java @@ -104,6 +104,7 @@ class ChooseColorEffect extends OneShotEffect { game.getState().setValue(source.getSourceId() + "_color", colorChoice.getColor()); permanent.addInfo("chosen color", "Chosen color: " + colorChoice.getColor().getDescription() + ""); } + return true; } return false; } diff --git a/Mage.Sets/src/mage/sets/tempest/Grindstone.java b/Mage.Sets/src/mage/sets/tempest/Grindstone.java index b7a6f3098cf..a96272b8a2a 100644 --- a/Mage.Sets/src/mage/sets/tempest/Grindstone.java +++ b/Mage.Sets/src/mage/sets/tempest/Grindstone.java @@ -53,7 +53,7 @@ public class Grindstone extends CardImpl { super(ownerId, 280, "Grindstone", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{1}"); this.expansionSetCode = "TMP"; - // {3}, {tap}: Target player puts the top two cards of his or her library into his or her graveyard. If both cards share a color, repeat this process. + // {3}, {T}: Target player puts the top two cards of his or her library into his or her graveyard. If both cards share a color, repeat this process. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GrindstoneEffect(), new ManaCostsImpl("{3}")); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetPlayer()); @@ -93,7 +93,18 @@ class GrindstoneEffect extends OneShotEffect { Player targetPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source)); boolean colorShared; if (targetPlayer != null) { + int possibleIterations = targetPlayer.getLibrary().size() / 2; + int iteration = 0; do { + iteration++; + if (iteration > possibleIterations + 20) { + // 801.16. If the game somehow enters a "loop" of mandatory actions, repeating a sequence of events + // with no way to stop, the game is a draw for each player who controls an object that's involved in + // that loop, as well as for each player within the range of influence of any of those players. They + // leave the game. All remaining players continue to play the game. + game.setDraw(source.getControllerId()); + return true; + } colorShared = false; Card card1 = targetPlayer.getLibrary().removeFromTop(game); if (card1 != null) { @@ -105,9 +116,7 @@ class GrindstoneEffect extends OneShotEffect { colorShared = card1.getColor().shares(card2.getColor()); } } - } - - + } } while (colorShared && targetPlayer.isInGame()); return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/GrindstoneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/GrindstoneTest.java new file mode 100644 index 00000000000..1e800575457 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/GrindstoneTest.java @@ -0,0 +1,69 @@ +/* + * 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.cards.replacement; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class GrindstoneTest extends CardTestPlayerBase { + + /** + * Tests that instead of one spore counter there were two spore counters added to Pallid Mycoderm + * if Doubling Season is on the battlefield. + */ + @Test + public void testGrindstoneTest() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // As Painter's Servant enters the battlefield, choose a color. + // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. + addCard(Zone.HAND, playerA, "Painter's Servant"); + // {3}, {T}: Target player puts the top two cards of his or her library into his or her graveyard. If both cards share a color, repeat this process. + addCard(Zone.BATTLEFIELD, playerA, "Grindstone"); + + addCard(Zone.LIBRARY, playerA, "Progenitus", 2); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); + setChoice(playerA, "Blue"); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3},{T}: Target player puts the top two cards of his or her library into his or her graveyard. If both cards share a color, repeat this process."); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Painter's Servant", 1); + } + +} diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 0b6609dd59b..689f545a5fc 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -131,6 +131,7 @@ public interface Game extends MageItem, Serializable { Combat getCombat(); GameState getState(); String getWinner(); + void setDraw(UUID playerId); boolean isADraw(); ContinuousEffects getContinuousEffects(); GameStates getGameStates(); diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 4467a77f343..23f920bb3d3 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -2520,4 +2520,15 @@ public abstract class GameImpl implements Game, Serializable { return startLife; } + @Override + public void setDraw(UUID playerId) { + Player player = getPlayer(playerId); + if (player != null) { + for (UUID playerToSetId :player.getInRange()) { + Player playerToDraw = getPlayer(playerToSetId); + playerToDraw.lostForced(this); + } + } + } + } diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 2b97d8f2aab..cf188f22257 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -260,6 +260,7 @@ public interface Player extends MageItem, Copyable { void discardToMax(Game game); boolean discard(Card card, Ability source, Game game); void lost(Game game); + void lostForced(Game game); void won(Game game); void leave(); void concede(Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 88c4b8e53d1..0e544ab4951 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -1854,20 +1854,25 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void lost(Game game) { if (canLose(game)) { - logger.debug(this.getName() + " has lost gameId: " + game.getId()); - //20100423 - 603.9 - if (!this.wins) { - this.loses = true; - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST, null, null, playerId)); - game.informPlayers(this.getName()+ " has lost the game."); - } else { - logger.debug(this.getName() + " has already won - stop lost"); - } - // for draw - first all players that have lost have to be set to lost - if (!hasLeft()) { - logger.debug("Game over playerId: " + playerId); - game.gameOver(playerId); - } + lostForced(game); + } + } + + @Override + public void lostForced(Game game) { + logger.debug(this.getName() + " has lost gameId: " + game.getId()); + //20100423 - 603.9 + if (!this.wins) { + this.loses = true; + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST, null, null, playerId)); + game.informPlayers(this.getName()+ " has lost the game."); + } else { + logger.debug(this.getName() + " has already won - stop lost"); + } + // for draw - first all players that have lost have to be set to lost + if (!hasLeft()) { + logger.debug("Game over playerId: " + playerId); + game.gameOver(playerId); } }