mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
- Added an experimental logic for Intuition (currently disabled by default, pending testing and fine-tuning).
This commit is contained in:
@@ -94,9 +94,10 @@ public enum AiProps { /** */
|
|||||||
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
|
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
|
||||||
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
|
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
|
||||||
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
|
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||||
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"); /** */
|
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||||
// Experimental features, must be removed after extensive testing and, ideally, defaulting
|
// Experimental features, must be removed after extensive testing and, ideally, defaulting
|
||||||
// <-- there are currently no experimental options here -->
|
// <-- there are currently no experimental options here -->
|
||||||
|
INTUITION_SPECIAL_LOGIC ("false"); /** */
|
||||||
|
|
||||||
private final String strDefaultVal;
|
private final String strDefaultVal;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import forge.card.ColorSet;
|
|||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -38,8 +39,12 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
import forge.util.TextUtil;
|
||||||
|
import forge.util.maps.LinkedHashMapToAmount;
|
||||||
|
import forge.util.maps.MapToAmount;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -521,6 +526,133 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intuition (and any other card that might potentially let you pick N cards from the library,
|
||||||
|
// one of which will then be picked for you by the opponent)
|
||||||
|
public static class Intuition {
|
||||||
|
public static CardCollection considerMultiple(final Player ai, final SpellAbility sa) {
|
||||||
|
if (ai.getController().isAI()) {
|
||||||
|
if (!((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.INTUITION_SPECIAL_LOGIC)) {
|
||||||
|
return new CardCollection(); // fall back to standard ChangeZoneAi considerations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int changeNum = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("ChangeNum"), sa);
|
||||||
|
CardCollection lib = CardLists.filter(ai.getCardsIn(ZoneType.Library),
|
||||||
|
Predicates.not(CardPredicates.nameEquals(sa.getHostCard().getName())));
|
||||||
|
Collections.sort(lib, CardLists.CmcComparatorInv);
|
||||||
|
|
||||||
|
// Additional cards which are difficult to auto-classify but which are generally good to Intuition for
|
||||||
|
List<String> highPriorityNamedCards = Lists.newArrayList("Accumulated Knowledge");
|
||||||
|
|
||||||
|
// figure out how many of each card we have in deck
|
||||||
|
MapToAmount<String> cardAmount = new LinkedHashMapToAmount<>();
|
||||||
|
for (Card c : lib) {
|
||||||
|
cardAmount.add(c.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trix: see if we can complete the combo
|
||||||
|
int numIllusionsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals("Illusions of Grandeur")).size();
|
||||||
|
int numDonateInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals("Donate")).size();
|
||||||
|
int numIllusionsInLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.nameEquals("Illusions of Grandeur")).size();
|
||||||
|
int numIllusionsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Illusions of Grandeur")).size();
|
||||||
|
int numDonateInLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.nameEquals("Donate")).size();
|
||||||
|
CardCollection comboList = new CardCollection();
|
||||||
|
if ((numIllusionsInHand > 0 || numIllusionsOTB > 0) && numDonateInHand == 0 && numDonateInLib >= 3) {
|
||||||
|
for (Card c : lib) {
|
||||||
|
if (c.getName().equals("Donate")) {
|
||||||
|
comboList.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comboList;
|
||||||
|
} else if (numDonateInHand > 0 && numIllusionsInHand == 0 && numIllusionsInLib >= 3) {
|
||||||
|
for (Card c : lib) {
|
||||||
|
if (c.getName().equals("Illusions of Grandeur")) {
|
||||||
|
comboList.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comboList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a priority list for cards that we have no more than 4 of and that are not lands
|
||||||
|
CardCollection libPriorityList = new CardCollection();
|
||||||
|
CardCollection libHighPriorityList = new CardCollection();
|
||||||
|
CardCollection libLowPriorityList = new CardCollection();
|
||||||
|
List<String> processed = Lists.newArrayList();
|
||||||
|
for (int i = 4; i > 0; i--) {
|
||||||
|
for (Card c : lib) {
|
||||||
|
if (cardAmount.get(c.getName()) == i && !c.isLand() && !processed.contains(c.getName())) {
|
||||||
|
// if it's a card that is generally good to place in the graveyard, also add it
|
||||||
|
// to the mix
|
||||||
|
boolean canRetFromGrave = false;
|
||||||
|
String name = c.getName().replace(',', ';');
|
||||||
|
for (Trigger t : c.getTriggers()) {
|
||||||
|
SpellAbility ab = null;
|
||||||
|
if (t.hasParam("Execute")) {
|
||||||
|
ab = AbilityFactory.getAbility(c.getSVar(t.getParam("Execute")), c);
|
||||||
|
}
|
||||||
|
if (ab == null) { continue; }
|
||||||
|
|
||||||
|
if (ab.getApi() == ApiType.ChangeZone
|
||||||
|
&& "Self".equals(ab.getParam("Defined"))
|
||||||
|
&& "Graveyard".equals(ab.getParam("Origin"))
|
||||||
|
&& "Battlefield".equals(ab.getParam("Destination"))) {
|
||||||
|
canRetFromGrave = true;
|
||||||
|
}
|
||||||
|
if (ab.getApi() == ApiType.ChangeZoneAll
|
||||||
|
&& TextUtil.concatNoSpace("Creature.named", name).equals(ab.getParam("ChangeType"))
|
||||||
|
&& "Graveyard".equals(ab.getParam("Origin"))
|
||||||
|
&& "Battlefield".equals(ab.getParam("Destination"))) {
|
||||||
|
canRetFromGrave = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean isGoodToPutInGrave = c.hasSVar("DiscardMe") || canRetFromGrave
|
||||||
|
|| (ComputerUtil.isPlayingReanimator(ai) && c.isCreature());
|
||||||
|
|
||||||
|
for (Card c1 : lib) {
|
||||||
|
if (c1.getName().equals(c.getName())) {
|
||||||
|
if (CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(c1.getName())).isEmpty()
|
||||||
|
&& ComputerUtilMana.hasEnoughManaSourcesToCast(c1.getFirstSpellAbility(), ai)) {
|
||||||
|
// Try not to search for things we already have in hand or that we can't cast
|
||||||
|
libPriorityList.add(c1);
|
||||||
|
} else {
|
||||||
|
libLowPriorityList.add(c1);
|
||||||
|
}
|
||||||
|
if (isGoodToPutInGrave || highPriorityNamedCards.contains(c.getName())) {
|
||||||
|
libHighPriorityList.add(c1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processed.add(c.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're playing Reanimator, we're really interested just in the highest CMC spells, not the
|
||||||
|
// ones we necessarily have multiples of
|
||||||
|
if (ComputerUtil.isPlayingReanimator(ai)) {
|
||||||
|
Collections.sort(libHighPriorityList, CardLists.CmcComparatorInv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try to grab something that is hopefully decent to grab, in priority order
|
||||||
|
CardCollection chosen = new CardCollection();
|
||||||
|
if (libHighPriorityList.size() >= changeNum) {
|
||||||
|
for (int i = 0; i < changeNum; i++) {
|
||||||
|
chosen.add(libHighPriorityList.get(i));
|
||||||
|
}
|
||||||
|
} else if (libPriorityList.size() >= changeNum) {
|
||||||
|
for (int i = 0; i < changeNum; i++) {
|
||||||
|
chosen.add(libPriorityList.get(i));
|
||||||
|
}
|
||||||
|
} else if (libLowPriorityList.size() >= changeNum) {
|
||||||
|
for (int i = 0; i < changeNum; i++) {
|
||||||
|
chosen.add(libLowPriorityList.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Living Death (and possibly other similar cards using AILogic LivingDeath)
|
// Living Death (and possibly other similar cards using AILogic LivingDeath)
|
||||||
public static class LivingDeath {
|
public static class LivingDeath {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* too much: blink/bounce/exile/tutor/Raise Dead/Surgical Extraction/......
|
* too much: blink/bounce/exile/tutor/Raise Dead/Surgical Extraction/......
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// multipleCardsToChoose is used by Intuition and can be adapted to be used by other
|
||||||
|
// cards where multiple cards are fetched at once and they need to be coordinated
|
||||||
|
private static CardCollection multipleCardsToChoose = new CardCollection();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
|
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
|
||||||
@@ -60,6 +64,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
// Checks for "return true" unlike checkAiLogic()
|
// Checks for "return true" unlike checkAiLogic()
|
||||||
|
|
||||||
|
multipleCardsToChoose.clear();
|
||||||
String aiLogic = sa.getParam("AILogic");
|
String aiLogic = sa.getParam("AILogic");
|
||||||
if (aiLogic != null) {
|
if (aiLogic != null) {
|
||||||
if (aiLogic.equals("Always")) {
|
if (aiLogic.equals("Always")) {
|
||||||
@@ -70,8 +76,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return this.doSacAndReturnFromGraveLogic(aiPlayer, sa);
|
return this.doSacAndReturnFromGraveLogic(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("Necropotence")) {
|
} else if (aiLogic.equals("Necropotence")) {
|
||||||
return SpecialCardAi.Necropotence.consider(aiPlayer, sa);
|
return SpecialCardAi.Necropotence.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("SameName")) { // Declaration in Stone
|
} else if (aiLogic.equals("SameName")) { // Declaration in Stone
|
||||||
return this.doSameNameLogic(aiPlayer, sa);
|
return this.doSameNameLogic(aiPlayer, sa);
|
||||||
|
} else if (aiLogic.equals("Intuition")) {
|
||||||
|
// This logic only fills the multiple cards array, the decision to play is made
|
||||||
|
// separately in hiddenOriginCanPlayAI later.
|
||||||
|
multipleCardsToChoose = SpecialCardAi.Intuition.considerMultiple(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isHidden(sa)) {
|
if (isHidden(sa)) {
|
||||||
@@ -1343,6 +1353,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
||||||
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
||||||
return SpecialCardAi.SurvivalOfTheFittest.considerCardToGet(decider, sa);
|
return SpecialCardAi.SurvivalOfTheFittest.considerCardToGet(decider, sa);
|
||||||
|
} else if ("Intuition".equals(logic)) {
|
||||||
|
if (!multipleCardsToChoose.isEmpty()) {
|
||||||
|
Card choice = multipleCardsToChoose.get(0);
|
||||||
|
multipleCardsToChoose.remove(0);
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fetchList.isEmpty()) {
|
if (fetchList.isEmpty()) {
|
||||||
|
|||||||
@@ -168,4 +168,8 @@ BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=5
|
|||||||
# -- Experimental feature toggles which only exist until the testing procedure for the relevant --
|
# -- Experimental feature toggles which only exist until the testing procedure for the relevant --
|
||||||
# -- features is over. These toggles will be removed later, or may be reintroduced under a --
|
# -- features is over. These toggles will be removed later, or may be reintroduced under a --
|
||||||
# -- different name if necessary --
|
# -- different name if necessary --
|
||||||
# <-- There are currently no experimental options here -->
|
|
||||||
|
# Experimental logic for Intuition that makes it work better in reanimator decks, combo decks like Trix,
|
||||||
|
# and generally tries to either grab cards that are also good to put in the graveyard or that are available
|
||||||
|
# in multiples in the deck.
|
||||||
|
INTUITION_SPECIAL_LOGIC=true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Intuition
|
Name:Intuition
|
||||||
ManaCost:2 U
|
ManaCost:2 U
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ ChangeZone | Cost$ 2 U | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ 3 | RememberChanged$ True | Reveal$ True | Shuffle$ False | SubAbility$ DBChangeZone1 | StackDescription$ Search your library for three cards and reveal them. Target opponent chooses one. Put that card into your hand and the rest into your graveyard. Then shuffle your library. | SpellDescription$ Search your library for three cards and reveal them. Target opponent chooses one. Put that card into your hand and the rest into your graveyard. Then shuffle your library.
|
A:SP$ ChangeZone | Cost$ 2 U | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ 3 | RememberChanged$ True | Reveal$ True | Shuffle$ False | AILogic$ Intuition | SubAbility$ DBChangeZone1 | StackDescription$ Search your library for three cards and reveal them. Target opponent chooses one. Put that card into your hand and the rest into your graveyard. Then shuffle your library. | SpellDescription$ Search your library for three cards and reveal them. Target opponent chooses one. Put that card into your hand and the rest into your graveyard. Then shuffle your library.
|
||||||
SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | Chooser$ Opponent | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card for the hand | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None
|
SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | Chooser$ Opponent | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card for the hand | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None
|
||||||
SVar:DBChangeZone2:DB$ ChangeZoneAll | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Shuffle$ True | StackDescription$ None | SubAbility$ DBCleanup
|
SVar:DBChangeZone2:DB$ ChangeZoneAll | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Shuffle$ True | StackDescription$ None | SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
|
|||||||
Reference in New Issue
Block a user