- AI improvements: restored playability of Exhaustion, made Cursed Scroll AI-playable, added some additional code support for programmable Exert logic (Ahn-Crop Champion is an example), added a bit of threshold to Living Death AI, moved Momir Vig Avatar logic to SpecialCardAi.

This commit is contained in:
Agetian
2017-08-27 18:11:06 +00:00
parent d8be84b617
commit 45d71d0c5f
7 changed files with 120 additions and 25 deletions

View File

@@ -29,6 +29,7 @@ import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView;
import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
import forge.game.card.*;
@@ -40,6 +41,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.collect.FCollectionView;
@@ -1212,18 +1214,51 @@ public class AiAttackController {
}
if (sa.usesTargeting()) {
sa.setActivatingPlayer(c.getController());
if (CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).isEmpty()) {
List<Card> validTargets = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
if (validTargets.isEmpty()) {
missTarget = true;
break;
} else if (sa.isCurse() && CardLists.filter(validTargets,
CardPredicates.isControlledByAnyOf(c.getController().getOpponents())).isEmpty()) {
// e.g. Ahn-Crop Crasher - the effect is only good when aimed at opponent's creatures
missTarget = true;
break;
}
}
}
if (missTarget) {
continue;
}
if (random.nextBoolean()) {
// A specific AI condition for Exert: if specified on the card, the AI will always
// exert creatures that meet this condition
if (c.hasSVar("AIExertCondition")) {
if (!c.getSVar("AIExertCondition").isEmpty()) {
final String needsToExert = c.getSVar("AIExertCondition");
int x = 0;
int y = 0;
String sVar = needsToExert.split(" ")[0];
String comparator = needsToExert.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
}
if (Expressions.compare(x, comparator, y)) {
shouldExert = true;
}
}
}
if (!shouldExert && random.nextBoolean()) {
// TODO Improve when the AI wants to use Exert powers
shouldExert = true;
}

View File

@@ -892,6 +892,8 @@ public class PlayerControllerAi extends PlayerController {
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard());
return ComputerUtilCard.getMostProminentCardName(cards);
} else if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.chooseCard(player, sa);
}
} else {
CardCollectionView list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());

View File

@@ -176,6 +176,35 @@ public class SpecialCardAi {
}
}
// Cursed Scroll
public static class CursedScroll {
public static boolean consider(Player ai, SpellAbility sa) {
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
if (hand.isEmpty()) {
return false;
}
// For now, see if all cards in hand have the same name, and then proceed if true
return CardLists.filter(hand, CardPredicates.nameEquals(hand.getFirst().getName())).size() == hand.size();
}
public static String chooseCard(Player ai, SpellAbility sa) {
int maxCount = 0;
Card best = null;
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
for (Card c : ai.getCardsIn(ZoneType.Hand)) {
int count = CardLists.filter(hand, CardPredicates.nameEquals(c.getName())).size();
if (count > maxCount) {
maxCount = count;
best = c;
}
}
return best.getName();
}
}
// Desecration Demon
public static class DesecrationDemon {
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
@@ -360,6 +389,8 @@ public class SpecialCardAi {
public static class LivingDeath {
public static boolean consider(Player ai, SpellAbility sa) {
int aiBattlefieldPower = 0, aiGraveyardPower = 0;
int threshold = 320; // approximately a 4/4 Flying creature worth of extra value
CardCollection aiCreaturesInGY = CardLists.filter(ai.getZone(ZoneType.Graveyard).getCards(), CardPredicates.Presets.CREATURES);
if (aiCreaturesInGY.isEmpty()) {
@@ -396,7 +427,7 @@ public class SpecialCardAi {
}
// if we get more value out of this than our opponent does (hopefully), go for it
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower);
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold);
}
}
@@ -428,6 +459,32 @@ public class SpecialCardAi {
}
}
// Momir Vig, Simic Visionary Avatar
public static class MomirVigAvatar {
public static boolean consider(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
return false;
}
// Set PayX here to maximum value.
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
// Some basic strategy for Momir
if (tokenSize < 2) {
return false;
}
if (tokenSize > 11) {
tokenSize = 11;
}
source.setSVar("PayX", Integer.toString(tokenSize));
return true;
}
}
// Necropotence
public static class Necropotence {
public static boolean consider(Player ai, SpellAbility sa) {

View File

@@ -6,10 +6,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.ai.*;
import forge.card.CardDb;
import forge.card.CardRules;
import forge.card.CardSplitType;
@@ -36,22 +33,9 @@ public class ChooseCardNameAi extends SpellAbilityAi {
String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) {
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
return false;
}
// Set PayX here to maximum value.
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
// Some basic strategy for Momir
if (tokenSize < 2) {
return false;
}
if (tokenSize > 11) {
tokenSize = 11;
}
source.setSVar("PayX", Integer.toString(tokenSize));
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.consider(ai, sa);
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -78,6 +62,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestAI(options);
}

View File

@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
@@ -18,6 +19,7 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -49,6 +51,19 @@ public class EffectAi extends SpellAbilityAi {
return false;
}
randomReturn = true;
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
for (Player opp : ai.getOpponents()) {
boolean worthHolding = false;
if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
Predicates.and(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES),
CardPredicates.Presets.TAPPED)).size() >= 3) {
worthHolding = true;
}
if (!worthHolding) {
return false;
}
randomReturn = true;
}
} else if (logic.equals("Fog")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;

View File

@@ -5,4 +5,6 @@ PT:4/4
K:You may exert CARDNAME as it attacks.
T:Mode$ Exerted | ValidCard$ Card.Self | Execute$ TrigUntapAll | TriggerDescription$ When you exert CARDNAME, untap all other creatures you control.
SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl+Other
SVar:AIExertCondition:NumCreats GE3
SVar:NumCreats:Count$Valid Creature.YouCtrl+tapped
Oracle:You may exert Ahn-Crop Champion as it attacks. When you do, untap all other creatures you control. (An exerted creature won't untap during your next untap step.)

View File

@@ -1,10 +1,9 @@
Name:Cursed Scroll
ManaCost:1
Types:Artifact
A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player.
A:AB$ NameCard | Cost$ 3 T | Defined$ You | SubAbility$ DBReveal | AILogic$ CursedScroll | SpellDescription$ Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, CARDNAME deals 2 damage to target creature or player.
SVar:DBReveal:DB$ Reveal | Random$ True | RememberRevealed$ True | Defined$ You | SubAbility$ DBDamage
SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | ConditionDefined$ Remembered | ConditionPresent$ Card.NamedCard | ConditionCompare$ EQ1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/cursed_scroll.jpg
Oracle:{3}, {T}: Choose a card name, then reveal a card at random from your hand. If that card has the chosen name, Cursed Scroll deals 2 damage to target creature or player.