AI: changed many calls to accept Collection<T> instead of List<T>

GameAction - legend rule and planeswalker rule are updated to match changes introduced with "Magic 2014 Core Set"
InputSelectCardsFromList also accepts any Collection<T>, not just List<T>
PlayerControllerHuman - chooseSingleCardForEffect tries to use InputSelectCardsFromList when all cards are in Battlefield or own hand
This commit is contained in:
Maxmtg
2013-07-10 22:07:08 +00:00
parent 15b3698619
commit ba93f2a431
19 changed files with 167 additions and 84 deletions

1
.gitattributes vendored
View File

@@ -14251,6 +14251,7 @@ src/main/java/forge/card/ability/ai/FlipACoinAi.java -text
src/main/java/forge/card/ability/ai/FogAi.java -text
src/main/java/forge/card/ability/ai/GameLossAi.java -text
src/main/java/forge/card/ability/ai/GameWinAi.java -text
src/main/java/forge/card/ability/ai/LegendaryRuleAi.java -text
src/main/java/forge/card/ability/ai/LifeExchangeAi.java -text
src/main/java/forge/card/ability/ai/LifeGainAi.java -text
src/main/java/forge/card/ability/ai/LifeLoseAi.java -text

View File

@@ -58,6 +58,7 @@ import forge.card.ability.ai.FlipACoinAi;
import forge.card.ability.ai.FogAi;
import forge.card.ability.ai.GameLossAi;
import forge.card.ability.ai.GameWinAi;
import forge.card.ability.ai.LegendaryRuleAi;
import forge.card.ability.ai.LifeExchangeAi;
import forge.card.ability.ai.LifeGainAi;
import forge.card.ability.ai.LifeLoseAi;
@@ -220,8 +221,8 @@ public enum ApiType {
WinsGame (GameWinEffect.class, GameWinAi.class),
InternalEtbReplacement(ETBReplacementEffect.class, CanPlayAsDrawbackAi.class);
InternalEtbReplacement(ETBReplacementEffect.class, CanPlayAsDrawbackAi.class),
InternalLegendaryRule(CharmEffect.class, LegendaryRuleAi.class); // Charm has empty resolve blocks, may act as a dummy
private final Class<? extends SpellAbilityEffect> clsEffect;
private final Class<? extends SpellAbilityAi> clsAi;

View File

@@ -1,8 +1,11 @@
package forge.card.ability;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
@@ -113,9 +116,9 @@ public abstract class SpellAbilityAi extends SaTargetRountines {
return true;
}
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return options.get(0);
return Iterables.getFirst(options, null);
}
public Player chooseSinglePlayer(Player ai, SpellAbility sa, List<Player> options) {

View File

@@ -1,6 +1,7 @@
package forge.card.ability.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -1208,7 +1209,7 @@ public class AttachAi extends SpellAbilityAi {
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
return attachToCardAIPreferences(ai, sa, true);
}

View File

@@ -17,7 +17,7 @@
*/
package forge.card.ability.ai;
import java.util.List;
import java.util.Collection;
import forge.Card;
import forge.card.ability.SpellAbilityAi;
@@ -52,7 +52,7 @@ public final class BondAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
return ComputerUtilCard.getBestCreatureAI(options);
}
}

View File

@@ -1,6 +1,7 @@
package forge.card.ability.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
@@ -1402,7 +1403,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
// Called when looking for creature to attach aura or equipment
return ComputerUtilCard.getBestAI(options);
}

View File

@@ -1,5 +1,6 @@
package forge.card.ability.ai;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Predicate;
@@ -97,7 +98,7 @@ public class ChooseCardAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
final Card host = sa.getSourceCard();
final String logic = sa.getParam("AILogic");
Card choice = null;

View File

@@ -1,5 +1,6 @@
package forge.card.ability.ai;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -126,7 +127,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
// Select a card to attach to
return ComputerUtilCard.getBestAI(options);
}

View File

@@ -17,6 +17,7 @@
*/
package forge.card.ability.ai;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Predicate;
@@ -71,7 +72,7 @@ public final class EncodeAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {

View File

@@ -0,0 +1,42 @@
package forge.card.ability.ai;
import java.util.Collection;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.game.player.Player;
/**
* TODO: Write javadoc for this type.
*
*/
public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?
}
return firstOption;
}
}

View File

