From 64e9d0867c5e1272396b0cc294e349ea3af80d56 Mon Sep 17 00:00:00 2001 From: excessum Date: Sat, 7 Mar 2015 12:26:33 +0000 Subject: [PATCH] - Expanded r28953 to support bouncing creatures to save them --- .../src/main/java/forge/ai/ComputerUtil.java | 14 ++- .../java/forge/ai/ability/ChangeZoneAi.java | 101 +++++++++++------- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index f9376c23d8b..053f203c485 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -42,6 +42,7 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; @@ -1072,8 +1073,17 @@ public class ComputerUtil { } // check stack for something that will kill this - final SpellAbility topStack = game.getStack().peekAbility(); - Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, topStack)); + for (SpellAbilityStackInstance si : game.getStack()) { + // iterate from top of stack to find SpellAbility, including sub-abilities, + // that does not match "sa" + SpellAbility spell = si.getSpellAbility(true), sub = spell.getSubAbility(); + while (sub != null && sub != sa) { + sub = sub.getSubAbility(); + } + if (sa != spell && sa != sub) { + Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, spell)); + } + } return objects; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index db08467edda..04de4ad5cf0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -306,7 +306,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } } } - return false; + return canBouncePermanent(ai, sa, list) != null; } } @@ -740,12 +740,6 @@ public class ChangeZoneAi extends SpellAbilityAi { // Narrow down the list: if (origin.equals(ZoneType.Battlefield)) { - // filter out untargetables - CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai); - - // Don't blink cards that will die. - aiPermanents = ComputerUtil.getSafeTargets(ai, sa, aiPermanents); - if ("Polymorph".equals(sa.getParam("AILogic"))) { list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); if (list.isEmpty()) { @@ -808,38 +802,10 @@ public class ChangeZoneAi extends SpellAbilityAi { && (subApi == ApiType.DelayedTrigger || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))))) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) { - // check stack for something on the stack that will kill - // anything i control - if (!game.getStack().isEmpty()) { - final List objects = ComputerUtil.predictThreatenedObjects(ai, sa); - - final List threatenedTargets = new ArrayList(); - - for (final Card c : aiPermanents) { - if (objects.contains(c)) { - threatenedTargets.add(c); - } - } - - if (!threatenedTargets.isEmpty()) { - // Choose "best" of the remaining to save - sa.getTargets().add(ComputerUtilCard.getBestAI(threatenedTargets)); - return true; - } - } - // Save combatants - else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - Combat combat = game.getCombat(); - final CardCollection combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES); - ComputerUtilCard.sortByEvaluateCreature(combatants); - - for (final Card c : combatants) { - if (c.getShieldCount() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) - && c.getOwner() == ai && !c.isToken()) { - sa.getTargets().add(c); - return true; - } - } + Card tobounce = canBouncePermanent(ai, sa, list); + if (tobounce != null) { + sa.getTargets().add(tobounce); + return true; } if (!CardLists.getNotType(list, "Land").isEmpty()) { @@ -1031,6 +997,57 @@ public class ChangeZoneAi extends SpellAbilityAi { return true; } + + /** + * Checks if a permanent threatened by a stack ability or in combat can + * be saved by bouncing. + * @param ai controlling player + * @param sa ChangeZone ability + * @param list possible targets + * @return target to bounce, null if no good targets + */ + private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) { + Game game = ai.getGame(); + // filter out untargetables + CardCollectionView aiPermanents = CardLists + .filterControlledBy(list, ai); + + // Don't blink cards that will die. + aiPermanents = ComputerUtil.getSafeTargets(ai, sa, aiPermanents); + if (!game.getStack().isEmpty()) { + final List objects = ComputerUtil + .predictThreatenedObjects(ai, sa); + + final List threatenedTargets = new ArrayList(); + + for (final Card c : aiPermanents) { + if (objects.contains(c)) { + threatenedTargets.add(c); + } + } + + if (!threatenedTargets.isEmpty()) { + // Choose "best" of the remaining to save + return ComputerUtilCard.getBestAI(threatenedTargets); + } + } + // Save combatants + else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + Combat combat = game.getCombat(); + final CardCollection combatants = CardLists.filter(aiPermanents, + CardPredicates.Presets.CREATURES); + ComputerUtilCard.sortByEvaluateCreature(combatants); + + for (final Card c : combatants) { + if (c.getShieldCount() == 0 + && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, + combat) && c.getOwner() == ai && !c.isToken()) { + return c; + } + } + } + return null; + } private static boolean isUnpreferredTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { if (!mandatory) { @@ -1224,6 +1241,12 @@ public class ChangeZoneAi extends SpellAbilityAi { c = ComputerUtilCard.getBestAI(fetchList); } else { c = ComputerUtilCard.getWorstAI(fetchList); + if (sa.getHostCard().getName().equals("Temur Sabertooth")) { + Card tobounce = canBouncePermanent(player, sa, fetchList); + if (tobounce != null) { + c = tobounce; + } + } } } else if (origin.contains(ZoneType.Library) && (type.contains("Basic") || areAllBasics(type))) { c = basicManaFixing(decider, fetchList);