CountersPutOrRemoveEffect: refactor to use better methods

CountersPutOrRemoveAI: use better logic to check when its good to remove a counter and when not
PlayerController: add chooseBinary with Params, use it for AddOrRemove. no need for chooseAndRemoveOrPutCounter
This commit is contained in:
Hanmac
2017-01-29 14:44:43 +00:00
parent 0f3e98ea77
commit e630fafb22
8 changed files with 323 additions and 130 deletions

View File

@@ -496,6 +496,24 @@ public class PlayerControllerAi extends PlayerController {
}
}
/*
* (non-Javadoc)
*
* @see
* forge.game.player.PlayerController#chooseBinary(forge.game.spellability.
* SpellAbility, java.lang.String,
* forge.game.player.PlayerController.BinaryChoiceType, java.util.Map)
*/
@Override
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice,
Map<String, Object> params) {
ApiType api = sa.getApi();
if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
}
return SpellApiToAi.Converter.get(api).chooseBinary(kindOfChoice, sa, params);
}
@Override
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
int i = MyRandom.getRandom().nextInt(options.size());
@@ -522,46 +540,6 @@ public class PlayerControllerAi extends PlayerController {
return sa.getChosenList();
}
@Override
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
if (!cardWithCounter.hasCounters()) {
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
return null;
}
final Player controller = cardWithCounter.getController();
final List<Player> enemies = player.getOpponents();
final List<Player> allies = player.getAllies();
allies.add(player);
List<CounterType> countersToIncrease = new ArrayList<CounterType>();
List<CounterType> countersToDecrease = new ArrayList<CounterType>();
for (final CounterType counter : cardWithCounter.getCounters().keySet()) {
if ((!ComputerUtil.isNegativeCounter(counter, cardWithCounter) && allies.contains(controller))
|| (ComputerUtil.isNegativeCounter(counter, cardWithCounter) && enemies.contains(controller))) {
countersToIncrease.add(counter);
} else {
countersToDecrease.add(counter);
}
}
if (!countersToIncrease.isEmpty()) {
int random = MyRandom.getRandom().nextInt(countersToIncrease.size());
return new ImmutablePair<CounterType,String>(countersToIncrease.get(random),"Put");
}
else if (!countersToDecrease.isEmpty()) {
int random = MyRandom.getRandom().nextInt(countersToDecrease.size());
return new ImmutablePair<CounterType,String>(countersToDecrease.get(random),"Remove");
}
// shouldn't reach here but just in case, remove random counter
List<CounterType> countersOnCard = new ArrayList<CounterType>();
int random = MyRandom.getRandom().nextInt(countersOnCard.size());
return new ImmutablePair<CounterType,String>(countersOnCard.get(random),"Remove");
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));

View File

@@ -16,6 +16,7 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
@@ -329,4 +330,8 @@ public abstract class SpellAbilityAi {
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
return Iterables.getFirst(options, null);
}
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
return MyRandom.getRandom().nextBoolean();
}
}

View File

