From 1d77d79ec3397b733aadb2f545155269e0d325aa Mon Sep 17 00:00:00 2001 From: Agetian Date: Fri, 27 Oct 2023 22:49:21 +0300 Subject: [PATCH] - Improve AI for Aether Vial, Ratchet Bomb. --- .../java/forge/ai/ability/CountersPutAi.java | 48 ++++++++++++++++++- forge-gui/res/cardsfolder/a/aether_vial.txt | 2 +- forge-gui/res/cardsfolder/r/ratchet_bomb.txt | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index dd576e9442a..39508102c23 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -305,7 +305,11 @@ public class CountersPutAi extends CountersAi { AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2); } return willActivate; - } + } else if (logic.equals("ChargeToBestCMC")) { + return doChargeToCMCLogic(ai, sa); + } else if (logic.equals("ChargeToBestOppControlledCMC")) { + return doChargeToOppCtrlCMCLogic(ai, sa); + } if (!sa.metConditions() && sa.getSubAbility() == null) { return false; @@ -740,6 +744,7 @@ public class CountersPutAi extends CountersAi { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { final SpellAbility root = sa.getRootAbility(); final Card source = sa.getHostCard(); + final String aiLogic = sa.getParamOrDefault("AILogic", ""); // boolean chance = true; boolean preferred = true; CardCollection list; @@ -747,6 +752,7 @@ public class CountersPutAi extends CountersAi { final boolean divided = sa.isDividedAsYouChoose(); final int amount = AbilityUtils.calculateAmount(source, amountStr, sa); int left = amount; + final String[] types; String type = ""; if (sa.hasParam("CounterType")) { @@ -759,6 +765,12 @@ public class CountersPutAi extends CountersAi { type = types[0]; } + if ("ChargeToBestCMC".equals(aiLogic)) { + return doChargeToCMCLogic(ai, sa) || mandatory; + } else if ("ChargeToBestOppControlledCMC".equals(aiLogic)) { + return doChargeToOppCtrlCMCLogic(ai, sa) || mandatory; + } + if (!sa.usesTargeting()) { // No target. So must be defined. (Unused at the moment) // list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); @@ -1206,4 +1218,38 @@ public class CountersPutAi extends CountersAi { return max; } + private boolean doChargeToCMCLogic(Player ai, SpellAbility sa) { + Card source = sa.getHostCard(); + CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Creature")); + int numCtrs = source.getCounters(CounterEnumType.CHARGE); + int maxCMC = Aggregates.max(ownLib, CardPredicates.Accessors.fnGetCmc); + int optimalCMC = 0; + int curAmount = 0; + // Assume the AI knows its deck list and realizes what it has left in its library. Could be improved to make this less cheat-y. + for (int cmc = numCtrs; cmc <= maxCMC; cmc++) { + int numPerCMC = CardLists.filter(ownLib, CardPredicates.hasCMC(cmc)).size(); + if (numPerCMC >= curAmount) { + curAmount = numPerCMC; + optimalCMC = cmc; + } + } + return numCtrs < optimalCMC; + } + + private boolean doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) { + Card source = sa.getHostCard(); + CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.NONLAND_PERMANENTS); + int numCtrs = source.getCounters(CounterEnumType.CHARGE); + int maxCMC = Aggregates.max(oppInPlay, CardPredicates.Accessors.fnGetCmc); + int optimalCMC = 0; + int curAmount = 0; + for (int cmc = numCtrs; cmc <= maxCMC; cmc++) { + int numPerCMC = CardLists.filter(oppInPlay, CardPredicates.hasCMC(cmc)).size(); + if (numPerCMC >= curAmount) { + curAmount = numPerCMC; + optimalCMC = cmc; + } + } + return numCtrs < optimalCMC; + } } diff --git a/forge-gui/res/cardsfolder/a/aether_vial.txt b/forge-gui/res/cardsfolder/a/aether_vial.txt index 86eb2fc2d82..116ef391405 100644 --- a/forge-gui/res/cardsfolder/a/aether_vial.txt +++ b/forge-gui/res/cardsfolder/a/aether_vial.txt @@ -3,6 +3,6 @@ ManaCost:1 Types:Artifact A:AB$ ChangeZone | Cost$ T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.cmcEQX+YouCtrl | Optional$ You | SpellDescription$ You may put a creature card with mana value equal to the number of charge counters on CARDNAME from your hand onto the battlefield. | StackDescription$ SpellDescription T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your upkeep, you may put a charge counter on CARDNAME. -SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | AILogic$ ChargeToBestCMC SVar:X:Count$CardCounters.CHARGE Oracle:At the beginning of your upkeep, you may put a charge counter on Aether Vial.\n{T}: You may put a creature card with mana value equal to the number of charge counters on Aether Vial from your hand onto the battlefield. diff --git a/forge-gui/res/cardsfolder/r/ratchet_bomb.txt b/forge-gui/res/cardsfolder/r/ratchet_bomb.txt index 65516eeb8b2..e0601054d68 100644 --- a/forge-gui/res/cardsfolder/r/ratchet_bomb.txt +++ b/forge-gui/res/cardsfolder/r/ratchet_bomb.txt @@ -1,7 +1,7 @@ Name:Ratchet Bomb ManaCost:2 Types:Artifact -A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME. +A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$ 1 | AILogic$ ChargeToBestOppControlledCMC | SpellDescription$ Put a charge counter on CARDNAME. A:AB$ DestroyAll | Cost$ T Sac<1/CARDNAME> | ValidCards$ Permanent.nonLand+cmcEQX | SpellDescription$ Destroy each nonland permanent with mana value equal to the number of charge counters on CARDNAME. SVar:X:Count$CardCounters.CHARGE Oracle:{T}: Put a charge counter on Ratchet Bomb.\n{T}, Sacrifice Ratchet Bomb: Destroy each nonland permanent with mana value equal to the number of charge counters on Ratchet Bomb.