From 85213873126176a3016b83413792cf78938399a9 Mon Sep 17 00:00:00 2001 From: Seravy Date: Tue, 13 Feb 2018 17:40:03 +0100 Subject: [PATCH 1/2] AI can play Necrologia --- .../main/java/forge/ai/AiCostDecision.java | 3 ++ .../src/main/java/forge/ai/ComputerUtil.java | 29 +++++++++++++++++++ .../java/forge/ai/ComputerUtilCombat.java | 18 ++++++++---- .../main/java/forge/ai/ability/DrawAi.java | 12 ++++++++ forge-gui/res/cardsfolder/n/necrologia.txt | 2 +- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 4d6e94929dd..2d93d12cf52 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -347,6 +347,9 @@ public class AiCostDecision extends CostDecisionMakerBase { if (source.getName().equals("Maralen of the Mornsong Avatar")) { return PaymentDecision.number(2); } + if (source.getName().equals("Necrologia")) { + return PaymentDecision.number(Integer.parseInt(ability.getSVar("ChosenX"))); + } return null; } else { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 8145507723b..4744818ca1e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2831,4 +2831,33 @@ public class ComputerUtil { return srcList; } + + // check if AI life is in dange/serious danger based on next expected combat + // assuming a loss of payment life + public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { + Player opponent = ComputerUtil.getOpponentFor(ai); + // test whether the human can kill the ai next turn + Combat combat = new Combat(opponent); + boolean containsAttacker = false; + for (Card att : opponent.getCreaturesInPlay()) { + if (ComputerUtilCombat.canAttackNextTurn(att, ai)) { + combat.addAttacker(att, ai); + containsAttacker = true; + } + } + if (!containsAttacker) { + return false; + } + AiBlockController block = new AiBlockController(ai); + block.assignBlockersForCombat(combat); + + if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) { + return true; + } + if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) { + return true; + } + return false; + + } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index c699e3eec7f..f9522430688 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -382,6 +382,10 @@ public class ComputerUtilCombat { * @return a boolean. */ public static boolean lifeInDanger(final Player ai, final Combat combat) { + return lifeInDanger(ai, combat, 0); + } + + public static boolean lifeInDanger(final Player ai, final Combat combat, final int payment) { // life in danger only cares about the player's life. Not Planeswalkers' life if (ai.cantLose() || combat == null || combat.getAttackingPlayer() == ai) { return false; @@ -401,12 +405,12 @@ public class ComputerUtilCombat { // check for creatures that must be blocked final List attackers = combat.getAttackersOf(ai); - + final List threateningCommanders = getLifeThreateningCommanders(ai,combat); for (final Card attacker : attackers) { - final List blockers = combat.getBlockers(attacker); + final List blockers = combat.getBlockers(attacker); if (blockers.isEmpty()) { if (!attacker.getSVar("MustBeBlocked").equals("")) { @@ -429,7 +433,7 @@ public class ComputerUtilCombat { } } - if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < Math.min(4, ai.getLife()) + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) - payment < Math.min(4, ai.getLife()) && !ai.cantLoseForZeroOrLessLife()) { return true; } @@ -463,12 +467,16 @@ public class ComputerUtilCombat { * @return a boolean. */ public static boolean lifeInSeriousDanger(final Player ai, final Combat combat) { + return lifeInSeriousDanger(ai, combat, 0); + } + + public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) { // life in danger only cares about the player's life. Not about a // Planeswalkers life if (ai.cantLose() || combat == null) { return false; } - + final List threateningCommanders = ComputerUtilCombat.getLifeThreateningCommanders(ai, combat); // check for creatures that must be blocked @@ -488,7 +496,7 @@ public class ComputerUtilCombat { } } - if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < 1 && !ai.cantLoseForZeroOrLessLife()) { + if (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) - payment < 1 && !ai.cantLoseForZeroOrLessLife()) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 9ffe3e22f16..48ab1eadc73 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -249,6 +249,18 @@ public class DrawAi extends SpellAbilityAi { if (sa.getSVar(num).equals("Count$Converge")) { numCards = ComputerUtilMana.getConvergeCount(sa, ai); } + // Necrologia, Pay X Life : Draw X Cards + if (sa.getSVar(num).equals("XChoice")) { + // Draw up to max hand size but leave at least 3 in library + numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3); + // But no more than what's "safe" and doesn't risk a near death experience + // Maybe would be better to check for "serious danger" and take more risk? + while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) { + numCards--; + } + sa.setSVar("ChosenX", Integer.toString(numCards)); + source.setSVar("ChosenX", Integer.toString(numCards)); + } } // Logic for cards that require special handling diff --git a/forge-gui/res/cardsfolder/n/necrologia.txt b/forge-gui/res/cardsfolder/n/necrologia.txt index d1a9487f995..2f88ead3188 100644 --- a/forge-gui/res/cardsfolder/n/necrologia.txt +++ b/forge-gui/res/cardsfolder/n/necrologia.txt @@ -2,7 +2,7 @@ Name:Necrologia ManaCost:3 B B Types:Instant Text:Cast CARDNAME only during your end step.\r\n -A:SP$ Draw | Cost$ 3 B B PayLife | NumCards$ ChosenX | Defined$ You | ActivationPhases$ End of Turn | PlayerTurn$ True | References$ X | SpellDescription$ Draw X cards. +A:SP$ Draw | Cost$ 3 B B PayLife | NumCards$ X | Defined$ You | ActivationPhases$ End of Turn | PlayerTurn$ True | References$ X | SpellDescription$ Draw X cards. SVar:X:XChoice #ChosenX SVar created by Cost payment SVar:RemAIDeck:True From ead64864f44bcc26b111d38737999649f03bebf2 Mon Sep 17 00:00:00 2001 From: Seravy Date: Tue, 13 Feb 2018 18:07:51 +0100 Subject: [PATCH 2/2] Now it actually works and doesn't break the card. Marked card AI playable. Note : This also adds a new function to ComputerUtil,that checks if the AI is in danger of being killed, or if it has a same amount of life remaning - useful for future "pay life" AI decisions. --- forge-ai/src/main/java/forge/ai/ComputerUtil.java | 10 ++++++++-- forge-ai/src/main/java/forge/ai/ability/DrawAi.java | 6 ++++-- forge-gui/res/cardsfolder/n/necrologia.txt | 3 +-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 4744818ca1e..41318103cd5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2832,8 +2832,10 @@ public class ComputerUtil { return srcList; } - // check if AI life is in dange/serious danger based on next expected combat - // assuming a loss of payment life + // Check if AI life is in danger/serious danger based on next expected combat + // assuming a loss of "payment" life + // call this to determine if it's safe to use a life payment spell + // or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell. public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { Player opponent = ComputerUtil.getOpponentFor(ai); // test whether the human can kill the ai next turn @@ -2851,6 +2853,10 @@ public class ComputerUtil { AiBlockController block = new AiBlockController(ai); block.assignBlockersForCombat(combat); + // TODO predict other, noncombat sources of damage and add them to the "payment" variable. + // examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc + // If added, might need a parameter to define whether we want to check all threats or combat threats. + if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 48ab1eadc73..2e5c7715823 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -249,8 +249,11 @@ public class DrawAi extends SpellAbilityAi { if (sa.getSVar(num).equals("Count$Converge")) { numCards = ComputerUtilMana.getConvergeCount(sa, ai); } + } + + if (num != null && num.equals("ChosenX")) { // Necrologia, Pay X Life : Draw X Cards - if (sa.getSVar(num).equals("XChoice")) { + if (sa.getSVar("X").equals("XChoice")) { // Draw up to max hand size but leave at least 3 in library numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3); // But no more than what's "safe" and doesn't risk a near death experience @@ -262,7 +265,6 @@ public class DrawAi extends SpellAbilityAi { source.setSVar("ChosenX", Integer.toString(numCards)); } } - // Logic for cards that require special handling if ("YawgmothsBargain".equals(logic)) { return SpecialCardAi.YawgmothsBargain.consider(ai, sa); diff --git a/forge-gui/res/cardsfolder/n/necrologia.txt b/forge-gui/res/cardsfolder/n/necrologia.txt index 2f88ead3188..4cfbc6b3919 100644 --- a/forge-gui/res/cardsfolder/n/necrologia.txt +++ b/forge-gui/res/cardsfolder/n/necrologia.txt @@ -2,9 +2,8 @@ Name:Necrologia ManaCost:3 B B Types:Instant Text:Cast CARDNAME only during your end step.\r\n -A:SP$ Draw | Cost$ 3 B B PayLife | NumCards$ X | Defined$ You | ActivationPhases$ End of Turn | PlayerTurn$ True | References$ X | SpellDescription$ Draw X cards. +A:SP$ Draw | Cost$ 3 B B PayLife | NumCards$ ChosenX | Defined$ You | ActivationPhases$ End of Turn | PlayerTurn$ True | References$ X | SpellDescription$ Draw X cards. SVar:X:XChoice #ChosenX SVar created by Cost payment -SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/necrologia.jpg Oracle:Cast Necrologia only during your end step.\nAs an additional cost to cast Necrologia, pay X life.\nDraw X cards.