From 45d71d0c5fde50d413342e7b90b32055a26cf1cc Mon Sep 17 00:00:00 2001 From: Agetian Date: Sun, 27 Aug 2017 18:11:06 +0000 Subject: [PATCH] - AI improvements: restored playability of Exhaustion, made Cursed Scroll AI-playable, added some additional code support for programmable Exert logic (Ahn-Crop Champion is an example), added a bit of threshold to Living Death AI, moved Momir Vig Avatar logic to SpecialCardAi. --- .../java/forge/ai/AiAttackController.java | 39 +++++++++++- .../java/forge/ai/PlayerControllerAi.java | 2 + .../src/main/java/forge/ai/SpecialCardAi.java | 59 ++++++++++++++++++- .../forge/ai/ability/ChooseCardNameAi.java | 25 ++------ .../main/java/forge/ai/ability/EffectAi.java | 15 +++++ .../res/cardsfolder/a/ahn_crop_champion.txt | 2 + forge-gui/res/cardsfolder/c/cursed_scroll.txt | 3 +- 7 files changed, 120 insertions(+), 25 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index d32c635a783..2e67d3f3b2f 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -29,6 +29,7 @@ import forge.ai.ability.AnimateAi; import forge.card.CardTypeView; import forge.game.GameEntity; import forge.game.ability.AbilityFactory; +import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.ProtectEffect; import forge.game.card.*; @@ -40,6 +41,7 @@ import forge.game.spellability.SpellAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; +import forge.util.Expressions; import forge.util.MyRandom; import forge.util.collect.FCollectionView; @@ -1212,18 +1214,51 @@ public class AiAttackController { } if (sa.usesTargeting()) { sa.setActivatingPlayer(c.getController()); - if (CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).isEmpty()) { + List validTargets = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa); + if (validTargets.isEmpty()) { + missTarget = true; + break; + } else if (sa.isCurse() && CardLists.filter(validTargets, + CardPredicates.isControlledByAnyOf(c.getController().getOpponents())).isEmpty()) { + // e.g. Ahn-Crop Crasher - the effect is only good when aimed at opponent's creatures missTarget = true; break; } } + } if (missTarget) { continue; } - if (random.nextBoolean()) { + // A specific AI condition for Exert: if specified on the card, the AI will always + // exert creatures that meet this condition + if (c.hasSVar("AIExertCondition")) { + if (!c.getSVar("AIExertCondition").isEmpty()) { + final String needsToExert = c.getSVar("AIExertCondition"); + int x = 0; + int y = 0; + String sVar = needsToExert.split(" ")[0]; + String comparator = needsToExert.split(" ")[1]; + String compareTo = comparator.substring(2); + try { + x = Integer.parseInt(sVar); + } catch (final NumberFormatException e) { + x = CardFactoryUtil.xCount(c, c.getSVar(sVar)); + } + try { + y = Integer.parseInt(compareTo); + } catch (final NumberFormatException e) { + y = CardFactoryUtil.xCount(c, c.getSVar(compareTo)); + } + if (Expressions.compare(x, comparator, y)) { + shouldExert = true; + } + } + } + + if (!shouldExert && random.nextBoolean()) { // TODO Improve when the AI wants to use Exert powers shouldExert = true; } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index a295c5f2e0c..837ab042bcf 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -892,6 +892,8 @@ public class PlayerControllerAi extends PlayerController { } else if (logic.equals("MostProminentSpellInComputerDeck")) { CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard()); return ComputerUtilCard.getMostProminentCardName(cards); + } else if (logic.equals("CursedScroll")) { + return SpecialCardAi.CursedScroll.chooseCard(player, sa); } } else { CardCollectionView list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents()); diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 90aa56e07b4..473c1597899 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -176,6 +176,35 @@ public class SpecialCardAi { } } + // Cursed Scroll + public static class CursedScroll { + public static boolean consider(Player ai, SpellAbility sa) { + CardCollectionView hand = ai.getCardsIn(ZoneType.Hand); + if (hand.isEmpty()) { + return false; + } + + // For now, see if all cards in hand have the same name, and then proceed if true + return CardLists.filter(hand, CardPredicates.nameEquals(hand.getFirst().getName())).size() == hand.size(); + } + + public static String chooseCard(Player ai, SpellAbility sa) { + int maxCount = 0; + Card best = null; + CardCollectionView hand = ai.getCardsIn(ZoneType.Hand); + + for (Card c : ai.getCardsIn(ZoneType.Hand)) { + int count = CardLists.filter(hand, CardPredicates.nameEquals(c.getName())).size(); + if (count > maxCount) { + maxCount = count; + best = c; + } + } + + return best.getName(); + } + } + // Desecration Demon public static class DesecrationDemon { private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer @@ -360,6 +389,8 @@ public class SpecialCardAi { public static class LivingDeath { public static boolean consider(Player ai, SpellAbility sa) { int aiBattlefieldPower = 0, aiGraveyardPower = 0; + int threshold = 320; // approximately a 4/4 Flying creature worth of extra value + CardCollection aiCreaturesInGY = CardLists.filter(ai.getZone(ZoneType.Graveyard).getCards(), CardPredicates.Presets.CREATURES); if (aiCreaturesInGY.isEmpty()) { @@ -396,7 +427,7 @@ public class SpecialCardAi { } // if we get more value out of this than our opponent does (hopefully), go for it - return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower); + return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold); } } @@ -428,6 +459,32 @@ public class SpecialCardAi { } } + // Momir Vig, Simic Visionary Avatar + public static class MomirVigAvatar { + public static boolean consider(Player ai, SpellAbility sa) { + Card source = sa.getHostCard(); + + if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) { + return false; + } + // Set PayX here to maximum value. + int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai); + + // Some basic strategy for Momir + if (tokenSize < 2) { + return false; + } + + if (tokenSize > 11) { + tokenSize = 11; + } + + source.setSVar("PayX", Integer.toString(tokenSize)); + + return true; + } + } + // Necropotence public static class Necropotence { public static boolean consider(Player ai, SpellAbility sa) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java index 0454f1176cf..7eccc1fa2d0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java @@ -6,10 +6,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.StaticData; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilCard; -import forge.ai.ComputerUtilMana; -import forge.ai.SpellAbilityAi; +import forge.ai.*; import forge.card.CardDb; import forge.card.CardRules; import forge.card.CardSplitType; @@ -36,22 +33,9 @@ public class ChooseCardNameAi extends SpellAbilityAi { String logic = sa.getParam("AILogic"); if (logic.equals("MomirAvatar")) { - if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) { - return false; - } - // Set PayX here to maximum value. - int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai); - - // Some basic strategy for Momir - if (tokenSize < 2) { - return false; - } - - if (tokenSize > 11) { - tokenSize = 11; - } - - source.setSVar("PayX", Integer.toString(tokenSize)); + return SpecialCardAi.MomirVigAvatar.consider(ai, sa); + } else if (logic.equals("CursedScroll")) { + return SpecialCardAi.CursedScroll.consider(ai, sa); } final TargetRestrictions tgt = sa.getTargetRestrictions(); @@ -78,6 +62,7 @@ public class ChooseCardNameAi extends SpellAbilityAi { */ @Override public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) { + return ComputerUtilCard.getBestAI(options); } diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index d8244c2262c..037759aaac8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Random; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import forge.ai.ComputerUtil; @@ -18,6 +19,7 @@ import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.card.CardPredicates; import forge.game.combat.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -49,6 +51,19 @@ public class EffectAi extends SpellAbilityAi { return false; } randomReturn = true; + } else if (logic.equals("KeepOppCreatsLandsTapped")) { + for (Player opp : ai.getOpponents()) { + boolean worthHolding = false; + if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), + Predicates.and(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES), + CardPredicates.Presets.TAPPED)).size() >= 3) { + worthHolding = true; + } + if (!worthHolding) { + return false; + } + randomReturn = true; + } } else if (logic.equals("Fog")) { if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { return false; diff --git a/forge-gui/res/cardsfolder/a/ahn_crop_champion.txt b/forge-gui/res/cardsfolder/a/ahn_crop_champion.txt index f8ba741c81e..ec550fd59d6 100644 --- a/forge-gui/res/cardsfolder/a/ahn_crop_champion.txt +++ b/forge-gui/res/cardsfolder/a/ahn_crop_champion.txt @@ -5,4 +5,6 @@ PT:4/4 K:You may exert CARDNAME as it attacks. T:Mode$ Exerted | ValidCard$ Card.Self | Execute$ TrigUntapAll | TriggerDescription$ When you exert CARDNAME, untap all other creatures you control. SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl+Other +SVar:AIExertCondition:NumCreats GE3 +SVar:NumCreats:Count$Valid Creature.YouCtrl+tapped Oracle:You may exert Ahn-Crop Champion as it attacks. When you do, untap all other creatures you control. (An exerted creature won't untap during your next untap step.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/cursed_scroll.txt b/forge-gui/res/cardsfolder/c/cursed_scroll.txt index abdfe387382..826b69a3362 100644 --- a/forge-gui/res/cardsfolder/c/cursed_scroll.txt +++ b/forge-gui/res/cardsfolder/c/cursed_scroll.txt @@ -1,10 +1,9 @@ Name:Cursed Scroll ManaCost:1 Types:Artifact -A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player. +A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | AILogic$ CursedScroll | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player. SVar:DBReveal:DB$ Reveal | Random$ True | RememberRevealed$ True | Defined$ You | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | ConditionDefined$ Remembered | ConditionPresent$ Card.NamedCard | ConditionCompare$ EQ1 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/cursed_scroll.jpg Oracle:{3}, {T}: Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, Cursed Scroll deals 2 damage to target creature or player.