moved copied cards to GameState

This commit is contained in:
betasteward 2015-04-03 13:37:11 -04:00
parent 42d34e754f
commit 18a4a98f1a
6 changed files with 328 additions and 95 deletions

View file

@ -0,0 +1,114 @@
/*
* 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.copy;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author BetaSteward
*/
public class IsochronScepterTest extends CardTestPlayerBase {
/**
* Isochron Scepter
* Artifact, 2 (2)
* Imprint When Isochron Scepter enters the battlefield, you may exile an
* instant card with converted mana cost 2 or less from your hand.
* {2}, {T}: You may copy the exiled card. If you do, you may cast the copy
* without paying its mana cost.
*
*/
@Test
public void testImprint() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.HAND, playerA, "Isochron Scepter");
addCard(Zone.HAND, playerA, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter");
addTarget(playerA, "Lightning Bolt");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Isochron Scepter", 1);
assertExileCount("Lightning Bolt", 1);
assertLife(playerB, 20);
}
@Test
public void testCopyCard() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.HAND, playerA, "Isochron Scepter");
addCard(Zone.HAND, playerA, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter");
addTarget(playerA, "Lightning Bolt");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2},{T}:");
setChoice(playerA, "Yes");
setChoice(playerA, "Yes");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Isochron Scepter", 1);
assertExileCount("Lightning Bolt", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 0);
assertLife(playerB, 17);
}
@Test
public void testCopyCardButDontCast() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.HAND, playerA, "Isochron Scepter");
addCard(Zone.HAND, playerA, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter");
addTarget(playerA, "Lightning Bolt");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2},{T}:");
setChoice(playerA, "Yes");
setChoice(playerA, "No");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Isochron Scepter", 1);
assertExileCount("Lightning Bolt", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 0);
assertLife(playerB, 20);
}
}

View file

@ -0,0 +1,90 @@
/*
* 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.copy;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author BetaSteward
*/
public class ReversalOfFortuneTest extends CardTestPlayerBase {
/**
* Reversal of Fortune
* Sorcery, 4RR (6)
* Target opponent reveals his or her hand. You may copy an instant or sorcery
* card in it. If you do, you may cast the copy without paying its mana cost.
*
*/
@Test
public void testCopyCard() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
addCard(Zone.HAND, playerA, "Reversal of Fortune");
addCard(Zone.HAND, playerB, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reversal of Fortune", playerB);
addTarget(playerA, "Lightning Bolt");
setChoice(playerA, "Yes");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Reversal of Fortune", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 0);
assertGraveyardCount(playerB, "Lightning Bolt", 0);
assertLife(playerB, 17);
}
@Test
public void testCopyCardButDontCast() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
addCard(Zone.HAND, playerA, "Reversal of Fortune");
addCard(Zone.HAND, playerB, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reversal of Fortune", playerB);
addTarget(playerA, "Lightning Bolt");
setChoice(playerA, "No");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Reversal of Fortune", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 0);
assertGraveyardCount(playerB, "Lightning Bolt", 0);
assertLife(playerB, 20);
}
}

View file

@ -0,0 +1,72 @@
/*
* 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.copy;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author BetaSteward
*/
public class SpelltwineTest extends CardTestPlayerBase {
/**
* Spelltwine
* Sorcery, 5U (6)
* Exile target instant or sorcery card from your graveyard and target instant
* or sorcery card from an opponent's graveyard. Copy those cards. Cast the
* copies if able without paying their mana costs. Exile Spelltwine.
*
*/
@Test
public void testCopyCards() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
addCard(Zone.HAND, playerA, "Spelltwine");
addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt");
addCard(Zone.GRAVEYARD, playerB, "Shock");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spelltwine");
addTarget(playerA, "Lightning Bolt");
addTarget(playerA, "Shock");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertExileCount("Spelltwine", 1);
assertExileCount("Lightning Bolt", 1);
assertExileCount("Shock", 1);
assertLife(playerB, 15);
}
}

View file