@@ -1,6 +1,7 @@
package forge.card.ability.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
@@ -112,7 +113,7 @@ public class PlayAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional) {
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {

View File

@@ -90,7 +90,7 @@ public class InputQueue extends Observable {
input.awaitLatchRelease();
}
public void setInput(InputSynchronized input) {
void setInput(InputSynchronized input) {
this.inputStack.push(input);
syncPoint();
this.updateObservers();

View File

@@ -28,9 +28,10 @@ import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.Card;
import forge.CardCharacteristicName;
@@ -46,6 +47,7 @@ import forge.ITargetable;
import forge.card.CardType;
import forge.card.TriggerReplacementBase;
import forge.card.ability.AbilityFactory;
import forge.card.ability.ApiType;
import forge.card.ability.effects.AttachEffect;
import forge.card.cardfactory.CardFactory;
import forge.card.cardfactory.CardFactoryUtil;
@@ -54,6 +56,7 @@ import forge.card.mana.ManaCost;
import forge.card.replacement.ReplacementResult;
import forge.card.spellability.Ability;
import forge.card.spellability.AbilityActivated;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.TargetRestrictions;
import forge.card.staticability.StaticAbility;
@@ -902,12 +905,14 @@ public class GameAction {
game.getStack().chooseOrderOfSimultaneousStackEntryAll();
}
if (this.handleLegendRule()) {
checkAgain = true;
}
if (this.handlePlaneswalkerRule()) {
checkAgain = true;
for(Player p : game.getPlayers() ) {
if (this.handleLegendRule(p)) {
checkAgain = true;
}
if (this.handlePlaneswalkerRule(p)) {
checkAgain = true;
}
}
if (!checkAgain) {
@@ -1139,15 +1144,13 @@ public class GameAction {
* destroyPlaneswalkers.
* </p>
*/
private boolean handlePlaneswalkerRule() {
private boolean handlePlaneswalkerRule(Player p) {
// get all Planeswalkers
final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS);
final List<Card> list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS);
boolean recheck = false;
Card c;
for (int i = 0; i < list.size(); i++) {
c = list.get(i);
final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create();
for (Card c : list) {
if (c.getCounters(CounterType.LOYALTY) <= 0) {
moveToGraveyard(c);
// Play the Destroy sound
@@ -1155,22 +1158,29 @@ public class GameAction {
recheck = true;
}
final ArrayList<String> types = c.getType();
for (final String type : types) {
if (!CardType.isAPlaneswalkerType(type)) {
continue;
}
final List<Card> cl = CardLists.getType(list, type);
if (cl.size() > 1) {
for (final Card crd : cl) {
moveToGraveyard(crd);
}
recheck = true;
for (final String type : c.getType()) {
if (CardType.isAPlaneswalkerType(type)) {
uniqueWalkers.put(type, c);
}
}
}
for(String key : uniqueWalkers.keySet())
{
Collection<Card> duplicates = uniqueWalkers.get(key);
if ( duplicates.size() < 2)
continue;
recheck = true;
Card toKeep = p.getController().chooseSingleCardForEffect(duplicates, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple planeswalkers of type \""+key+"\"in play.\n\nChoose one to stay on battlefield (the rest will be moved to graveyard)");
for(Card c: duplicates) {
if ( c != toKeep )
moveToGraveyard(c);
}
}
return recheck;
}
@@ -1179,8 +1189,8 @@ public class GameAction {
* destroyLegendaryCreatures.
* </p>
*/
private boolean handleLegendRule() {
final List<Card> a = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "Legendary");
private boolean handleLegendRule(Player p) {
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
return false;
}
@@ -1189,24 +1199,26 @@ public class GameAction {
if (yamazaki.size() == 2) {
a.removeAll(yamazaki);
}
while (!a.isEmpty()) {
List<Card> b = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(a.get(0).getName()));
b = CardLists.getType(b, "Legendary");
b = CardLists.filter(b, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isFaceDown();
}
});
a.remove(0);
if (1 < b.size()) {
for (int i = 0; i < b.size(); i++) {
sacrificeDestroy(b.get(i));
}
recheck = true;
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
Multimap<String, Card> uniqueLegends = ArrayListMultimap.create();
for(Card c : a) {
if ( !c.isFaceDown() )
uniqueLegends.put(c.getName(), c);
}
for(String name : uniqueLegends.keySet()) {
Collection<Card> cc = uniqueLegends.get(name);
if ( cc.size() < 2 )
continue;
recheck = true;
Card toKeep = p.getController().chooseSingleCardForEffect(cc, new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary creatures named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)");
for(Card c: cc) {
if ( c != toKeep )
sacrificeDestroy(c);
}
game.fireEvent(new GameEventCardDestroyed());
}
return recheck;

View File

@@ -1,6 +1,7 @@
package forge.game.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -124,7 +125,7 @@ public class ComputerUtilCard {
* a {@link forge.CardList} object.
* @return a {@link forge.Card} object.
*/
public static Card getBestLandAI(final List<Card> list) {
public static Card getBestLandAI(final Collection<Card> list) {
final List<Card> land = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (land.isEmpty()) {
return null;
@@ -180,8 +181,7 @@ public class ComputerUtilCard {
* a boolean.
* @return a {@link forge.Card} object.
*/
public static Card getCheapestPermanentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = list;
public static Card getCheapestPermanentAI(Collection<Card> all, final SpellAbility spell, final boolean targeted) {
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
@@ -190,17 +190,16 @@ public class ComputerUtilCard {
}
});
}
if (all.size() == 0) {
if (all.isEmpty()) {
return null;
}
// get cheapest card:
Card cheapest = null;
cheapest = all.get(0);
for (int i = 0; i < all.size(); i++) {
if (cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = all.get(i);
for (Card c : all) {
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c;
}
}
@@ -218,7 +217,7 @@ public class ComputerUtilCard {
* a {@link forge.CardList} object.
* @return a {@link forge.Card} object.
*/
public static Card getBestAI(final List<Card> list) {
public static Card getBestAI(final Collection<Card> list) {
// Get Best will filter by appropriate getBest list if ALL of the list
// is of that type
if (Iterables.all(list, CardPredicates.Presets.CREATURES))
@@ -239,7 +238,7 @@ public class ComputerUtilCard {
* the list
* @return the card
*/
public static Card getBestCreatureAI(final List<Card> list) {
public static Card getBestCreatureAI(final Collection<Card> list) {
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
}
@@ -292,7 +291,7 @@ public class ComputerUtilCard {
* a {@link forge.CardList} object.
* @return a {@link forge.Card} object.
*/
public static Card getWorstAI(final List<Card> list) {
public static Card getWorstAI(final Collection<Card> list) {
return ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
}
@@ -313,7 +312,7 @@ public class ComputerUtilCard {
* a boolean.
* @return a {@link forge.Card} object.
*/
public static Card getWorstPermanentAI(final List<Card> list, final boolean biasEnch, final boolean biasLand,
public static Card getWorstPermanentAI(final Collection<Card> list, final boolean biasEnch, final boolean biasLand,
final boolean biasArt, final boolean biasCreature) {
if (list.size() == 0) {
return null;
@@ -628,7 +627,7 @@ public class ComputerUtilCard {
* the all
* @return the card
*/
public static Card getMostExpensivePermanentAI(final List<Card> all) {
public static Card getMostExpensivePermanentAI(final Collection<Card> all) {
Card biggest = null;
int bigCMC = -1;

View File

@@ -112,8 +112,8 @@ public abstract class PlayerController {
// Specify a target of a spell (Spellskite)
public abstract Pair<SpellAbilityStackInstance, ITargetable> chooseTarget(SpellAbility sa, List<Pair<SpellAbilityStackInstance, ITargetable>> allTargets);
public Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); }
public abstract Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title, boolean isOptional);
public Card chooseSingleCardForEffect(Collection<Card> sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); }
public abstract Card chooseSingleCardForEffect(Collection<Card> sourceList, SpellAbility sa, String title, boolean isOptional);
public abstract Player chooseSinglePlayerForEffect(List<Player> options, SpellAbility sa, String title);
public abstract SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title);

View File

@@ -126,7 +126,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public Card chooseSingleCardForEffect(List<Card> options, SpellAbility sa, String title, boolean isOptional) {
public Card chooseSingleCardForEffect(Collection<Card> options, SpellAbility sa, String title, boolean isOptional) {
ApiType api = sa.getApi();
if ( null == api ) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet");

View File

@@ -37,6 +37,7 @@ import forge.game.Game;
import forge.game.GameType;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
@@ -272,17 +273,33 @@ public class PlayerControllerHuman extends PlayerController {
}
@Override
public Card chooseSingleCardForEffect(List<Card> options, SpellAbility sa, String title, boolean isOptional) {
public Card chooseSingleCardForEffect(Collection<Card> options, SpellAbility sa, String title, boolean isOptional) {
// Human is supposed to read the message and understand from it what to choose
if (options.isEmpty())
return null;
if ( !isOptional && options.size() == 1 )
return Iterables.getFirst(options, null);
boolean canUseSelectCardsInput = true;
for(Card c : options) {
Zone cz = c.getZone();
// can point at cards in own hand and anyone's battlefield
boolean canUiPointAtCards = cz != null && ( cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield));
if ( !canUiPointAtCards ) {
canUseSelectCardsInput = false;
break;
}
}
if ( isOptional )
return GuiChoose.oneOrNone(title, options);
else if ( options.size() > 1 )
return GuiChoose.one(title, options);
else
return options.get(0);
if ( canUseSelectCardsInput ) {
InputSelectCardsFromList input = new InputSelectCardsFromList(isOptional ? 0 : 1, 1, options);
input.setCancelAllowed(isOptional);
input.setMessage(title);
Singletons.getControl().getInputQueue().setInputAndWait(input);
return Iterables.getFirst(input.getSelected(), null);
}
return isOptional ? GuiChoose.oneOrNone(title, options) : GuiChoose.one(title, options);
}
@Override

View File

@@ -1,15 +1,14 @@
package forge.gui.input;
import java.util.List;
import java.util.Collection;
import forge.Card;
public class InputSelectCardsFromList extends InputSelectCards {
private static final long serialVersionUID = 6230360322294805986L;
private final List<Card> validChoices;
private final Collection<Card> validChoices;
public InputSelectCardsFromList(int min, int max, List<Card> validCards) {
public InputSelectCardsFromList(int min, int max, Collection<Card> validCards) {
super(Math.min(min, validCards.size()), Math.min(max, validCards.size())); // to avoid hangs
this.validChoices = validCards;

View File

@@ -369,9 +369,12 @@ public enum CMatchUI {
}
}
private final static boolean LOG_UIEVENTS = false;
// UI-related events should arrive here
public void fireEvent(UiEvent uiEvent) {
if ( LOG_UIEVENTS )
System.out.println("UI: " + uiEvent.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true));
uiEvents.post(uiEvent);
}