Refactor confirmTrigger into controller class

This commit is contained in:
drdev
2013-12-13 02:02:54 +00:00
parent f48952aa94
commit b19b5daf48
5 changed files with 297 additions and 274 deletions

View File

@@ -10,6 +10,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.deck.Deck; import forge.deck.Deck;
import forge.game.Game; import forge.game.Game;
@@ -27,6 +28,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
@@ -135,8 +137,9 @@ public abstract class PlayerController {
public abstract SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title); public abstract SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title);
public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message); public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message);
public abstract boolean getWillPlayOnFirstTurn(boolean isFirstGame);
public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message); public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message);
public abstract boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory);
public abstract boolean getWillPlayOnFirstTurn(boolean isFirstGame);
public abstract List<Card> orderBlockers(Card attacker, List<Card> blockers); public abstract List<Card> orderBlockers(Card attacker, List<Card> blockers);
public abstract List<Card> orderAttackers(Card blocker, List<Card> attackers); public abstract List<Card> orderAttackers(Card blocker, List<Card> attackers);

View File

@@ -47,6 +47,7 @@ import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -73,9 +74,11 @@ public class PlayerControllerAi extends PlayerController {
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, MouseEvent triggerEvent) { public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, MouseEvent triggerEvent) {
if (abilities.size() == 0) { if (abilities.size() == 0) {
return null; return null;
} else }
else {
return abilities.get(0); return abilities.get(0);
// } else { }
// else {
// return GuiChoose.oneOrNone("Choose ability for AI to play", abilities); // some day network interaction will be here // return GuiChoose.oneOrNone("Choose ability for AI to play", abilities); // some day network interaction will be here
// } // }
} }
@@ -144,7 +147,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public Card chooseSingleCardForEffect(Collection<Card> options, SpellAbility sa, String title, boolean isOptional) { public Card chooseSingleCardForEffect(Collection<Card> options, SpellAbility sa, String title, boolean isOptional) {
ApiType api = sa.getApi(); ApiType api = sa.getApi();
if ( null == api ) { if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet"); throw new InvalidParameterException("SA is not api-based, this is not supported yet");
} }
return api.getAi().chooseSingleCard(player, sa, options, isOptional); return api.getAi().chooseSingleCard(player, sa, options, isOptional);
@@ -153,7 +156,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public Player chooseSinglePlayerForEffect(List<Player> options, SpellAbility sa, String title) { public Player chooseSinglePlayerForEffect(List<Player> options, SpellAbility sa, String title) {
ApiType api = sa.getApi(); ApiType api = sa.getApi();
if ( null == api ) { if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet"); throw new InvalidParameterException("SA is not api-based, this is not supported yet");
} }
return api.getAi().chooseSinglePlayer(player, sa, options); return api.getAi().chooseSinglePlayer(player, sa, options);
@@ -162,27 +165,67 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) { public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) {
ApiType api = sa.getApi(); ApiType api = sa.getApi();
if ( null == api ) { if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet"); throw new InvalidParameterException("SA is not api-based, this is not supported yet");
} }
return api.getAi().chooseSingleSpellAbility(player, sa, spells); return api.getAi().chooseSingleSpellAbility(player, sa, spells);
} }
@Override @Override
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return getAi().confirmAction(sa, mode, message); return getAi().confirmAction(sa, mode, message);
} }
@Override
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
return true; // AI is brave :)
}
@Override @Override
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
return getAi().confirmStaticApplication(hostCard, affected, logic, message); return getAi().confirmStaticApplication(hostCard, affected, logic, message);
} }
@Override
public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
if (triggerParams.containsKey("DelayedTrigger")) {
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
// needs to be expanded when a more difficult cards comes up
return true;
}
// Store/replace target choices more properly to get this SA cleared.
TargetChoices tc = null;
TargetChoices subtc = null;
boolean storeChoices = sa.getTargetRestrictions() != null;
final SpellAbility sub = sa.getSubAbility();
boolean storeSubChoices = sub != null && sub.getTargetRestrictions() != null;
boolean ret = true;
if (storeChoices) {
tc = sa.getTargets();
sa.resetTargets();
}
if (storeSubChoices) {
subtc = sub.getTargets();
sub.resetTargets();
}
// There is no way this doTrigger here will have the same target as stored above
// So it's possible it's making a different decision here than will actually happen
if (!sa.doTrigger(isMandatory, player)) {
ret = false;
}
if (storeChoices) {
sa.resetTargets();
sa.setTargets(tc);
}
if (storeSubChoices) {
sub.resetTargets();
sub.setTargets(subtc);
}
return ret;
}
@Override
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
return true; // AI is brave :)
}
@Override @Override
public List<Card> orderBlockers(Card attacker, List<Card> blockers) { public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
return AiBlockController.orderBlockers(attacker, blockers); return AiBlockController.orderBlockers(attacker, blockers);
@@ -207,18 +250,19 @@ public class PlayerControllerAi extends PlayerController {
List<Card> toTop = new ArrayList<Card>(); List<Card> toTop = new ArrayList<Card>();
for (Card c: topN) { for (Card c: topN) {
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
toBottom.add(c); toBottom.add(c);
else }
else {
toTop.add(c); toTop.add(c);
} }
}
// put the rest on top in random order // put the rest on top in random order
Collections.shuffle(toTop); Collections.shuffle(toTop);
return ImmutablePair.of(toTop, toBottom); return ImmutablePair.of(toTop, toBottom);
} }
@Override @Override
public boolean willPutCardOnTop(Card c) { public boolean willPutCardOnTop(Card c) {
return true; // AI does not know what will happen next (another clash or that would become his topdeck) return true; // AI does not know what will happen next (another clash or that would become his topdeck)
@@ -232,8 +276,9 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> validCards, int min, int max) { public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> validCards, int min, int max) {
if ( p == player ) if (p == player) {
return brains.getCardsToDiscard(min, max, validCards, sa); return brains.getCardsToDiscard(min, max, validCards, sa);
}
boolean isTargetFriendly = !p.isOpponentOf(player); boolean isTargetFriendly = !p.isOpponentOf(player);
@@ -253,13 +298,14 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChooseNewTargets) { public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChooseNewTargets) {
// Ai is known to set targets in doTrigger, so if it cannot choose new targets, we won't call canPlays // Ai is known to set targets in doTrigger, so if it cannot choose new targets, we won't call canPlays
if( mayChooseNewTargets ) { if (mayChooseNewTargets) {
if (copySA instanceof Spell) { if (copySA instanceof Spell) {
Spell spell = (Spell) copySA; Spell spell = (Spell) copySA;
if (!spell.canPlayFromEffectAI(player, true, true)) { if (!spell.canPlayFromEffectAI(player, true, true)) {
return; // is this legal at all? return; // is this legal at all?
} }
} else { }
else {
copySA.canPlayAI(player); copySA.canPlayAI(player);
} }
} }
@@ -268,7 +314,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) { public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
if ( canSetupTargets ) if (canSetupTargets)
effectSA.doTrigger(true, player); // first parameter does not matter, since return value won't be used effectSA.doTrigger(true, player); // first parameter does not matter, since return value won't be used
ComputerUtil.playNoStack(player, effectSA, game); ComputerUtil.playNoStack(player, effectSA, game);
} }
@@ -320,10 +366,10 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes) { public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes) {
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes); String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
if( StringUtils.isBlank(chosen) && !validTypes.isEmpty() ) if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
{ {
chosen = validTypes.get(0); chosen = validTypes.get(0);
Log.warn("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen" ); Log.warn("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
} }
game.getAction().nofityOfValue(sa, null, "Computer picked: " + chosen, player); game.getAction().nofityOfValue(sa, null, "Computer picked: " + chosen, player);
return chosen; return chosen;
@@ -339,21 +385,23 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) { public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
if( !ComputerUtil.wantMulligan(player) ) if (!ComputerUtil.wantMulligan(player)) {
return null; return null;
}
if (!isCommander) if (!isCommander) {
return player.getCardsIn(ZoneType.Hand); return player.getCardsIn(ZoneType.Hand);
else }
else {
return ComputerUtil.getPartialParisCandidates(player); return ComputerUtil.getPartialParisCandidates(player);
} }
}
@Override @Override
public void declareAttackers(Player attacker, Combat combat) { public void declareAttackers(Player attacker, Combat combat) {
brains.declareAttackers(attacker, combat); brains.declareAttackers(attacker, combat);
} }
@Override @Override
public void declareBlockers(Player defender, Combat combat) { public void declareBlockers(Player defender, Combat combat) {
brains.declareBlockersFor(defender, combat); brains.declareBlockersFor(defender, combat);
@@ -361,8 +409,9 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public void takePriority() { public void takePriority() {
if ( !game.isGameOver() ) if (!game.isGameOver()) {
brains.onPriorityRecieved(); brains.onPriorityRecieved();
}
// use separate thread for AI? // use separate thread for AI?
} }
@@ -436,7 +485,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) {
if(kindOfChoice == BinaryChoiceType.TapOrUntap) return true; if (kindOfChoice == BinaryChoiceType.TapOrUntap) { return true; }
return MyRandom.getRandom().nextBoolean(); return MyRandom.getRandom().nextBoolean();
} }