@ -80,7 +80,6 @@ public interface Game extends MageItem, Serializable {
//game data methods
void loadCards(Set<Card> cards, UUID ownerId);
void unloadCard(Card card);
Collection<Card> getCards();
Object getCustomData();
void setCustomData(Object data);

View file

@ -220,10 +220,7 @@ public abstract class GameImpl implements Game, Serializable {
this.freeMulligans = game.freeMulligans;
this.attackOption = game.attackOption;
this.state = game.state.copy();
// Ai simulation modifies e.g. zoneChangeCounter so copy is needed if AI active
for (Map.Entry<UUID, Card> entry: game.gameCards.entrySet()) {
this.gameCards.put(entry.getKey(), entry.getValue().copy());
}
this.gameCards = game.gameCards;
this.simulation = game.simulation;
this.gameOptions = game.gameOptions;
this.lki.putAll(game.lki);
@ -294,20 +291,6 @@ public abstract class GameImpl implements Game, Serializable {
}
}
@Override
public void unloadCard(Card card) {
gameCards.remove(card.getId());
state.removeCard(card);
if (card.isSplitCard()) {
Card leftCard = ((SplitCard)card).getLeftHalfCard();
gameCards.remove(leftCard.getId());
state.removeCard(leftCard);
Card rightCard = ((SplitCard)card).getRightHalfCard();
gameCards.remove(rightCard.getId());
state.removeCard(rightCard);
}
}
@Override
public Collection<Card> getCards() {
return gameCards.values();
@ -428,7 +411,11 @@ public abstract class GameImpl implements Game, Serializable {
if (cardId == null) {
return null;
}
return gameCards.get(cardId);
Card card = gameCards.get(cardId);
if (card == null) {
return state.getCopiedCard(cardId);
}
return card;
}
@Override
@ -1328,14 +1315,7 @@ public abstract class GameImpl implements Game, Serializable {
@Override
public Card copyCard(Card cardToCopy, Ability source, UUID newController) {
Card copiedCard = cardToCopy.copy();
copiedCard.assignNewId();
copiedCard.setCopy(true);
Set<Card> cards = new HashSet<>();
cards.add(copiedCard);
loadCards(cards, source.getControllerId());
return copiedCard;
return state.copyCard(cardToCopy, source, this);
}
@Override
@ -1437,30 +1417,13 @@ public abstract class GameImpl implements Game, Serializable {
}
}
// 704.5e
for (Player player: getPlayers().values()) {
for (Card card: player.getHand().getCards(this)) {
if (card.isCopy()) {
player.getHand().remove(card);
this.unloadCard(card);
}
}
for (Card card: player.getGraveyard().getCards(this)) {
if (card.isCopy()) {
player.getGraveyard().remove(card);
this.unloadCard(card);
}
}
}
// 704.5e If a copy of a spell is in a zone other than the stack, it ceases to exist. If a copy of a card is in any zone other than the stack or the battlefield, it ceases to exist.
// (Isochron Scepter) 12/1/2004: If you don't want to cast the copy, you can choose not to; the copy ceases to exist the next time state-based actions are checked.
for (Card card: this.getState().getExile().getAllCards(this)) {
if (card.isCopy()) {
this.getState().getExile().removeCard(card, this);
this.unloadCard(card);
}
}
// TODO Library + graveyard
for (Card card: this.getState().getCopiedCards()) {
Zone zone = state.getZone(card.getId());
if (zone != Zone.BATTLEFIELD && zone != Zone.STACK)
state.removeCopiedCard(card);
}
List<Permanent> planeswalkers = new ArrayList<>();
List<Permanent> legendary = new ArrayList<>();
@ -2277,14 +2240,11 @@ public abstract class GameImpl implements Game, Serializable {
switch (command.getKey()) {
case HAND:
if (command.getValue().equals("clear")) {
removeCards(player.getHand());
player.getHand().clear();
}
break;
case LIBRARY:
if (command.getValue().equals("clear")) {
for (UUID card : player.getLibrary().getCardList()) {
removeCard(card);
}
player.getLibrary().clear();
}
break;
@ -2315,22 +2275,6 @@ public abstract class GameImpl implements Game, Serializable {
return lki;
}
private void removeCards(Cards cards) {
for (UUID card : cards) {
removeCard(card);
}
cards.clear();
}
private void removeCard(UUID cardId) {
Card card = this.getCard(cardId);
if(card != null && card.isSplitCard()) {
gameCards.remove(((SplitCard)card).getLeftHalfCard().getId());
gameCards.remove(((SplitCard)card).getRightHalfCard().getId());
}
gameCards.remove(cardId);
}
@Override
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard) {
Player player = getPlayer(ownerId);
@ -2374,25 +2318,6 @@ public abstract class GameImpl implements Game, Serializable {
loadCards(set, ownerId);
}
public void replaceLibrary(List<Card> cardsDownToTop, UUID ownerId) {
Player player = getPlayer(ownerId);
if (player != null) {
for (UUID card : player.getLibrary().getCardList()) {
removeCard(card);
}
player.getLibrary().clear();
Set<Card> cards = new HashSet<>();
for (Card card : cardsDownToTop) {
cards.add(card);
}
loadCards(cards, ownerId);
for (Card card : cards) {
player.getLibrary().putOnTop(card, this);
}
}
}
@Override
public boolean endTurn() {
getTurn().endTurn(this, getActivePlayerId());

View file

@ -105,7 +105,8 @@ public class GameState implements Serializable, Copyable<GameState> {
private List<GameEvent> simultaneousEvents = new ArrayList<>();
private Map<UUID, CardState> cardState = new HashMap<>();
private Map<UUID, Integer> zoneChangeCounter = new HashMap<>();
private Map<UUID, Card> copiedCards = new HashMap<>();
public GameState() {
players = new Players();
playerList = new PlayerList();
@ -161,6 +162,7 @@ public class GameState implements Serializable, Copyable<GameState> {
cardState.put(entry.getKey(), entry.getValue().copy());
}
this.zoneChangeCounter.putAll(state.zoneChangeCounter);
this.copiedCards.putAll(state.copiedCards);
}
@Override
@ -499,6 +501,7 @@ public class GameState implements Serializable, Copyable<GameState> {
this.simultaneousEvents = state.simultaneousEvents;
this.cardState = state.cardState;
this.zoneChangeCounter = state.zoneChangeCounter;
this.copiedCards = state.copiedCards;
}
public void addSimultaneousEvent(GameEvent event, Game game) {
@ -540,13 +543,18 @@ public class GameState implements Serializable, Copyable<GameState> {
}
}
public void removeCard(Card card) {
zones.remove(card.getId());
public void removeCopiedCard(Card card) {
if (copiedCards.containsKey(card.getId())) {
copiedCards.remove(card.getId());
cardState.remove(card.getId());
zones.remove(card.getId());
zoneChangeCounter.remove(card.getId());
}
// TODO Watchers?
// TODO Abilities?
if (card.isSplitCard()) {
removeCard( ((SplitCard)card).getLeftHalfCard());
removeCard( ((SplitCard)card).getRightHalfCard());
removeCopiedCard( ((SplitCard)card).getLeftHalfCard());
removeCopiedCard( ((SplitCard)card).getRightHalfCard());
}
}
@ -819,4 +827,29 @@ public class GameState implements Serializable, Copyable<GameState> {
this.zoneChangeCounter.put(objectId, value);
}
public Card getCopiedCard(UUID cardId) {
return copiedCards.get(cardId);
}
public Collection<Card> getCopiedCards() {
return copiedCards.values();
}
public Card copyCard(Card cardToCopy, Ability source, Game game) {
Card copiedCard = cardToCopy.copy();
copiedCard.assignNewId();
copiedCard.setOwnerId(source.getControllerId());
copiedCard.setCopy(true);
copiedCards.put(copiedCard.getId(), copiedCard);
addCard(copiedCard);
if (copiedCard.isSplitCard()) {
Card leftCard = ((SplitCard)copiedCard).getLeftHalfCard();
copiedCards.put(leftCard.getId(), leftCard);
addCard(leftCard);
Card rightCard = ((SplitCard)copiedCard).getRightHalfCard();
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
}
return copiedCard;
}
}