From 898697a212c96a3d373f5388e1a75f24c33d1568 Mon Sep 17 00:00:00 2001 From: mcrawford620 Date: Thu, 5 Jul 2012 05:45:01 +0000 Subject: [PATCH] - Better AI for fetching cards. --- res/cardsfolder/c/crumbling_colossus.txt | 1 - res/cardsfolder/d/diabolic_tutor.txt | 1 - res/cardsfolder/r/reclaim.txt | 1 - .../AbilityFactoryChangeZone.java | 173 +++++++-- .../AbilityFactoryPermanentState.java | 36 +- .../card/cardfactory/CardFactoryInstants.java | 2 +- .../cardfactory/CardFactorySorceries.java | 4 +- .../java/forge/game/player/ComputerUtil.java | 357 ++++++++++-------- 8 files changed, 358 insertions(+), 217 deletions(-) diff --git a/res/cardsfolder/c/crumbling_colossus.txt b/res/cardsfolder/c/crumbling_colossus.txt index 63844bb3f65..ea92de7ce88 100644 --- a/res/cardsfolder/c/crumbling_colossus.txt +++ b/res/cardsfolder/c/crumbling_colossus.txt @@ -7,7 +7,6 @@ K:Trample T:Mode$ Attacks | ValidCard$ Card.Self | DelayedTrigger$ DelTrig | TriggerDescription$ When CARDNAME attacks, sacrifice it at end of combat. SVar:DelTrig:Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | Execute$ TrigSacrifice | TriggerDescription$ Sacrifice CARDNAME at end of combat. SVar:TrigSacrifice:AB$ Sacrifice | Cost$ 0 | Defined$ Self -SVar:RemAIDeck:True SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/crumbling_colossus.jpg SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/204.jpg diff --git a/res/cardsfolder/d/diabolic_tutor.txt b/res/cardsfolder/d/diabolic_tutor.txt index 1029882b7fe..ff425bffb93 100644 --- a/res/cardsfolder/d/diabolic_tutor.txt +++ b/res/cardsfolder/d/diabolic_tutor.txt @@ -3,7 +3,6 @@ ManaCost:2 B B Types:Sorcery Text:no text A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library. -SVar:RemAIDeck:True SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/diabolic_tutor.jpg SetInfo:8ED|Uncommon|http://magiccards.info/scans/en/8e/128.jpg diff --git a/res/cardsfolder/r/reclaim.txt b/res/cardsfolder/r/reclaim.txt index e143329f607..e31cb10f196 100644 --- a/res/cardsfolder/r/reclaim.txt +++ b/res/cardsfolder/r/reclaim.txt @@ -3,7 +3,6 @@ ManaCost:G Types:Instant Text:no text A:SP$ ChangeZone | Cost$ G | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target card in your graveyard | ValidTgts$ Card.YouCtrl | SpellDescription$ Put target card from your graveyard on top of your library. -SVar:RemAIDeck:True SVar:Rarity:Common SVar:Picture:http://www.wizards.com/global/images/magic/general/reclaim.jpg SetInfo:EXO|Common|http://magiccards.info/scans/en/ex/120.jpg diff --git a/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java b/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java index 59b16edf9d3..393f81a39bc 100644 --- a/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java +++ b/src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java @@ -43,9 +43,11 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellPermanent; import forge.card.spellability.Target; +import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.ComputerUtil; +import forge.game.player.ComputerUtilBlock; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -1111,12 +1113,14 @@ public final class AbilityFactoryChangeZone { } // Improve the AI for fetching. - Card c; + Card c = null; if (params.containsKey("AtRandom")) { c = CardUtil.getRandom(fetchList.toArray()); } else if (defined) { c = fetchList.get(0); } else { + fetchList.shuffle(); + // Save a card as a default, in case we can't find anything suitable. Card first = fetchList.get(0); fetchList = fetchList.filter(new CardListFilter() { @Override @@ -1130,19 +1134,15 @@ public final class AbilityFactoryChangeZone { } }); if (type.contains("Basic")) { - c = AbilityFactoryChangeZone.basicManaFixing(fetchList); + c = AbilityFactoryChangeZone.basicManaFixing(fetchList); } else if (AbilityFactoryChangeZone.areAllBasics(type)) { c = AbilityFactoryChangeZone.basicManaFixing(fetchList, type); } else if (fetchList.getNotType("Creature").size() == 0) { - c = CardFactoryUtil.getBestCreatureAI(fetchList); // if only - // creatures - // take the - // best + c = AbilityFactoryChangeZone.chooseCreature(fetchList); } else if (ZoneType.Battlefield.equals(destination) || ZoneType.Graveyard.equals(destination)) { c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); } else if (ZoneType.Exile.equals(destination)) { - // Exiling your own stuff, if Exiling opponents stuff choose - // best + // Exiling your own stuff, if Exiling opponents stuff choose best if (destZone.getPlayer().isHuman()) { c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); } else { @@ -1153,13 +1153,49 @@ public final class AbilityFactoryChangeZone { if (origin.contains(ZoneType.Library) && !fetchList.getNotName(card.getName()).isEmpty()) { fetchList = fetchList.getNotName(card.getName()); } - fetchList.shuffle(); - c = fetchList.get(0); + Player ai = AllZone.getComputerPlayer(); + // Does AI need a land? + CardList hand = ai.getCardsIn(ZoneType.Hand); + System.out.println("Lands in hand = " + hand.filter(CardListFilter.LANDS).size() + ", on battlefield = " + ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size()); + if (hand.filter(CardListFilter.LANDS).size() == 0 && ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size() < 4) { + boolean canCastSomething = false; + for (Card cardInHand : hand) { + canCastSomething |= ComputerUtil.payManaCost(cardInHand.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false); + } + if (!canCastSomething) { + System.out.println("Pulling a land as there are none in hand, less than 4 on the board, and nothing in hand is castable."); + c = basicManaFixing(fetchList); + } + } + if (c == null) { + System.out.println("Don't need a land or none available; trying for a creature."); + fetchList = fetchList.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + c = chooseCreature(fetchList.getType("Creature")); + } + if (c == null) { // Could not find a creature. + if (ai.getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(fetchList); + for (Card potentialCard : fetchList) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + c = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + c = CardFactoryUtil.getBestAI(fetchList); + } + } } if (c == null) { c = first; } } + System.out.println("Chose " + c.toString()); fetched.add(c); fetchList.remove(c); @@ -1336,6 +1372,38 @@ public final class AbilityFactoryChangeZone { return true; } + + /** + * Some logic for picking a creature card from a list. + * @param list + * @return Card + */ + private static Card chooseCreature(CardList list) { + Card card = null; + Combat combat = new Combat(); + combat.initiatePossibleDefenders(AllZone.getComputerPlayer()); + CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); + for (Card att : attackers) { + combat.addAttacker(att); + } + combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer())); + + System.out.println("Life would remain = " + CombatUtil.lifeThatWouldRemain(combat)); + if (CombatUtil.lifeInDanger(combat)) { + // need something AI can cast now + CardListUtil.sortByEvaluateCreature(list); + for (Card c : list) { + if (ComputerUtil.payManaCost(c.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + card = c; + break; + } + } + } else { + // not urgent, get the largest creature possible + card = CardFactoryUtil.getBestCreatureAI(list); + } + return card; + } // ************************************************************************************* // **************** Known Origin (Battlefield/Graveyard/Exile) @@ -1508,6 +1576,7 @@ public final class AbilityFactoryChangeZone { CardList list = AllZoneUtil.getCardsIn(origin); list = list.getValidCards(tgt.getValidTgts(), AllZone.getComputerPlayer(), source); + list = list.getNotName(source.getName()); // Don't get the same card back. if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) { return false; @@ -1523,7 +1592,6 @@ public final class AbilityFactoryChangeZone { aiPermanents = aiPermanents.filter(new CardListFilter() { @Override public boolean addCard(final Card c) { - System.out.println("Not Changing Zone"); return !c.getSVar("Targeting").equals("Dies"); } }); @@ -1574,15 +1642,8 @@ public final class AbilityFactoryChangeZone { } // counters TODO check good and // bad counters - return SpellPermanent.checkETBEffects(c, null, null); // checks - // only - // if - // there - // is - // a - // dangerous - // ETB - // effect + // checks only if there is a dangerous ETB effect + return SpellPermanent.checkETBEffects(c, null, null); } }); if (!aiPermanents.isEmpty()) { @@ -1666,11 +1727,34 @@ public final class AbilityFactoryChangeZone { } else { choice = mostExpensive; } + } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { + CardList nonLands = list.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + choice = chooseCreature(nonLands.getType("Creature")); + if (choice == null) { // Could not find a creature. + if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(nonLands); + for (Card potentialCard : nonLands) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + choice = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + choice = CardFactoryUtil.getBestAI(nonLands); + } + } + if (choice == null) { + // No creatures or spells? + list.shuffle(); + choice = list.get(0); + } } else { - // TODO AI needs more improvement to it's retrieval (reuse - // some code from spReturn here) - list.shuffle(); - choice = list.get(0); + choice = CardFactoryUtil.getBestAI(list); } } if (choice == null) { // can't find anything left @@ -1680,7 +1764,9 @@ public final class AbilityFactoryChangeZone { } return false; } else { - // TODO is this good enough? for up to amounts? + if (!ComputerUtil.shouldCastLessThanMax(source)) { + return false; + } break; } } @@ -1691,7 +1777,7 @@ public final class AbilityFactoryChangeZone { return true; } - + /** *