View File

@@ -49,6 +49,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetSelection; import forge.game.spellability.TargetSelection;
import forge.game.trigger.Trigger;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
@@ -403,6 +404,36 @@ public class PlayerControllerHuman extends PlayerController {
return GuiDialog.confirm(hostCard, message); return GuiDialog.confirm(hostCard, message);
} }
@Override
public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
if (this.shouldAlwaysAcceptTrigger(regtrig.getId())) {
return true;
}
if (this.shouldAlwaysDeclineTrigger(regtrig.getId())) {
return false;
}
String triggerDesc = triggerParams.get("TriggerDescription").replace("CARDNAME", regtrig.getHostCard().getName());
final StringBuilder buildQuestion = new StringBuilder("Use triggered ability of ");
buildQuestion.append(regtrig.getHostCard().toString()).append("?");
buildQuestion.append("\r\n(").append(triggerDesc).append(")\r\n");
HashMap<String, Object> tos = sa.getTriggeringObjects();
if (tos.containsKey("Attacker")) {
buildQuestion.append("[Attacker: " + tos.get("Attacker") + "]");
}
if (tos.containsKey("Card")) {
Card card = (Card) tos.get("Card");
if (card != null && (card.getController() == player || game.getZoneOf(card) == null
|| game.getZoneOf(card).getZoneType().isKnown())) {
buildQuestion.append("[Triggering card: " + tos.get("Card") + "]");
}
}
if (!GuiDialog.confirm(regtrig.getHostCard(), buildQuestion.toString())) {
return false;
}
return true;
}
@Override @Override
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) { public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
InputPlayOrDraw inp = new InputPlayOrDraw(player, isFirstGame); InputPlayOrDraw inp = new InputPlayOrDraw(player, isFirstGame);
@@ -869,10 +900,10 @@ public class PlayerControllerHuman extends PlayerController {
default: default:
String[] colorNames = new String[cntColors]; String[] colorNames = new String[cntColors];
int i = 0; int i = 0;
for(byte b : colors) { for (byte b : colors) {
colorNames[i++] = MagicColor.toLongString(b); colorNames[i++] = MagicColor.toLongString(b);
} }
if(colorNames.length > 2) { if (colorNames.length > 2) {
return MagicColor.fromName(GuiChoose.one(message, colorNames)); return MagicColor.fromName(GuiChoose.one(message, colorNames));
} }
int idxChosen = GuiDialog.confirm(sa.getSourceCard(), message, colorNames) ? 0 : 1; int idxChosen = GuiDialog.confirm(sa.getSourceCard(), message, colorNames) ? 0 : 1;
@@ -890,8 +921,9 @@ public class PlayerControllerHuman extends PlayerController {
@Override @Override
public CounterType chooseCounterType(Collection<CounterType> options, SpellAbility sa, String prompt) { public CounterType chooseCounterType(Collection<CounterType> options, SpellAbility sa, String prompt) {
if( options.size() <= 1) if (options.size() <= 1) {
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
}
return GuiChoose.one(prompt, options); return GuiChoose.one(prompt, options);
} }
} }

View File

@@ -18,7 +18,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction; import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.gui.GuiDialog;
// Wrapper ability that checks the requirements again just before // Wrapper ability that checks the requirements again just before
// resolving, for intervening if clauses. // resolving, for intervening if clauses.
@@ -356,8 +355,9 @@ public class WrappedAbility extends Ability implements ISpellAbility {
TriggerHandler th = game.getTriggerHandler(); TriggerHandler th = game.getTriggerHandler();
Map<String, String> triggerParams = regtrig.getMapParams(); Map<String, String> triggerParams = regtrig.getMapParams();
if (decider != null && !confirmTrigger(decider, triggerParams)) if (decider != null && !decider.getController().confirmTrigger(sa, regtrig, triggerParams, this.isMandatory())) {
return; return;
}
getActivatingPlayer().getController().playSpellAbilityNoStack(sa, false); getActivatingPlayer().getController().playSpellAbilityNoStack(sa, false);
@@ -370,71 +370,4 @@ public class WrappedAbility extends Ability implements ISpellAbility {
th.registerDelayedTrigger(deltrig); th.registerDelayedTrigger(deltrig);
} }
} }
private boolean confirmTrigger(Player decider, Map<String, String> triggerParams) {
if (decider.isHuman()) {
if(decider.getController().shouldAlwaysAcceptTrigger(regtrig.getId()))
return true;
else if(decider.getController().shouldAlwaysDeclineTrigger(regtrig.getId()))
return false;
String triggerDesc = triggerParams.get("TriggerDescription").replace("CARDNAME", regtrig.getHostCard().getName());
final StringBuilder buildQuestion = new StringBuilder("Use triggered ability of ");
buildQuestion.append(regtrig.getHostCard().toString()).append("?");
buildQuestion.append("\r\n(").append(triggerDesc).append(")\r\n");
HashMap<String, Object> tos = sa.getTriggeringObjects();
if (tos.containsKey("Attacker")) {
buildQuestion.append("[Attacker: " + tos.get("Attacker") + "]");
}
if (tos.containsKey("Card")) {
Card card = (Card) tos.get("Card");
if (card != null && (card.getController() == decider || decider.getGame().getZoneOf(card) == null
|| decider.getGame().getZoneOf(card).getZoneType().isKnown())) {
buildQuestion.append("[Triggering card: " + tos.get("Card") + "]");
}
}
if (!GuiDialog.confirm(regtrig.getHostCard(), buildQuestion.toString())) {
return false;
}
return true;
} // human end
if (triggerParams.containsKey("DelayedTrigger")) {
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
// needs to be expanded when a more difficult cards comes up
return true;
}
// Store/replace target choices more properly to get this SA cleared.
TargetChoices tc = null;
TargetChoices subtc = null;
boolean storeChoices = sa.getTargetRestrictions() != null;
final SpellAbility sub = sa.getSubAbility();
boolean storeSubChoices = sub != null && sub.getTargetRestrictions() != null;
boolean ret = true;
if (storeChoices) {
tc = sa.getTargets();
sa.resetTargets();
}
if (storeSubChoices) {
subtc = sub.getTargets();
sub.resetTargets();
}
// There is no way this doTrigger here will have the same target as stored above
// So it's possible it's making a different decision here than will actually happen
if (!sa.doTrigger(this.isMandatory(), decider)) {
ret = false;
}
if (storeChoices) {
sa.resetTargets();
sa.setTargets(tc);
}
if (storeSubChoices) {
sub.resetTargets();
sub.setTargets(subtc);
}
return ret;
}
} }

