From c9d6f72d77971e8b105223f97caf8ce76b571df6 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sun, 20 Nov 2016 15:03:27 +0000 Subject: [PATCH] CountersMultiply: add new API type for double counters on cards remove it from RepeatEachAi add more AI logic into it. it does support Strive Spell --- .gitattributes | 2 + .../main/java/forge/ai/ComputerUtilCard.java | 18 +- .../src/main/java/forge/ai/SpellApiToAi.java | 1 + .../forge/ai/ability/CountersMultiplyAi.java | 212 ++++++++++++++++++ .../java/forge/ai/ability/RepeatEachAi.java | 20 +- .../main/java/forge/game/ability/ApiType.java | 1 + .../effects/CountersMultiplyEffect.java | 64 ++++++ 7 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java create mode 100644 forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java diff --git a/.gitattributes b/.gitattributes index 97ece20fdff..17f461e4788 100644 --- a/.gitattributes +++ b/.gitattributes @@ -68,6 +68,7 @@ forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java -text forge-ai/src/main/java/forge/ai/ability/CounterAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersAi.java svneol=native#text/plain forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java -text +forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java -text svneol=unset#text/plain forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java -text forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java -text @@ -369,6 +370,7 @@ forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java -te forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java -text +forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java -text svneol=unset#text/plain forge-game/src/main/java/forge/game/ability/effects/CountersNoteEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersProliferateEffect.java -text forge-game/src/main/java/forge/game/ability/effects/CountersPutAllEffect.java -text diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 550096efffd..cf4aa873e43 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -43,7 +43,6 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbility; import forge.game.zone.MagicStack; import forge.game.zone.ZoneType; @@ -1381,9 +1380,8 @@ public class ComputerUtilCard { public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) { final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true); final CardCollection threatenedTargets = new CardCollection(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt == null) { + if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); for (final Card card : cards) { if (objects.contains(card)) { @@ -1393,9 +1391,8 @@ public class ComputerUtilCard { // For pumps without targeting restrictions, just return immediately until this is fleshed out. return false; } + CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); - CardCollection targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa); - targetables = CardLists.getTargetableCards(targetables, sa); targetables = ComputerUtil.getSafeTargets(ai, sa, targetables); for (final Card c : targetables) { if (objects.contains(c)) { @@ -1405,13 +1402,14 @@ public class ComputerUtilCard { if (!threatenedTargets.isEmpty()) { ComputerUtilCard.sortByEvaluateCreature(threatenedTargets); for (Card c : threatenedTargets) { - sa.getTargets().add(c); - if (sa.getTargets().getNumTargeted() >= tgt.getMaxTargets(sa.getHostCard(), sa)) { - break; + if (sa.canAddMoreTarget()) { + sa.getTargets().add(c); + if (!sa.canAddMoreTarget()) { + break; + } } } - if (sa.getTargets().getNumTargeted() > tgt.getMaxTargets(sa.getHostCard(), sa) - || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { + if (!sa.isTargetNumberValid()) { sa.resetTargets(); return false; } diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 3323da15e9c..7ca249be3cf 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -88,6 +88,7 @@ public enum SpellApiToAi { .put(ApiType.Mill, MillAi.class) .put(ApiType.MoveCounter, CountersMoveAi.class) .put(ApiType.MultiplePiles, CannotPlayAi.class) + .put(ApiType.MultiplyCounter, CountersMultiplyAi.class) .put(ApiType.MustAttack, MustAttackAi.class) .put(ApiType.MustBlock, MustBlockAi.class) .put(ApiType.NameCard, ChooseCardNameAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java new file mode 100644 index 00000000000..e4af2db5360 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java @@ -0,0 +1,212 @@ +package forge.ai.ability; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import forge.ai.ComputerUtil; +import forge.ai.ComputerUtilCost; +import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CounterType; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class CountersMultiplyAi extends SpellAbilityAi { + + @Override + protected boolean checkApiLogic(Player ai, SpellAbility sa) { + final CounterType counterType = getCounterType(sa); + + if (!sa.usesTargeting()) { + // defined are mostly Self or Creatures you control + CardCollection list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); + + list = CardLists.filter(list, new Predicate() { + + @Override + public boolean apply(Card c) { + if (!c.hasCounters()) { + return false; + } + + if (counterType != null) { + if (c.getCounters(counterType) <= 0) { + return false; + } + if (!c.canReceiveCounters(counterType)) { + return false; + } + } else { + for (Map.Entry e : c.getCounters().entrySet()) { + // has negative counter it would double + if (ComputerUtil.isNegativeCounter(e.getKey(), c)) { + return false; + } + } + } + + return true; + } + + }); + + if (list.isEmpty()) { + return false; + } + } else { + return setTargets(ai, sa); + } + + return super.checkApiLogic(ai, sa); + } + + @Override + protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { + final CounterType counterType = getCounterType(sa); + + if (!CounterType.P1P1.equals(counterType) && counterType != null) { + if (!sa.hasParam("ActivationPhases")) { + // Don't use non P1P1/M1M1 counters before main 2 if possible + if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) { + return false; + } + if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa)) { + return false; + } + } + } + if (ComputerUtil.waitForBlocking(sa)) { + return false; + } + + return true; + } + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + if (sa.usesTargeting() && !setTargets(ai, sa) && !mandatory) { + return false; + } + return true; + } + + private CounterType getCounterType(SpellAbility sa) { + if (sa.hasParam("CounterType")) { + try { + return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa); + } catch (Exception e) { + System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); + return null; + } + } + return null; + } + + private boolean setTargets(Player ai, SpellAbility sa) { + final CounterType counterType = getCounterType(sa); + + final Game game = ai.getGame(); + + CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); + + // pre filter targetable cards with counters and can receive one of + // them + list = CardLists.filter(list, new Predicate() { + + @Override + public boolean apply(Card c) { + if (!c.hasCounters()) { + return false; + } + + if (counterType != null) { + if (c.getCounters(counterType) <= 0) { + return false; + } + if (!c.canReceiveCounters(counterType)) { + return false; + } + } + + return true; + } + + }); + + CardCollection aiList = CardLists.filterControlledBy(list, ai); + if (!aiList.isEmpty()) { + // counter type list to check + // first loyalty, then P1P!, then Charge Counter + List typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE); + for (CounterType type : typeList) { + // enough targets + if (!sa.canAddMoreTarget()) { + break; + } + + if (counterType == null || counterType == type) { + addTargetsByCounterType(ai, sa, aiList, type); + } + } + } + + CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents()); + if (!oppList.isEmpty()) { + // not enough targets + if (sa.canAddMoreTarget()) { + final CounterType type = CounterType.M1M1; + if (counterType == null || counterType == type) { + addTargetsByCounterType(ai, sa, oppList, type); + } + } + } + + // targeting does failed + if (!sa.isTargetNumberValid()) { + sa.resetTargets(); + return false; + } + + return true; + } + + private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list, + final CounterType type) { + + CardCollection newList = CardLists.filter(list, CardPredicates.hasCounter(type)); + if (newList.isEmpty()) { + return; + } + + newList.sort(Collections.reverseOrder(CardPredicates.compareByCounterType(type))); + while (sa.canAddMoreTarget()) { + if (newList.isEmpty()) { + break; + } + + Card c = newList.remove(0); + sa.getTargets().add(c); + + // check if Spell with Strive is still playable + if (sa.isSpell() && sa.getHostCard().hasStartOfKeyword("Strive")) { + // if not remove target again and break list + if (!ComputerUtilCost.canPayCost(sa, ai)) { + sa.getTargets().remove(c); + break; + } + } + } + } +} diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java index 5b95425fb11..b0519e99eb4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java @@ -1,11 +1,12 @@ package forge.ai.ability; +import java.util.List; + import com.google.common.base.Predicate; import forge.ai.ComputerUtilCard; import forge.ai.SpellAbilityAi; 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.Presets; @@ -14,8 +15,6 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import java.util.List; - public class RepeatEachAi extends SpellAbilityAi { @@ -38,21 +37,6 @@ public class RepeatEachAi extends SpellAbilityAi { if (compTokenCreats.size() <= humTokenCreats.size()) { return false; } - } else if ("DoubleCounters".equals(logic)) { - // TODO Improve this logic, double Planeswalker counters first, then +1/+1 on Useful creatures - // Then Charge Counters, then -1/-1 on Opposing Creatures - CardCollection perms = new CardCollection(aiPlayer.getCardsIn(ZoneType.Battlefield)); - perms = CardLists.filter(CardLists.getTargetableCards(perms, sa), new Predicate() { - @Override - public boolean apply(final Card c) { - return c.hasCounters(); - } - }); - if (perms.isEmpty()) { - return false; - } - CardLists.shuffle(perms); - sa.setTargetCard(perms.get(0)); } else if ("RemoveAllCounters".equals(logic)) { // Break Dark Depths CardCollectionView depthsList = aiPlayer.getCardsIn(ZoneType.Battlefield, "Dark Depths"); diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 31bee3ba532..98e528c6eaa 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -86,6 +86,7 @@ public enum ApiType { Mill (MillEffect.class), MoveCounter (CountersMoveEffect.class), MultiplePiles (MultiplePilesEffect.class), + MultiplyCounter (CountersMultiplyEffect.class), MustAttack (MustAttackEffect.class), MustBlock (MustBlockEffect.class), NameCard (ChooseCardNameEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java new file mode 100644 index 00000000000..baaabc95aa7 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersMultiplyEffect.java @@ -0,0 +1,64 @@ +package forge.game.ability.effects; + +import java.util.Map; + +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CounterType; +import forge.game.spellability.SpellAbility; +import forge.util.Lang; + +public class CountersMultiplyEffect extends SpellAbilityEffect { + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + final CounterType counterType = getCounterType(sa); + + sb.append("Double the number of "); + + if (counterType != null) { + sb.append(counterType.getName()); + sb.append(" counters"); + } else { + sb.append("each kind of counter"); + } + sb.append(" on "); + + sb.append(Lang.joinHomogenous(getTargetCards(sa))); + + sb.append("."); + + return sb.toString(); + } + @Override + public void resolve(SpellAbility sa) { + + final CounterType counterType = getCounterType(sa); + final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1; + + for (final Card tgtCard : getTargetCards(sa)) { + if (counterType != null) { + tgtCard.addCounter(counterType, tgtCard.getCounters(counterType) * n, false); + } else { + for (Map.Entry e : tgtCard.getCounters().entrySet()) { + tgtCard.addCounter(e.getKey(), e.getValue() * n, false); + } + } + } + } + + + private CounterType getCounterType(SpellAbility sa) { + if (sa.hasParam("CounterType")) { + try { + return AbilityUtils.getCounterType(sa.getParam("CounterType"), sa); + } catch (Exception e) { + System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); + return null; + } + } + return null; + } +}