- Added an experimental logic for Intuition (currently disabled by default, pending testing and fine-tuning).

This commit is contained in:
Agetian
2017-09-19 15:15:58 +00:00
parent d1ba022ce9
commit c79d04f29a
5 changed files with 159 additions and 6 deletions

View File

@@ -94,9 +94,10 @@ public enum AiProps { /** */
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
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
// <-- there are currently no experimental options here -->
INTUITION_SPECIAL_LOGIC ("false"); /** */
private final String strDefaultVal;

View File

@@ -25,6 +25,7 @@ import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -38,8 +39,12 @@ import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType;
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 java.util.Arrays;
@@ -520,7 +525,134 @@ public class SpecialCardAi {
return chosen;
}
}
// 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)
public static class LivingDeath {
public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -37,7 +37,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
* idea to re-factor ChangeZoneAi into more specific effects since it is really doing
* 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
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
@@ -60,6 +64,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
// Checks for "return true" unlike checkAiLogic()
multipleCardsToChoose.clear();
String aiLogic = sa.getParam("AILogic");
if (aiLogic != null) {
if (aiLogic.equals("Always")) {
@@ -70,8 +76,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
return this.doSacAndReturnFromGraveLogic(aiPlayer, sa);
} else if (aiLogic.equals("Necropotence")) {
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);
} 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)) {
@@ -1343,6 +1353,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
} else if ("SurvivalOfTheFittest".equals(logic)) {
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()) {

View File

@@ -168,4 +168,8 @@ BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=5
# -- 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 --
# -- 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

View File

@@ -1,7 +1,7 @@
Name:Intuition
ManaCost:2 U
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:DBChangeZone2:DB$ ChangeZoneAll | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Shuffle$ True | StackDescription$ None | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True