@@ -17,17 +17,24 @@
*/
package forge.ai.ability;
import com.google.common.base.Predicate;
import java.util.List;
import java.util.Map;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
/**
* <p>
@@ -39,54 +46,252 @@ import java.util.List;
*/
public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return doTriggerAINoCost(ai, sa, false);
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
return super.checkApiLogic(ai, sa);
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
// need to targetable
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
if (list.isEmpty()) {
return false;
}
if (sa.hasParam("AITgts")) {
String aiTgts = sa.getParam("AITgts");
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa);
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) {
list = prefList;
}
}
if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
if (countersList.isEmpty()) {
return false;
}
// currently can only target cards you control or you own
final Card best = ComputerUtilCard.getBestAI(countersList);
// currently both cards only has one target
sa.getTargets().add(best);
return true;
} else {
// currently only Clockspinning
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// logic to remove some counter
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
if (!countersList.isEmpty()) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
}
}
// Get rid of Planeswalkers, currently only if it can kill them
// with one touch
CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
}
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
// fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
if (!oppList.isEmpty()) {
final Card best = ComputerUtilCard.getBestAI(oppList);
for (final CounterType aType : best.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(aType, best)) {
sa.getTargets().add(best);
return true;
} else if (!ComputerUtil.isUselessCounter(aType)) {
// whould remove positive counter
if (best.getCounters(aType) <= amount) {
sa.getTargets().add(best);
return true;
}
}
}
}
}
}
if (mandatory) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
// if Defined, don't worry about targeting
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (validCards.isEmpty()) {
return false;
return doTgt(ai, sa, true);
}
List<Card> cWithCounters = CardLists.filter(validCards, new Predicate<Card>() {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public boolean apply(final Card crd) {
return crd.hasCounters();
}
});
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (cWithCounters.isEmpty()) {
if (mandatory) {
cWithCounters = validCards;
if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
Card tgt = (Card) params.get("Target");
// planeswalker has high priority for loyalty counters
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
return CounterType.LOYALTY;
}
if (tgt.getController().isOpponentOf(ai)) {
// creatures with BaseToughness below or equal zero might be
// killed if their counters are removed
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
if (options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
}
// fallback logic, select positive counter to remove it
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
} else {
return false;
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return CounterType.ICE;
}
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
// fallback logic, select positive counter to add more
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card targetCard = null;
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0))) {
sa.resetTargets();
return super.chooseCounterType(options, sa, params);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
}
int random = MyRandom.getRandom().nextInt(cWithCounters.size());
targetCard = cWithCounters.get(random);
sa.getTargets().add(targetCard);
cWithCounters.remove(targetCard);
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
}
return true;
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
return false;
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
return false;
}
return !ComputerUtil.isNegativeCounter(type, tgt);
}
}
return super.chooseBinary(kindOfChoice, sa, params);
}
}

View File