View File

@@ -41,6 +41,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gamesimulationtests.util.card.CardSpecification; import forge.gamesimulationtests.util.card.CardSpecification;
import forge.gamesimulationtests.util.card.CardSpecificationHandler; import forge.gamesimulationtests.util.card.CardSpecificationHandler;
@@ -64,11 +65,11 @@ import forge.item.PaperCard;
public class PlayerControllerForTests extends PlayerController { public class PlayerControllerForTests extends PlayerController {
private PlayerActions playerActions; private PlayerActions playerActions;
public PlayerControllerForTests( Game game, Player player, LobbyPlayer lobbyPlayer ) { public PlayerControllerForTests(Game game, Player player, LobbyPlayer lobbyPlayer) {
super( game, player, lobbyPlayer ); super(game, player, lobbyPlayer);
} }
public void setPlayerActions( PlayerActions playerActions ) { public void setPlayerActions(PlayerActions playerActions) {
this.playerActions = playerActions; this.playerActions = playerActions;
} }
@@ -85,210 +86,215 @@ public class PlayerControllerForTests extends PlayerController {
} }
@Override @Override
public SpellAbility getAbilityToPlay( List<SpellAbility> abilities ) { public SpellAbility getAbilityToPlay(List<SpellAbility> abilities) {
System.out.println( "getAbilityToPlay " + StringUtils.join( abilities, ", " ) ); System.out.println("getAbilityToPlay " + StringUtils.join(abilities, ", "));
return null; return null;
} }
@Override @Override
public void playSpellAbilityForFree( SpellAbility copySA, boolean mayChoseNewTargets ) { public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets) {
throw new IllegalStateException( "Callers of this method currently assume that it performs extra functionality!" ); throw new IllegalStateException("Callers of this method currently assume that it performs extra functionality!");
} }
@Override @Override
public void playSpellAbilityNoStack( SpellAbility effectSA, boolean mayChoseNewTargets ) { public void playSpellAbilityNoStack(SpellAbility effectSA, boolean mayChoseNewTargets) {
//TODO: eventually (when the real code is refactored) this should be handled normally... //TODO: eventually (when the real code is refactored) this should be handled normally...
if( effectSA.getDescription().equals( "At the beginning of your upkeep, if you have exactly 1 life, you win the game." ) ) {//test_104_2b_effect_may_state_that_player_wins_the_game if (effectSA.getDescription().equals("At the beginning of your upkeep, if you have exactly 1 life, you win the game.")) {//test_104_2b_effect_may_state_that_player_wins_the_game
HumanPlay.playSpellAbilityNoStack( player, effectSA, !mayChoseNewTargets ); HumanPlay.playSpellAbilityNoStack(player, effectSA, !mayChoseNewTargets);
return; return;
} }
if( if (
( effectSA.getSourceCard().getName().equals( "Nefarious Lich" ) && effectSA.getApi().getAi() instanceof DrawAi ) || (effectSA.getSourceCard().getName().equals("Nefarious Lich") && effectSA.getApi().getAi() instanceof DrawAi) ||
( effectSA.getSourceCard().getName().equals( "Laboratory Maniac" ) && effectSA.getApi().getAi() instanceof GameWinAi ) || (effectSA.getSourceCard().getName().equals("Laboratory Maniac") && effectSA.getApi().getAi() instanceof GameWinAi) ||
( effectSA.getSourceCard().getName().equals( "Nefarious Lich" ) && effectSA.getApi().getAi() instanceof ChangeZoneAi ) (effectSA.getSourceCard().getName().equals("Nefarious Lich") && effectSA.getApi().getAi() instanceof ChangeZoneAi)
) {//test_104_3f_if_a_player_would_win_and_lose_simultaneously_he_loses ) {//test_104_3f_if_a_player_would_win_and_lose_simultaneously_he_loses
HumanPlay.playSpellAbilityNoStack( player, effectSA, !mayChoseNewTargets ); HumanPlay.playSpellAbilityNoStack(player, effectSA, !mayChoseNewTargets);
return; return;
} }
throw new IllegalStateException( "Callers of this method currently assume that it performs extra functionality!" ); throw new IllegalStateException("Callers of this method currently assume that it performs extra functionality!");
} }
@Override @Override
public Deck sideboard( Deck deck, GameType gameType ) { public Deck sideboard(Deck deck, GameType gameType) {
return deck; return deck;
} }
@Override @Override
public Map<Card, Integer> assignCombatDamage( Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder ) { public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder) {
if( blockers.size() == 1 && damageDealt == 2 && ( if (blockers.size() == 1 && damageDealt == 2 && (
( attacker.getName().equals( "Grizzly Bears" ) && blockers.get( 0 ).getName().equals( "Ajani's Sunstriker" ) ) || (attacker.getName().equals("Grizzly Bears") && blockers.get(0).getName().equals("Ajani's Sunstriker")) ||
( attacker.getName().equals( "Ajani's Sunstriker" ) && blockers.get( 0 ).getName().equals( "Grizzly Bears" ) ) (attacker.getName().equals("Ajani's Sunstriker") && blockers.get(0).getName().equals("Grizzly Bears"))
) ) {//test_104_3b_player_with_less_than_zero_life_loses_the_game_only_when_a_player_receives_priority_variant_with_combat )) {//test_104_3b_player_with_less_than_zero_life_loses_the_game_only_when_a_player_receives_priority_variant_with_combat
Map<Card, Integer> result = new HashMap<Card, Integer>(); Map<Card, Integer> result = new HashMap<Card, Integer>();
result.put( blockers.get( 0 ), damageDealt ); result.put(blockers.get(0), damageDealt);
return result; return result;
} }
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public Integer announceRequirements( SpellAbility ability, String announce, boolean allowZero ) { public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public List<Card> choosePermanentsToSacrifice( SpellAbility sa, int min, int max, List<Card> validTargets, String message ) { public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
return chooseItems( validTargets, min ); return chooseItems(validTargets, min);
} }
@Override @Override
public List<Card> choosePermanentsToDestroy( SpellAbility sa, int min, int max, List<Card> validTargets, String message ) { public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
return chooseItems( validTargets, min ); return chooseItems(validTargets, min);
} }
@Override @Override
public TargetChoices chooseNewTargetsFor( SpellAbility ability ) { public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget( SpellAbility sa, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets ) { public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility sa, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
return chooseItem( allTargets ); return chooseItem(allTargets);
} }
@Override @Override
public List<Card> chooseCardsForEffect( List<Card> sourceList, SpellAbility sa, String title, int amount, boolean isOptional ) { public List<Card> chooseCardsForEffect(List<Card> sourceList, SpellAbility sa, String title, int amount, boolean isOptional) {
return chooseItems( sourceList, amount ); return chooseItems(sourceList, amount);
} }
@Override @Override
public Card chooseSingleCardForEffect( Collection<Card> sourceList, SpellAbility sa, String title, boolean isOptional ) { public Card chooseSingleCardForEffect(Collection<Card> sourceList, SpellAbility sa, String title, boolean isOptional) {
return chooseItem( sourceList ); return chooseItem(sourceList);
} }
@Override @Override
public Player chooseSinglePlayerForEffect( List<Player> options, SpellAbility sa, String title ) { public Player chooseSinglePlayerForEffect(List<Player> options, SpellAbility sa, String title) {
return chooseItem( options ); return chooseItem(options);
} }
@Override @Override
public SpellAbility chooseSingleSpellForEffect( List<SpellAbility> spells, SpellAbility sa, String title ) { public SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title) {
return chooseItem( spells ); return chooseItem(spells);
} }
@Override @Override
public boolean confirmAction( SpellAbility sa, PlayerActionConfirmMode mode, String message ) { public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true; return true;
} }
@Override @Override
public boolean getWillPlayOnFirstTurn( boolean isFirstGame ) { public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
return true; return true;
} }
@Override @Override
public boolean confirmStaticApplication( Card hostCard, GameEntity affected, String logic, String message ) { public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
return true; return true;
} }
@Override @Override
public List<Card> orderBlockers( Card attacker, List<Card> blockers ) { public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
return true;
}
@Override
public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
return blockers; return blockers;
} }
@Override @Override
public List<Card> orderAttackers( Card blocker, List<Card> attackers ) { public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
return attackers; return attackers;
} }
@Override @Override
public void reveal( String string, Collection<Card> cards, ZoneType zone, Player owner ) { public void reveal(String string, Collection<Card> cards, ZoneType zone, Player owner) {
//nothing needs to be done here //nothing needs to be done here
} }
@Override @Override
public void notifyOfValue( SpellAbility saSource, GameObject realtedTarget, String value ) { public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value) {
//nothing needs to be done here //nothing needs to be done here
} }
@Override @Override
public ImmutablePair<List<Card>, List<Card>> arrangeForScry( List<Card> topN ) { public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
return ImmutablePair.of( topN, null ); return ImmutablePair.of(topN, null);
} }
@Override @Override
public boolean willPutCardOnTop( Card c ) { public boolean willPutCardOnTop(Card c) {
return false; return false;
} }
@Override @Override
public List<Card> orderMoveToZoneList( List<Card> cards, ZoneType destinationZone ) { public List<Card> orderMoveToZoneList(List<Card> cards, ZoneType destinationZone) {
return cards; return cards;
} }
@Override @Override
public List<Card> chooseCardsToDiscardFrom( Player playerDiscard, SpellAbility sa, List<Card> validCards, int min, int max ) { public List<Card> chooseCardsToDiscardFrom(Player playerDiscard, SpellAbility sa, List<Card> validCards, int min, int max) {
return chooseItems( validCards, min ); return chooseItems(validCards, min);
} }
@Override @Override
public Card chooseCardToDredge( List<Card> dredgers ) { public Card chooseCardToDredge(List<Card> dredgers) {
return null; return null;
} }
@Override @Override
public void playMiracle( SpellAbility miracle, Card card ) { public void playMiracle(SpellAbility miracle, Card card) {
throw new IllegalStateException( "Callers of this method currently assume that it performs extra functionality!" ); throw new IllegalStateException("Callers of this method currently assume that it performs extra functionality!");
} }
@Override @Override
public List<Card> chooseCardsToDelve( int colorLessAmount, List<Card> grave ) { public List<Card> chooseCardsToDelve(int colorLessAmount, List<Card> grave) {
return Collections.<Card>emptyList(); return Collections.<Card>emptyList();
} }
@Override @Override
public List<Card> chooseCardsToRevealFromHand( int min, int max, List<Card> valid ) { public List<Card> chooseCardsToRevealFromHand(int min, int max, List<Card> valid) {
return chooseItems( valid, min ); return chooseItems(valid, min);
} }
@Override @Override
public List<Card> chooseCardsToDiscardUnlessType( int min, List<Card> hand, String param, SpellAbility sa ) { public List<Card> chooseCardsToDiscardUnlessType(int min, List<Card> hand, String param, SpellAbility sa) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public List<SpellAbility> chooseSaToActivateFromOpeningHand( List<SpellAbility> usableFromOpeningHand ) { public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
return usableFromOpeningHand; return usableFromOpeningHand;
} }
@Override @Override
public Mana chooseManaFromPool( List<Mana> manaChoices ) { public Mana chooseManaFromPool(List<Mana> manaChoices) {
return chooseItem( manaChoices ); return chooseItem(manaChoices);
} }
@Override @Override
public Pair<CounterType, String> chooseAndRemoveOrPutCounter( Card cardWithCounter ) { public Pair<CounterType, String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public boolean confirmReplacementEffect( ReplacementEffect replacementEffect, SpellAbility effectSA, String question ) { public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return true; return true;
} }
@Override @Override
public List<Card> getCardsToMulligan( boolean isCommander, Player firstPlayer ) { public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
return null; return null;
} }
@Override @Override
public void declareAttackers( Player attacker, Combat combat ) { public void declareAttackers(Player attacker, Combat combat) {
//Doing nothing is safe in most cases, but not all (creatures that must attack etc). TODO: introduce checks? //Doing nothing is safe in most cases, but not all (creatures that must attack etc). TODO: introduce checks?
if( playerActions == null ) { if (playerActions == null) {
return; return;
} }
DeclareAttackersAction declareAttackers = playerActions.getNextActionIfApplicable( player, game, DeclareAttackersAction.class ); DeclareAttackersAction declareAttackers = playerActions.getNextActionIfApplicable(player, game, DeclareAttackersAction.class);
if( declareAttackers == null ) { if (declareAttackers == null) {
return; return;
} }
@@ -296,47 +302,47 @@ public class PlayerControllerForTests extends PlayerController {
//TODO: check that the chosen attack configuration was a complete match to what was requested? //TODO: check that the chosen attack configuration was a complete match to what was requested?
//TODO: banding (don't really care at the moment...) //TODO: banding (don't really care at the moment...)
for( Map.Entry<CardSpecification, PlayerSpecification> playerAttackAssignment : declareAttackers.getPlayerAttackAssignments().entrySet() ) { for (Map.Entry<CardSpecification, PlayerSpecification> playerAttackAssignment : declareAttackers.getPlayerAttackAssignments().entrySet()) {
Player defender = getPlayerBeingAttacked( game, player, playerAttackAssignment.getValue() ); Player defender = getPlayerBeingAttacked(game, player, playerAttackAssignment.getValue());
attack( combat, playerAttackAssignment.getKey(), defender ); attack(combat, playerAttackAssignment.getKey(), defender);
} }
for( Map.Entry<CardSpecification, CardSpecification> planeswalkerAttackAssignment: declareAttackers.getPlaneswalkerAttackAssignments().entrySet() ) { for (Map.Entry<CardSpecification, CardSpecification> planeswalkerAttackAssignment: declareAttackers.getPlaneswalkerAttackAssignments().entrySet()) {
Card defender = CardSpecificationHandler.INSTANCE.find( game.getCardsInGame(), planeswalkerAttackAssignment.getKey() ); Card defender = CardSpecificationHandler.INSTANCE.find(game.getCardsInGame(), planeswalkerAttackAssignment.getKey());
attack( combat, planeswalkerAttackAssignment.getKey(), defender ); attack(combat, planeswalkerAttackAssignment.getKey(), defender);
} }
} }
private Player getPlayerBeingAttacked( Game game, Player attacker, PlayerSpecification defenderSpecification ) { private Player getPlayerBeingAttacked(Game game, Player attacker, PlayerSpecification defenderSpecification) {
if( defenderSpecification != null ) { if (defenderSpecification != null) {
return PlayerSpecificationHandler.INSTANCE.find( game.getPlayers(), defenderSpecification ); return PlayerSpecificationHandler.INSTANCE.find(game.getPlayers(), defenderSpecification);
} }
if( game.getPlayers().size() != 2 ) { if (game.getPlayers().size() != 2) {
throw new IllegalStateException( "Can't use implicit defender specification in this situation!" ); throw new IllegalStateException("Can't use implicit defender specification in this situation!");
} }
for( Player player : game.getPlayers() ) { for (Player player : game.getPlayers()) {
if( !attacker.equals( player ) ) { if (!attacker.equals(player)) {
return player; return player;
} }
} }
throw new IllegalStateException( "Couldn't find implicit defender!" ); throw new IllegalStateException("Couldn't find implicit defender!");
} }
private void attack( Combat combat, CardSpecification attackerSpecification, GameEntity defender ) { private void attack(Combat combat, CardSpecification attackerSpecification, GameEntity defender) {
Card attacker = CardSpecificationHandler.INSTANCE.find( combat.getAttackingPlayer().getCreaturesInPlay(), attackerSpecification ); Card attacker = CardSpecificationHandler.INSTANCE.find(combat.getAttackingPlayer().getCreaturesInPlay(), attackerSpecification);
if ( !CombatUtil.canAttack( attacker, defender, combat ) ) { if (!CombatUtil.canAttack(attacker, defender, combat)) {
throw new IllegalStateException( attacker + " can't attack " + defender ); throw new IllegalStateException(attacker + " can't attack " + defender);
} }
combat.addAttacker( attacker, defender ); combat.addAttacker(attacker, defender);
} }
@Override @Override
public void declareBlockers( Player defender, Combat combat ) { public void declareBlockers(Player defender, Combat combat) {
//Doing nothing is safe in most cases, but not all (creatures that must block, attackers that must be blocked etc). TODO: legality checks? //Doing nothing is safe in most cases, but not all (creatures that must block, attackers that must be blocked etc). TODO: legality checks?
if( playerActions == null ) { if (playerActions == null) {
return; return;
} }
DeclareBlockersAction declareBlockers = playerActions.getNextActionIfApplicable( player, game, DeclareBlockersAction.class ); DeclareBlockersAction declareBlockers = playerActions.getNextActionIfApplicable(player, game, DeclareBlockersAction.class);
if( declareBlockers == null ) { if (declareBlockers == null) {
return; return;
} }
@@ -344,106 +350,106 @@ public class PlayerControllerForTests extends PlayerController {
//TODO: check that the chosen block configuration was a 100% match to what was requested? //TODO: check that the chosen block configuration was a 100% match to what was requested?
//TODO: where do damage assignment orders get handled? //TODO: where do damage assignment orders get handled?
for( Map.Entry<CardSpecification, Collection<CardSpecification>> blockingAssignment : declareBlockers.getBlockingAssignments().asMap().entrySet() ) { for (Map.Entry<CardSpecification, Collection<CardSpecification>> blockingAssignment : declareBlockers.getBlockingAssignments().asMap().entrySet()) {
Card attacker = CardSpecificationHandler.INSTANCE.find( combat.getAttackers(), blockingAssignment.getKey() ); Card attacker = CardSpecificationHandler.INSTANCE.find(combat.getAttackers(), blockingAssignment.getKey());
for( CardSpecification blockerSpecification : blockingAssignment.getValue() ) { for (CardSpecification blockerSpecification : blockingAssignment.getValue()) {
Card blocker = CardSpecificationHandler.INSTANCE.find( game, blockerSpecification ); Card blocker = CardSpecificationHandler.INSTANCE.find(game, blockerSpecification);
if( !CombatUtil.canBlock( attacker, blocker ) ) { if (!CombatUtil.canBlock(attacker, blocker)) {
throw new IllegalStateException( blocker + " can't block " + blocker ); throw new IllegalStateException(blocker + " can't block " + blocker);
} }
combat.addBlocker( attacker, blocker ); combat.addBlocker(attacker, blocker);
} }
} }
String blockValidation = CombatUtil.validateBlocks( combat, player ); String blockValidation = CombatUtil.validateBlocks(combat, player);
if( blockValidation != null ) { if (blockValidation != null) {
throw new IllegalStateException( blockValidation ); throw new IllegalStateException(blockValidation);
} }
} }
@Override @Override
public void takePriority() { public void takePriority() {
//TODO: just about everything... //TODO: just about everything...
if( playerActions != null ) { if (playerActions != null) {
CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable( player, game, CastSpellFromHandAction.class ); CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, game, CastSpellFromHandAction.class);
if( castSpellFromHand != null ) { if (castSpellFromHand != null) {
castSpellFromHand.castSpellFromHand( player, game ); castSpellFromHand.castSpellFromHand(player, game);
} }
ActivateAbilityAction activateAbilityAction = playerActions.getNextActionIfApplicable( player, game, ActivateAbilityAction.class ); ActivateAbilityAction activateAbilityAction = playerActions.getNextActionIfApplicable(player, game, ActivateAbilityAction.class);
if( activateAbilityAction != null ) { if (activateAbilityAction != null) {
activateAbilityAction.activateAbility( player, game ); activateAbilityAction.activateAbility(player, game);
} }
} }
} }
@Override @Override
public List<Card> chooseCardsToDiscardToMaximumHandSize( int numDiscard ) { public List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
return chooseItems( player.getZone( ZoneType.Hand ).getCards(), numDiscard ); return chooseItems(player.getZone(ZoneType.Hand).getCards(), numDiscard);
} }
@Override @Override
public boolean payManaOptional( Card card, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose ) { public boolean payManaOptional(Card card, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) {
throw new IllegalStateException( "Callers of this method currently assume that it performs extra functionality!" ); throw new IllegalStateException("Callers of this method currently assume that it performs extra functionality!");
} }
@Override @Override
public int chooseNumber( SpellAbility sa, String title, int min, int max ) { public int chooseNumber(SpellAbility sa, String title, int min, int max) {
return min; return min;
} }
@Override @Override
public boolean chooseBinary( SpellAbility sa, String question, BinaryChoiceType kindOfChoice ) { public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) {
return true; return true;
} }
@Override @Override
public boolean chooseFlipResult( SpellAbility sa, Player flipper, boolean[] results, boolean call ) { public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) {
return true; return true;
} }
@Override @Override
public Card chooseProtectionShield( GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap ) { public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
return choiceMap.get( options.get( 0 ) ); return choiceMap.get(options.get(0));
} }
@Override @Override
public List<AbilitySub> chooseModeForAbility( SpellAbility sa, int min, int num ) { public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override
public byte chooseColor( String message, SpellAbility sa, ColorSet colors ) { public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.WHITE); return Iterables.getFirst(colors, MagicColor.WHITE);
} }
private <T> List<T> chooseItems( Collection<T> items, int amount ) { private <T> List<T> chooseItems(Collection<T> items, int amount) {
if( items == null || items.isEmpty() ) { if (items == null || items.isEmpty()) {
return new ArrayList<T>( items ); return new ArrayList<T>(items);
} }
return new ArrayList<T>( items ).subList( 0, Math.max( amount, items.size() ) ); return new ArrayList<T>(items).subList(0, Math.max(amount, items.size()));
} }
private <T> T chooseItem( Collection<T> items ) { private <T> T chooseItem(Collection<T> items) {
if( items == null || items.isEmpty() ) { if (items == null || items.isEmpty()) {
return null; return null;
} }
return Iterables.getFirst( items, null ); return Iterables.getFirst(items, null);
} }
@Override @Override
public SpellAbility getAbilityToPlay( List<SpellAbility> abilities, MouseEvent triggerEvent ) { public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, MouseEvent triggerEvent) {
return getAbilityToPlay( abilities ); return getAbilityToPlay(abilities);
} }
@Override @Override
public String chooseSomeType( String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes ) { public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes) {
return chooseItem( validTypes ); return chooseItem(validTypes);
} }
@Override @Override
public PaperCard chooseSinglePaperCard( SpellAbility sa, String message, Predicate<PaperCard> cpp, String name ) { public PaperCard chooseSinglePaperCard(SpellAbility sa, String message, Predicate<PaperCard> cpp, String name) {
throw new IllegalStateException( "Erring on the side of caution here..." ); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override @Override