* changeKnownUnpreferredTarget. @@ -1754,11 +1840,34 @@ public final class AbilityFactoryChangeZone { choice = CardFactoryUtil.getBestCreatureToBounceAI(list); } else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) { choice = CardFactoryUtil.getMostExpensivePermanentAI(list, sa, false); + } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { + CardList nonLands = list.getNotType("Land"); + // Prefer to pull a creature, generally more useful for AI. + choice = chooseCreature(nonLands.getType("Creature")); + if (choice == null) { // Could not find a creature. + if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate? + // Get something AI can cast soon. + System.out.println("5 Life or less, trying to find something castable."); + CardListUtil.sortByMostExpensive(nonLands); + for (Card potentialCard : nonLands) { + if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) { + choice = potentialCard; + break; + } + } + } else { + // Get the best card in there. + System.out.println("No creature and lots of life, finding something good."); + choice = CardFactoryUtil.getBestAI(nonLands); + } + } + if (choice == null) { + // No creatures or spells? + list.shuffle(); + choice = list.get(0); + } } else { - // TODO AI needs more improvement to it's retrieval (reuse - // some code from spReturn here) - list.shuffle(); - choice = list.get(0); + choice = CardFactoryUtil.getBestAI(list); } } if (choice == null) { // can't find anything left @@ -1766,7 +1875,9 @@ public final class AbilityFactoryChangeZone { tgt.resetTargets(); return false; } else { - // TODO is this good enough? for up to amounts? + if (!ComputerUtil.shouldCastLessThanMax(source)) { + return false; + } break; } } diff --git a/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java b/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java index 9e55b3fd3f0..c5d368e316e 100644 --- a/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java +++ b/src/main/java/forge/card/abilityfactory/AbilityFactoryPermanentState.java @@ -36,12 +36,9 @@ import forge.card.spellability.AbilitySub; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.game.phase.Combat; -import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.ComputerUtil; -import forge.game.player.ComputerUtilBlock; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiUtils; @@ -1018,7 +1015,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; @@ -1044,7 +1041,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; @@ -1058,31 +1055,6 @@ public class AbilityFactoryPermanentState { return true; } - /** - * Is it OK to cast this for less than the Max Targets? - * @param source - */ - private static boolean tapCastLessThanMax(final Card source) { - boolean ret = true; - if (source.getManaCost().countX() > 0) { - // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. - } else { - // Otherwise, if life is possibly in danger, then this is fine. - Combat combat = new Combat(); - combat.initiatePossibleDefenders(AllZone.getComputerPlayer()); - CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); - for (Card att : attackers) { - combat.addAttacker(att); - } - combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer())); - if (!CombatUtil.lifeInDanger(combat)) { - // Otherwise, return false. Do not play now. - ret = false; - } - } - return ret; - } - /** *

* tapUnpreferredTargeting. @@ -1168,7 +1140,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; @@ -1189,7 +1161,7 @@ public class AbilityFactoryPermanentState { } return false; } else { - if (!tapCastLessThanMax(source)) { + if (!ComputerUtil.shouldCastLessThanMax(source)) { return false; } break; diff --git a/src/main/java/forge/card/cardfactory/CardFactoryInstants.java b/src/main/java/forge/card/cardfactory/CardFactoryInstants.java index 22d4267d312..a65c6af5964 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryInstants.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryInstants.java @@ -266,7 +266,7 @@ public class CardFactoryInstants { public boolean canPlayAI() { final CardList graveList = AllZone.getHumanPlayer().getCardsIn(ZoneType.Graveyard); - final int maxX = ComputerUtil.getAvailableMana().size() - 1; + final int maxX = ComputerUtil.getAvailableMana(true).size() - 1; return (maxX >= 3) && (graveList.size() > 0); } }; diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java index 5744ca77ee7..fcb92e14208 100644 --- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java +++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java @@ -393,7 +393,7 @@ public class CardFactorySorceries { // the computer will at least destroy 2 more human creatures return ((computer.size() < (human.size() - 1)) || ((AllZone.getComputerPlayer().getLife() < 7) && !human - .isEmpty())) && (ComputerUtil.getAvailableMana().size() >= 7); + .isEmpty())) && (ComputerUtil.getAvailableMana(true).size() >= 7); } }; // SpellAbility @@ -905,7 +905,7 @@ public class CardFactorySorceries { @Override public boolean canPlayAI() { - final int maxX = ComputerUtil.getAvailableMana().size() - 1; + final int maxX = ComputerUtil.getAvailableMana(true).size() - 1; final int humanLife = AllZone.getHumanPlayer().getLife(); if (maxX >= humanLife) { targetPlayers.add(AllZone.getHumanPlayer()); diff --git a/src/main/java/forge/game/player/ComputerUtil.java b/src/main/java/forge/game/player/ComputerUtil.java index a25de470a68..73912ab4a00 100644 --- a/src/main/java/forge/game/player/ComputerUtil.java +++ b/src/main/java/forge/game/player/ComputerUtil.java @@ -125,7 +125,7 @@ public class ComputerUtil { if (sa instanceof AbilityStatic) { final Cost cost = sa.getPayCosts(); - if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0)) { + if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true)) { sa.resolve(); } else { final CostPayment pay = new CostPayment(cost, sa); @@ -473,7 +473,7 @@ public class ComputerUtil { * @return a boolean. */ public static boolean canPayCost(final SpellAbility sa, final Player player) { - if (!ComputerUtil.payManaCost(sa, player, true, 0)) { + if (!ComputerUtil.payManaCost(sa, player, true, 0, true)) { return false; } @@ -510,7 +510,7 @@ public class ComputerUtil { int xMana = 0; for (int i = 1; i < 99; i++) { - if (!ComputerUtil.payManaCost(sa, player, true, i)) { + if (!ComputerUtil.payManaCost(sa, player, true, i, true)) { break; } xMana = i; @@ -563,7 +563,7 @@ public class ComputerUtil { * a {@link forge.card.spellability.SpellAbility} object. */ public static void payManaCost(final SpellAbility sa) { - ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0); + ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true); } /** @@ -579,67 +579,17 @@ public class ComputerUtil { * (is for canPayCost, if true does not change the game state) * @param extraMana * a int. + * @param checkPlayable + * should we check if playable? use for hypothetical "can AI play this" * @return a boolean. * @since 1.0.15 */ public static boolean payManaCost(final SpellAbility sa, final Player player, final boolean test, - final int extraMana) { - final String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost(); - - ManaCost cost = new ManaCost(mana); - - cost = Singletons.getModel().getGameAction().getSpellCostChange(sa, cost); + final int extraMana, boolean checkPlayable) { + ManaCost cost = calculateManaCost(sa, test, extraMana); final ManaPool manapool = player.getManaPool(); - final Card card = sa.getSourceCard(); - // Tack xMana Payments into mana here if X is a set value - if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0)) { - - int manaToAdd = 0; - if (test && (extraMana > 0)) { - manaToAdd = extraMana * cost.getXcounter(); - } else { - // For Count$xPaid set PayX in the AFs then use that here - // Else calculate it as appropriate. - final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; - if (!card.getSVar(xSvar).equals("")) { - if (xSvar.equals("PayX")) { - manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X - } else { - manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter(); - } - } - } - - cost.increaseColorlessMana(manaToAdd); - if (!test) { - card.setXManaCostPaid(manaToAdd); - } - } - - // Make mana needed to avoid negative effect a mandatory cost for the AI - if (card.getSVar("ManaNeededToAvoidNegativeEffect") != "") { - final String[] negEffects = card.getSVar("ManaNeededToAvoidNegativeEffect").split(","); - int amountAdded = 0; - for (int nStr = 0; nStr < negEffects.length; nStr++) { - // convert long color strings to short color strings - if (negEffects[nStr].length() > 1) { - negEffects[nStr] = InputPayManaCostUtil.getShortColorString(negEffects[nStr]); - } - // make mana mandatory for AI - if (!cost.isColor(negEffects[nStr])) { - cost.combineManaCost(negEffects[nStr]); - amountAdded++; - } - } - cost.setManaNeededToAvoidNegativeEffect(negEffects); - // TODO: should it be an error condition if amountAdded is greater - // than the colorless in the original cost? (ArsenalNut - 120102) - // adjust colorless amount to account for added mana - cost.decreaseColorlessMana(amountAdded); - } - cost = manapool.payManaFromPool(sa, cost); if (cost.isPaid()) { @@ -649,82 +599,12 @@ public class ComputerUtil { } // get map of mana abilities - final HashMap> manaAbilityMap = ComputerUtil.mapManaSources(player); + final HashMap> manaAbilityMap = ComputerUtil.mapManaSources(player, checkPlayable); // initialize ArrayList list for mana needed final ArrayList> partSources = new ArrayList>(); final ArrayList partPriority = new ArrayList(); final String[] costParts = cost.toString().replace("X ", "").replace("P", "").split(" "); - Boolean foundAllSources = true; - if (manaAbilityMap.isEmpty()) { - foundAllSources = false; - } else { - final String[] shortColors = { "W", "U", "B", "R", "G" }; - // loop over cost parts - for (int nPart = 0; nPart < costParts.length; nPart++) { - final ArrayList srcFound = new ArrayList(); - // Test for: - // 1) Colorless - // 2) Split e.g. 2/G - // 3) Hybrid e.g. U/G - // defaults to single short color - if (costParts[nPart].matches("[0-9]+")) { // Colorless - srcFound.addAll(manaAbilityMap.get("1")); - } else if (costParts[nPart].contains("2/")) { // Split - final String colorKey = costParts[nPart].replace("2/", ""); - // add specified color sources first - if (manaAbilityMap.containsKey(colorKey)) { - srcFound.addAll(manaAbilityMap.get(colorKey)); - } - // add other available colors - for (final String color : shortColors) { - if (!colorKey.contains(color)) { - // Is source available? - if (manaAbilityMap.containsKey(color)) { - srcFound.addAll(manaAbilityMap.get(color)); - } - } - } - } else if (costParts[nPart].length() > 1) { // Hybrid - final String firstColor = costParts[nPart].substring(0, 1); - final String secondColor = costParts[nPart].substring(2); - final Boolean foundFirst = manaAbilityMap.containsKey(firstColor); - final Boolean foundSecond = manaAbilityMap.containsKey(secondColor); - if (foundFirst || foundSecond) { - if (!foundFirst) { - srcFound.addAll(manaAbilityMap.get(secondColor)); - } else if (!foundSecond) { - srcFound.addAll(manaAbilityMap.get(firstColor)); - } else if (manaAbilityMap.get(firstColor).size() > manaAbilityMap.get(secondColor).size()) { - srcFound.addAll(manaAbilityMap.get(firstColor)); - srcFound.addAll(manaAbilityMap.get(secondColor)); - } else { - srcFound.addAll(manaAbilityMap.get(secondColor)); - srcFound.addAll(manaAbilityMap.get(firstColor)); - } - } - } else { // single color - if (manaAbilityMap.containsKey(costParts[nPart])) { - srcFound.addAll(manaAbilityMap.get(costParts[nPart])); - } - } - - // add sources to array lists - partSources.add(nPart, srcFound); - // add to sorted priority list - if (srcFound.size() > 0) { - int i; - for (i = 0; i < partPriority.size(); i++) { - if (srcFound.size() <= partSources.get(i).size()) { - break; - } - } - partPriority.add(i, nPart); - } else { - foundAllSources = false; - break; - } - } - } + Boolean foundAllSources = findManaSources(manaAbilityMap, partSources, partPriority, costParts); if (!foundAllSources) { if (!test) { // real payment should not arrive here @@ -746,8 +626,7 @@ public class ComputerUtil { final int nPart = partPriority.get(nPriority); final ArrayList manaAbilities = partSources.get(nPart); final ManaCost costPart = new ManaCost(costParts[nPart]); - // Loop over mana abilities that can be used to current mana cost - // part + // Loop over mana abilities that can be used to current mana cost part for (final AbilityMana m : manaAbilities) { final Card sourceCard = m.getSourceCard(); @@ -758,13 +637,12 @@ public class ComputerUtil { // Check if AI can still play this mana ability m.setActivatingPlayer(player); - // if the AI can't pay the additional costs skip the mana - // ability - if (m.getPayCosts() != null) { + // if the AI can't pay the additional costs skip the mana ability + if (m.getPayCosts() != null && checkPlayable) { if (!ComputerUtil.canPayAdditionalCosts(m, player)) { continue; } - } else if (sourceCard.isTapped()) { + } else if (sourceCard.isTapped() && checkPlayable) { continue; } @@ -874,6 +752,157 @@ public class ComputerUtil { } // payManaCost() + /** + * Find all mana sources. + * @param manaAbilityMap + * @param partSources + * @param partPriority + * @param costParts + * @param foundAllSources + * @return Were all mana sources found? + */ + private static Boolean findManaSources(final HashMap> manaAbilityMap, + final ArrayList> partSources, final ArrayList partPriority, + final String[] costParts) { + final String[] shortColors = { "W", "U", "B", "R", "G" }; + Boolean foundAllSources; + if (manaAbilityMap.isEmpty()) { + foundAllSources = false; + } else { + foundAllSources = true; + // loop over cost parts + for (int nPart = 0; nPart < costParts.length; nPart++) { + final ArrayList srcFound = new ArrayList(); + // Test for: + // 1) Colorless + // 2) Split e.g. 2/G + // 3) Hybrid e.g. U/G + // defaults to single short color + if (costParts[nPart].matches("[0-9]+")) { // Colorless + srcFound.addAll(manaAbilityMap.get("1")); + } else if (costParts[nPart].contains("2/")) { // Split + final String colorKey = costParts[nPart].replace("2/", ""); + // add specified color sources first + if (manaAbilityMap.containsKey(colorKey)) { + srcFound.addAll(manaAbilityMap.get(colorKey)); + } + // add other available colors + for (final String color : shortColors) { + if (!colorKey.contains(color)) { + // Is source available? + if (manaAbilityMap.containsKey(color)) { + srcFound.addAll(manaAbilityMap.get(color)); + } + } + } + } else if (costParts[nPart].length() > 1) { // Hybrid + final String firstColor = costParts[nPart].substring(0, 1); + final String secondColor = costParts[nPart].substring(2); + final Boolean foundFirst = manaAbilityMap.containsKey(firstColor); + final Boolean foundSecond = manaAbilityMap.containsKey(secondColor); + if (foundFirst || foundSecond) { + if (!foundFirst) { + srcFound.addAll(manaAbilityMap.get(secondColor)); + } else if (!foundSecond) { + srcFound.addAll(manaAbilityMap.get(firstColor)); + } else if (manaAbilityMap.get(firstColor).size() > manaAbilityMap.get(secondColor).size()) { + srcFound.addAll(manaAbilityMap.get(firstColor)); + srcFound.addAll(manaAbilityMap.get(secondColor)); + } else { + srcFound.addAll(manaAbilityMap.get(secondColor)); + srcFound.addAll(manaAbilityMap.get(firstColor)); + } + } + } else { // single color + if (manaAbilityMap.containsKey(costParts[nPart])) { + srcFound.addAll(manaAbilityMap.get(costParts[nPart])); + } + } + + // add sources to array lists + partSources.add(nPart, srcFound); + // add to sorted priority list + if (srcFound.size() > 0) { + int i; + for (i = 0; i < partPriority.size(); i++) { + if (srcFound.size() <= partSources.get(i).size()) { + break; + } + } + partPriority.add(i, nPart); + } else { + foundAllSources = false; + break; + } + } + } + return foundAllSources; + } + + /** + * Calculate the ManaCost for the given SpellAbility. + * @param sa + * @param test + * @param extraMana + * @return ManaCost + */ + private static ManaCost calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) { + final String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost(); + + ManaCost cost = new ManaCost(mana); + + cost = Singletons.getModel().getGameAction().getSpellCostChange(sa, cost); + + final Card card = sa.getSourceCard(); + // Tack xMana Payments into mana here if X is a set value + if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0)) { + + int manaToAdd = 0; + if (test && (extraMana > 0)) { + manaToAdd = extraMana * cost.getXcounter(); + } else { + // For Count$xPaid set PayX in the AFs then use that here + // Else calculate it as appropriate. + final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; + if (!card.getSVar(xSvar).equals("")) { + if (xSvar.equals("PayX")) { + manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X + } else { + manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter(); + } + } + } + + cost.increaseColorlessMana(manaToAdd); + if (!test) { + card.setXManaCostPaid(manaToAdd); + } + } + + // Make mana needed to avoid negative effect a mandatory cost for the AI + if (card.getSVar("ManaNeededToAvoidNegativeEffect") != "") { + final String[] negEffects = card.getSVar("ManaNeededToAvoidNegativeEffect").split(","); + int amountAdded = 0; + for (int nStr = 0; nStr < negEffects.length; nStr++) { + // convert long color strings to short color strings + if (negEffects[nStr].length() > 1) { + negEffects[nStr] = InputPayManaCostUtil.getShortColorString(negEffects[nStr]); + } + // make mana mandatory for AI + if (!cost.isColor(negEffects[nStr])) { + cost.combineManaCost(negEffects[nStr]); + amountAdded++; + } + } + cost.setManaNeededToAvoidNegativeEffect(negEffects); + // TODO: should it be an error condition if amountAdded is greater + // than the colorless in the original cost? (ArsenalNut - 120102) + // adjust colorless amount to account for added mana + cost.decreaseColorlessMana(amountAdded); + } + return cost; + } + /** *

* getProduceableColors. @@ -914,11 +943,12 @@ public class ComputerUtil { *

* getAvailableMana. *

+ * @param checkPlayable * * @return a {@link forge.CardList} object. */ - public static CardList getAvailableMana() { - return ComputerUtil.getAvailableMana(AllZone.getComputerPlayer()); + public static CardList getAvailableMana(boolean checkPlayable) { + return ComputerUtil.getAvailableMana(AllZone.getComputerPlayer(), checkPlayable); } // getAvailableMana() // gets available mana sources and sorts them @@ -929,21 +959,26 @@ public class ComputerUtil { * * @param player * a {@link forge.game.player.Player} object. + * @param checkPlayable * @return a {@link forge.CardList} object. */ - public static CardList getAvailableMana(final Player player) { + public static CardList getAvailableMana(final Player player, final boolean checkPlayable) { final CardList list = player.getCardsIn(ZoneType.Battlefield); final CardList manaSources = list.filter(new CardListFilter() { @Override public boolean addCard(final Card c) { - for (final AbilityMana am : c.getAIPlayableMana()) { - am.setActivatingPlayer(player); - if (am.canPlay()) { - return true; + if (checkPlayable) { + for (final AbilityMana am : c.getAIPlayableMana()) { + am.setActivatingPlayer(player); + if (am.canPlay()) { + return true; + } } + + return false; + } else { + return true; } - - return false; } }); // CardListFilter @@ -1098,9 +1133,10 @@ public class ComputerUtil { * * @param player * a {@link forge.game.player.Player} object. + * @param checkPlayable TODO * @return HashMap */ - public static HashMap> mapManaSources(final Player player) { + public static HashMap> mapManaSources(final Player player, boolean checkPlayable) { final HashMap> manaMap = new HashMap>(); final ArrayList whiteSources = new ArrayList(); @@ -1112,7 +1148,7 @@ public class ComputerUtil { final ArrayList snowSources = new ArrayList(); // Get list of current available mana sources - final CardList manaSources = ComputerUtil.getAvailableMana(); + final CardList manaSources = ComputerUtil.getAvailableMana(checkPlayable); // Loop over all mana sources for (int i = 0; i < manaSources.size(); i++) { @@ -1122,7 +1158,7 @@ public class ComputerUtil { // Loop over all mana abilities for a source for (final AbilityMana m : manaAbilities) { m.setActivatingPlayer(AllZone.getComputerPlayer()); - if (!m.canPlay()) { + if (!m.canPlay() && checkPlayable) { continue; } @@ -1999,4 +2035,29 @@ public class ComputerUtil { } return false; } + + /** + * Is it OK to cast this for less than the Max Targets? + * @param source + */ + public static boolean shouldCastLessThanMax(final Card source) { + boolean ret = true; + if (source.getManaCost().countX() > 0) { + // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. + } else { + // Otherwise, if life is possibly in danger, then this is fine. + Combat combat = new Combat(); + combat.initiatePossibleDefenders(AllZone.getComputerPlayer()); + CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer()); + for (Card att : attackers) { + combat.addAttacker(att); + } + combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer())); + if (!CombatUtil.lifeInDanger(combat)) { + // Otherwise, return false. Do not play now. + ret = false; + } + } + return ret; + } }