From ec11581b04d2a7e4a3f09dae649c20212953d01c Mon Sep 17 00:00:00 2001 From: excessum Date: Sun, 1 Mar 2015 02:10:50 +0000 Subject: [PATCH] - Moved PumpAiBase.shouldPumpCard() into ComputerUtilCard for potential re-use by CountersPutAi --- .../main/java/forge/ai/ComputerUtilCard.java | 261 ++++++++++++++++++ .../main/java/forge/ai/ability/PumpAi.java | 2 +- .../java/forge/ai/ability/PumpAiBase.java | 247 +---------------- 3 files changed, 263 insertions(+), 247 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index a798b491bf0..f49ad2a05ae 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -16,6 +16,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -924,6 +925,266 @@ public class ComputerUtilCard { return chance < valueNow; } } + + /** + * Decides if the "pump" is worthwhile + * @param ai casting player + * @param sa Pump* or CounterPut* + * @param c target of sa + * @param toughness +T + * @param power +P + * @param keywords additional keywords from sa (only for Pump) + * @return + */ + public static boolean shouldPumpCard(final Player ai, + final SpellAbility sa, final Card c, final int toughness, + final int power, final List keywords) { + final Game game = ai.getGame(); + final PhaseHandler phase = game.getPhaseHandler(); + final Combat combat = phase.getCombat(); + + if (!c.canBeTargetedBy(sa)) { + return false; + } + + if (c.getNetToughness() + toughness <= 0) { + return false; + } + + /* -- currently disabled until better conditions are devised and the spell prediction is made smarter -- + // Determine if some mana sources need to be held for the future spell to cast in Main 2 before determining whether to pump. + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) { + // only hold mana sources once + SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Pump); + if (futureSpell != null && futureSpell.getHostCard() != null) { + aic.reserveManaSourcesForMain2(futureSpell); + } + } + */ + + // will the creature attack (only relevant for sorcery speed)? + if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) + && phase.isPlayerTurn(ai) + && SpellAbilityAi.isSorcerySpeed(sa) + && power > 0 + && ComputerUtilCard.doesCreatureAttackAI(ai, c)) { + return true; + } + + // buff attacker/blocker using triggered pump + if (sa.isTrigger() && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { + if (phase.isPlayerTurn(ai)) { + if (CombatUtil.canAttack(c)) { + return true; + } + } else { + if (CombatUtil.canBlock(c)) { + return true; + } + } + } + + final Player opp = ai.getOpponent(); + Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords); + List oppCreatures = opp.getCreaturesInPlay(); + float chance = 0; + + //create and buff attackers + if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai)) { + //1. become attacker for whatever reason + if (!ComputerUtilCard.doesCreatureAttackAI(ai, c) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { + float threat = 1.0f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); + if (CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(pumped)).isEmpty()) { + threat *= 2; + } + if (c.getNetPower() == 0 && c == sa.getHostCard() && power > 0 ) { + threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking + } + chance += threat; + } + + //2. grant haste + if (keywords.contains("Haste") && c.hasSickness() && !c.isTapped()) { + chance += 0.5f; + if (ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { + chance += 0.5f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); + } + } + + //3. grant evasive + if (!CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(c)).isEmpty()) { + if (CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(pumped)).isEmpty() + && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { + chance += 0.5f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); + } + } + } + + //combat trickery + if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + //clunky code because ComputerUtilCombat.combatantWouldBeDestroyed() does not work for this sort of artificial combat + Combat pumpedCombat = new Combat(phase.isPlayerTurn(ai) ? ai : opp); + List opposing = null; + boolean pumpedWillDie = false; + final boolean isAttacking = combat.isAttacking(c); + if (isAttacking) { + pumpedCombat.addAttacker(pumped, opp); + opposing = combat.getBlockers(c); + for (Card b : opposing) { + pumpedCombat.addBlocker(pumped, b); + } + if (ComputerUtilCombat.attackerWouldBeDestroyed(ai, pumped, pumpedCombat)) { + pumpedWillDie = true; + } + } else { + opposing = combat.getAttackersBlockedBy(c); + for (Card a : opposing) { + pumpedCombat.addAttacker(a, ai); + pumpedCombat.addBlocker(a, pumped); + } + if (ComputerUtilCombat.blockerWouldBeDestroyed(ai, pumped, pumpedCombat)) { + pumpedWillDie = true; + } + } + + //1. save combatant + if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie) { + return true; + } + + //2. kill combatant + boolean survivor = false; + for (Card o : opposing) { + if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat)) { + survivor = true; + break; + } + } + if (survivor) { + for (Card o : opposing) { + if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat)) { + if (isAttacking) { + if (ComputerUtilCombat.blockerWouldBeDestroyed(opp, o, pumpedCombat)) { + return true; + } + } else { + if (ComputerUtilCombat.attackerWouldBeDestroyed(opp, o, pumpedCombat)) { + return true; + } + } + } + } + } + + //3. buff attacker + if (combat.isAttacking(c)) { + int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true); + int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true); + if (combat.isBlocked(c)) { + if (!c.hasKeyword("Trample")) { + dmg = 0; + } + if (c.hasKeyword("Trample") || keywords.contains("Trample")) { + for (Card b : combat.getBlockers(c)) { + pumpedDmg -= ComputerUtilCombat.getDamageToKill(b); + } + } else { + pumpedDmg = 0; + } + } + if (pumpedDmg >= opp.getLife()) { + return true; + } + float value = 1.0f * (pumpedDmg - dmg); + if (c == sa.getHostCard() && power > 0) { + int divisor = sa.getPayCosts().getTotalMana().getCMC(); + if (divisor <= 0) { + divisor = 1; + } + value *= power / divisor; + } else { + value /= opp.getLife(); + } + chance += value; + } + + //4. lifelink + if (ai.canGainLife() && !c.hasKeyword("Lifelink") && keywords.contains("Lifelink") + && (combat.isAttacking(c) || combat.isBlocking(c))) { + int dmg = pumped.getNetCombatDamage(); + //The actual dmg inflicted should be the sum of ComputerUtilCombat.predictDamageTo() for opposing creature + //and trample damage (if any) + chance += 1.0f * dmg / ai.getLife(); + } + + //5. if the life of the computer is in danger, try to pump blockers blocking Tramplers + if (combat.isBlocking(c) && toughness > 0 ) { + List blockedBy = combat.getAttackersBlockedBy(c); + boolean attackerHasTrample = false; + for (Card b : blockedBy) { + attackerHasTrample |= b.hasKeyword("Trample"); + } + if (attackerHasTrample && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, combat))) { + return true; + } + } + } + return MyRandom.getRandom().nextFloat() < chance; + } + + /** + * Apply "pump" ability and return modified creature + * @param ai casting player + * @param sa Pump* or CounterPut* + * @param c target of sa + * @param toughness +T + * @param power +P + * @param keywords additional keywords from sa (only for Pump) + * @return + */ + public static Card getPumpedCreature(final Player ai, final SpellAbility sa, + final Card c, final int toughness, final int power, + final List keywords) { + Card pumped = CardFactory.copyCard(c, true); + pumped.setSickness(c.hasSickness()); + final long timestamp = c.getGame().getNextTimestamp(); + final ArrayList kws = new ArrayList(); + for (String kw : keywords) { + if (kw.startsWith("HIDDEN")) { + pumped.addHiddenExtrinsicKeyword(kw); + } else { + kws.add(kw); + } + } + pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp); + pumped.addTempPowerBoost(c.getTempPowerBoost() + power); + pumped.addTempToughnessBoost(c.getTempToughnessBoost() + toughness); + pumped.addChangedCardKeywords(kws, new ArrayList(), false, timestamp); + Set types = c.getCounters().keySet(); + for(CounterType ct : types) { + pumped.addCounterFireNoEvents(ct, c.getCounters(ct), true); + } + //Copies tap-state and extra keywords (auras, equipment, etc.) + if (c.isTapped()) { + pumped.setTapped(true); + } + final List copiedKeywords = pumped.getKeywords(); + List toCopy = new ArrayList(); + for (String kw : c.getKeywords()) { + if (!copiedKeywords.contains(kw)) { + if (kw.startsWith("HIDDEN")) { + pumped.addHiddenExtrinsicKeyword(kw); + } else { + toCopy.add(kw); + } + } + } + final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? + pumped.addChangedCardKeywords(toCopy, new ArrayList(), false, timestamp2); + ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); + return pumped; + } /** * Applies static continuous Power/Toughness effects to a (virtual) creature. diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 5babf3ed532..807e41481dd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -182,7 +182,7 @@ public class PumpAi extends PumpAiBase { return true; } - if (!card.getController().isOpponentOf(ai) && shouldPumpCard(ai, sa, card, defense, attack, keywords)) { + if (!card.getController().isOpponentOf(ai) && ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords)) { return true; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java index ba0a4c45ff6..7a36ac1d30b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java @@ -12,11 +12,9 @@ import forge.card.MagicColor; import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CardFactory; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; -import forge.game.card.CounterType; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.phase.PhaseHandler; @@ -25,11 +23,8 @@ import forge.game.phase.Untap; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import forge.util.MyRandom; -import java.util.ArrayList; import java.util.List; -import java.util.Set; public abstract class PumpAiBase extends SpellAbilityAi { @@ -426,204 +421,6 @@ public abstract class PumpAiBase extends SpellAbilityAi { return true; } - protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack, - final List keywords) { - final Game game = ai.getGame(); - final PhaseHandler phase = game.getPhaseHandler(); - final Combat combat = phase.getCombat(); - - if (!c.canBeTargetedBy(sa)) { - return false; - } - - if (c.getNetToughness() + defense <= 0) { - return false; - } - - /* -- currently disabled until better conditions are devised and the spell prediction is made smarter -- - // Determine if some mana sources need to be held for the future spell to cast in Main 2 before determining whether to pump. - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) { - // only hold mana sources once - SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Pump); - if (futureSpell != null && futureSpell.getHostCard() != null) { - aic.reserveManaSourcesForMain2(futureSpell); - } - } - */ - - // will the creature attack (only relevant for sorcery speed)? - if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) - && phase.isPlayerTurn(ai) - && SpellAbilityAi.isSorcerySpeed(sa) - && attack > 0 - && ComputerUtilCard.doesCreatureAttackAI(ai, c)) { - return true; - } - - // buff attacker/blocker using triggered pump - if (sa.isTrigger() && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { - if (phase.isPlayerTurn(ai)) { - if (CombatUtil.canAttack(c)) { - return true; - } - } else { - if (CombatUtil.canBlock(c)) { - return true; - } - } - } - - final Player opp = ai.getOpponent(); - Card pumped = pumpedCreature(ai, sa, c, defense, attack, keywords); - List oppCreatures = opp.getCreaturesInPlay(); - float chance = 0; - - //create and buff attackers - if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai)) { - //1. become attacker for whatever reason - if (!ComputerUtilCard.doesCreatureAttackAI(ai, c) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { - float threat = 1.0f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); - if (CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(pumped)).isEmpty()) { - threat *= 2; - } - if (c.getNetPower() == 0 && c == sa.getHostCard() && attack > 0 ) { - threat *= 4; //over-value self +attack for 0 power creatures which may be pumped further after attacking - } - chance += threat; - } - - //2. grant haste - if (keywords.contains("Haste") && c.hasSickness() && !c.isTapped()) { - chance += 0.5f; - if (ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { - chance += 0.5f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); - } - } - - //3. grant evasive - if (!CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(c)).isEmpty()) { - if (CardLists.filter(oppCreatures, CardPredicates.possibleBlockers(pumped)).isEmpty() - && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) { - chance += 0.5f * ComputerUtilCombat.damageIfUnblocked(pumped, opp, combat, true) / opp.getLife(); - } - } - } - - //combat trickery - if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - //clunky code because ComputerUtilCombat.combatantWouldBeDestroyed() does not work for this sort of artificial combat - Combat pumpedCombat = new Combat(phase.isPlayerTurn(ai) ? ai : opp); - List opposing = null; - boolean pumpedWillDie = false; - final boolean isAttacking = combat.isAttacking(c); - if (isAttacking) { - pumpedCombat.addAttacker(pumped, opp); - opposing = combat.getBlockers(c); - for (Card b : opposing) { - pumpedCombat.addBlocker(pumped, b); - } - if (ComputerUtilCombat.attackerWouldBeDestroyed(ai, pumped, pumpedCombat)) { - pumpedWillDie = true; - } - } else { - opposing = combat.getAttackersBlockedBy(c); - for (Card a : opposing) { - pumpedCombat.addAttacker(a, ai); - pumpedCombat.addBlocker(a, pumped); - } - if (ComputerUtilCombat.blockerWouldBeDestroyed(ai, pumped, pumpedCombat)) { - pumpedWillDie = true; - } - } - - //1. save combatant - if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie) { - return true; - } - - //2. kill combatant - boolean survivor = false; - for (Card o : opposing) { - if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat)) { - survivor = true; - break; - } - } - if (survivor) { - for (Card o : opposing) { - if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat)) { - if (isAttacking) { - if (ComputerUtilCombat.blockerWouldBeDestroyed(opp, o, pumpedCombat)) { - return true; - } - } else { - if (ComputerUtilCombat.attackerWouldBeDestroyed(opp, o, pumpedCombat)) { - return true; - } - } - } - } - } - - //3. buff attacker - if (combat.isAttacking(c)) { - int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true); - int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true); - if (combat.isBlocked(c)) { - if (!c.hasKeyword("Trample")) { - dmg = 0; - } - if (c.hasKeyword("Trample") || keywords.contains("Trample")) { - for (Card b : combat.getBlockers(c)) { - pumpedDmg -= ComputerUtilCombat.getDamageToKill(b); - } - } else { - pumpedDmg = 0; - } - } - if (pumpedDmg >= opp.getLife()) { - return true; - } - float value = 1.0f * (pumpedDmg - dmg); - if (c == sa.getHostCard() && attack > 0) { - int divisor = sa.getPayCosts().getTotalMana().getCMC(); - if (divisor <= 0) { - divisor = 1; - } - value *= attack / divisor; - } else { - value /= opp.getLife(); - } - chance += value; - } - - //4. lifelink - if (ai.canGainLife() && !c.hasKeyword("Lifelink") && keywords.contains("Lifelink") - && (combat.isAttacking(c) || combat.isBlocking(c))) { - int dmg = pumped.getNetCombatDamage(); - //The actual dmg inflicted should be the sum of ComputerUtilCombat.predictDamageTo() for opposing creature - //and trample damage (if any) - chance += 1.0f * dmg / ai.getLife(); - } - - //5. if the life of the computer is in danger, try to pump blockers blocking Tramplers - if (combat.isBlocking(c) && defense > 0 ) { - List blockedBy = combat.getAttackersBlockedBy(c); - boolean attackerHasTrample = false; - for (Card b : blockedBy) { - attackerHasTrample |= b.hasKeyword("Trample"); - } - if (attackerHasTrample && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, combat))) { - return true; - } - } - } - //TODO:how to consider repeatable pump abilities? This probably requires the AI to keep track of future decisions and/or - //plan a sequence of decisions. - return MyRandom.getRandom().nextFloat() < chance; - } - /** *

* getPumpCreatures. @@ -636,7 +433,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { list = CardLists.filter(list, new Predicate() { @Override public boolean apply(final Card c) { - return shouldPumpCard(ai, sa, c, defense, attack, keywords); + return ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, attack, keywords); } }); return list; @@ -740,46 +537,4 @@ public abstract class PumpAiBase extends SpellAbilityAi { } return false; } - - public Card pumpedCreature(final Player ai, final SpellAbility sa, final Card c, final int d, final int a, - final List keywords) { - Card pumped = CardFactory.copyCard(c, true); - pumped.setSickness(c.hasSickness()); - final long timestamp = c.getGame().getNextTimestamp(); - final ArrayList kws = new ArrayList(); - for (String kw : keywords) { - if (kw.startsWith("HIDDEN")) { - pumped.addHiddenExtrinsicKeyword(kw); - } else { - kws.add(kw); - } - } - pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp); - pumped.addTempPowerBoost(c.getTempPowerBoost() + a); - pumped.addTempToughnessBoost(c.getTempToughnessBoost() + d); - pumped.addChangedCardKeywords(kws, new ArrayList(), false, timestamp); - Set types = c.getCounters().keySet(); - for(CounterType ct : types) { - pumped.addCounterFireNoEvents(ct, c.getCounters(ct), true); - } - //Copies tap-state and extra keywords (auras, equipment, etc.) - if (c.isTapped()) { - pumped.setTapped(true); - } - final List copiedKeywords = pumped.getKeywords(); - List toCopy = new ArrayList(); - for (String kw : c.getKeywords()) { - if (!copiedKeywords.contains(kw)) { - if (kw.startsWith("HIDDEN")) { - pumped.addHiddenExtrinsicKeyword(kw); - } else { - toCopy.add(kw); - } - } - } - final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? - pumped.addChangedCardKeywords(toCopy, new ArrayList(), false, timestamp2); - ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); - return pumped; - } }