From abf66a5555689ed6e6c7b5d03ce00d4a2bbb2b40 Mon Sep 17 00:00:00 2001 From: Sol Date: Thu, 7 Jul 2016 17:54:43 +0000 Subject: [PATCH] - Improving how the AI handles certain counterspells especially Hive Mind + Pact of Negation. --- .../main/java/forge/ai/ability/CounterAi.java | 118 ++++++++++++------ .../forge/game/spellability/SpellAbility.java | 4 + 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index 6a75a47f25b..8a73f2e69d6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -15,6 +15,8 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetRestrictions; import forge.util.MyRandom; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; public class CounterAi extends SpellAbilityAi { @@ -41,7 +43,7 @@ public class CounterAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { - final SpellAbility topSA = game.getStack().peekAbility(); + final SpellAbility topSA = findTopSpellAbility(game, sa); if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai || ai.getAllies().contains(topSA.getActivatingPlayer())) { // might as well check for player's friendliness @@ -52,7 +54,7 @@ public class CounterAi extends SpellAbilityAi { return false; } - if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() == 0) { + if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) { return false; } @@ -123,39 +125,24 @@ public class CounterAi extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Game game = ai.getGame(); + if (tgt != null) { - final Game game = ai.getGame(); if (game.getStack().isEmpty()) { return false; } - - SpellAbility topSA = game.getStack().peekAbility(); - - // triggered abilities see themselves on the stack, so find another spell on the stack - if (sa.isTrigger() && topSA.isTrigger() && game.getStack().size() > 1) { - Iterator it = game.getStack().iterator(); - SpellAbilityStackInstance si = game.getStack().peek(); - while (it.hasNext()) { - si = it.next(); - if (si.isTrigger()) { - it.remove(); - } else { - break; - } - } - topSA = si.getSpellAbility(true); - } - - if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) { - return false; - } sa.resetTargets(); - if (sa.canTargetSpellAbility(topSA)) { - sa.getTargets().add(topSA); - } else { + Pair pair = chooseTargetSpellAbility(game, sa, ai, mandatory); + SpellAbility tgtSA = pair.getLeft(); + + if (tgtSA == null) { + return false; + } + sa.getTargets().add(tgtSA); + if (!mandatory && !pair.getRight()) { + // If not mandatory and not preferred, bail out after setting target return false; } @@ -174,16 +161,18 @@ public class CounterAi extends SpellAbilityAi { toPay = AbilityUtils.calculateAmount(source, unlessCost, sa); } - if (toPay == 0) { - return false; - } - - if (toPay <= usableManaSources) { - // If this is a reusable Resource, feel free to play it most - // of the time - if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) { + if (!mandatory) { + if (toPay == 0) { return false; } + + if (toPay <= usableManaSources) { + // If this is a reusable Resource, feel free to play it most + // of the time + if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) { + return false; + } + } } if (setPayX) { @@ -201,4 +190,61 @@ public class CounterAi extends SpellAbilityAi { return true; } + public Pair chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) { + SpellAbility tgtSA; + SpellAbility leastBadOption = null; + SpellAbility bestOption = null; + + Iterator it = game.getStack().iterator(); + SpellAbilityStackInstance si = null; + while(it.hasNext()) { + si = it.next(); + tgtSA = si.getSpellAbility(true); + if (!sa.canTargetSpellAbility(tgtSA)) { + continue; + } + if (leastBadOption == null) { + leastBadOption = tgtSA; + } + + if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) || + tgtSA.getActivatingPlayer() == ai || + !tgtSA.getActivatingPlayer().isOpponentOf(ai)) { + // Is this a "better" least bad option + if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) { + // NOOP + } else if (sa.getActivatingPlayer().isOpponentOf(ai)) { + // Target opponents uncounterable stuff, before our own stuff + leastBadOption = tgtSA; + } + continue; + } + + if (bestOption == null) { + bestOption = tgtSA; + } else { + // TODO Determine if this option is better than the current best option + boolean betterThanBest = false; + if (betterThanBest) { + bestOption = tgtSA; + } + // Don't really need to keep updating leastBadOption once we have a bestOption + } + } + + return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null); + } + + public SpellAbility findTopSpellAbility(Game game, SpellAbility sa) { + Iterator it = game.getStack().iterator(); + SpellAbility tgtSA = it.next().getSpellAbility(true); + // Grab the topmost spellability that isn't this SA and use that for comparisons + if (sa.equals(tgtSA) && game.getStack().size() > 1) { + if (!it.hasNext()) { + return null; + } + tgtSA = it.next().getSpellAbility(true); + } + return tgtSA; + } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 60bfae53865..3571b59453d 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1125,6 +1125,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean canTargetSpellAbility(final SpellAbility topSA) { final TargetRestrictions tgt = getTargetRestrictions(); + if (this.equals(topSA)) { + return false; + } + if (hasParam("TargetType") && !topSA.isValid(getParam("TargetType").split(","), getActivatingPlayer(), getHostCard(), this)) { return false; }