@@ -1,18 +1,22 @@
package forge.game.ability.effects;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.Lang;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
/**
* API for adding to or subtracting from existing counters on a target.
@@ -23,10 +27,16 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
sb.append(sa.getActivatingPlayer().getName());
sb.append(" removes a counter from or puts another of those counters on ");
final List<GameObject> targets = getTargets(sa);
sb.append(Lang.joinHomogenous(targets));
if (sa.hasParam("CounterType")) {
CounterType ctype = CounterType.valueOf(sa.getParam("CounterType"));
sb.append(" removes a ").append(ctype.getName());
sb.append(" counter from or put another ").append(ctype.getName()).append(" counter on ");
} else {
sb.append(" removes a counter from or puts another of those counters on ");
}
sb.append(Lang.joinHomogenous(getTargets(sa)));
return sb.toString();
}
@@ -34,28 +44,47 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Player activator = sa.getActivatingPlayer();
final int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
final Card source = sa.getHostCard();
final int counterAmount = AbilityUtils.calculateAmount(source, sa.getParam("CounterNum"), sa);
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
CounterType ctype = null;
if (sa.hasParam("CounterType")) {
ctype = CounterType.valueOf(sa.getParam("CounterType"));
}
for (final Card tgtCard : tgtCards) {
PlayerController pc = activator.getController();
for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) {
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
if (tgtCard.hasCounters()) {
Pair<CounterType,String> selection = activator.getController().chooseAndRemoveOrPutCounter(tgtCard);
final CounterType chosenCounter = selection.getLeft();
final boolean putCounter = selection.getRight().startsWith("Put");
final Map<CounterType, Integer> tgtCounters = tgtCard.getCounters();
Map<String, Object> params = Maps.newHashMap();
params.put("Target", tgtCard);
List<CounterType> list = Lists.newArrayList(tgtCounters.keySet());
if (ctype != null) {
list = Lists.newArrayList(ctype);
}
String prompt = "Select type of counters to add or remove";
CounterType chosenType = pc.chooseCounterType(list, sa, prompt, params);
params.put("CounterType", chosenType);
prompt = "What to do with that '" + chosenType.getName() + "' counter ";
Boolean putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
if (putCounter) {
// Put another of the chosen counter on card
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
tgtCard.addCounter(chosenCounter, counterAmount, true);
tgtCard.addCounter(chosenType, counterAmount, true);
} else {
// adding counters to something like re-suspend cards
tgtCard.addCounter(chosenCounter, counterAmount, false);
tgtCard.addCounter(chosenType, counterAmount, false);
}
} else {
tgtCard.subtractCounter(chosenCounter, counterAmount);
tgtCard.subtractCounter(chosenType, counterAmount);
}
}
}

View File

@@ -38,7 +38,6 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
@@ -69,6 +68,7 @@ public abstract class PlayerController {
UntapOrLeaveTapped,
UntapTimeVault,
LeftOrRight,
AddOrRemove,
}
protected final Game game;
@@ -164,7 +164,6 @@ public abstract class PlayerController {
}
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
public abstract Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter);
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);
@@ -182,8 +181,10 @@ public abstract class PlayerController {
return chooseNumber(sa, string, min, max);
};
public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, null); }
public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, (Boolean) null); }
public abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce);
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map<String, Object> params) { return chooseBinary(sa, question, kindOfChoice); }
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);

View File

@@ -57,7 +57,6 @@ import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
import forge.game.trigger.Trigger;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.gamesimulationtests.util.card.CardSpecification;
@@ -294,11 +293,6 @@ public class PlayerControllerForTests extends PlayerController {
return chooseItem(manaChoices);
}
@Override
public Pair<CounterType, String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
throw new IllegalStateException("Erring on the side of caution here...");
}
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return true;

View File

@@ -2,9 +2,7 @@ Name:Jhoira's Timebug
ManaCost:2
Types:Artifact Creature Insect
PT:1/2
A:AB$ GenericChoice | Cost$ T | ValidTgts$ Permanent.inZoneBattlefield+YouCtrl,Card.suspended+YouOwn | TgtPrompt$ Choose target permanent you control or suspended card you own | Defined$ You | Choices$ LoseTime,AddTime | TgtZone$ Battlefield,Exile | ConditionDefined$ Targeted | ConditionPresent$ Card.counters_GE1_TIME | ConditionCompare$ GE1 | SpellDescription$ Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
SVar:LoseTime:DB$ RemoveCounter | Defined$ Targeted | TgtPrompt$ Select target permanent or suspended card. | CounterType$ TIME | CounterNum$ 1 | SpellDescription$ Remove a time counter.
SVar:AddTime:DB$ PutCounter | Defined$ Targeted | TgtPrompt$ Select target permanent with a time counter on it or suspended card. | CounterType$ TIME | CounterNum$ 1 | TgtZone$ Battlefield,Exile | SpellDescription$ Add a time counter.
SVar:RemAIDeck:True
A:AB$ AddOrRemoveCounter | Cost$ T | CounterNum$ 1 | ValidTgts$ Permanent.inZoneBattlefield+YouCtrl,Card.suspended+YouOwn | TgtPrompt$ Choose target permanent you control or suspended card you own | TgtZone$ Battlefield,Exile | ConditionDefined$ Targeted | ConditionPresent$ Card.counters_GE1_TIME | SpellDescription$ Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.
DeckNeeds:Keyword$Suspend & Keyword$Vanishing
SVar:Picture:http://www.wizards.com/global/images/magic/general/jhoiras_timebug.jpg
Oracle:{T}: Choose target permanent you control or suspended card you own. If that permanent or card has a time counter on it, you may remove a time counter from it or put another time counter on it.

View File

@@ -1016,6 +1016,7 @@ public class PlayerControllerHuman
case UntapTimeVault: labels = ImmutableList.of("Untap (and skip this turn)", "Leave tapped"); break;
case PlayOrDraw: labels = ImmutableList.of("Play", "Draw"); break;
case LeftOrRight: labels = ImmutableList.of("Left", "Right"); break;
case AddOrRemove: labels = ImmutableList.of("Add Counter", "Remove Counter"); break;
default: labels = ImmutableList.copyOf(kindOfChoice.toString().split("Or"));
}
@@ -1038,24 +1039,6 @@ public class PlayerControllerHuman
return choiceMap.get(getGui().one(title, options));
}
@Override
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(final Card cardWithCounter) {
if (!cardWithCounter.hasCounters()) {
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
return null;
}
final String counterChoiceTitle = "Choose a counter type on " + cardWithCounter;
final CounterType chosen = getGui().one(counterChoiceTitle, ImmutableList.copyOf(cardWithCounter.getCounters().keySet()));
final String putOrRemoveTitle = "What to do with that '" + chosen.getName() + "' counter ";
final String putString = "Put another " + chosen.getName() + " counter on " + cardWithCounter;
final String removeString = "Remove a " + chosen.getName() + " counter from " + cardWithCounter;
final String addOrRemove = getGui().one(putOrRemoveTitle, ImmutableList.of(putString, removeString));
return new ImmutablePair<CounterType,String>(chosen,addOrRemove);
}
@Override
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(final SpellAbility saSpellskite, final List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
if (allTargets.size() < 2) {