From a481c0b53603eb1b5c4834a1668078d62d6255cc Mon Sep 17 00:00:00 2001 From: Agetian Date: Thu, 4 Sep 2014 20:18:48 +0000 Subject: [PATCH] - Highly experimental: added a way for the AI to reserve mana for casting a Main 2 phase spell when deciding whether to pump a creature or not. This may not be optimal and probably needs refactoring as well. It's a somewhat drastic changes so bugs may arise (though the main use cases were tested). Please assist if possible. --- .../src/main/java/forge/ai/AiController.java | 30 ++++ .../main/java/forge/ai/ComputerUtilMana.java | 134 +++++++++++++++++- .../main/java/forge/ai/ability/PumpAi.java | 6 + 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 4877a77ccbc..d479388d46e 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -32,12 +32,14 @@ import com.esotericsoftware.minlog.Log; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.card.CardType; import forge.card.MagicColor; import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostShard; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; @@ -60,6 +62,7 @@ import forge.game.combat.Combat; import forge.game.cost.Cost; import forge.game.cost.CostDiscard; import forge.game.cost.CostPart; +import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -634,6 +637,33 @@ public class AiController { return bestSA; } // playCounterSpell() + public SpellAbility evaluateSpellInMain2(ApiType exceptSA) { + final List cards = getAvailableCards(); + + ArrayList all = getSpellAbilities(cards); + Collections.sort(all, saComparator); // put best spells first + + for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) { + if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) { + continue; + } + sa.setActivatingPlayer(player); + if (sa.canPlay() && !ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().getType().contains("Land") && ComputerUtilCost.canPayCost(sa, player)) { + return sa; + } + } + + return null; + } + + public void reserveManaSourcesForMain2(SpellAbility sa) { + ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); + ArrayList manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player); + for (Card c : manaSources) { + ((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES); + } + } + // This is for playing spells regularly (no Cascade/Ripple etc.) private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) { if (!sa.canPlay()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index b7534ab9923..70cda68edae 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -21,6 +21,7 @@ import forge.game.mana.Mana; import forge.game.mana.ManaCostAdjustment; import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaPool; +import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; import forge.game.spellability.AbilityManaPart; @@ -230,6 +231,121 @@ public class ComputerUtilMana { } + public static ArrayListMultimap getManaSourcesToPayCost(final Player ai, final ManaCostBeingPaid cost) { + final ArrayListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true); + ArrayListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost); + sortManaAbilities(sourcesForShards); + return sourcesForShards; + } + + public static ArrayList getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) { + ArrayList manaSources = new ArrayList(); + + adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai); + List manaSpentToPay = new ArrayList(); + + final ManaPool manapool = ai.getManaPool(); + List unpaidShards = cost.getUnpaidShards(); + Collections.sort(unpaidShards); // most difficult shards must come first + for (ManaCostShard part : unpaidShards) { + if (part != ManaCostShard.X) { + if (cost.isPaid()) { + continue; + } + + // get a mana of this type from floating, bail if none available + final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction()); + if (mana != null) { + if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) { + manaSpentToPay.add(0, mana); + } + } + } + } + + if (cost.isPaid()) { + // refund any mana taken from mana pool when test + refundMana(manaSpentToPay, ai, sa); + + handleOfferingsAI(sa, true, cost.isPaid()); + return manaSources; + } + + // arrange all mana abilities by color produced. + final ArrayListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true); + if (manaAbilityMap.isEmpty()) { + refundMana(manaSpentToPay, ai, sa); + + handleOfferingsAI(sa, true, cost.isPaid()); + return manaSources; + } + + // select which abilities may be used for each shard + ArrayListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost); + + sortManaAbilities(sourcesForShards); + + ManaCostShard toPay = null; + // Loop over mana needed + while (!cost.isPaid()) { + toPay = getNextShardToPay(cost); + + Collection saList = sourcesForShards.get(toPay); + SpellAbility saPayment = null; + if (saList != null) { + for (final SpellAbility ma : saList) { + if (ma.getHostCard() == sa.getHostCard()) { + continue; + } + + final String typeRes = cost.getSourceRestriction(); + if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) { + continue; + } + + if (canPayShardWithSpellAbility(toPay, ai, ma, sa, true)) { + saPayment = ma; + manaSources.add(saPayment.getHostCard()); + break; + } + } + } else { + break; + } + + if (saPayment == null) { + if (!toPay.isPhyrexian() || !ai.canPayLife(2)) { + break; // cannot pay + } + + cost.payPhyrexian(); + continue; + } + + setExpressColorChoice(sa, ai, cost, toPay, saPayment); + + String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment); + manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced); + //System.out.println(manaProduced); + payMultipleMana(cost, manaProduced, ai); + + // remove from available lists + Iterator itSa = sourcesForShards.values().iterator(); + while (itSa.hasNext()) { + SpellAbility srcSa = itSa.next(); + if (srcSa.getHostCard().equals(saPayment.getHostCard())) { + itSa.remove(); + } + } + } + + handleOfferingsAI(sa, true, cost.isPaid()); + + refundMana(manaSpentToPay, ai, sa); + + return manaSources; + } // getManaSourcesToPayCost() + private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) { adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai); List manaSpentToPay = test ? new ArrayList() : sa.getPayingMana(); @@ -514,8 +630,12 @@ public class ComputerUtilMana { } private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) { - final Card sourceCard = ma.getHostCard(); + Card sourceCard = ma.getHostCard(); + if (isManaSourceReserved(ai, sourceCard)) { + return false; + } + if (toPay.isSnow() && !sourceCard.isSnow()) { return false; } AbilityManaPart m = ma.getManaPart(); @@ -556,6 +676,18 @@ public class ComputerUtilMana { return true; } + private static boolean isManaSourceReserved(Player ai, Card sourceCard) { + if (ai.getGame().getPhaseHandler().getPhase() == PhaseType.MAIN2) { + ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearRememberedManaSources(); + } else { + if (((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) { + // This mana source is held elsewhere for a Main Phase 2 spell. + return true; + } + } + return false; + } + private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) { // mind the priorities diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 24b5edc2b68..b34072f49fb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -5,6 +5,7 @@ import forge.game.Game; import forge.game.GameObject; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CardPredicates.Presets; @@ -48,6 +49,11 @@ public class PumpAi extends PumpAiBase { */ @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { + SpellAbility futureSpell = ((PlayerControllerAi)ai.getController()).getAi().evaluateSpellInMain2(ApiType.Pump); + if (futureSpell != null && futureSpell.getHostCard() != null) { + ((PlayerControllerAi)ai.getController()).getAi().reserveManaSourcesForMain2(futureSpell); + } + final Cost cost = sa.getPayCosts(); final Game game = ai.getGame(); final PhaseHandler ph = game.getPhaseHandler();