- Some improvements for the Aristocrats AI logic.

This commit is contained in:
Agetian
2017-10-04 14:58:06 +00:00
parent f155244997
commit db291eb9c4
3 changed files with 150 additions and 18 deletions

View File

@@ -76,6 +76,8 @@ public class PumpAi extends PumpAiBase {
} }
} else if ("Aristocrat".equals(aiLogic)) { } else if ("Aristocrat".equals(aiLogic)) {
return doAristocratLogic(sa, ai); return doAristocratLogic(sa, ai);
} else if (aiLogic.startsWith("AristocratCounters")) {
return doAristocratWithCountersLogic(sa, ai);
} }
return super.checkAiLogic(ai, sa, aiLogic); return super.checkAiLogic(ai, sa, aiLogic);
@@ -102,7 +104,7 @@ public class PumpAi extends PumpAiBase {
} }
return super.checkPhaseRestrictions(ai, sa, ph); return super.checkPhaseRestrictions(ai, sa, ph);
} }
@Override @Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Game game = ai.getGame(); final Game game = ai.getGame();
@@ -123,7 +125,7 @@ public class PumpAi extends PumpAiBase {
} }
return true; return true;
} }
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
@@ -134,13 +136,15 @@ public class PumpAi extends PumpAiBase {
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
final String aiLogic = sa.getParam("AILogic"); final String aiLogic = sa.getParamOrDefault("AILogic", "");
final boolean isFight = "Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic); final boolean isFight = "Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic);
final boolean isBerserk = "Berserk".equals(aiLogic); final boolean isBerserk = "Berserk".equals(aiLogic);
if ("Pummeler".equals(aiLogic)) { if ("Pummeler".equals(aiLogic)) {
return SpecialCardAi.ElectrostaticPummeler.consider(ai, sa); return SpecialCardAi.ElectrostaticPummeler.consider(ai, sa);
} else if (aiLogic.startsWith("AristocratCounters")) {
return true; // the preconditions to this are already tested in checkAiLogic
} else if ("MoveCounter".equals(aiLogic)) { } else if ("MoveCounter".equals(aiLogic)) {
final SpellAbility moveSA = sa.findSubAbilityByType(ApiType.MoveCounter); final SpellAbility moveSA = sa.findSubAbilityByType(ApiType.MoveCounter);
@@ -150,7 +154,7 @@ public class PumpAi extends PumpAiBase {
final String counterType = moveSA.getParam("CounterType"); final String counterType = moveSA.getParam("CounterType");
final CounterType cType = "Any".equals(counterType) ? null : CounterType.valueOf(counterType); final CounterType cType = "Any".equals(counterType) ? null : CounterType.valueOf(counterType);
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) { if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) {
CardCollection attr = ph.getCombat().getAttackers(); CardCollection attr = ph.getCombat().getAttackers();
@@ -401,14 +405,14 @@ public class PumpAi extends PumpAiBase {
return true; return true;
} // pumpPlayAI() } // pumpPlayAI()
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory, private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
boolean immediately) { boolean immediately) {
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
: Lists.<String>newArrayList(); : Lists.<String>newArrayList();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final boolean isFight = "Fight".equals(sa.getParam("AILogic")) || "PowerDmg".equals(sa.getParam("AILogic")); final boolean isFight = "Fight".equals(sa.getParam("AILogic")) || "PowerDmg".equals(sa.getParam("AILogic"));
immediately |= ComputerUtil.playImmediately(ai, sa); immediately |= ComputerUtil.playImmediately(ai, sa);
if (!mandatory if (!mandatory
@@ -515,13 +519,13 @@ public class PumpAi extends PumpAiBase {
// Don't target cards that will die. // Don't target cards that will die.
list = ComputerUtil.getSafeTargets(ai, sa, list); list = ComputerUtil.getSafeTargets(ai, sa, list);
} }
if ("Snapcaster".equals(sa.getParam("AILogic"))) { if ("Snapcaster".equals(sa.getParam("AILogic"))) {
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) { if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
return false; return false;
} }
} }
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) { while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card t = null; Card t = null;
// boolean goodt = false; // boolean goodt = false;
@@ -543,7 +547,7 @@ public class PumpAi extends PumpAiBase {
t = ComputerUtilCard.getBestAI(list); t = ComputerUtilCard.getBestAI(list);
//option to hold removal instead only applies for single targeted removal //option to hold removal instead only applies for single targeted removal
if (!immediately && tgt.getMaxTargets(source, sa) == 1 && sa.isCurse() && defense < 0) { if (!immediately && tgt.getMaxTargets(source, sa) == 1 && sa.isCurse() && defense < 0) {
if (!ComputerUtilCard.useRemovalNow(sa, t, -defense, ZoneType.Graveyard) if (!ComputerUtilCard.useRemovalNow(sa, t, -defense, ZoneType.Graveyard)
&& !ComputerUtil.activateForCost(sa, ai)) { && !ComputerUtil.activateForCost(sa, ai)) {
return false; return false;
} }
@@ -697,7 +701,7 @@ public class PumpAi extends PumpAiBase {
} }
return false; return false;
} }
int defense; int defense;
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
defense = Integer.parseInt(source.getSVar("PayX")); defense = Integer.parseInt(source.getSVar("PayX"));
@@ -754,6 +758,7 @@ public class PumpAi extends PumpAiBase {
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1); final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
final int powerBonus = sa.hasParam("NumAtt") ? AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa) : 0; final int powerBonus = sa.hasParam("NumAtt") ? AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa) : 0;
final int toughnessBonus = sa.hasParam("NumDef") ? AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa) : 0; final int toughnessBonus = sa.hasParam("NumDef") ? AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa) : 0;
final boolean indestructible = sa.hasParam("KW") && sa.getParam("KW").contains("Indestructible");
final int selfEval = ComputerUtilCard.evaluateCreature(source); final int selfEval = ComputerUtilCard.evaluateCreature(source);
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source); final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
@@ -762,18 +767,18 @@ public class PumpAi extends PumpAiBase {
} }
// Try to save the card from death by pumping it if it's threatened with a damage spell // Try to save the card from death by pumping it if it's threatened with a damage spell
if (isThreatened && toughnessBonus > 0) { if (isThreatened && (toughnessBonus > 0 || indestructible)) {
SpellAbility saTop = game.getStack().peekAbility(); SpellAbility saTop = game.getStack().peekAbility();
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) { if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop) + source.getDamage(); int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop) + source.getDamage();
final int numCreatsToSac = Math.max(1, (int)Math.ceil((dmg - source.getNetToughness() + 1) / toughnessBonus)); final int numCreatsToSac = indestructible ? 1 : Math.max(1, (int)Math.ceil((dmg - source.getNetToughness() + 1) / toughnessBonus));
if (numCreatsToSac > 1) { // probably not worth sacrificing too much if (numCreatsToSac > 1) { // probably not worth sacrificing too much
return false; return false;
} }
if (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg) { if (indestructible || (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg)) {
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(), final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
new Predicate<Card>() { new Predicate<Card>() {
@Override @Override
@@ -816,7 +821,7 @@ public class PumpAi extends PumpAiBase {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
} }
final int numCreatsToSac = (lethalDmg - source.getNetCombatDamage()) / powerBonus; final int numCreatsToSac = indestructible ? 1 : (lethalDmg - source.getNetCombatDamage()) / powerBonus;
if (defTappedOut || numCreatsToSac < numOtherCreats / 2) { if (defTappedOut || numCreatsToSac < numOtherCreats / 2) {
return source.getNetCombatDamage() < lethalDmg return source.getNetCombatDamage() < lethalDmg
@@ -842,7 +847,7 @@ public class PumpAi extends PumpAiBase {
} }
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness); final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
final int DefP = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower); final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature // Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT; return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
@@ -864,4 +869,128 @@ public class PumpAi extends PumpAiBase {
} }
} }
public boolean doAristocratWithCountersLogic(final SpellAbility sa, final Player ai) {
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
final Card source = sa.getHostCard();
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
if (numOtherCreats == 0) {
// Cut short if there's nothing to sac at all
return false;
}
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
if (isDeclareBlockers || isThreatened) {
if (doAristocratLogic(sa, ai)) {
return true;
}
}
// Check if anything is to be gained from the PutCounter subability
if (sa.getSubAbility() == null || sa.getSubAbility().getApi() != ApiType.PutCounter) {
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter subability!");
return false;
}
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final int selfEval = ComputerUtilCard.evaluateCreature(source);
String typeToGainCtr = "";
if (logic.contains(".")) {
typeToGainCtr = logic.substring(logic.indexOf(".") + 1);
}
CardCollection relevantCreats = typeToGainCtr.isEmpty() ? ai.getCreaturesInPlay()
: CardLists.filter(ai.getCreaturesInPlay(), CardPredicates.isType(typeToGainCtr));
relevantCreats.remove(source);
if (relevantCreats.isEmpty()) {
// No relevant creatures to sac
return false;
}
int numCtrs = AbilityUtils.calculateAmount(source, sa.getSubAbility().getParam("CounterNum"), sa.getSubAbility());
if (combat != null && combat.isAttacking(source) && isDeclareBlockers) {
if (combat.getBlockers(source).isEmpty()) {
// Unblocked. Check if we can deal lethal after receiving counters.
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
final boolean isInfect = source.hasKeyword("Infect");
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
}
// Check if there's anything that will die anyway that can be eaten to gain a perma-bonus
final CardCollection forcedSacTgts = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat));
}
}
);
if (!forcedSacTgts.isEmpty()) {
return true;
}
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
return source.getNetCombatDamage() < lethalDmg
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
} else {
return false;
}
} else {
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
// than the card we attacked with. Since we're getting a permanent bonus, consider sacrificing
// things that are also threatened to be destroyed anyway.
final CardCollection sacTgts = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
}
}
);
if (sacTgts.isEmpty()) {
return false;
}
final boolean sourceCantDie = ComputerUtilCombat.attackerCantBeDestroyedInCombat(ai, source);
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
}
} else {
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
final boolean isBlocking = combat != null && combat.isBlocking(source);
final CardCollection sacFodder = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
}
}
);
return !sacFodder.isEmpty();
}
}
} }

