From c685fc5919a567a99f962de69b4caef7af23b4c8 Mon Sep 17 00:00:00 2001 From: Agetian Date: Sat, 3 Oct 2015 15:09:34 +0000 Subject: [PATCH] - Smarter and more concise mechanism for deciding whether to play a shockland untapped or tapped. --- .../src/main/java/forge/ai/ComputerUtil.java | 64 ++++--------------- .../main/java/forge/ai/ComputerUtilCost.java | 29 ++++----- 2 files changed, 24 insertions(+), 69 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 812dfde010e..d62a0b7303e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1942,66 +1942,26 @@ public class ComputerUtil { return bestBoardPosition; } - public static boolean hasGoodTargetForAura(final Player ai, final Card aura) { - // This is currently used by ComputerUtilCost.willPayUnlessCost to determine if there's a viable target for a spell - // that can be paid for with an untapped shockland. - - if (ai == null || aura == null) { + public static boolean hasReasonToPlayCardThisTurn(final Player ai, final Card c) { + if (ai == null || c == null) { return false; } if (!(ai.getController() instanceof PlayerControllerAi)) { - System.err.println("Unexpected behavior: ComputerUtil::hasGoodTargetforAura called with the non-AI player as a parameter."); + System.err.println("Unexpected behavior: ComputerUtil::getReasonToPlayCard called with the non-AI player as a parameter."); return false; } - if (aura.getFirstAttachSpell() == null) { - // Something went majorly wrong here, should never happen - System.err.println("Unexpected behavior: first attach spell for Aura card " + aura.getName() + " was null in ComputerUtil::hasGoodTargetForAura."); - return false; - } - - boolean hasTarget = false; - boolean aiHasTargets = false, oppHasTargets = false; - - aiHasTargets = !CardLists.filter(ai.getAllCards(), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty(); - for (Player p : ai.getOpponents()) { - if (!CardLists.filter(p.getAllCards(), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty()) { - oppHasTargets = true; - break; + for (SpellAbility sa : c.getAllPossibleAbilities(ai, true)) { + if (sa.getApi() == ApiType.Counter) { + // return true for counterspells so that the AI can take into account that it may need to cast it later in the opponent's turn + return true; + } + AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(sa); + if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) { + return true; } } - boolean isCurse = false; - for (SpellAbility ability : aura.getAllSpellAbilities()) { - if (ability.isCurse() || (ability.hasParam("AILogic") && ability.getParam("AILogic").equals("Curse"))) { - isCurse = true; - break; - } - } - if (isCurse && oppHasTargets) { - hasTarget = true; - } else if (!isCurse && aiHasTargets) { - hasTarget = true; - } - - return hasTarget; - } - - public static boolean hasReasonToPlaySaThisTurn(final Player ai, final SpellAbility sa) { - // This is currently used by ComputerUtilCost.willPayUnlessCost to determine if there's a viable reason to cast a spell - // that can be paid for with an untapped shockland. - - if (ai == null || sa == null) { - return false; - } - if (!(ai.getController() instanceof PlayerControllerAi)) { - System.err.println("Unexpected behavior: ComputerUtil::hasReasonToPlaySaThisTurn called with the non-AI player as a parameter."); - return false; - } - - AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(sa); - boolean willPlay = decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2; - - return willPlay; + return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 6e5666f86bc..81fa9530b88 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -7,6 +7,7 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.card.CardPredicates.Presets; import forge.game.card.CounterType; import forge.game.combat.Combat; import forge.game.cost.*; @@ -417,25 +418,19 @@ public class ComputerUtilCost { } } else if (shockland) { if (payer.getLife() > 3 && payer.canPayLife(2)) { - // If the new land size would equal the CMC of a card in AIs hand, play it untapped final int landsize = payer.getLandsInPlay().size() + 1; for (Card c : payer.getCardsIn(ZoneType.Hand)) { - int cmc = c.getCMC(); + // if the new land size would equal the CMC of a card in AIs hand, consider playing it untapped, + // otherwise don't bother running other checks + if (landsize != c.getCMC()) { + continue; + } + // try to determine in the AI is actually planning to play a spell ability from the card + boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c); // try to determine if the mana shards provided by the lands would be applicable to pay the mana cost boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor()); // try to determine that there is a valid target for a spell (very basic, consider expanding) - boolean requirementsMet = true; - if (c.isAura()) { - // for auras, make sure that a good enough target exists - requirementsMet = ComputerUtil.hasGoodTargetForAura(payer, c); - } else if (c.isSorcery()) { - // for sorceries with one mode, consider actually checking for a reason for the AI to decide to play the spell ability - if (c.getAllSpellAbilities().size() == 1) { - SpellAbility ability = c.getFirstSpellAbility(); - requirementsMet = ComputerUtil.hasReasonToPlaySaThisTurn(payer, ability); - } - } - if (landsize == cmc && canPay && requirementsMet) { + if (canPay && willPlay) { return true; } } @@ -492,15 +487,15 @@ public class ComputerUtilCost { } public static Set getAvailableManaColors(Player ai, List additionalLands) { - CardCollection lands = ai.getLandsInPlay(); + CardCollection cardsToConsider = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Presets.UNTAPPED); Set colorsAvailable = new HashSet<>(); if (additionalLands != null) { GameActionUtil.grantBasicLandsManaAbilities(additionalLands); - lands.addAll(additionalLands); + cardsToConsider.addAll(additionalLands); } - for (Card c : lands) { + for (Card c : cardsToConsider) { for (SpellAbility sa : c.getManaAbilities()) { colorsAvailable.add(sa.getManaPart().getOrigProduced()); }