View File

@@ -4,8 +4,9 @@ Types:Creature Vampire
PT:4/1 PT:4/1
K:Flying K:Flying
K:Haste K:Haste
A:AB$ Pump | Cost$ Sac<1/Creature> | Defined$ Self | KW$ Indestructible | SubAbility$ DBPutCounter | SpellDescription$ CARDNAME gains indestructible until end of turn. If the sacrificed creature was a Human, put a +1/+1 counter on CARDNAME. A:AB$ Pump | Cost$ Sac<1/Creature> | Defined$ Self | KW$ Indestructible | AILogic$ AristocratCounters.Human | SubAbility$ DBPutCounter | SpellDescription$ CARDNAME gains indestructible until end of turn. If the sacrificed creature was a Human, put a +1/+1 counter on CARDNAME.
SVar:DBPutCounter:DB$PutCounter | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | CounterNum$ 1 | CounterType$ P1P1 | References$ X SVar:DBPutCounter:DB$PutCounter | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | CounterNum$ 1 | CounterType$ P1P1 | References$ X
SVar:AIPreference:SacCost$Creature.Human+token,Creature.Human,Creature.Other+token,Creature.Other
SVar:X:Sacrificed$Valid Human SVar:X:Sacrificed$Valid Human
SVar:Picture:http://www.wizards.com/global/images/magic/general/falkenrath_aristocrat.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/falkenrath_aristocrat.jpg
Oracle:Flying, haste\nSacrifice a creature: Falkenrath Aristocrat gains indestructible until end of turn. If the sacrificed creature was a Human, put a +1/+1 counter on Falkenrath Aristocrat. Oracle:Flying, haste\nSacrifice a creature: Falkenrath Aristocrat gains indestructible until end of turn. If the sacrificed creature was a Human, put a +1/+1 counter on Falkenrath Aristocrat.

View File

@@ -3,9 +3,11 @@ ManaCost:B
Types:Creature Vampire Types:Creature Vampire
PT:1/1 PT:1/1
K:Lifelink K:Lifelink
A:AB$ PutCounterAll | Cost$ 2 Sac<1/Creature> | ValidCards$ Vampire.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on each Vampire you control. A:AB$ PutCounterAll | Cost$ 2 Sac<1/Creature> | ValidCards$ Vampire.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Vampire you control.
SVar:RemAIDeck:True SVar:AIPreference:SacCost$Creature.token+nonVampire,Creature.nonVampire+cmcEQ1,Creature.nonVampire+cmcEQ2+powerLE1
DeckHints:Type$Vampire DeckHints:Type$Vampire
# TODO: improve the logic when the AI wants to sac creatures
SVar:RemRandomDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/indulgent_aristocrat.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/indulgent_aristocrat.jpg
Oracle:Lifelink\n{2}, Sacrifice a creature: Put a +1/+1 counter on each Vampire you control. Oracle:Lifelink\n{2}, Sacrifice a creature: Put a +1/+1 counter on each Vampire you control.