the great move

This commit is contained in:
Maxmtg
2014-01-21 06:27:36 +00:00
parent 6b1d9356f3
commit 2bc61a9e68
500 changed files with 502 additions and 520 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,809 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.game.GameEntity;
import forge.game.TriggerReplacementBase;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
/**
* <p>
* ComputerUtil_Block2 class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class AiBlockController {
private final Player ai;
/** Constant <code>attackers</code>. */
private List<Card> attackers = new ArrayList<Card>(); // all attackers
/** Constant <code>attackersLeft</code>. */
private List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
// all currently
// unblocked
// attackers
/** Constant <code>blockedButUnkilled</code>. */
private List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
// attackers
// that
// currently
// wouldn't be
// destroyed
/** Constant <code>blockersLeft</code>. */
private List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
// unassigned
// blockers
private int diff = 0;
private boolean lifeInDanger = false;
public AiBlockController(Player aiPlayer) {
this.ai = aiPlayer;
}
// finds the creatures able to block the attacker
private List<Card> getPossibleBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft, final boolean solo) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card blocker : blockersLeft) {
// if the blocker can block a creature with lure it can't block a
// creature without
if (CombatUtil.canBlock(attacker, blocker, combat)) {
if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) {
continue;
}
blockers.add(blocker);
}
}
return blockers;
}
// finds blockers that won't be destroyed
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) {
blockers.add(b);
}
}
return blockers;
}
// finds blockers that destroy the attacker
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) {
blockers.add(b);
}
}
return blockers;
}
private List<List<Card>> sortAttackerByDefender(final Combat combat) {
List<GameEntity> defenders = combat.getDefenders();
final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size());
for (GameEntity defender : defenders) {
attackers.add(combat.getAttackersOf(defender));
}
return attackers;
}
private List<Card> sortPotentialAttackers(final Combat combat) {
final List<List<Card>> attackerLists = sortAttackerByDefender(combat);
final List<Card> sortedAttackers = new ArrayList<Card>();
final List<Card> firstAttacker = attackerLists.get(0);
final List<GameEntity> defenders = combat.getDefenders();
// Begin with the attackers that pose the biggest threat
CardLists.sortByEvaluateCreature(firstAttacker);
CardLists.sortByPowerDesc(firstAttacker);
// If I don't have any planeswalkers than sorting doesn't really matter
if (defenders.size() == 1) {
return firstAttacker;
}
final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
// TODO Add creatures attacking Planeswalkers in order of which we want
// to protect
// defend planeswalkers with more loyalty before planeswalkers with less
// loyalty
// if planeswalker will be too difficult to defend don't even bother
for (List<Card> attacker : attackerLists) {
// Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attacker);
for (final Card c : attacker) {
sortedAttackers.add(c);
}
}
if (bLifeInDanger) {
// add creatures attacking the Player to the front of the list
for (final Card c : firstAttacker) {
sortedAttackers.add(0, c);
}
} else {
// add creatures attacking the Player to the back of the list
for (final Card c : firstAttacker) {
sortedAttackers.add(c);
}
}
return sortedAttackers;
}
// ======================= block assignment functions
// ================================
// Good Blocks means a good trade or no trade
private void makeGoodBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
"all creatures defending player controls block it.")) {
continue;
}
Card blocker = null;
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
List<Card> killingBlockers;
if (safeBlockers.size() > 0) {
// 1.Blockers that can destroy the attacker but won't get
// destroyed
killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
if (!killingBlockers.isEmpty()) {
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
blockedButUnkilled.add(attacker);
}
} // no safe blockers
else {
// 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) {
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|| !b.getSVar("SacMe").equals("")) {
blocker = b;
break;
}
}
// 4.Blockers that can destroy the attacker and are worth less
if (blocker == null && !killingBlockers.isEmpty()) {
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
int value = ComputerUtilCard.evaluateCreature(attacker);
// check for triggers when unblocked
for (Trigger trigger : attacker.getTriggers()) {
final HashMap<String, String> trigParams = trigger.getMapParams();
TriggerType mode = trigger.getMode();
if (!trigger.requirementsCheck(attacker.getGame())) {
continue;
}
if (mode == TriggerType.DamageDone) {
if ((!trigParams.containsKey("ValidSource")
|| TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
&& attacker.getNetCombatDamage() > 0
&& (!trigParams.containsKey("ValidTarget")
|| TriggerReplacementBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
value += 50;
}
} else if (mode == TriggerType.AttackerUnblocked) {
if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
value += 50;
}
}
}
if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) {
blocker = worst;
}
}
}
if (blocker != null) {
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Good Gang Blocks means a good trade or no trade
/**
* <p>
* makeGangBlocks.
* </p>
*
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a {@link forge.game.combat.Combat} object.
*/
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private void makeGangBlocks(final Combat combat) {
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> blockers;
// Try to block an attacker without first strike with a gang of first strikers
for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
final List<Card> firstStrikeBlockers = new ArrayList<Card>();
final List<Card> blockGang = new ArrayList<Card>();
for (int i = 0; i < blockers.size(); i++) {
if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) {
firstStrikeBlockers.add(blockers.get(i));
}
}
if (firstStrikeBlockers.size() > 1) {
CardLists.sortByPowerDesc(firstStrikeBlockers);
for (final Card blocker : firstStrikeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// if the total damage of the blockgang was not enough
// without but is enough with this blocker finish the
// blockgang
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
blockGang.add(blocker);
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
currentAttackers.remove(attacker);
for (final Card b : blockGang) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, b);
}
}
}
}
}
}
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
currentAttackers = new ArrayList<Card>(attackersLeft);
// Try to block an attacker with two blockers of which only one will die
for (final Card attacker : attackersLeft) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
List<Card> usableBlockers;
final List<Card> blockGang = new ArrayList<Card>();
int absorbedDamage = 0; // The amount of damage needed to kill the first blocker
int currentValue = 0; // The value of the creatures in the blockgang
// AI can't handle good triple blocks yet
if (CombatUtil.needsBlockers(attacker) > 2) {
continue;
}
// Try to add blockers that could be destroyed, but are worth less than the attacker
// Don't use blockers without First Strike or Double Strike if attacker has it
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike"))
&& !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) {
return false;
}
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
}
});
if (usableBlockers.size() < 2) {
return;
}
final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers);
blockGang.add(leader);
usableBlockers.remove(leader);
absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true);
currentValue = ComputerUtilCard.evaluateCreature(leader);
for (final Card blocker : usableBlockers) {
// Add an additional blocker if the current blockers are not
// enough and the new one would deal the remaining damage
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang);
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true);
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
&& !(damageNeeded > currentDamage + additionalDamage)
// The attacker will be killed
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
// only one blocker can be killed
|| currentValue + addedValue - 50 <= ComputerUtilCard.evaluateCreature(attacker)
// or attacker is worth more
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
// or life is in danger
&& CombatUtil.canBlock(attacker, blocker, combat)) {
// this is needed for attackers that can't be blocked by
// more than 1
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
if (CombatUtil.canBlock(attacker, leader, combat)) {
combat.addBlocker(attacker, leader);
}
break;
}
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Bad Trade Blocks (should only be made if life is in danger)
/**
* <p>
* makeTradeBlocks.
* </p>
*
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a {@link forge.game.combat.Combat} object.
*/
private void makeTradeBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
List<Card> killingBlockers;
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
"all creatures defending player controls block it.")) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
combat.addBlocker(attacker, blocker);
currentAttackers.remove(attacker);
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Chump Blocks (should only be made if life is in danger)
private void makeChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
makeChumpBlocks(combat, currentAttackers);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeMultiChumpBlocks(combat);
}
}
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
return;
}
Card attacker = attackers.get(0);
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
return;
}
List<Card> chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
if (!chumpBlockers.isEmpty()) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
// check if it's better to block a creature with lower power and without trample
if (attacker.hasKeyword("Trample")) {
final int damageAbsorbed = blocker.getLethalDamage();
if (attacker.getNetCombatDamage() > damageAbsorbed) {
for (Card other : attackers) {
if (other.equals(attacker)) {
continue;
}
if (other.getNetCombatDamage() >= damageAbsorbed
&& !other.hasKeyword("Trample")
&& CombatUtil.canBlock(other, blocker, combat)) {
combat.addBlocker(other, blocker);
attackersLeft.remove(other);
blockedButUnkilled.add(other);
attackers.remove(other);
makeChumpBlocks(combat, attackers);
return;
}
}
}
}
combat.addBlocker(attacker, blocker);
attackersLeft.remove(attacker);
blockedButUnkilled.add(attacker);
}
attackers.remove(0);
makeChumpBlocks(combat, attackers);
}
// Block creatures with "can't be blocked except by two or more creatures"
private void makeMultiChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) {
continue;
}
List<Card> usedBlockers = new ArrayList<Card>();
for (Card blocker : possibleBlockers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
usedBlockers.add(blocker);
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
break;
}
}
}
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
attackersLeft.remove(attacker);
} else {
for (Card blocker : usedBlockers) {
combat.removeBlockAssignment(attacker, blocker);
}
}
}
}
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
private void reinforceBlockersAgainstTrample(final Combat combat) {
List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : tramplingAttackers) {
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
chumpBlockers.removeAll(combat.getBlockers(attacker));
for (final Card blocker : chumpBlockers) {
// Add an additional blocker if the current blockers are not
// enough and the new one would suck some of the damage
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat.addBlocker(attacker, blocker);
}
}
}
}
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
private void reinforceBlockersToKill(final Combat combat) {
List<Card> safeBlockers;
List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : targetAttackers) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
blockers.removeAll(combat.getBlockers(attacker));
// Try to use safe blockers first
safeBlockers = getSafeBlockers(combat, attacker, blockers);
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal additional damage
if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)))
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
}
blockers.remove(blocker); // Don't check them again next
}
// Try to add blockers that could be destroyed, but are worth less
// than the attacker
// Don't use blockers without First Strike or Double Strike if
// attacker has it
if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
} else {
safeBlockers = new ArrayList<Card>(blockers);
}
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal the remaining damage
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker));
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
if ((damageNeeded > currentDamage)
&& !(damageNeeded > (currentDamage + additionalDamage))
&& ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard
.evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
blockersLeft.remove(blocker);
}
}
}
}
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
final List<Card> oldBlockers = combat.getAllBlockers();
for (final Card blocker : oldBlockers) {
if ( blocker.getController() == ai ) // don't touch other player's blockers
combat.removeFromCombat(blocker);
}
attackersLeft = new ArrayList<Card>(attackers); // keeps track of all currently unblocked attackers
blockersLeft = new ArrayList<Card>(possibleBlockers); // keeps track of all unassigned blockers
blockedButUnkilled = new ArrayList<Card>(); // keeps track of all blocked attackers that currently wouldn't be destroyed
}
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
public void assignBlockers(final Combat combat) {
final List<Card> possibleBlockers = ai.getCreaturesInPlay();
attackers = sortPotentialAttackers(combat);
if (attackers.isEmpty()) {
return;
}
clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
// remove all attackers that can't be blocked anyway
for (final Card a : attackers) {
if (!CombatUtil.canBeBlocked(a, ai)) {
attackersLeft.remove(a);
}
}
// remove all blockers that can't block anyway
for (final Card b : possibleBlockers) {
if (!CombatUtil.canBlock(b, combat)) {
blockersLeft.remove(b);
}
}
if (attackersLeft.isEmpty()) {
return;
}
// Begin with the weakest blockers
CardLists.sortByPowerAsc(blockersLeft);
// == 1. choose best blocks first ==
makeGoodBlocks(combat);
makeGangBlocks(combat);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade blocks
}
// if life is in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat); // choose necessary chump blocks
}
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still
// in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
}
// Support blockers not destroying the attacker with more blockers to
// try to kill the attacker
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer approach ==
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
lifeInDanger = true;
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
// choose necessary chump blocks if life is still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
}
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
else {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers
// to try to kill the attacker
reinforceBlockersToKill(combat);
}
// assign blockers that have to block
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
// if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
CardLists.shuffle(attackers);
for (final Card attacker : attackers) {
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) {
combat.addBlocker(attacker, blocker);
if (blocker.getMustBlockCards() != null) {
int mustBlockAmt = blocker.getMustBlockCards().size();
List<Card> blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
}
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {
// ordering of blockers, sort by evaluate, then try to kill the best
int damage = attacker.getNetCombatDamage();
CardLists.sortByEvaluateCreature(blockers);
final List<Card> first = new ArrayList<Card>();
final List<Card> last = new ArrayList<Card>();
for (Card blocker : blockers) {
int lethal = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true);
if (lethal > damage) {
last.add(blocker);
} else {
first.add(blocker);
damage -= lethal;
}
}
first.addAll(last);
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
return first;
}
public static List<Card> orderAttackers(Card blocker, List<Card> attackers) {
// This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers
// ordering of blockers, sort by evaluate, then try to kill the best
int damage = blocker.getNetCombatDamage();
CardLists.sortByEvaluateCreature(attackers);
final List<Card> first = new ArrayList<Card>();
final List<Card> last = new ArrayList<Card>();
for (Card attacker : attackers) {
int lethal = ComputerUtilCombat.getEnoughDamageToKill(attacker, damage, blocker, true);
if (lethal > damage) {
last.add(attacker);
} else {
first.add(attacker);
damage -= lethal;
}
}
first.addAll(last);
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
return first;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,609 +0,0 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.CostAddMana;
import forge.game.cost.CostChooseCreatureType;
import forge.game.cost.CostDamage;
import forge.game.cost.CostDecisionMakerBase;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostDraw;
import forge.game.cost.CostExile;
import forge.game.cost.CostExiledMoveToGrave;
import forge.game.cost.CostFlipCoin;
import forge.game.cost.CostGainControl;
import forge.game.cost.CostGainLife;
import forge.game.cost.CostMill;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayLife;
import forge.game.cost.CostPutCardToLib;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveAnyCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostReturn;
import forge.game.cost.CostReveal;
import forge.game.cost.CostSacrifice;
import forge.game.cost.CostTap;
import forge.game.cost.CostTapType;
import forge.game.cost.CostUnattach;
import forge.game.cost.CostUntap;
import forge.game.cost.CostUntapType;
import forge.game.cost.PaymentDecision;
import forge.game.cost.ICostVisitor;
import forge.game.player.Player;
import forge.game.player.PlayerControllerAi;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class AiCostDecision extends CostDecisionMakerBase implements ICostVisitor<PaymentDecision> {
private final SpellAbility ability;
private final Card source;
public AiCostDecision(Player ai0, SpellAbility sa) {
super(ai0);
ability = sa;
source = ability.getSourceCard();
}
@Override
public PaymentDecision visit(CostAddMana cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostChooseCreatureType cost) {
String choice = player.getController().chooseSomeType("Creature", ability, new ArrayList<String>(CardType.getCreatureTypes()), new ArrayList<String>());
return PaymentDecision.type(choice);
}
@Override
public PaymentDecision visit(CostDiscard cost) {
final String type = cost.getType();
final List<Card> hand = player.getCardsIn(ZoneType.Hand);
if (type.equals("LastDrawn")) {
if (!hand.contains(player.getLastDrawnCard())) {
return null;
}
return PaymentDecision.card(player.getLastDrawnCard());
}
else if (cost.payCostFromSource()) {
if (!hand.contains(source)) {
return null;
}
return PaymentDecision.card(source);
}
else if (type.equals("Hand")) {
return PaymentDecision.card(hand);
}
if (type.contains("WithSameName")) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
if (type.equals("Random")) {
return PaymentDecision.card(CardLists.getRandomSubList(hand, c));
}
else {
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
}
}
@Override
public PaymentDecision visit(CostDamage cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null; // cannot pay
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostDraw cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostExile cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
if (cost.getType().equals("All")) {
return PaymentDecision.card(player.getCardsIn(cost.getFrom()));
}
else if (cost.getType().contains("FromTopGrave")) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
if (cost.getFrom().equals(ZoneType.Library)) {
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
}
else if (cost.sameZone) {
// TODO Determine exile from same zone for AI
return null;
}
else {
List<Card> chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
return null == chosen ? null : PaymentDecision.card(chosen);
}
}
@Override
public PaymentDecision visit(CostExiledMoveToGrave cost) {
Integer c = cost.convertAmount();
List<Card> chosen = new ArrayList<Card>();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Exile);
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, source);
if (typeList.size() < c) {
return null;
}
CardLists.sortByPowerAsc(typeList);
Collections.reverse(typeList);
for (int i = 0; i < c; i++) {
chosen.add(typeList.get(i));
}
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
@Override
public PaymentDecision visit(CostFlipCoin cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostGainControl cost) {
if (cost.payCostFromSource())
return PaymentDecision.card(source);
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
if (typeList.size() < c) {
return null;
}
CardLists.sortByPowerAsc(typeList);
final List<Card> res = new ArrayList<Card>();
for (int i = 0; i < c; i++) {
res.add(typeList.get(i));
}
return res.isEmpty() ? null : PaymentDecision.card(res);
}
@Override
public PaymentDecision visit(CostGainLife cost) {
final List<Player> oppsThatCanGainLife = new ArrayList<Player>();
for (final Player opp : cost.getPotentialTargets(player, source)) {
if (opp.canGainLife()) {
oppsThatCanGainLife.add(opp);
}
}
if (oppsThatCanGainLife.size() == 0) {
return null;
}
return PaymentDecision.players(oppsThatCanGainLife);
}
@Override
public PaymentDecision visit(CostMill cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> topLib = player.getCardsIn(ZoneType.Library, c);
return topLib.size() < c ? null : PaymentDecision.card(topLib);
}
@Override
public PaymentDecision visit(CostPartMana cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostPayLife cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
if (!player.canPayLife(c)) {
return null;
}
// activator.payLife(c, null);
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostPutCardToLib cost) {
Integer c = cost.convertAmount();
final Game game = player.getGame();
List<Card> chosen = new ArrayList<Card>();
List<Card> list;
if (cost.isSameZone()) {
list = new ArrayList<Card>(game.getCardsIn(cost.getFrom()));
} else {
list = new ArrayList<Card>(player.getCardsIn(cost.getFrom()));
}
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
list = CardLists.getValidCards(list, cost.getType().split(";"), player, source);
if (cost.isSameZone()) {
// Jotun Grunt
// TODO: improve AI
final List<Player> players = game.getPlayers();
for (Player p : players) {
List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p));
if (enoughType.size() >= c) {
chosen.addAll(enoughType);
break;
}
}
chosen = chosen.subList(0, c);
} else {
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
}
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
@Override
public PaymentDecision visit(CostPutCounter cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
Card card = null;
if (cost.getType().equals("Creature.YouCtrl")) {
card = ComputerUtilCard.getWorstCreatureAI(typeList);
} else {
card = ComputerUtilCard.getWorstPermanentAI(typeList, false, false, false, false);
}
return PaymentDecision.card(card);
}
@Override
public PaymentDecision visit(CostTap cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostTapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
List<Card> typeList =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), ability.getActivatingPlayer(), ability.getSourceCard());
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
c = typeList.size();
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
if (cost.getType().contains("sharesCreatureTypeWith") || cost.getType().contains("withTotalPowerGE")) {
return null;
}
List<Card> totap = ComputerUtil.chooseTapType(player, cost.getType(), source, !cost.canTapSource, c);
if (totap == null) {
System.out.println("Couldn't find a valid card to tap for: " + source.getName());
return null;
}
return PaymentDecision.card(totap);
}
@Override
public PaymentDecision visit(CostSacrifice cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
if (cost.getAmount().equals("All")) {
/*List<Card> typeList = new ArrayList<Card>(activator.getCardsIn(ZoneType.Battlefield));
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activator, source);
if (activator.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
typeList = CardLists.getNotType(typeList, "Creature");
}*/
// Does the AI want to use Sacrifice All?
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> list = ComputerUtil.chooseSacrificeType(player, cost.getType(), source, ability.getTargetCard(), c);
return PaymentDecision.card(list);
}
@Override
public PaymentDecision visit(CostReturn cost) {
if (cost.payCostFromSource())
return PaymentDecision.card(source);
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
return res.isEmpty() ? null : PaymentDecision.card(res);
}
@Override
public PaymentDecision visit(CostReveal cost) {
final String type = cost.getType();
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
if (cost.payCostFromSource()) {
if (!hand.contains(source)) {
return null;
}
return PaymentDecision.card(source);
}
if (cost.getType().equals("Hand"))
return PaymentDecision.card(player.getCardsIn(ZoneType.Hand));
if (cost.getType().equals("SameColor")) {
return null;
}
hand = CardLists.getValidCards(hand, type.split(";"), player, source);
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
if (sVar.equals("XChoice")) {
c = hand.size();
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
}
@Override
public PaymentDecision visit(CostRemoveAnyCounter cost) {
final String amount = cost.getAmount();
final int c = AbilityUtils.calculateAmount(source, amount, ability);
final String type = cost.getType();
List<Card> typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source);
List<Card> hperms = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
// Only find cards with enough negative counters
// TODO: add ai for Chisei, Heart of Oceans
return hperms.isEmpty() ? null : PaymentDecision.card(hperms);
}
@Override
public PaymentDecision visit(CostRemoveCounter cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
final String type = cost.getType();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
return null;
}
if (amount.equals("All")) {
c = source.getCounters(cost.counter);
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
if (!cost.payCostFromSource()) {
List<Card> typeList;
if (type.equals("OriginalHost")) {
typeList = Lists.newArrayList(ability.getOriginalHost());
} else {
typeList = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source);
}
for (Card card : typeList) {
if (card.getCounters(cost.counter) >= c) {
return PaymentDecision.card(card);
}
}
return null;
}
if (c > source.getCounters(cost.counter)) {
System.out.println("Not enough " + cost.counter + " on " + source.getName());
return null;
}
PaymentDecision result = PaymentDecision.card(source);
result.c = c; // cost.cntRemoved = c;
return result;
}
@Override
public PaymentDecision visit(CostUntapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, ability.getSourceCard());
if (!cost.canUntapSource) {
typeList.remove(source);
}
typeList = CardLists.filter(typeList, Presets.TAPPED);
c = typeList.size();
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
List<Card> list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
if (list == null) {
System.out.println("Couldn't find a valid card to untap for: " + source.getName());
return null;
}
return PaymentDecision.card(list);
}
@Override
public PaymentDecision visit(CostUntap cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostUnattach cost) {
Card cardToUnattach = cost.findCardToUnattach(source, (Player) player, ability);
if (cardToUnattach == null) {
// We really shouldn't be able to get here if there's nothing to unattach
return null;
}
return PaymentDecision.card(cardToUnattach);
}
@Override
public boolean paysRightAfterDecision() {
return false;
}
}

View File

@@ -1,194 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
import forge.game.ai.AiProps;
import forge.game.player.LobbyPlayer;
import forge.game.player.LobbyPlayerAi;
import forge.util.Aggregates;
import forge.util.FileUtil;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import org.apache.commons.lang3.ArrayUtils;
/**
* Holds default AI personality profile values in an enum.
* Loads profile from the given text file when setProfile is called.
* If a requested value is not loaded from a profile, default is returned.
*
* @author Forge
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
*/
public class AiProfileUtil {
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
private static final String AI_PROFILE_DIR = "res/ai";
private static final String AI_PROFILE_EXT = ".ai";
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
public static final String AI_PROFILE_RANDOM_DUEL = "* Random (Duel) *";
/** Builds an AI profile file name with full relative
* path based on the profile name.
* @param profileName the name of the profile.
* @return the full relative path and file name for the given profile.
*/
private static String buildFileName(final String profileName) {
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
}
/**
* Load all profiles
*/
public static final void loadAllProfiles() {
loadedProfiles.clear();
ArrayList<String> availableProfiles = getAvailableProfiles();
for (String profile : availableProfiles) {
loadedProfiles.put(profile, loadProfile(profile));
}
}
/**
* Load a single profile.
* @param profileName a profile to load.
*/
private static final Map<AiProps, String> loadProfile(final String profileName) {
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
List<String> lines = FileUtil.readFile(buildFileName(profileName));
for (String line : lines) {
if (line.startsWith("#") || (line.length() == 0)) {
continue;
}
final String[] split = line.split("=");
if (split.length == 2) {
profileMap.put(AiProps.valueOf(split[0]), split[1]);
} else if (split.length == 1 && line.endsWith("=")) {
profileMap.put(AiProps.valueOf(split[0]), "");
}
}
return profileMap;
}
/**
* Returns an AI property value for the current profile.
*
* @param fp0 an AI property.
* @return String
*/
public static String getAIProp(final LobbyPlayer p, final AiProps fp0) {
String val = null;
if (!(p instanceof LobbyPlayerAi))
return "";
String profile = ((LobbyPlayerAi) p).getAiProfile();
if (loadedProfiles.get(profile) != null) {
val = loadedProfiles.get(profile).get(fp0);
}
if (val == null) { val = fp0.getDefault(); }
return val;
}
/**
* Returns an array of strings containing all available profiles.
* @return ArrayList<String> - an array of strings containing all
* available profiles.
*/
public static ArrayList<String> getAvailableProfiles()
{
final ArrayList<String> availableProfiles = new ArrayList<String>();
final File dir = new File(AI_PROFILE_DIR);
final String[] children = dir.list();
if (children == null) {
System.err.println("AIProfile > can't find AI profile directory!");
} else {
for (int i = 0; i < children.length; i++) {
if (children[i].endsWith(AI_PROFILE_EXT)) {
availableProfiles.add(children[i].substring(0, children[i].length() - AI_PROFILE_EXT.length()));
}
}
}
return availableProfiles;
}
/**
* Returns an array of strings containing all available profiles including
* the special "Random" profiles.
* @return ArrayList<String> - an array list of strings containing all
* available profiles including special random profile tags.
*/
public static ArrayList<String> getProfilesDisplayList() {
final ArrayList<String> availableProfiles = new ArrayList<String>();
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
availableProfiles.addAll(getAvailableProfiles());
return availableProfiles;
}
public static String[] getProfilesArray() {
return getProfilesDisplayList().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
/**
* Returns a random personality from the currently available ones.
* @return String - a string containing a random profile from all the
* currently available ones.
*/
public static String getRandomProfile() {
return Aggregates.random(getAvailableProfiles());
}
/**
* Simple class test facility for AiProfileUtil.
*-/
public static void selfTest() {
final LobbyPlayer activePlayer = Singletons.getControl().getPlayer().getLobbyPlayer();
System.out.println(String.format("Current profile = %s", activePlayer.getAiProfile()));
ArrayList<String> profiles = getAvailableProfiles();
System.out.println(String.format("Available profiles: %s", profiles));
if (profiles.size() > 0) {
System.out.println(String.format("Loading all profiles..."));
loadAllProfiles();
System.out.println(String.format("Setting profile %s...", profiles.get(0)));
activePlayer.setAiProfile(profiles.get(0));
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
String randomProfile = getRandomProfile();
System.out.println(String.format("Loading random profile %s...", randomProfile));
activePlayer.setAiProfile(randomProfile);
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
}
}
*/
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,894 +0,0 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.combat.Combat;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
/**
* TODO: Write javadoc for this type.
*
*/
public class ComputerUtilCard {
/**
* <p>
* getMostExpensivePermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getMostExpensivePermanentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = list;
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
return ComputerUtilCard.getMostExpensivePermanentAI(all);
}
// The AI doesn't really pick the best artifact, just the most expensive.
/**
* <p>
* getBestArtifactAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestArtifactAI(final List<Card> list) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ARTIFACTS);
if (all.size() == 0) {
return null;
}
// get biggest Artifact
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
// The AI doesn't really pick the best enchantment, just the most expensive.
/**
* <p>
* getBestEnchantmentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS);
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
// get biggest Enchantment
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
/**
* <p>
* getBestLandAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestLandAI(final Collection<Card> list) {
final List<Card> land = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (land.isEmpty()) {
return null;
}
// prefer to target non basic lands
final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
if (!nbLand.isEmpty()) {
// TODO - Rank non basics?
return Aggregates.random(nbLand);
}
// if no non-basic lands, target the least represented basic land type
String sminBL = "";
int iminBL = 20000; // hopefully no one will ever have more than 20000
// lands of one type....
int n = 0;
for (String name : MagicColor.Constant.BASIC_LANDS) {
n = CardLists.getType(land, name).size();
if ((n < iminBL) && (n > 0)) {
// if two or more are tied, only the
// first
// one checked will be used
iminBL = n;
sminBL = name;
}
}
if (iminBL == 20000) {
return null; // no basic land was a minimum
}
final List<Card> bLand = CardLists.getType(land, sminBL);
for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) {
return ut;
}
return Aggregates.random(bLand); // random tapped land of least represented type
}
/**
* <p>
* getCheapestPermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getCheapestPermanentAI(Collection<Card> all, final SpellAbility spell, final boolean targeted) {
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
if (all.isEmpty()) {
return null;
}
// get cheapest card:
Card cheapest = null;
for (Card c : all) {
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c;
}
}
return cheapest;
}
// returns null if list.size() == 0
/**
* <p>
* getBestAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestAI(final Collection<Card> list) {
// Get Best will filter by appropriate getBest list if ALL of the list
// is of that type
if (Iterables.all(list, CardPredicates.Presets.CREATURES))
return ComputerUtilCard.getBestCreatureAI(list);
if (Iterables.all(list, CardPredicates.Presets.LANDS))
return getBestLandAI(list);
// TODO - Once we get an EvaluatePermanent this should call
// getBestPermanent()
return ComputerUtilCard.getMostExpensivePermanentAI(list);
}
/**
* getBestCreatureAI.
*
* @param list
* the list
* @return the card
*/
public static Card getBestCreatureAI(final Collection<Card> list) {
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
}
/**
* <p>
* getWorstCreatureAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstCreatureAI(final Collection<Card> list) {
return Aggregates.itemWithMin(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
}
// This selection rates tokens higher
/**
* <p>
* getBestCreatureToBounceAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestCreatureToBounceAI(final List<Card> list) {
final int tokenBonus = 60;
Card biggest = null;
int biggestvalue = -1;
for(Card card : CardLists.filter(list, CardPredicates.Presets.CREATURES)) {
int newvalue = ComputerUtilCard.evaluateCreature(card);
newvalue += card.isToken() ? tokenBonus : 0; // raise the value of tokens
if (biggestvalue < newvalue) {
biggest = card;
biggestvalue = newvalue;
}
}
return biggest;
}
/**
* <p>
* getWorstAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstAI(final Collection<Card> list) {
return ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
}
/**
* <p>
* getWorstPermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param biasEnch
* a boolean.
* @param biasLand
* a boolean.
* @param biasArt
* a boolean.
* @param biasCreature
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstPermanentAI(final Collection<Card> list, final boolean biasEnch, final boolean biasLand,
final boolean biasArt, final boolean biasCreature) {
if (list.size() == 0) {
return null;
}
final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS);
if (biasEnch && hasEnchantmants) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false);
}
final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS);
if (biasArt && hasArtifacts) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false);
}
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
}
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
if (biasCreature && hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (lands.size() > 6) {
return ComputerUtilCard.getWorstLand(lands);
}
if (hasEnchantmants || hasArtifacts) {
final List<Card> ae = CardLists.filter(list, Predicates.<Card>or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS));
return getCheapestPermanentAI(ae, null, false);
}
if (hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
// Planeswalkers fall through to here, lands will fall through if there
// aren't very many
return getCheapestPermanentAI(list, null, false);
}
public static final Function<Card, Integer> fnEvaluateCreature = new Function<Card, Integer>() {
@Override
public Integer apply(Card a) {
return ComputerUtilCard.evaluateCreature(a);
}
};
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
}
};
/**
* <p>
* evaluateCreature.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public static int evaluateCreature(final Card c) {
int value = 100;
if (c.isToken()) {
value = 80; // tokens should be worth less than actual cards
}
int power = c.getNetCombatDamage();
final int toughness = c.getNetDefense();
for (String keyword : c.getKeyword()) {
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
break;
}
}
value += power * 15;
value += toughness * 10;
value += c.getCMC() * 5;
// Evasion keywords
if (c.hasKeyword("Flying")) {
value += power * 10;
}
if (c.hasKeyword("Horsemanship")) {
value += power * 10;
}
if (c.hasKeyword("Unblockable")) {
value += power * 10;
} else {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += power * 6;
}
if (c.hasKeyword("Fear")) {
value += power * 6;
}
if (c.hasKeyword("Intimidate")) {
value += power * 6;
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += power * 3;
}
}
// Other good keywords
if (power > 0) {
if (c.hasKeyword("Double Strike")) {
value += 10 + (power * 15);
} else if (c.hasKeyword("First Strike")) {
value += 10 + (power * 5);
}
if (c.hasKeyword("Deathtouch")) {
value += 25;
}
if (c.hasKeyword("Lifelink")) {
value += power * 10;
}
if (power > 1 && c.hasKeyword("Trample")) {
value += (power - 1) * 5;
}
if (c.hasKeyword("Vigilance")) {
value += (power * 5) + (toughness * 5);
}
if (c.hasKeyword("Wither")) {
value += power * 10;
}
if (c.hasKeyword("Infect")) {
value += power * 15;
}
value += c.getKeywordMagnitude("Rampage");
}
value += c.getKeywordMagnitude("Bushido") * 16;
value += c.getAmountOfKeyword("Flanking") * 15;
value += c.getAmountOfKeyword("Exalted") * 15;
value += c.getKeywordMagnitude("Annihilator") * 50;
// Defensive Keywords
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
value += 5;
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += 3;
}
// Protection
if (c.hasKeyword("Indestructible")) {
value += 70;
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
value += 60;
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += 50;
}
if (c.hasKeyword("Hexproof")) {
value += 35;
} else if (c.hasKeyword("Shroud")) {
value += 30;
}
if (c.hasStartOfKeyword("Protection")) {
value += 20;
}
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
value += 10;
}
value += c.getKeywordMagnitude("Absorb") * 11;
// Bad keywords
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
value -= (power * 9) + 40;
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= 40;
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= 10;
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")) {
value -= 10;
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= 10;
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= toughness * 5;
}
if (c.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it.")) {
value -= (toughness - 1) * 9;
}
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = 50 + (c.getCMC() * 5); // reset everything - useless
}
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
if (c.isTapped()) {
value = 50 + (c.getCMC() * 5); // reset everything - useless
} else {
value -= 50;
}
}
if (c.hasKeyword("At the beginning of the end step, destroy CARDNAME.")
|| c.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|| c.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")) {
value -= 50;
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
value -= 30;
} else if (c.hasStartOfKeyword("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")
|| c.hasStartOfKeyword("Upkeep:")) {
value -= 20;
} else if (c.hasStartOfKeyword("(Echo unpaid)")) {
value -= 10;
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= 20;
}
if (c.hasStartOfKeyword("Fading")) {
value -= 20;
}
if (c.hasStartOfKeyword("Vanishing")) {
value -= 20;
}
if (c.getSVar("Targeting").equals("Dies")) {
value -= 25;
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += 10;
}
}
if (!c.getManaAbility().isEmpty()) {
value += 10;
}
if (c.isUntapped()) {
value += 1;
}
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += 14;
}
if (!c.getEncoded().isEmpty()) {
value += 24;
}
return value;
} // evaluateCreature
/**
* <p>
* evaluatePermanentList.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a int.
*/
public static int evaluatePermanentList(final List<Card> list) {
int value = 0;
for (int i = 0; i < list.size(); i++) {
value += list.get(i).getCMC() + 1;
}
return value;
}
/**
* <p>
* evaluateCreatureList.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a int.
*/
public static int evaluateCreatureList(final List<Card> list) {
return Aggregates.sum(list, fnEvaluateCreature);
}
/**
* <p>
* doesCreatureAttackAI.
* </p>
*
* @param ai
* the AI player
* @param card
* a {@link forge.game.card.Card} object.
* @return a boolean.
*/
public static boolean doesCreatureAttackAI(final Player ai, final Card card) {
AiAttackController aiAtk = new AiAttackController(ai);
Combat combat = new Combat(ai);
aiAtk.declareAttackers(combat);
return combat.isAttacking(card);
}
/**
* getMostExpensivePermanentAI.
*
* @param all
* the all
* @return the card
*/
public static Card getMostExpensivePermanentAI(final Collection<Card> all) {
Card biggest = null;
int bigCMC = -1;
for (final Card card : all) {
int curCMC = card.getCMC();
// Add all cost of all auras with the same controller
final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
if (curCMC >= bigCMC) {
bigCMC = curCMC;
biggest = card;
}
}
return biggest;
}
/**
* <p>
* getMostProminentCardName.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentCardName(final List<Card> list) {
if (list.size() == 0) {
return "";
}
final Map<String, Integer> map = new HashMap<String, Integer>();
for (final Card c : list) {
final String name = c.getName();
Integer currentCnt = map.get(name);
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
} // for
int max = 0;
String maxName = "";
for (final Entry<String, Integer> entry : map.entrySet()) {
final String type = entry.getKey();
// Log.debug(type + " - " + entry.getValue());
if (max < entry.getValue()) {
max = entry.getValue();
maxName = type;
}
}
return maxName;
}
/**
* <p>
* getMostProminentCreatureType.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentCreatureType(final List<Card> list) {
if (list.size() == 0) {
return "";
}
final Map<String, Integer> map = new HashMap<String, Integer>();
for (final Card c : list) {
final ArrayList<String> typeList = c.getType();
for (final String var : typeList) {
if (CardType.isACreatureType(var)) {
if (!map.containsKey(var)) {
map.put(var, 1);
} else {
map.put(var, map.get(var) + 1);
}
}
}
} // for
int max = 0;
String maxType = "";
for (final Entry<String, Integer> entry : map.entrySet()) {
final String type = entry.getKey();
// Log.debug(type + " - " + entry.getValue());
if (max < entry.getValue()) {
max = entry.getValue();
maxType = type;
}
}
return maxType;
}
/**
* <p>
* getMostProminentColor.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentColor(final List<Card> list) {
byte colors = CardFactoryUtil.getMostProminentColors(list);
for(byte c : MagicColor.WUBRG) {
if ( (colors & c) != 0 )
return MagicColor.toLongString(c);
}
return MagicColor.Constant.WHITE; // no difference, there was no prominent color
}
public static String getMostProminentColor(final List<Card> list, final List<String> restrictedToColors) {
byte colors = CardFactoryUtil.getMostProminentColorsFromList(list, restrictedToColors);
for (byte c : MagicColor.WUBRG) {
if ((colors & c) != 0) {
return MagicColor.toLongString(c);
}
}
return restrictedToColors.get(0); // no difference, there was no prominent color
}
public static List<String> getColorByProminence(final List<Card> list) {
int cntColors = MagicColor.WUBRG.length;
final List<Pair<Byte,Integer>> map = new ArrayList<Pair<Byte,Integer>>();
for(int i = 0; i < cntColors; i++) {
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
}
for (final Card crd : list) {
ColorSet color = CardUtil.getColors(crd);
if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1));
if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1));
if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1));
if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue()+1));
if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue()+1));
} // for
Collections.sort(map, new Comparator<Pair<Byte,Integer>>() {
@Override public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
// will this part be once dropped?
List<String> result = new ArrayList<String>(cntColors);
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
result.add(MagicColor.toLongString(idx.getKey()));
}
// reverse to get indices for most prominent colors first.
return result;
}
/**
* <p>
* getWorstLand.
* </p>
*
* @param lands
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstLand(final List<Card> lands) {
Card worstLand = null;
int maxScore = 0;
// first, check for tapped, basic lands
for (Card tmp : lands) {
int score = tmp.isTapped() ? 2 : 0;
score += tmp.isBasicLand() ? 1 : 0;
score += tmp.isCreature() ? 4 : 0;
if (score >= maxScore) {
worstLand = tmp;
maxScore = score;
}
}
return worstLand;
} // end getWorstLand
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
@Override
public boolean apply(Deck d) {
for(Entry<DeckSection, CardPool> cp: d) {
for(Entry<PaperCard, Integer> e : cp.getValue()) {
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
return false;
}
}
return true;
}
};
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
List<String> chosen = new ArrayList<String>();
Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Player opp = ai.getOpponent();
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if (logic.equals("MostProminentInHumanDeck")) {
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
} else if (logic.equals("MostProminentInComputerDeck")) {
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices));
} else if (logic.equals("MostProminentDualInComputerDeck")) {
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
chosen.add(prominence.get(0));
chosen.add(prominence.get(1));
}
else if (logic.equals("MostProminentInGame")) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCardsInGame(), colorChoices));
}
else if (logic.equals("MostProminentHumanCreatures")) {
List<Card> list = opp.getCreaturesInPlay();
if (list.isEmpty()) {
list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES);
}
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
}
else if (logic.equals("MostProminentComputerControls")) {
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentHumanControls")) {
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getOpponent().getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentPermanent")) {
final List<Card> list = game.getCardsIn(ZoneType.Battlefield);
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
}
else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers(), colorChoices));
}
else if (logic.equals("MostProminentInActivePlayerHand")) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices));
}
else if (logic.equals("MostProminentKeywordInComputerDeck")) {
List<Card> list = ai.getAllCards();
int m1 = 0;
String chosenColor = MagicColor.Constant.WHITE;
for (final String c : MagicColor.Constant.ONLY_COLORS) {
final int cmp = CardLists.filter(list, CardPredicates.containsKeyword(c)).size();
if (cmp > m1) {
m1 = cmp;
chosenColor = c;
}
}
chosen.add(chosenColor);
}
}
if (chosen.size() == 0) {
chosen.add(MagicColor.Constant.GREEN);
}
return chosen;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,418 +0,0 @@
package forge.ai;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.cost.CostDamage;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.cost.CostPayLife;
import forge.game.cost.CostPayment;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostSacrifice;
import forge.game.player.Player;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
/**
* TODO: Write javadoc for this type.
*
*/
public class ComputerUtilCost {
/**
* Check add m1 m1 counter cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkAddM1M1CounterCost(final Cost cost, final Card source) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPutCounter) {
final CostPutCounter addCounter = (CostPutCounter) part;
final CounterType type = addCounter.getCounter();
if (type.equals(CounterType.M1M1)) {
return false;
}
}
}
return true;
}
/**
* Check remove counter cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkRemoveCounterCost(final Cost cost, final Card source) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostRemoveCounter) {
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
final CounterType type = remCounter.counter;
if (!part.payCostFromSource()) {
if (type.name().equals("P1P1")) {
return false;
}
continue;
}
//don't kill the creature
if (type.name().equals("P1P1") && source.getLethalDamage() <= 1) {
return false;
}
}
}
return true;
}
/**
* Check discard cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostDiscard) {
final CostDiscard disc = (CostDiscard) part;
final String type = disc.getType();
final List<Card> typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Hand), type.split(","), source.getController(), source);
if (typeList.size() > ai.getMaxHandSize()) {
continue;
}
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
for (int i = 0; i < num; i++) {
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
if (pref == null) {
return false;
} else {
typeList.remove(pref);
}
}
}
}
return true;
}
/**
* Check life cost.
*
* @param cost
* the cost
* @param source
* the source
* @param remainingLife
* the remaining life
* @return true, if successful
*/
public static boolean checkDamageCost(final Player ai, final Cost cost, final Card source, final int remainingLife) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostDamage) {
final CostDamage pay = (CostDamage) part;
int realDamage = ComputerUtilCombat.predictDamageTo(ai, pay.convertAmount(), source, false);
if (ai.getLife() - realDamage < remainingLife
&& realDamage > 0 && !ai.cantLoseForZeroOrLessLife()
&& ai.canLoseLife()) {
return false;
}
if (source.getName().equals("Skullscorch") && ai.getCardsIn(ZoneType.Hand).size() < 2) {
return false;
}
}
}
return true;
}
/**
* Check life cost.
*
* @param cost
* the cost
* @param source
* the source
* @param remainingLife
* the remaining life
* @param sourceAbility TODO
* @return true, if successful
*/
public static boolean checkLifeCost(final Player ai, final Cost cost, final Card source, final int remainingLife, SpellAbility sourceAbility) {
// TODO - Pass in SA for everything else that calls this function
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) {
final CostPayLife payLife = (CostPayLife) part;
Integer amount = payLife.convertAmount();
if (amount == null) {
amount = AbilityUtils.calculateAmount(source, payLife.getAmount(), sourceAbility);
}
if ((ai.getLife() - amount) < remainingLife) {
return false;
}
}
}
return true;
}
/**
* Check creature sacrifice cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkCreatureSacrificeCost(final Player ai, final Cost cost, final Card source) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) {
final CostSacrifice sac = (CostSacrifice) part;
if (sac.payCostFromSource() && source.isCreature()) {
return false;
}
final String type = sac.getType();
if (type.equals("CARDNAME")) {
continue;
}
final List<Card> typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source);
if (ComputerUtil.getCardPreference(ai, source, "SacCost", typeList) == null) {
return false;
}
}
}
return true;
}
/**
* Check sacrifice cost.
*
* @param cost
* the cost
* @param source
* the source
* @param important
* is the gain important enough?
* @return true, if successful
*/
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final boolean important) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice) {
final CostSacrifice sac = (CostSacrifice) part;
final String type = sac.getType();
if (type.equals("CARDNAME")) {
if (!important) {
return false;
}
List<Card> auras = new ArrayList<Card>(source.getEnchantedBy());
if (!CardLists.filterControlledBy(auras, source.getController()).isEmpty()) {
return false;
}
continue;
}
final List<Card> typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source);
if (ComputerUtil.getCardPreference(ai, source, "SacCost", typeList) == null) {
return false;
}
}
}
return true;
}
/**
* Check sacrifice cost.
*
* @param cost
* the cost
* @param source
* the source
* @return true, if successful
*/
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source) {
return checkSacrificeCost(ai, cost, source, true);
}
/**
* <p>
* shouldPayCost.
* </p>
*
* @param hostCard
* a {@link forge.game.card.Card} object.
* @param costString
* a {@link java.lang.String} object.
* @return a boolean.
*/
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) {
final int remainingLife = ai.getLife();
final int lifeCost = ((CostPayLife) part).convertAmount();
if ((remainingLife - lifeCost) < 10) {
return false; //Don't pay life if it would put AI under 10 life
} else if ((remainingLife / lifeCost) < 4) {
return false; //Don't pay life if it is more than 25% of current life
}
}
}
return true;
} // shouldPayCost()
/**
* <p>
* canPayCost.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @return a boolean.
*/
public static boolean canPayCost(final SpellAbility sa, final Player player) {
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
// Check for stuff like Nether Void
int extraManaNeeded = 0;
if (sa instanceof Spell) {
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
if (!StringUtils.isBlank(snem)) {
String[] parts = TextUtil.split(snem, ' ');
boolean meetsRestriction = parts.length == 1 || player.isValid(parts[1], c.getController(), c);
if(!meetsRestriction)
continue;
try {
extraManaNeeded += Integer.parseInt(snem);
} catch (final NumberFormatException e) {
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
}
}
}
for (Card c : player.getCardsIn(ZoneType.Command)) {
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
if (!StringUtils.isBlank(snem)) {
try {
extraManaNeeded += Integer.parseInt(snem);
} catch (final NumberFormatException e) {
System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c);
}
}
}
}
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, List<Player> payers) {
final Card source = sa.getSourceCard();
boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI"));
boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false;
boolean payNever = "Never".equals(sa.getParam("UnlessAI"));
boolean shockland = "Shockland".equals(sa.getParam("UnlessAI"));
boolean isMine = sa.getActivatingPlayer().equals(payer);
if (payNever) { return false; }
if (payForOwnOnly && !isMine) { return false; }
if (payOwner) {
final String defined = sa.getParam("UnlessAI").substring(7);
final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0);
if (!payer.equals(player)) {
return false;
}
} else if (shockland) {
if (payer.getLife() > 3 && payer.canPayLife(2)) {
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
if (landsize == c.getCMC()) {
return true;
}
}
}
return false;
} else if ("Paralyze".equals(sa.getParam("UnlessAI"))) {
final Card c = source.getEnchantingCard();
if (c == null || c.isUntapped()) {
return false;
}
} else if ("MorePowerful".equals(sa.getParam("UnlessAI"))) {
final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size();
final int payerCreatures = payer.getCreaturesInPlay().size();
if (payerCreatures > sourceCreatures + 1) {
return false;
}
}
// AI will only pay when it's not already payed and only opponents abilities
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
return false;
}
// AI was crashing because the blank ability used to pay costs
// Didn't have any of the data on the original SA to pay dependant costs
return checkLifeCost(payer, cost, source, 4, sa)
&& checkDamageCost(payer, cost, source, 4)
&& (isMine || checkSacrificeCost(payer, cost, source))
&& (isMine || checkDiscardCost(payer, cost, source))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
&& (!source.getName().equals("Chain of Vapor") || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
}
}

View File

@@ -1,781 +0,0 @@
package forge.ai;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import forge.FThreads;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CollectionSuppliers;
import forge.util.maps.EnumMapOfLists;
import forge.util.maps.MapOfLists;
/**
* TODO: Write javadoc for this type.
*
*/
public class ComputerUtilMana {
private final static boolean DEBUG_MANA_PAYMENT = false;
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
cost = new ManaCostBeingPaid(cost); //check copy of cost so it doesn't modify the exist cost being paid
return payManaCost(cost, sa, ai, true, 0, true, true);
}
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
return payManaCost(cost, sa, ai, false, 0, true, false);
}
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
return payManaCost(sa, ai, true, extraMana, true);
}
// Does not check if mana sources can be used right now, just checks for potential chance.
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
sa.setActivatingPlayer(ai);
return payManaCost(sa, ai, true, 0, false);
}
public static boolean payManaCost(final Player ai, final SpellAbility sa) {
return payManaCost(sa, ai, false, 0, true);
}
/**
* <p>
* payManaCost.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param ai
* a {@link forge.game.player.Player} object.
* @param test
* (is for canPayCost, if true does not change the game state)
* @param extraMana
* a int.
* @param checkPlayable
* should we check if playable? use for hypothetical "can AI play this"
* @return a boolean.
* @since 1.0.15
*/
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana);
return payManaCost(cost, sa, ai, test, extraMana, checkPlayable, true);
}
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable, boolean clearManaPaid) {
adjustManaCostToAvoidNegEffects(cost, sa.getSourceCard());
final ManaPool manapool = ai.getManaPool();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
manapool.payManaFromPool(sa, cost, part);
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
if (clearManaPaid) {
manapool.clearManaPaid(sa, test);
}
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
// arrange all mana abilities by color produced.
final Multimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
if (manaAbilityMap.isEmpty()) {
if (clearManaPaid) {
manapool.clearManaPaid(sa, test);
}
handleOfferingsAI(sa, test, cost.isPaid());
return false;
}
if (DEBUG_MANA_PAYMENT) {
System.out.println("DEBUG_MANA_PAYMENT: manaAbilityMap = " + manaAbilityMap);
}
// select which abilities may be used for each shard
MapOfLists<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
if (DEBUG_MANA_PAYMENT) {
System.out.println((test ? "test -- " : "PROD -- ") + FThreads.debugGetStackTraceItem(5, true));
for (Entry<ManaCostShard, Collection<SpellAbility>> src : sourcesForShards.entrySet()) {
System.out.println("\t" +src.getKey() + " : " + src.getValue().size() + " source(s)");
for (SpellAbility sss : src.getValue()) {
System.out.printf("\t\t%s - %s%n", sss.getSourceCard(), sss);
}
}
}
List<String> paymentPlan = new ArrayList<String>();
String originalCost = cost.toString(false);
ManaCostShard toPay = null;
// Loop over mana needed
while (!cost.isPaid()) {
toPay = getNextShardToPay(cost, sourcesForShards);
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
SpellAbility saPayment = null;
if (saList != null) {
for (final SpellAbility ma : saList) {
if (ma.getSourceCard() == sa.getSourceCard()) {
continue;
}
final String typeRes = cost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !ma.getSourceCard().isType(typeRes)) {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, checkPlayable || !test)) {
saPayment = ma;
break;
}
}
} else {
break;
}
if (DEBUG_MANA_PAYMENT) {
paymentPlan.add(String.format("%s : (%s) %s", toPay, saPayment == null ? "LIFE" : saPayment.getSourceCard(), saPayment));
}
if (saPayment == null) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
break; // cannot pay
}
cost.payPhyrexian();
if (!test) {
ai.payLife(2, sa.getSourceCard());
}
continue;
}
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
if (test) {
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
//System.out.println(manaProduced);
cost.payMultipleMana(manaProduced);
// remove from available lists
for (Collection<SpellAbility> kv : sourcesForShards.values()) {
Iterator<SpellAbility> itSa = kv.iterator();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
if (srcSa.getSourceCard().equals(saPayment.getSourceCard())) {
itSa.remove();
}
}
}
}
else {
if (saPayment.getPayCosts() != null) {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
saList.remove(saPayment);
continue;
}
}
else {
System.err.println("Ability " + saPayment + " from " + saPayment.getSourceCard() + " had NULL as payCost");
saPayment.getSourceCard().tap();
}
ai.getGame().getStack().addAndUnfreeze(saPayment);
// subtract mana from mana pool
manapool.payManaFromAbility(sa, cost, saPayment);
// no need to remove abilities from resource map,
// once their costs are paid and consume resources, they can not be used again
}
}
if (clearManaPaid) {
manapool.clearManaPaid(sa, test);
}
handleOfferingsAI(sa, test, cost.isPaid());
if (DEBUG_MANA_PAYMENT) {
System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost,
extraMana, sa.getSourceCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
}
if (!cost.isPaid()) {
if (test) {
return false;
}
else {
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getSourceCard().getName() + ". Didn't find what to pay for " + toPay);
return false;
}
}
sa.getSourceCard().setColorsPaid(cost.getColorsPaid());
// if (sa instanceof Spell_Permanent) // should probably add this
sa.getSourceCard().setSunburstValue(cost.getSunburst());
return true;
} // payManaCost()
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart();
if (m.isComboMana())
getComboManaChoice(ai, saPayment, sa, cost);
else if (saPayment.getApi() == ApiType.ManaReflected) {
System.out.println("Evaluate reflected mana of: " + saPayment.getSourceCard());
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
for (byte c : MagicColor.WUBRG) {
if (toPay.canBePaidWithManaOfColor(c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return;
}
}
} else if (m.isAnyMana()) {
byte colorChoice = 0;
if (toPay.isOr2Colorless())
colorChoice = toPay.getColorMask();
else {
for (byte c : MagicColor.WUBRG) {
if (toPay.canBePaidWithManaOfColor(c)) {
colorChoice = c;
break;
}
}
}
m.setExpressChoice(MagicColor.toShortString(colorChoice));
}
}
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
final Card sourceCard = ma.getSourceCard();
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
AbilityManaPart m = ma.getManaPart();
if (!m.meetsManaRestrictions(sa)) {
return false;
}
if (checkCosts) {
// Check if AI can still play this mana ability
ma.setActivatingPlayer(ai);
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
} else if (sourceCard.isTapped()) {
return false;
}
}
if (m.isComboMana()) {
for (String s : m.getComboColors().split(" ")) {
if ("Any".equals(s) || toPay.canBePaidWithManaOfColor(MagicColor.fromName(s)))
return true;
}
return false;
} else if (ma.getApi() == ApiType.ManaReflected) {
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
if (toPay.canBePaidWithManaOfColor(c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return true;
}
}
return false;
}
return true;
}
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost, Map<ManaCostShard, Collection<SpellAbility>> sourcesForShards) {
// mind the priorities
// * Pay mono-colored first,
// * Pay 2/C with matching colors
// * pay hybrids
// * pay phyrexian, keep mana for colorless
// * pay colorless
for (ManaCostShard s : cost.getDistinctShards()) { // should check in which order EnumMap enumerates keys. If it's same as enum member declaration, nothing else needs to be done.
return s;
}
return null;
}
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card) {
// Make mana needed to avoid negative effect a mandatory cost for the AI
for (String manaPart : card.getSVar("ManaNeededToAvoidNegativeEffect").split(",")) {
// convert long color strings to short color strings
byte mask = MagicColor.fromName(manaPart);
// make mana mandatory for AI
if (!cost.needsColor(mask) && cost.getColorlessManaAmount() > 0) {
ManaCostShard shard = ManaCostShard.valueOf(mask);
cost.increaseShard(shard, 1);
cost.decreaseColorlessMana(1);
}
}
}
/**
* <p>
* getComboManaChoice.
* </p>
*
* @param abMana
* a {@link forge.card.spellability.AbilityMana} object.
* @param saRoot
* a {@link forge.game.spellability.SpellAbility} object.
* @param cost
* a {@link forge.game.mana.ManaCostBeingPaid} object.
* @return String
*/
private static void getComboManaChoice(final Player ai, final SpellAbility manaAb, final SpellAbility saRoot, final ManaCostBeingPaid cost) {
final StringBuilder choiceString = new StringBuilder();
final Card source = manaAb.getSourceCard();
final AbilityManaPart abMana = manaAb.getManaPart();
if (abMana.isComboMana()) {
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
final String[] comboColors = abMana.getComboColors().split(" ");
for (int nMana = 1; nMana <= amount; nMana++) {
String choice = "";
// Use expressChoice first
if (!abMana.getExpressChoice().isEmpty()) {
choice = abMana.getExpressChoice();
abMana.clearExpressChoice();
byte colorMask = MagicColor.fromName(choice);
if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask)) {
choiceString.append(choice);
testCost.payMultipleMana(choice);
continue;
}
}
// check colors needed for cost
if (!testCost.isPaid()) {
// Loop over combo colors
for (String color : comboColors) {
if (testCost.isAnyPartPayableWith(MagicColor.fromName(color))) {
testCost.payMultipleMana(color);
if (nMana != 1) {
choiceString.append(" ");
}
choiceString.append(color);
choice = color;
break;
}
}
if (!choice.isEmpty()) {
continue;
}
}
// check if combo mana can produce most common color in hand
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(
ZoneType.Hand));
if (!commonColor.isEmpty() && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
choice = MagicColor.toShortString(commonColor);
}
else {
// default to first color
choice = comboColors[0];
}
if (nMana != 1) {
choiceString.append(" ");
}
choiceString.append(choice);
}
}
if (choiceString.toString().isEmpty()) {
choiceString.append("0");
}
abMana.setExpressChoice(choiceString.toString());
}
/**
* Find all mana sources.
* @param manaAbilityMap
* @param partSources
* @param partPriority
* @param costParts
* @param foundAllSources
* @return Were all mana sources found?
*/
private static MapOfLists<ManaCostShard, SpellAbility> groupAndOrderToPayShards(final Player ai, final Multimap<Integer, SpellAbility> manaAbilityMap,
final ManaCostBeingPaid cost) {
MapOfLists<ManaCostShard, SpellAbility> res = new EnumMapOfLists<ManaCostShard, SpellAbility>(ManaCostShard.class, CollectionSuppliers.<SpellAbility>arrayLists());
// loop over cost parts
for (ManaCostShard shard : cost.getDistinctShards()) {
if (shard == ManaCostShard.S) {
res.put(shard, manaAbilityMap.get(ManaAtom.IS_SNOW));
continue;
}
if (shard.isOr2Colorless()) {
Integer colorKey = Integer.valueOf(shard.getColorMask());
if (manaAbilityMap.containsKey(colorKey))
res.addAll(shard, manaAbilityMap.get(colorKey));
if (manaAbilityMap.containsKey(ManaAtom.COLORLESS))
res.addAll(shard, manaAbilityMap.get(ManaAtom.COLORLESS));
continue;
}
for (Entry<Integer, SpellAbility> kv : manaAbilityMap.entries()) {
if (shard.canBePaidWithManaOfColor(kv.getKey().byteValue())) {
res.add(shard, kv.getValue());
}
}
}
if (cost.getColorlessManaAmount() > 0 && manaAbilityMap.containsKey(ManaAtom.COLORLESS)) {
res.addAll(ManaCostShard.COLORLESS, manaAbilityMap.get(ManaAtom.COLORLESS));
}
return res;
}
/**
* Calculate the ManaCost for the given SpellAbility.
* @param sa
* @param test
* @param extraMana
* @return ManaCost
*/
private static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
ZoneType castFromBackup = null;
if (test && sa.isSpell()) {
castFromBackup = sa.getSourceCard().getCastFrom();
sa.getSourceCard().setCastFrom(sa.getSourceCard().getZone().getZoneType());
}
Cost payCosts = sa.getPayCosts();
final ManaCost mana = payCosts != null ? payCosts.getTotalMana() : ManaCost.NO_COST;
String restriction = null;
if (payCosts != null && payCosts.getCostMana() != null) {
restriction = payCosts.getCostMana().getRestiction();
}
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
cost.applySpellCostChange(sa, test);
final Card card = sa.getSourceCard();
// Tack xMana Payments into mana here if X is a set value
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0 || extraMana > 0)) {
int manaToAdd = 0;
if (test && extraMana > 0) {
final int multiplicator = Math.max(cost.getXcounter(), 1);
manaToAdd = extraMana * multiplicator;
} else {
// For Count$xPaid set PayX in the AFs then use that here
// Else calculate it as appropriate.
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
if (!card.getSVar(xSvar).equals("")) {
if (xSvar.equals("PayX")) {
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
} else {
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
}
}
String manaXColor = sa.getParam("XColor");
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
cost.increaseShard(shardToGrow, manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
}
}
if (test && sa.isSpell()) {
sa.getSourceCard().setCastFrom(castFromBackup);
}
return cost;
}
//This method is currently used by AI to estimate available mana
public static List<Card> getAvailableMana(final Player ai, final boolean checkPlayable) {
final List<Card> list = ai.getCardsIn(ZoneType.Battlefield);
list.addAll(ai.getCardsIn(ZoneType.Hand));
final List<Card> manaSources = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai);
if (!checkPlayable || am.canPlay()) {
return true;
}
}
return false;
}
}); // CardListFilter
final List<Card> sortedManaSources = new ArrayList<Card>();
final List<Card> otherManaSources = new ArrayList<Card>();
final List<Card> colorlessManaSources = new ArrayList<Card>();
final List<Card> oneManaSources = new ArrayList<Card>();
final List<Card> twoManaSources = new ArrayList<Card>();
final List<Card> threeManaSources = new ArrayList<Card>();
final List<Card> fourManaSources = new ArrayList<Card>();
final List<Card> fiveManaSources = new ArrayList<Card>();
final List<Card> anyColorManaSources = new ArrayList<Card>();
// Sort mana sources
// 1. Use lands that can only produce colorless mana without
// drawback/cost first
// 2. Search for mana sources that have a certain number of abilities
// 3. Use lands that produce any color many
// 4. all other sources (creature, costs, drawback, etc.)
for (Card card : manaSources) {
if (card.isCreature() || card.isEnchanted()) {
otherManaSources.add(card);
continue; // don't use creatures before other permanents
}
int usableManaAbilities = 0;
boolean needsLimitedResources = false;
boolean producesAnyColor = false;
final ArrayList<SpellAbility> manaAbilities = getAIPlayableMana(card);
for (final SpellAbility m : manaAbilities) {
if (m.getManaPart().isAnyMana()) {
producesAnyColor = true;
}
final Cost cost = m.getPayCosts();
if (cost != null) {
needsLimitedResources |= !cost.isReusuableResource();
}
// if the AI can't pay the additional costs skip the mana ability
if (cost != null) {
m.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
continue;
}
}
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
if (!sub.getAi().chkDrawbackWithSubs(ai, sub)) {
continue;
}
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
}
usableManaAbilities++;
}
if (needsLimitedResources) {
otherManaSources.add(card);
} else if (producesAnyColor) {
anyColorManaSources.add(card);
} else if (usableManaAbilities == 1) {
if (manaAbilities.get(0).getManaPart().mana().equals("1")) {
colorlessManaSources.add(card);
} else {
oneManaSources.add(card);
}
} else if (usableManaAbilities == 2) {
twoManaSources.add(card);
} else if (usableManaAbilities == 3) {
threeManaSources.add(card);
} else if (usableManaAbilities == 4) {
fourManaSources.add(card);
} else {
fiveManaSources.add(card);
}
}
sortedManaSources.addAll(colorlessManaSources);
sortedManaSources.addAll(oneManaSources);
sortedManaSources.addAll(twoManaSources);
sortedManaSources.addAll(threeManaSources);
sortedManaSources.addAll(fourManaSources);
sortedManaSources.addAll(fiveManaSources);
sortedManaSources.addAll(anyColorManaSources);
//use better creatures later
CardLists.sortByEvaluateCreature(otherManaSources);
Collections.reverse(otherManaSources);
sortedManaSources.addAll(otherManaSources);
return sortedManaSources;
} // getAvailableMana()
//This method is currently used by AI to estimate mana available
private static Multimap<Integer, SpellAbility> groupSourcesByManaColor(final Player ai, boolean checkPlayable) {
final Multimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
final Game game = ai.getGame();
// Loop over all current available mana sources
for (final Card sourceCard : getAvailableMana(ai, checkPlayable)) {
for (final SpellAbility m : getAIPlayableMana(sourceCard)) {
m.setActivatingPlayer(ai);
if (checkPlayable && !m.canPlay()) {
continue;
}
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null) {
if (!sub.getAi().chkDrawbackWithSubs(ai, sub)) {
continue;
}
}
manaMap.put(ManaAtom.COLORLESS, m); // add to colorless source list
AbilityManaPart mp = m.getManaPart();
// setup produce mana replacement effects
final HashMap<String, Object> repParams = new HashMap<String, Object>();
repParams.put("Event", "ProduceMana");
repParams.put("Mana", mp.getOrigProduced());
repParams.put("Affected", sourceCard);
repParams.put("Player", ai);
repParams.put("AbilityMana", m);
for (final Player p : game.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect.requirementsCheck(game)
&& replacementEffect.canReplace(repParams)
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
if (repType.contains("Chosen")) {
repType = repType.replace("Chosen", MagicColor.toShortString(crd.getChosenColor().get(0)));
}
mp.setManaReplaceType(repType);
}
}
}
}
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
// find possible colors
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
manaMap.put(ManaAtom.WHITE, m);
}
if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
manaMap.put(ManaAtom.BLUE, m);
}
if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
manaMap.put(ManaAtom.BLACK, m);
}
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
manaMap.put(ManaAtom.RED, m);
}
if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
manaMap.put(ManaAtom.GREEN, m);
}
if (mp.isSnow()) {
manaMap.put(ManaAtom.IS_SNOW, m);
}
} // end of mana abilities loop
} // end of mana sources loop
return manaMap;
}
/**
* <p>
* determineLeftoverMana.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @return a int.
* @since 1.0.15
*/
public static int determineLeftoverMana(final SpellAbility sa, final Player player) {
for (int i = 1; i < 100; i++)
if (!canPayManaCost(sa, player, i))
return i - 1;
return 99;
}
// Returns basic mana abilities plus "reflected mana" abilities
/**
* <p>
* getAIPlayableMana.
* </p>
*
* @return a {@link java.util.ArrayList} object.
*/
public static final ArrayList<SpellAbility> getAIPlayableMana(Card c) {
final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>();
for (final SpellAbility a : c.getManaAbility()) {
// if a mana ability has a mana cost the AI will miscalculate
// if there is a parent ability the AI can't use it
final Cost cost = a.getPayCosts();
if (!cost.hasNoManaCost() || (a.getApi() != ApiType.Mana && a.getApi() != ApiType.ManaReflected)) {
continue;
}
if (!res.contains(a)) {
res.add(a);
}
}
return res;
}
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getSourceCard().getController().getGame().getAction().sacrifice(offering, sa);
}
sa.resetSacrificedAsOffering();
}
}
}

View File

@@ -1,175 +0,0 @@
package forge.ai;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Iterables;
import forge.game.GameEntity;
import forge.game.ability.SaTargetRoutines;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public abstract class SpellAbilityAi extends SaTargetRoutines {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
if (!canPlayAI(aiPlayer, sa)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
}
protected abstract boolean canPlayAI(final Player aiPlayer, final SpellAbility sa);
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
return false;
}
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
}
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
{
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
}
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (canPlayAI(aiPlayer, sa)) {
return true;
}
if (mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getOpponent();
if (tgt != null) {
if (opp.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(opp);
} else if (mandatory) {
if (aiPlayer.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(opp);
} else {
return false;
}
} else {
return false;
}
}
return true;
}
return false;
}
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
return true;
}
/**
* <p>
* isSorcerySpeed.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return ( sa.isSpell() && sa.getSourceCard().isSorcery() )
|| ( sa.isAbility() && sa.getRestrictions().isSorcerySpeed() );
}
/**
* <p>
* playReusable.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
// TODO probably also consider if winter orb or similar are out
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
return true; // This is only true for Drawbacks and triggers
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (!sa.getPayCosts().isReusuableResource()) {
return false;
}
if (sa.getRestrictions().getPlaneswalker() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
return true;
}
if (sa.isTrigger()) {
return true;
}
if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false;
}
PhaseHandler phase = ai.getGame().getPhaseHandler();
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
}
/**
* TODO: Write javadoc for this method.
* @param ai
* @param subAb
* @return
*/
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
return ab.getAi().chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true;
}
@SuppressWarnings("unchecked")
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
T firstOption = Iterables.getFirst(options, null);
if( firstOption instanceof Card)
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
else if ( firstOption instanceof Player)
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
else
return null;
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return spells.get(0);
}
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
}

View File

@@ -1,18 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class AddPhaseAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}

View File

@@ -1,80 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactory_Turns class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class AddTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else if (mandatory) {
for (final Player ally : ai.getAllies()) {
if (sa.canTarget(ally)) {
sa.getTargets().add(ally);
break;
}
}
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
} else {
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa);
for (final Player p : tgtPlayers) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;
}
}
// not sure if the AI should be playing with cards that give the
// Human more turns.
}
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return doTriggerAINoCost(aiPlayer, sa, false);
}
}

View File

@@ -1,16 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AlwaysPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
}

View File

@@ -1,189 +0,0 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* <p>
* AbilityFactoryAnimate class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryAnimate.java 17608 2012-10-20 22:27:27Z Max mtg $
*/
public class AnimateAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Game game = aiPlayer.getGame();
final PhaseHandler ph = game.getPhaseHandler();
// TODO - add some kind of check to answer
// "Am I going to attack with this?"
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
// don't use instant speed animate abilities outside computers
// Combat_Begin step
if (!ph.is(PhaseType.COMBAT_BEGIN)
&& ph.isPlayerTurn(aiPlayer)
&& !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
Player opponent = aiPlayer.getWeakestOpponent();
// don't animate if the AI won't attack anyway
if (ph.isPlayerTurn(aiPlayer)
&& aiPlayer.getLife() < 6
&& opponent.getLife() > 6
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
return false;
}
// don't use instant speed animate abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (ph.getPlayerTurn().isOpponentOf(aiPlayer) &&
(!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || game.getCombat().getAttackersOf(aiPlayer).isEmpty())) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
if (sa.hasParam("AILogic")) {
if ("EOT".equals(sa.getParam("AILogic"))) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
} else {
bFlag = true;
}
} if ("Never".equals(sa.getParam("AILogic"))) {
return false;
}
} else for (final Card c : defined) {
bFlag |= !c.isCreature() && !c.isTapped()
&& !(c.getTurnInZone() == game.getPhaseHandler().getTurn())
&& !c.isEquipping();
// for creatures that could be improved (like Figure of Destiny)
if (!bFlag && c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
}
int toughness = -5;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
bFlag = true;
}
}
}
if (!bFlag) { // All of the defined stuff is animated, not very
// useful
return false;
}
} else {
sa.resetTargets();
if (!animateTgtAI(sa)) {
return false;
}
}
return true;
}
// end animateCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!animateTgtAI(sa)) {
return false;
}
}
return true;
}
/**
* <p>
* animateTriggerAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting() && !animateTgtAI(sa) && !mandatory) {
return false;
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something
return true;
}
/**
* <p>
* animateTgtAI.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean animateTgtAI(final SpellAbility sa) {
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that animate a target. Those can just use SVar:RemAIDeck:True until
// this can do a reasonably
// good job of picking a good target
return false;
}
}

View File

@@ -1,19 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AnimateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
} // end animateAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
} // end class AbilityFactoryAnimate

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BalanceAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = aiPlayer.getOpponent();
final List<Card> humPerms = opp.getCardsIn(ZoneType.Battlefield);
final List<Card> compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
} else if ("BalancePermanents".equals(logic)) {
// Don't cast if you have to sacrifice permanents
diff += humPerms.size() - compPerms.size();
}
if (diff < 0) {
// Don't sacrifice permanents even if opponent has a ton of cards in hand
return false;
}
final List<Card> humHand = opp.getCardsIn(ZoneType.Hand);
final List<Card> compHand = aiPlayer.getCardsIn(ZoneType.Hand);
diff += 0.5 * (humHand.size() - compHand.size());
// Larger differential == more chance to actually cast this spell
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
}
}

View File

@@ -1,74 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class BecomesBlockedAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = aiPlayer.getGame();
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
return false;
}
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.filterControlledBy(list, aiPlayer.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getNotKeyword(list, "Trample");
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
if (list.isEmpty()) {
return false;
}
choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice == null) { // can't find anything left
return false;
}
list.remove(choice);
sa.getTargets().add(choice);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// TODO - implement AI
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -1,58 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.Collection;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class BondAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
} // end bondCanPlayAI()
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestCreatureAI(options);
}
}

View File

@@ -1,44 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/**
* <p>
* copySpellTriggerAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
return spells.get(0);
}
}

View File

@@ -1,24 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CannotPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
}

View File

@@ -1,26 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChangeTargetsAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,270 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ChangeZoneAllAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Change Zone All, can be any type moving from one zone to another
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
}
final Random r = MyRandom.getRandom();
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// TODO targeting with ChangeZoneAll
// really two types of targeting.
// Target Player has all their types change zones
// or target permanent and do something relative to that permanent
// ex. "Return all Auras attached to target"
// ex. "Return all blocking/blocked by target creature"
final Player opp = ai.getOpponent();
final List<Card> humanType = AbilityUtils.filterListByType(opp.getCardsIn(origin), sa.getParam("ChangeType"), sa);
List<Card> computerType = ai.getCardsIn(origin);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
final TargetRestrictions tgt = sa.getTargetRestrictions();
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell
// offensively
// if the AI is using it defensively, then something else needs to
// occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
computerType.clear();
}
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + 200) >= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + 3) >= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
// Don't cast during main1?
if (ai.getGame().getPhaseHandler().is(PhaseType.MAIN1, ai)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Graveyard).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Exile)) {
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 400) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 6) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
.evaluateCreatureList(humanType) + 100)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
.evaluatePermanentList(humanType) + 2)) {
return false;
}
}
}
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
}
/**
* <p>
* changeZoneAllPlayDrawbackAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Change Zone All, can be any type moving from one zone to another
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.smartValueOf(sa.getParam("Origin"));
final Player opp = ai.getOpponent();
final List<Card> humanType = AbilityUtils.filterListByType(opp.getCardsIn(origin), sa.getParam("ChangeType"), sa);
List<Card> computerType = ai.getCardsIn(origin);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell offensively
// if the AI is using it defensively, then something else needs to occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Graveyard).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Exile)) {
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 1) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,96 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.CharmEffect;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class CharmAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
List<AbilitySub> chosenList = chooseOptionsAi(sa, ai, timingRight, num, min, false);
if (chosenList.isEmpty()) {
return false;
}
// prevent run-away activations - first time will always return true
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
public static List<AbilitySub> chooseOptionsAi(SpellAbility sa, final Player ai, boolean playNow, int num, int min, boolean opponentChoser) {
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList = new ArrayList<AbilitySub>();
if (opponentChoser) {
// This branch is for "An Opponent chooses" Charm spells from Alliances
// Current just choose the first available spell, which seem generally less disastrous for the AI.
//return choices.subList(0, 1);
return choices.subList(1, choices.size());
}
for (int i = 0; i < num; i++) {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (!playNow && sub.canPlayAI(ai)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
playNow = true;
break;
}
if ((playNow || i < num - 1) && sub.doTrigger(false, ai)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;
}
}
if (thisPick != null) {
chosenList.add(thisPick);
}
}
if (playNow && chosenList.size() < min) {
for (int i = 0; i < min; i++) {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (sub.doTrigger(true, ai)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;
}
}
if (thisPick != null) {
chosenList.add(thisPick);
}
}
}
return chosenList;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> opponents) {
return Aggregates.random(opponents);
}
}

View File

@@ -1,189 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class ChooseCardAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
final Card host = sa.getSourceCard();
final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
ZoneType choiceZone = ZoneType.Battlefield;
String logic = sa.getParam("AILogic");
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
List<Card> choices = ai.getGame().getCardsIn(choiceZone);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
if (sa.hasParam("TargetControls")) {
choices = CardLists.filterControlledBy(choices, ai.getOpponent());
}
if (logic.equals("AtLeast1") || logic.equals("OppPreferred")) {
if (choices.isEmpty()) {
return false;
}
} else if (logic.equals("AtLeast2") || logic.equals("BestBlocker")) {
if (choices.size() < 2) {
return false;
}
} else if (logic.equals("Clone")) {
choices = CardLists.getValidCards(choices, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
if (choices.isEmpty()) {
return false;
}
} else if (logic.equals("Never")) {
return false;
} else if (logic.equals("NeedsPrevention")) {
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
final Card host = sa.getSourceCard();
final String logic = sa.getParam("AILogic");
Card choice = null;
if (logic == null) {
// Base Logic is choose "best"
choice = ComputerUtilCard.getBestAI(options);
} else if ("WorstCard".equals(logic)) {
choice = ComputerUtilCard.getWorstAI(options);
} else if (logic.equals("BestBlocker")) {
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
options = CardLists.filter(options, Presets.UNTAPPED);
}
choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone")) {
if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) {
options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("Untap")) {
if (!CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host).isEmpty()) {
options = CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host);
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
List<Card> better = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
});
if (!better.isEmpty()) {
choice = ComputerUtilCard.getBestAI(better);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
} else if ("OppPreferred".equals(logic)) {
List<Card> oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
if (!oppControlled.isEmpty()) {
choice = ComputerUtilCard.getBestAI(oppControlled);
} else {
List<Card> aiControlled = CardLists.filterControlledBy(options, ai);
choice = ComputerUtilCard.getWorstAI(aiControlled);
}
} else if ("LowestCMCCreature".equals(logic)) {
List<Card> creats = CardLists.filter(options, Presets.CREATURES);
creats = CardLists.filterToughness(creats, 1);
if (creats.isEmpty()) {
choice = ComputerUtilCard.getWorstAI(options);
} else {
CardLists.sortByCmcDesc(creats);
Collections.reverse(creats);
choice = creats.get(0);
}
} else if ("TangleWire".equals(logic)) {
List<Card> betterList = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
return false;
}
}
return true;
}
});
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) {
choice = betterList.get(0);
} else {
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
}
} else {
choice = ComputerUtilCard.getBestAI(options);
}
return choice;
}
}

View File

@@ -1,63 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class ChooseCardNameAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getSourceCard();
if (sa.hasParam("AILogic")) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
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));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getOpponent());
} else {
sa.getTargets().add(ai);
}
}
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// TODO - there is no AILogic implemented yet
return false;
}
}

View File

@@ -1,24 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class ChooseColorAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
}
}

View File

@@ -1,36 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
/**
* TODO: Write javadoc for this type.
*
*/
public class ChooseGenericEffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
if ("Random".equals(sa.getParam("AILogic"))) {
return Aggregates.random(spells);
} else {
return spells.get(0);
}
}
}

View File

@@ -1,88 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class ChoosePlayerAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> choices) {
Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) {
if (pc.isOpponentOf(ai)) {
chosen = pc;
break;
}
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("Pump".equals(sa.getParam("AILogic"))) {
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
} else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
List<Player> prefChoices = Lists.newArrayList(choices);
prefChoices.removeAll(ai.getOpponents());
if (!prefChoices.isEmpty()) {
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
int cardsInHand = 0;
for (final Player p : choices) {
int hand = p.getCardsIn(ZoneType.Hand).size();
if (hand >= cardsInHand) {
chosen = p;
cardsInHand = hand;
}
}
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
int creats = 50;
for (final Player p : choices) {
int curr = p.getCreaturesInPlay().size();
if (curr <= creats) {
chosen = p;
creats = curr;
}
}
} else {
System.out.println("Default player choice logic.");
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
}
return chosen;
}
}

View File

@@ -1,188 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class ChooseSourceAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// TODO: AI Support! Currently this is copied from AF ChooseCard.
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
// to the player because a CoP was pre-activated on it - unless, of course, there's another
// possible reason to attack with that creature).
final Card host = sa.getSourceCard();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
final Game game = ai.getGame();
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (!game.getStack().isEmpty()) {
final SpellAbility topStack = game.getStack().peekAbility();
if (sa.hasParam("Choices") && !topStack.getSourceCard().isValid(sa.getParam("Choices"), ai, source)) {
return false;
}
final ApiType threatApi = topStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
return false;
}
final Card threatSource = topStack.getSourceCard();
List<? extends GameObject> objects = getTargets(sa);
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
}
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
return false;
}
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
return false;
}
return true;
}
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
return false;
}
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
if (!game.getStack().isEmpty()) {
Card choseCard = chooseCardOnStack(sa, ai, game);
if (choseCard != null) {
return choseCard;
}
}
final Combat combat = game.getCombat();
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
}
});
return ComputerUtilCard.getBestCreatureAI(permanentSources);
} else {
return ComputerUtilCard.getBestAI(options);
}
}
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
for (SpellAbilityStackInstance si : game.getStack()) {
final Card source = si.getSourceCard();
final SpellAbility abilityOnStack = si.getSpellAbility();
if (sa.hasParam("Choices") && !abilityOnStack.getSourceCard().isValid(sa.getParam("Choices"), ai, sa.getSourceCard())) {
continue;
}
final ApiType threatApi = abilityOnStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
continue;
}
List<? extends GameObject> objects = getTargets(abilityOnStack);
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
continue;
}
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
continue;
}
return source;
}
return null;
}
}

View File

@@ -1,33 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChooseTypeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
}
return doTriggerAINoCost(aiPlayer, sa, false);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai);
} else {
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa)) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,36 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class ClashAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getOpponent();
if (tgt != null) {
if (!sa.canTarget(opp)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
}

View File

@@ -1,155 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Game game = source.getGame();
boolean useAbility = true;
// if (card.getController().isComputer()) {
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
// if (!creatures.isEmpty()) {
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
// }
// }
// TODO - add some kind of check to answer
// "Am I going to attack with this?"
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!phase.is(PhaseType.COMBAT_BEGIN)
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
for (final Card c : defined) {
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
// for creatures that could be improved (like Figure of Destiny)
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
}
int toughness = -5;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
bFlag = true;
}
}
}
if (!bFlag) { // All of the defined stuff is cloned, not very
// useful
return false;
}
} else {
sa.resetTargets();
useAbility &= cloneTgtAI(sa);
}
return useAbility;
} // end cloneCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something
return chance || mandatory;
}
/**
* <p>
* cloneTgtAI.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean cloneTgtAI(final SpellAbility sa) {
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that clone a target. Those can just use SVar:RemAIDeck:True until
// this can do a reasonably
// good job of picking a good target
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// Didn't confirm in the original code
return false;
}
}

View File

@@ -1,75 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ControlExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card object1 = null;
Card object2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getSourceCard());
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
object1 = ComputerUtilCard.getBestAI(list);
if (sa.hasParam("Defined")) {
object2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0);
} else if (tgt.getMinTargets(sa.getSourceCard(), sa) > 1) {
List<Card> list2 = ai.getCardsIn(ZoneType.Battlefield);
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getSourceCard());
object2 = ComputerUtilCard.getWorstAI(list2);
sa.getTargets().add(object2);
}
if (object1 == null || object2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
sa.getTargets().add(object1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return canPlayAI(aiPlayer, sa);
}
return true;
}
}

View File

@@ -1,229 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
//GainControl specific sa:
// LoseControl - the lose control conditions (as a comma separated list)
// -Untap - source card becomes untapped
// -LoseControl - you lose control of source card
// -LeavesPlay - source card leaves the battlefield
// -PowerGT - (not implemented yet for Old Man of the Sea)
// AddKWs - Keywords to add to the controlled card
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
// Untap - set to True if target card should untap when control is taken
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
/**
* <p>
* AbilityFactory_GainControl class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/
public class ControlGainAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
boolean hasCreature = false;
boolean hasArtifact = false;
boolean hasEnchantment = false;
boolean hasLand = false;
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
// if Defined, then don't worry about targeting
if (tgt == null) {
if (sa.hasParam("AllValid")) {
List<Card> tgtCards = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
if (!opp.canBeTargetedBy(sa)) {
return false;
}
if (tgt.isRandomTarget()) {
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
} else {
sa.getTargets().add(opp);
}
}
}
// Don't steal something if I can't Attack without, or prevent it from
// blocking at least
if (lose != null && lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) {
return false;
}
List<Card> list =
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
if (!c.canBeTargetedBy(sa)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
if (c.isCreature() && (!CombatUtil.canAttackNextTurn(c, ai.getOpponent()) || c.getNetCombatDamage() == 0)) {
return false;
}
return !vars.containsKey("RemAIDeck");
}
});
if (list.isEmpty()) {
return false;
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
Card t = null;
for (final Card c : list) {
if (c.isCreature()) {
hasCreature = true;
}
if (c.isArtifact()) {
hasArtifact = true;
}
if (c.isLand()) {
hasLand = true;
}
if (c.isEnchantment()) {
hasEnchantment = true;
}
}
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
if (hasCreature) {
t = ComputerUtilCard.getBestCreatureAI(list);
} else if (hasArtifact) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (hasLand) {
t = ComputerUtilCard.getBestLandAI(list);
} else if (hasEnchantment) {
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
} else {
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
sa.getTargets().add(t);
list.remove(t);
hasCreature = false;
hasArtifact = false;
hasLand = false;
hasEnchantment = false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
if(!this.canPlayAI(ai, sa) && mandatory) {
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
} else {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (sa.hasParam("AllValid")) {
List<Card> tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponent());
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
if ((lose != null) && lose.contains("EOT")
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
return this.canPlayAI(ai, sa);
}
return true;
} // pumpDrawbackAI()
}

View File

@@ -1,128 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class CopyPermanentAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Card source = sa.getSourceCard();
// TODO - I'm sure someone can do this AI better
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
return false;
} else {
return this.doTriggerAINoCost(aiPlayer, sa, false);
}
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
// ////
// Targeting
final TargetRestrictions abTgt = sa.getTargetRestrictions();
if (abTgt != null) {
List<Card> list = aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck");
}
});
sa.resetTargets();
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isType("Legendary") || c.getController().isOpponentOf(aiPlayer);
}
});
Card choice;
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(list);
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else {
// if no targeting, it should always be ok
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO: add logic here
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
// Select a card to attach to
return ComputerUtilCard.getBestAI(options);
}
}

View File

@@ -1,172 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class CounterAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean toReturn = true;
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Game game = ai.getGame();
if (game.getStack().isEmpty()) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
// might as well check for player's friendliness
return false;
}
if (sa.hasParam("AITgts") && (topSA.getSourceCard() == null
|| !topSA.getSourceCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
return false;
}
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
} else {
return false;
}
} else {
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
if (unlessCost != null && !unlessCost.endsWith(">")) {
// Is this Usable Mana Sources? Or Total Available Mana?
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of
// the time
if (!SpellAbilityAi.playReusable(ai,sa)) {
return false;
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
return toReturn;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return doTriggerAINoCost(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final Game game = ai.getGame();
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
return false;
}
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
} else {
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
final Card source = sa.getSourceCard();
if (unlessCost != null) {
// Is this Usable Mana Sources? Or Total Available Mana?
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most
// of the time
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
return false;
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
return true;
}
}

View File

@@ -1,109 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.util.Aggregates;
/**
* <p>
* AbilityFactory_Counters class.
* </p>
*
* @author Forge
* @version $Id$
*/
public abstract class CountersAi {
// An AbilityFactory subclass for Putting or Removing Counters on Cards.
/**
* <p>
* chooseCursedTarget.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param type
* a {@link java.lang.String} object.
* @param amount
* a int.
* @return a {@link forge.game.card.Card} object.
*/
public static Card chooseCursedTarget(final List<Card> list, final String type, final int amount) {
Card choice;
if (type.equals("M1M1")) {
// try to kill the best killable creature, or reduce the best one
final List<Card> killable = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetDefense() <= amount;
}
});
if (killable.size() > 0) {
choice = ComputerUtilCard.getBestCreatureAI(killable);
} else {
choice = ComputerUtilCard.getBestCreatureAI(list);
}
} else {
// improve random choice here
choice = Aggregates.random(list);
}
return choice;
}
/**
* <p>
* chooseBoonTarget.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param type
* a {@link java.lang.String} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card chooseBoonTarget(final List<Card> list, final String type) {
Card choice;
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getBestCreatureAI(list);
} else if (type.equals("DIVINITY")) {
final List<Card> boon = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getCounters(CounterType.DIVINITY) == 0;
}
});
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
} else {
// The AI really should put counters on cards that can use it.
// Charge counters on things with Charge abilities, etc. Expand
// these above
choice = Aggregates.random(list);
}
return choice;
}
}

View File

@@ -1,133 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Random r = MyRandom.getRandom();
final String amountStr = sa.getParam("CounterNum");
// TODO handle proper calculation of X values based on Cost
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
// don't use it if no counters to add
if (amount <= 0) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = false;
if (SpellAbilityAi.playReusable(ai, sa)) {
return chance;
}
return ((r.nextFloat() < .6667) && chance);
} // moveCounterCanPlayAI
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card host = sa.getSourceCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
boolean chance = false;
boolean preferred = true;
final CounterType cType = CounterType.valueOf(sa.getParam("CounterType"));
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if ((srcCards.size() > 0 && sa.getParam("CounterNum").equals("All"))) {
amount = srcCards.get(0).getCounters(cType);
}
if (abTgt == null) {
if ((srcCards.size() > 0)
&& cType.equals(CounterType.P1P1) // move +1/+1 counters away
// from
// permanents that cannot use
// them
&& (destCards.size() > 0) && destCards.get(0).getController() == ai
&& (!srcCards.get(0).isCreature() || srcCards.get(0).hasStartOfKeyword("CARDNAME can't attack"))) {
chance = true;
}
} else { // targeted
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
List<Card> list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), host.getController(), host);
if (list.isEmpty() && mandatory) {
// If there isn't any prefered cards to target, gotta choose
// non-preferred ones
list = CardLists.getTargetableCards(player.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), host.getController(), host);
preferred = false;
}
// Not mandatory, or the the list was regenerated and is still
// empty,
// so return false since there are no targets
if (list.isEmpty()) {
return false;
}
Card choice = null;
// Choose targets here:
if (sa.isCurse()) {
if (preferred) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
}
else {
if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
} else {
if (preferred) {
choice = CountersAi.chooseBoonTarget(list, type);
}
else {
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
}
// TODO - I think choice can be null here. Is that ok for
// addTarget()?
sa.getTargets().add(choice);
}
return chance;
}
}

View File

@@ -1,69 +0,0 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class CountersProliferateAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean chance = true;
List<Card> cperms = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) != 0 && !ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
List<Card> hperms = CardLists.filter(ai.getOpponent().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) != 0 && ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
if (cperms.isEmpty() && hperms.isEmpty() && ai.getOpponent().getPoisonCounters() == 0) {
return false;
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
// TODO Make sure Human has poison counters or there are some counters
// we want to proliferate
return chance;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,403 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class CountersPutAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on
// what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
List<Card> list;
Card choice = null;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
return false;
}
if (sa.hasParam("LevelUp")) {
// creatures enchanted by curse auras have low priority
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
for (Card aura : source.getEnchantedBy()) {
if (aura.getController().isOpponentOf(ai)) {
return false;
}
}
}
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
return source.getCounters(CounterType.LEVEL) < maxLevel;
}
// TODO handle proper calculation of X values based on Cost
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
}
// don't use it if no counters to add
if (amount <= 0) {
return false;
}
// Targeting
if (abTgt != null) {
sa.resetTargets();
// target loop
list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(sa) && c.canReceiveCounters(CounterType.valueOf(type));
}
});
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (list.size() < abTgt.getMinTargets(source, sa)) {
return false;
}
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
} else {
choice = CountersAi.chooseBoonTarget(list, type);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
abTgt.addDividedAllocation(choice, amount);
break;
}
}
} else {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
// Don't activate Curse abilities on my cards and non-curse abilites
// on my opponents
if (cards.isEmpty() || !cards.get(0).getController().equals(player)) {
return false;
}
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
// each non +1/+1 counter on the card is a 10% chance of not
// activating this ability.
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (r.nextFloat() < (.1 * currCounters))) {
return false;
}
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use non P1P1/M1M1 counters before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !(type.equals("P1P1") || type.equals("M1M1"))
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
return true;
} // putCanPlayAI
@Override
public boolean chkAIDrawback(final SpellAbility sa, Player ai) {
boolean chance = true;
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
Card choice = null;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
if (abTgt != null) {
List<Card> list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), abTgt.getValidTgts(), source.getController(), source);
if (list.size() == 0) {
return false;
}
sa.resetTargets();
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return sa.canTarget(c);
}
});
if (list.size() == 0) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
break;
}
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
} else {
choice = CountersAi.chooseBoonTarget(list, type);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
abTgt.addDividedAllocation(choice, amount);
break;
}
}
}
return chance;
} // putPlayDrawbackAI
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
// boolean chance = true;
boolean preferred = true;
List<Card> list;
boolean isCurse = sa.isCurse();
final Player player = isCurse ? ai.getOpponent() : ai;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
if (abTgt == null) {
// No target. So must be defined
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
if (!mandatory) {
// TODO - If Trigger isn't mandatory, when wouldn't we want to
// put a counter?
// things like Powder Keg, which are way too complex for the AI
}
} else {
list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (list.isEmpty() && mandatory) {
// If there isn't any prefered cards to target, gotta choose
// non-preferred ones
list = player.getOpponent().getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
preferred = false;
}
// Not mandatory, or the the list was regenerated and is still
// empty,
// so return false since there are no targets
if (list.isEmpty()) {
return false;
}
Card choice = null;
// Choose targets here:
if (isCurse) {
if (preferred) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
}
else {
if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
} else {
if (preferred) {
choice = CountersAi.chooseBoonTarget(list, type);
}
else {
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
if (choice != null && divided) {
abTgt.addDividedAllocation(choice, amount);
}
}
// TODO - I think choice can be null here. Is that ok for
// addTarget()?
sa.getTargets().add(choice);
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
final Card source = sa.getSourceCard();
if (mode == PlayerActionConfirmMode.Tribute) {
// add counter if that opponent has a giant creature
final List<Card> creats = player.getCreaturesInPlay();
final int tributeAmount = source.getKeywordMagnitude("Tribute");
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c, true)
&& (c.getNetDefense() > source.getNetAttack() + tributeAmount || c.hasKeyword("DeathTouch"));
}
});
if (!threatening.isEmpty()) {
return true;
}
if (source.hasSVar("TributeAILogic")) {
final String logic = source.getSVar("TributeAILogic");
if (logic.equals("Always")) {
return true;
} else if (logic.equals("Never")) {
return false;
} else if (logic.equals("CanBlockThisTurn")) {
// pump haste
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c) && (c.getNetDefense() > source.getNetAttack() || c.hasKeyword("DeathTouch"));
}
});
if (!canBlock.isEmpty()) {
return false;
}
}
}
}
return MyRandom.getRandom().nextBoolean();
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(Player, SpellAbility, Collection<Player>)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
// logic?
return Iterables.getFirst(options, null);
}
}

View File

@@ -1,144 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class CountersPutAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
List<Card> hList;
List<Card> cList;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final String valid = sa.getParam("ValidCards");
final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
hList = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
}
if (tgt != null) {
Player pl = curse ? ai.getOpponent() : ai;
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
cList = CardLists.filterControlledBy(cList, pl);
}
// TODO improve X value to don't overpay when extra mana won't do
// anything more useful
final int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (curse) {
if (type.equals("M1M1")) {
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetDefense() <= amount;
}
});
if (!(killable.size() > 2)) {
return false;
}
} else {
// make sure compy doesn't harm his stuff more than human's
// stuff
if (cList.size() > hList.size()) {
return false;
}
}
} else {
// human has more things that will benefit, don't play
if (hList.size() >= cList.size()) {
return false;
}
//Check for cards that could profit from the ability
PhaseHandler phase = ai.getGame().getPhaseHandler();
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
&& sa instanceof AbilitySub
&& (!phase.getNextTurn().equals(ai)
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
boolean combatants = false;
for (Card c : hList) {
if (!c.equals(source) && c.isUntapped()) {
combatants = true;
break;
}
}
if (!combatants) {
return false;
}
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return chance;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size();
}
}

View File

@@ -1,93 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
/**
* <p>
* AbilityFactory_PutOrRemoveCountersAi class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class CountersPutOrRemoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return doTriggerAINoCost(ai, sa, false);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
// if Defined, don't worry about targeting
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
if (validCards.isEmpty()) {
return false;
}
List<Card> cWithCounters = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.hasCounters();
}
});
if (cWithCounters.isEmpty()) {
if (mandatory) {
cWithCounters = validCards;
} else {
return false;
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
Card targetCard = null;
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0))) {
sa.resetTargets();
return false;
}
int random = MyRandom.getRandom().nextInt(cWithCounters.size());
targetCard = cWithCounters.get(random);
sa.getTargets().add(targetCard);
cWithCounters.remove(targetCard);
}
return true;
}
}

View File

@@ -1,106 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class CountersRemoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Cost abCost = sa.getPayCosts();
TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
// List<Card> list;
// Card choice = null;
final String type = sa.getParam("CounterType");
// String amountStr = sa.get("CounterNum");
// TODO - currently, not targeted, only for Self
// Player player = af.isCurse() ? AllZone.getHumanPlayer() :
// AllZone.getComputerPlayer();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if ("EndOfOpponentsTurn".equals(sa.getParam("AILogic"))) {
if (!source.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) || source.getGame().getPhaseHandler().getNextTurn() != ai) {
return false;
}
}
// TODO handle proper calculation of X values based on Cost
// final int amount = calculateAmount(sa.getSourceCard(), amountStr, sa);
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// currently, not targeted
if (abTgt != null) {
return false;
}
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !type.equals("M1M1")) {
return false;
}
if (!type.matches("Any")) {
final int currCounters = sa.getSourceCard().getCounters(CounterType.valueOf(type));
if (currCounters < 1) {
return false;
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the
// expected targets could be
boolean chance = true;
// TODO - currently, not targeted, only for Self
// Note: Not many cards even use Trigger and Remove Counters. And even
// fewer are not mandatory
// Since the targeting portion of this would be what
return chance;
}
}

View File

@@ -1,66 +0,0 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
int restDamage = d;
final Game game = comp.getGame();
final Player enemy = comp.getOpponent();
if (!sa.canTarget(enemy)) {
return false;
}
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
return false;
}
// burn Planeswalkers
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
return true;
}
if (!noPrevention) {
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getSourceCard(), false);
} else {
restDamage = enemy.staticReplaceDamage(restDamage, sa.getSourceCard(), false);
}
if (restDamage == 0) {
return false;
}
if (!enemy.canLoseLife()) {
return false;
}
final List<Card> hand = comp.getCardsIn(ZoneType.Hand);
if (sa.isSpell()) {
// If this is a spell, cast it instead of discarding
if ((game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().is(PhaseType.MAIN2))
&& game.getPhaseHandler().isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
return true;
}
}
if ((enemy.getLife() - restDamage) < 5) {
// drop the human to less than 5
// life
return true;
}
return false;
}
}

View File

@@ -1,241 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DamageAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
Player opp = ai.getOpponent();
final List<Card> humanList = this.getKillableCreatures(sa, opp, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);
computerList = new ArrayList<Card>();
}
// abCost stuff that should probably be centralized...
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
}
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
// Don't kill yourself
if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// prevent run-away activations - first time will always return true
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
return false;
}
// if we can kill human, do it
if ((validP.contains("Each") || validP.contains("EachOpponent"))
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
return true;
}
// wait until stack is empty (prevents duplicate kills)
if (!ai.getGame().getStack().isEmpty()) {
return false;
}
int minGain = 200; // The minimum gain in destroyed creatures
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
minGain = 100;
}
// evaluate both lists and pass only if human creatures are more valuable
if ((ComputerUtilCard.evaluateCreatureList(computerList) + minGain) >= ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card source = sa.getSourceCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ai.getOpponent();
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// Don't get yourself killed
if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.contains("Each") || validP.contains("EachOpponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
/**
* <p>
* getKillableCreatures.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @param dmg
* a int.
* @return a {@link forge.CardList} object.
*/
private List<Card> getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
final Card source = sa.getSourceCard();
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
// TODO: X may be something different than X paid
List<Card> list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source);
final Predicate<Card> filterKillable = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
}
};
list = CardLists.getNotKeyword(list, "Indestructible");
list = CardLists.filter(list, filterKillable);
return list;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ai.getOpponent();
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// If it's not mandatory check a few things
if (mandatory) {
return true;
}
// Don't get yourself killed
if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.contains("Each") || validP.contains("EachOpponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
}

View File

@@ -1,522 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class DamageDealAi extends DamageAiBase {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
final Card source = sa.getSourceCard();
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (!this.damageTargetAI(ai, sa, dmg)) {
return false;
}
return true;
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
String logic = sa.getParam("AILogic");
if ("DiscardLands".equals(logic)) {
dmg = 2;
} else if ("WildHunt".equals(logic)) {
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetAttack);
}
if (dmg <= 0) {
return false;
}
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (sa.isAbility()) {
final Random r = MyRandom.getRandom(); // prevent run-away
// activations
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
return false;
}
}
if (!this.damageTargetAI(ai, sa, dmg)) {
return false;
}
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) {
// If I can kill my target by paying less mana, do it
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
for (final Card c : sa.getTargets().getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
actualPay = adjDamage;
}
}
source.setSVar("PayX", Integer.toString(actualPay));
}
}
return true;
}
/**
* <p>
* dealDamageChooseTgtC.
* </p>
*
* @param d
* a int.
* @param noPrevention
* a boolean.
* @param pl
* a {@link forge.game.player.Player} object.
* @param mandatory
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
final Player pl, final boolean mandatory) {
// wait until stack is empty (prevents duplicate kills)
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
return null;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
List<Card> hPlay = CardLists.getValidCards(pl.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source);
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
if (sa.hasParam("TargetUnique")) {
objects.addAll(sa.getUniqueTargets());
}
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
if (hPlay.contains(c)) {
hPlay.remove(c);
}
}
}
hPlay = CardLists.getTargetableCards(hPlay, sa);
final List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c)
&& !(c.getSVar("SacMe").length() > 0);
}
});
Card targetCard;
if (pl.isOpponentOf(ai) && !killables.isEmpty()) {
targetCard = ComputerUtilCard.getBestCreatureAI(killables);
return targetCard;
}
if (!mandatory) {
return null;
}
if (!hPlay.isEmpty()) {
if (pl.isOpponentOf(ai)) {
targetCard = ComputerUtilCard.getBestCreatureAI(hPlay);
} else {
targetCard = ComputerUtilCard.getWorstCreatureAI(hPlay);
}
return targetCard;
}
return null;
}
/**
* <p>
* damageTargetAI.
* </p>
*
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
* a int.
* @return a boolean.
*/
private boolean damageTargetAI(final Player ai, final SpellAbility saMe, final int dmg) {
final TargetRestrictions tgt = saMe.getTargetRestrictions();
if (tgt == null) {
return this.damageChooseNontargeted(ai, saMe, dmg);
}
if (tgt.isRandomTarget()) {
return false;
}
return this.damageChoosingTargets(ai, saMe, tgt, dmg, false, false);
}
/**
* <p>
* damageChoosingTargets.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
* a {@link forge.game.spellability.TargetRestrictions} object.
* @param dmg
* a int.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, int dmg,
final boolean isTrigger, final boolean mandatory) {
final Card source = sa.getSourceCard();
final boolean noPrevention = sa.hasParam("NoPrevention");
final Game game = source.getGame();
final PhaseHandler phase = game.getPhaseHandler();
final boolean divided = sa.hasParam("DividedAsYouChoose");
// target loop
sa.resetTargets();
TargetChoices tcs = sa.getTargets();
Player enemy = ai.getOpponent();
if (tgt.getMaxTargets(source, sa) <= 0) {
return false;
}
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (tgt.canTgtCreatureAndPlayer()) {
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
continue;
}
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
if (c != null) {
tcs.add(c);
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
}
}
continue;
}
// When giving priority to targeting Creatures for mandatory
// triggers
// feel free to add the Human after we run out of good targets
// TODO: add check here if card is about to die from something
// on the stack
// or from taking combat damage
boolean freePing = isTrigger || sa.getPayCosts() == null || sa.getTargets().getNumTargeted() > 0;
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility()) {
if (phase.getNextTurn().equals(ai))
freePing = true;
}
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
if (sa.getRestrictions().getPlaneswalker() || source.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|| source.hasKeyword("At the beginning of the end step, sacrifice CARDNAME."))
freePing = true;
}
if (freePing && sa.canTarget(enemy)) {
tcs.add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
}
} else if (tgt.canTgtCreature()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory);
if (c != null) {
tcs.add(c);
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
} else {
tgt.addDividedAllocation(c, dmg);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
}
}
continue;
}
}
// TODO: Improve Damage, we shouldn't just target the player just
// because we can
else if (sa.canTarget(enemy)) {
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|| sa.getPayCosts() == null || isTrigger
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) {
sa.getTargets().add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
continue;
}
}
// fell through all the choices, no targets left?
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
// If the trigger is mandatory, gotta choose my own stuff now
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
}
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
return true;
}
/**
* <p>
* damageChooseNontargeted.
* </p>
* @param ai
*
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
* a int.
* @return a boolean.
*/
private boolean damageChooseNontargeted(Player ai, final SpellAbility saMe, final int dmg) {
// TODO: Improve circumstances where the Defined Damage is unwanted
final List<GameObject> objects = AbilityUtils.getDefinedObjects(saMe.getSourceCard(), saMe.getParam("Defined"), saMe);
boolean urgent = false; // can it wait?
boolean positive = false;
for (final Object o : objects) {
if (o instanceof Card) {
Card c = (Card) o;
final int restDamage = ComputerUtilCombat.predictDamageTo(c, dmg, saMe.getSourceCard(), false);
if (!c.hasKeyword("Indestructible") && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
if (c.getController().equals(ai)) {
return false;
} else {
urgent = true;
}
}
if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) {
positive = true;
}
} else if (o instanceof Player) {
final Player p = (Player) o;
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getSourceCard(), false);
if (!p.isOpponentOf(ai) && p.canLoseLife() && restDamage + 3 >= p.getLife() && restDamage > 0) {
// from this spell will kill me
return false;
}
if (p.isOpponentOf(ai) && p.canLoseLife()) {
positive = true;
if (p.getLife() + 3 <= restDamage) {
urgent = true;
}
}
}
}
if (!positive && !(saMe instanceof AbilitySub)) {
return false;
}
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
return false;
}
return true;
}
/**
* <p>
* damageChooseRequiredTargets.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
* a {@link forge.game.spellability.TargetRestrictions} object.
* @param dmg
* a int.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
final boolean mandatory) {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean divided = sa.hasParam("DividedAsYouChoose");
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
// TODO: Consider targeting the planeswalker
if (tgt.canTgtCreature()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
if (c != null) {
sa.getTargets().add(c);
if (divided) {
tgt.addDividedAllocation(c, dmg);
break;
}
continue;
}
}
if (sa.canTarget(ai)) {
if (sa.getTargets().add(ai)) {
if (divided) {
tgt.addDividedAllocation(ai, dmg);
break;
}
continue;
}
}
// if we get here then there isn't enough targets, this is the only
// time we can return false
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
// Remove all damage
if (sa.hasParam("Remove")) {
return true;
}
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If it's not mandatory check a few things
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
return false;
}
} else {
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, true, mandatory) && !mandatory) {
return false;
}
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
// If I can kill my target by paying less mana, do it
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
//target is a player
if (!sa.getTargets().isTargetingAnyCard()) {
actualPay = dmg;
}
for (final Card c : sa.getTargets().getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (adjDamage > actualPay) {
actualPay = adjDamage;
}
}
source.setSVar("PayX", Integer.toString(actualPay));
}
}
return true;
}
}

View File

@@ -1,43 +0,0 @@
package forge.ai.ability;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class DamageEachAi extends DamageAiBase {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(ai.getOpponent())) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
return this.shouldTgtP(ai, sa, iDmg, false);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// check AI life before playing this drawback?
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,228 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DamagePreventAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getSourceCard(), sa.getParam("Defined"), sa);
// react to threats on the stack
if (!game.getStack().isEmpty()) {
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) {
if (threatenedObjects.contains(o)) {
chance = true;
}
}
} else {
PhaseHandler handler = game.getPhaseHandler();
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false;
for (final Object o : objects) {
if (o instanceof Card) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
} else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
}
}
}
chance = flag;
} else { // if nothing on the stack, and it's not declare
// blockers. no need to prevent
return false;
}
}
} // non-targeted
// react to threats on the stack
else if (!game.getStack().isEmpty()) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
// check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) {
tcs.add(ai);
chance = true;
}
final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
for (final Card c : targetables) {
if (objects.contains(c)) {
threatenedTargets.add(c);
}
}
if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to save
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true;
}
} // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
tcs.add(ai);
chance = true;
} else {
// filter AIs battlefield by what I can target
List<Card> targetables = ai.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.isEmpty()) {
return false;
}
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants);
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
tcs.add(c);
chance = true;
}
}
}
}
if (sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa));
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If there's no target on the trigger, just say yes.
chance = true;
} else {
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
}
return chance;
}
/**
* <p>
* preventDamageMandatoryTarget.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
final Game game = ai.getGame();
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getSourceCard());
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null;
if (targetables.isEmpty()) {
return false;
}
if (!mandatory && compTargetables.isEmpty()) {
return false;
}
if (!compTargetables.isEmpty()) {
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
target = c;
break;
}
}
}
if (target == null) {
target = combatants.get(0);
}
} else {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
}
sa.getTargets().add(target);
if (sa.hasParam("DividedAsYouChoose")) {
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("Amount"), sa));
}
return true;
}
}

View File

@@ -1,59 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class DamagePreventAllAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
if (!ai.getGame().getStack().isEmpty()) {
// TODO check stack for something on the stack will kill anything i
// control
} // Protect combatants
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// TODO
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
return chance;
}
}

View File

@@ -1,299 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
// if there is no target and host card isn't in play, don't activate
final Card source = sa.getSourceCard();
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until AI is improved
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, null)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
final SpellAbilityRestriction restrict = sa.getRestrictions();
final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
}
final int activations = restrict.getNumberTurnActivations();
final int sacActivations = restrict.getActivationNumberSacrifice();
// don't risk sacrificing a creature just to pump it
if ((sacActivations != -1) && (activations >= (sacActivations - 1))) {
return false;
}
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false;
}
// don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}
});
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
// here?
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
return true;
} // debuffDrawbackAI()
/**
* <p>
* debuffTgtAI.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
// this would be for evasive things like Flying, Unblockable, etc
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
// several uses here:
// 1. make human creatures lose evasion when they are attacking
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
// Comp is attacking
// 3. remove Indestructible keyword so it can be destroyed?
// 3a. remove Persist?
if (list.isEmpty()) {
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return debuffMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // pumpTgtAI()
/**
* <p>
* getCurseCreatures.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @return a {@link forge.CardList} object.
*/
private List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getOpponent();
List<Card> list = opp.getCreaturesInPlay();
list = CardLists.getTargetableCards(list, sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative
// keywords
}
});
}
return list;
} // getCurseCreatures()
/**
* <p>
* debuffMandatoryTarget.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
List<Card> list = ai.getGame().getCardsIn(ZoneType.Battlefield);
final TargetRestrictions tgt = sa.getTargetRestrictions();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
final List<Card> pref = CardLists.filterControlledBy(list, ai.getOpponent());
final List<Card> forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getSourceCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
if (forced.isEmpty()) {
break;
}
// TODO - if forced targeting, just pick something without the given
// keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // pumpMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return debuffTgtAI(ai, sa, kws, mandatory);
}
return true;
}
}

View File

@@ -1,65 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DebuffAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
String valid = "";
final Random r = MyRandom.getRandom();
// final Card source = sa.getSourceCard();
final Card hostCard = sa.getSourceCard();
final Player opp = ai.getOpponent();
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // to
// prevent
// runaway
// activations
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
List<Card> comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, hostCard.getController(), hostCard);
List<Card> human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, hostCard.getController(), hostCard);
// TODO - add blocking situations here also
// only count creatures that can attack
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
// don't use DebuffAll after Combat_Begin until AI is improved
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
return false;
}
if (comp.size() > human.size()) {
return false;
}
return (r.nextFloat() < .6667) && chance;
} // debuffAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,45 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
public class DelayedTriggerAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getSourceCard().getSVar(svarName), sa.getSourceCard());
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return ((AbilitySub) trigsa).getAi().chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return trigsa.canPlayAI(ai);
}
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getSourceCard().getSVar(svarName), sa.getSourceCard());
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return trigsa.doTrigger(true, ai);
} else {
return trigsa.doTrigger(!sa.getParam("OptionalDecider").equals("You"), ai);
}
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getSourceCard().getSVar(svarName), sa.getSourceCard());
trigsa.setActivatingPlayer(ai);
return trigsa.canPlayAI(ai);
}
}

View File

@@ -1,267 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DestroyAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final boolean noRegen = sa.hasParam("NoRegen");
List<Card> list;
if (abCost != null) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// Targeting
if (abTgt != null) {
sa.resetTargets();
list = CardLists.getTargetableCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (sa.hasParam("AITgts")) {
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
}
list = CardLists.getNotKeyword(list, "Indestructible");
if (!SpellAbilityAi.playReusable(ai, sa)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
return false;
}
}
}
}
//Check for undying
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
}
});
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that might be tougher?
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.getShield().isEmpty() && !ComputerUtil.canRegenerate(ai, c));
}
});
}
if (list.size() == 0) {
return false;
}
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getSourceCard(), sa)) {
if (list.size() == 0) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
Card choice = null;
// If the targets are only of one type, take the best
if (CardLists.getNotType(list, "Creature").isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(list);
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
choice = ComputerUtilCard.getBestLandAI(list);
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getSourceCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
} else {
// Don't destroy stolen permanents when the stealing aura can be destroyed
if (choice.getOwner() == ai) {
for (Card aura : choice.getEnchantedBy()) {
SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
&& aura.getController() != ai && sa.canTarget(aura)) {
choice = aura;
}
}
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else {
if (sa.hasParam("Defined")) {
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
return false;
}
}
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final boolean noRegen = sa.hasParam("NoRegen");
if (tgt != null) {
List<Card> list;
list = ai.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
return false;
}
sa.resetTargets();
List<Card> preferred = CardLists.getNotKeyword(list, "Indestructible");
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that could regenerate in response?
// might be tougher?
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShield().isEmpty();
}
});
}
for (final Card c : preferred) {
list.remove(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) {
if (preferred.isEmpty()) {
if ((sa.getTargets().getNumTargeted() == 0)
|| (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa))) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
break;
}
} else {
break;
}
} else {
Card c;
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
c = ComputerUtilCard.getBestCreatureAI(preferred);
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
c = ComputerUtilCard.getBestLandAI(preferred);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
}
sa.getTargets().add(c);
preferred.remove(c);
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
if (list.isEmpty()) {
break;
} else {
Card c;
if (CardLists.getNotType(list, "Creature").isEmpty()) {
c = ComputerUtilCard.getWorstCreatureAI(list);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
}
sa.getTargets().add(c);
list.remove(c);
}
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
return false;
}
} else {
if (!mandatory) {
return false;
}
}
return true;
}
}

View File

@@ -1,143 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
}
};
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
String valid = "";
if (mandatory) {
return true;
}
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
computerlist.clear();
}
humanlist = CardLists.filter(humanlist, predicate);
computerlist = CardLists.filter(computerlist, predicate);
if (humanlist.isEmpty() && !computerlist.isEmpty()) {
return false;
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
if (ComputerUtilCard.evaluateCreatureList(computerlist) >= ComputerUtilCard.evaluateCreatureList(humanlist)
&& !computerlist.isEmpty()) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if (ComputerUtilCard.evaluatePermanentList(computerlist) >= ComputerUtilCard.evaluatePermanentList(humanlist)) {
return false;
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
//TODO: Check for bad outcome
return true;
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
valid = valid.replace("X", Integer.toString(xPay));
}
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
computerlist.clear();
}
humanlist = CardLists.filter(humanlist, predicate);
computerlist = CardLists.filter(computerlist, predicate);
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
.evaluateCreatureList(humanlist)) {
return false;
}
} // only lands involved
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
return chance;
}
}

View File

@@ -1,102 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.Random;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DigAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Player opp = ai.getOpponent();
final Card host = sa.getSourceCard();
Player libraryOwner = ai;
if (sa.usesTargeting()) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
// don't deck yourself
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 1) {
return false;
}
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
return true;
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> valid, boolean isOptional, Player relatedPlayer) {
Card chosen = ComputerUtilCard.getBestAI(valid);
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstAI(valid);
}
return chosen;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// looks like perfect code for Delver of Secrets, but what about other cards?
Card topc = player.getZone(ZoneType.Library).get(0);
return topc.isInstant() || topc.isSorcery();
}
}

View File

@@ -1,117 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DigUntilAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getSourceCard();
double chance = .4; // 40 percent chance with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
}
final Random r = MyRandom.getRandom();
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ai.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
} else {
if (sa.hasParam("Valid")) {
final String valid = sa.getParam("Valid");
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source).isEmpty()) {
return false;
}
}
}
final String num = sa.getParam("Amount");
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (numCards <= 0) {
return false;
}
source.setSVar("PayX", Integer.toString(numCards));
}
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
break;
}
}
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
} else {
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if ("OathOfDruids".equals(logic)) {
final List<Card> creaturesInLibrary =
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
final List<Card> creaturesInBattlefield =
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
// if there are at least 3 creatures in library,
// or none in play with one in library, oath
return creaturesInLibrary.size() > 2
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
}
}
return true;
}
}

View File

@@ -1,178 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DiscardAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
final boolean humanHasHand = ai.getOpponent().getCardsIn(ZoneType.Hand).size() > 0;
if (tgt != null) {
if (!discardTargetAI(ai, sa)) {
return false;
}
} else {
// TODO: Add appropriate restrictions
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getSourceCard(),
sa.getParam("Defined"), sa);
if (players.size() == 1) {
if (players.get(0) == ai) {
// the ai should only be using something like this if he has
// few cards in hand,
// cards like this better have a good drawback to be in the
// AIs deck
} else {
// defined to the human, so that's fine as long the human
// has cards
if (!humanHasHand) {
return false;
}
}
} else {
// Both players discard, any restrictions?
}
}
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
.getCardsIn(ZoneType.Hand).size());
if (cardsToDiscard < 1) {
return false;
}
source.setSVar("PayX", Integer.toString(cardsToDiscard));
} else {
if (AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) < 1) {
return false;
}
}
}
// TODO: Implement support for Discard AI for cards with AnyNumber set to true.
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa) && !sa.hasParam("ActivationPhases")) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
// some other variables here, like handsize vs. maxHandSize
return randomReturn;
} // discardCanPlayAI()
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
}
return false;
} // discardTargetAI()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ai.getOpponent();
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSourceCard().getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
.getCardsIn(ZoneType.Hand).size());
sa.getSourceCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
return true;
} // discardTrigger()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// Drawback AI improvements
// if parent draws cards, make sure cards in hand + cards drawn > 0
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return discardTargetAI(ai, sa);
}
// TODO: check for some extra things
return true;
} // discardCheckDrawbackAI()
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ( mode == PlayerActionConfirmMode.Random ) { //
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true;
}
return super.confirmAction(player, sa, mode, message);
}
}

View File

@@ -1,91 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class DrainManaAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Player opp = ai.getOpponent();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
return randomReturn;
}
}

View File

@@ -1,272 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.AiCostDecision;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.cost.PaymentDecision;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DrawAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return targetAI(ai, sa, false);
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Cost abCost = sa.getPayCosts();
final Game game = ai.getGame();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
for (final CostPart part : abCost.getCostParts()) {
if (part instanceof CostDiscard) {
PaymentDecision decision = part.accept(aiDecisions);
if ( null == decision )
return false;
for (Card discard : decision.cards) {
if (!ComputerUtil.isWorseThanDraw(ai, discard)) {
return false;
}
}
}
}
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (!targetAI(ai, sa, false)) {
return false;
}
if (tgt != null) {
final Player player = sa.getTargets().getFirstTargetedPlayer();
if (player != null && player.isOpponentOf(ai)) {
return true;
}
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use draw abilities before main 2 if possible
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& ai.getCardsIn(ZoneType.Hand).size() > 1
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa) && !sa.hasParam("ActivationPhases")) {
return false;
}
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final boolean drawback = (sa instanceof AbilitySub);
final Game game = ai.getGame();
Player opp = ai.getOpponent();
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
final int humanLibrarySize = opp.getCardsIn(ZoneType.Library).size();
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
final int computerMaxHandSize = ai.getMaxHandSize();
//if a spell is used don't count the card
if (sa.isSpell() && source.isInZone(ZoneType.Hand)) {
computerHandSize -= 1;
}
int numCards = 1;
if (sa.hasParam("NumCards")) {
numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
}
boolean xPaid = false;
final String num = sa.getParam("NumCards");
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (sa instanceof AbilitySub && !source.getSVar("PayX").equals("")) {
numCards = Integer.parseInt(source.getSVar("PayX"));
} else {
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(numCards));
}
xPaid = true;
}
//if (n)
// TODO: if xPaid and one of the below reasons would fail, instead of
// bailing
// reduce toPay amount to acceptable level
if (tgt != null) {
// ability is targeted
sa.resetTargets();
final boolean canTgtHuman = sa.canTarget(opp);
final boolean canTgtComp = sa.canTarget(ai);
boolean tgtHuman = false;
if (!canTgtHuman && !canTgtComp) {
return false;
}
if (canTgtHuman && !opp.cantLose() && numCards >= humanLibrarySize) {
// Deck the Human? DO IT!
sa.getTargets().add(opp);
return true;
}
if (numCards >= computerLibrarySize) {
if (xPaid) {
numCards = computerLibrarySize - 1;
source.setSVar("PayX", Integer.toString(numCards));
} else {
// Don't deck your self
if (!mandatory) {
return false;
}
tgtHuman = true;
}
}
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
if (xPaid) {
numCards = computerMaxHandSize - computerHandSize;
source.setSVar("PayX", Integer.toString(numCards));
} else {
// Don't draw too many cards and then risk discarding cards
// at EOT
// TODO: "NextUpkeep" is deprecated
if (!(sa.hasParam("NextUpkeep") || (sa instanceof AbilitySub)) && !mandatory) {
return false;
}
}
}
if (numCards == 0 && !mandatory && !drawback) {
return false;
}
if ((!tgtHuman || !canTgtHuman) && canTgtComp) {
sa.getTargets().add(ai);
} else if (mandatory && canTgtHuman) {
sa.getTargets().add(opp);
} else {
return false;
}
} else if (!mandatory) {
// TODO: consider if human is the defined player
// ability is not targeted
if (numCards >= computerLibrarySize) {
if (ai.isCardInPlay("Laboratory Maniac")) {
return true;
}
// Don't deck yourself
return false;
}
if (numCards == 0 && !drawback) {
return false;
}
if ((computerHandSize + numCards > computerMaxHandSize)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& !sa.isTrigger()) {
// Don't draw too many cards and then risk discarding cards at
// EOT
if (!sa.hasParam("NextUpkeep") && !drawback) {
return false;
}
}
}
return true;
} // drawTargetAI()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return targetAI(ai, sa, mandatory);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1;
// AI shouldn't mill itself
return numCards < player.getZone(ZoneType.Library).size();
}
}

View File

@@ -1,164 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class EffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= .6667;
final Player opp = ai.getOpponent();
String logic = "";
if (sa.hasParam("AILogic")) {
logic = sa.getParam("AILogic");
final PhaseHandler phase = game.getPhaseHandler();
if (logic.equals("BeginningOfOppTurn")) {
if (phase.isPlayerTurn(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
return false;
}
randomReturn = true;
} else if (logic.equals("EndOfOppTurn")) {
if (phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Fog")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
if (!game.getStack().isEmpty()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
List<Card> list = game.getCombat().getAttackers();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
list = CardLists.getTargetableCards(list, sa);
Card target = ComputerUtilCard.getBestCreatureAI(list);
if (target == null) {
return false;
}
sa.getTargets().add(target);
}
randomReturn = true;
} else if (logic.equals("Always")) {
randomReturn = true;
} else if (logic.equals("Main2")) {
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Evasion")) {
List<Card> comp = ai.getCreaturesInPlay();
List<Card> human = opp.getCreaturesInPlay();
// only count creatures that can attack or block
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canBlock(c);
}
});
if (comp.size() < 2 || human.size() < 1) {
randomReturn = false;
}
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
if (game.getStack().isEmpty()) {
return false;
}
boolean threatened = false;
for (final SpellAbilityStackInstance stackSA : game.getStack()) {
if (!stackSA.isSpell()) { continue; }
if (stackSA.getSpellAbility().getApi() == ApiType.DealDamage) {
final SpellAbility saTargeting = stackSA.getSpellAbility().getSATargetingPlayer();
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
threatened = true;
}
}
}
randomReturn = threatened;
}
} else { //no AILogic
return false;
}
if ("False".equals(sa.getParam("Stackable"))) {
String name = sa.getParam("Name");
if (name == null) {
name = sa.getSourceCard().getName() + "'s Effect";
}
final List<Card> list = sa.getActivatingPlayer().getCardsIn(ZoneType.Command, name);
if (!list.isEmpty()) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
sa.getTargets().add(ai.getOpponent());
} else {
sa.getTargets().add(ai);
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Player opp = aiPlayer.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(aiPlayer)) {
sa.getTargets().add(aiPlayer);
}
}
return true;
}
}

View File

@@ -1,101 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class EncodeAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {
final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !CombatUtil.canBeBlocked(c, ai.getOpponent());
}
});
if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables);
} else if (!attackers.isEmpty()) {
choice = ComputerUtilCard.getBestAI(attackers);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
// }
return choice;
}
}

View File

@@ -1,29 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class EndTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}

View File

@@ -1,139 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class FightAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
sa.resetTargets();
final Card source = sa.getSourceCard();
List<Card> aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
final Random r = MyRandom.getRandom();
if (r.nextFloat() > Math.pow(.6667, sa.getActivationsThisTurn())) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetAttack()
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
return true;
}
}
}
if (sa.hasParam("TargetsFromDifferentZone")) {
if (humCreatures.isEmpty() && aiCreatures.isEmpty()) {
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
}
}
}
}
return false;
}
for (Card creature1 : humCreatures) {
for (Card creature2 : humCreatures) {
if (creature1.equals(creature2)) {
continue;
}
if (sa.hasParam("TargetsWithoutSameCreatureType")
&& creature1.sharesCreatureTypeWith(creature2)) {
continue;
}
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetAttack()
&& creature1.getNetAttack() >= ComputerUtilCombat.getDamageToKill(creature2)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(creature1);
sa.getTargets().add(creature2);
return true;
}
}
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (canPlayAI(ai, sa)) {
return true;
}
if (!mandatory) {
return false;
}
//try to make a good trade or no trade
final Card source = sa.getSourceCard();
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (humCreatures.isEmpty()) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
sa.getTargets().add(humanCreature);
return true;
}
}
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetAttack()) {
sa.getTargets().add(humanCreature);
return true;
}
}
sa.getTargets().add(humCreatures.get(0));
return true;
}
return true;
}
}

View File

@@ -1,27 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FlipACoinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
if (sa.getParam("AILogic").equals("Never")) {
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,69 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FogAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
// AI should only activate this during Human's Declare Blockers phase
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
// Only cast when Stack is empty, so Human uses spells/abilities first
if (!game.getStack().isEmpty()) {
return false;
}
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
// Cast it if life is in danger
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn
boolean chance;
final Game game = ai.getGame();
// should really check if other player is attacking this player
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
boolean chance;
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getOpponent())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
}

View File

@@ -1,49 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getOpponent();
if (opp.cantLose()) {
return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(opp);
}
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
if (!mandatory && ai.getOpponent().cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
return true;
}
}

View File

@@ -1,32 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class GameWinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.cantWin()) {
return false;
}
// TODO Check conditions are met on card (e.g. Coalition Victory)
// TODO Consider likelihood of SA getting countered
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,30 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class HauntAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> creats, boolean isOptional, Player targetedPlayer) {
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats);
}
}

View File

@@ -1,42 +0,0 @@
package forge.ai.ability;
import java.util.Collection;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?
}
return firstOption;
}
}

View File

@@ -1,64 +0,0 @@
package forge.ai.ability;
import java.util.Random;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class LifeExchangeAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final int myLife = aiPlayer.getLife();
Player opponent = aiPlayer.getOpponent();
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
/*
* TODO - There is one card that takes two targets (Soul Conduit)
* and one card that has a conditional (Psychic Transfer) that are
* not currently handled
*/
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (opponent.canBeTargetedBy(sa)) {
// never target self, that would be silly for exchange
sa.getTargets().add(opponent);
if (!opponent.canLoseLife()) {
return false;
}
}
}
// if life is in danger, always activate
if ((myLife < 5) && (hLife > myLife)) {
return true;
}
// cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8));
return ((r.nextFloat() < .6667) && chance);
}
}

View File

@@ -1,165 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class LifeGainAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Game game = source.getGame();
final int life = ai.getLife();
final String amountStr = sa.getParam("LifeAmount");
int lifeAmount = 0;
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
lifeAmount = xPay;
} else {
lifeAmount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
// don't use it if no life to gain
if (!activateForCost && lifeAmount <= 0) {
return false;
}
// don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
if (!activateForCost && !sa.getConditions().areMet(sa)) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
if (!abSub.getConditions().areMet(abSub)) {
return false;
}
} else {
return false;
}
}
boolean lifeCritical = life <= 5;
lifeCritical |= game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
if (abCost != null && !lifeCritical) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (!activateForCost && !ai.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use lifegain before main 2 if possible
if (!lifeCritical && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (!lifeCritical && !activateForCost && (!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
return true;
}
/**
* <p>
* gainLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to be
// handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else if (mandatory && sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
final Card source = sa.getSourceCard();
final String amountStr = sa.getParam("LifeAmount");
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
}
return true;
}
}

View File

@@ -1,170 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class LifeLoseAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
List<Player> tgtPlayers = getTargetPlayers(sa);
final Card source = sa.getSourceCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final String amountStr = sa.getParam("LifeAmount");
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
}
if (amount <= 0) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
Player opp = ai.getOpponent();
if (!opp.canLoseLife()) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
if (amount >= opp.getLife()) {
return true; // killing the human should be done asap
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use loselife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (SpellAbilityAi.isSorcerySpeed(sa)
|| sa.hasParam("ActivationPhases")
|| SpellAbilityAi.playReusable(ai, sa)
|| ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
final Card source = sa.getSourceCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
List<Player> tgtPlayers = getTargetPlayers(sa);
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
// For cards like Foul Imp, ETB you lose life
return false;
}
return true;
}
}

View File

@@ -1,152 +0,0 @@
package forge.ai.ability;
import java.util.Random;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final int myLife = ai.getLife();
final Player opponent = ai.getOpponent();
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
if (!ai.canGainLife()) {
return false;
}
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if (sa.hasParam("Each") && sa.getParam("Defined").equals("Each")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
}
}
if (amount < myLife) {
return false;
}
}
// if life is in danger, always activate
if ((myLife < 3) && (amount > myLife)) {
return true;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ai.getOpponent();
final int hlife = opponent.getLife();
final Card source = sa.getSourceCard();
final String amountStr = sa.getParam("LifeAmount");
int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
}
if (source.getName().equals("Eternity Vessel")
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
return false;
}
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to
// be
// handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
}
return true;
}
}

View File

@@ -1,35 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ManaEffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.getGame().getPhaseHandler().is(PhaseType.MAIN2) && ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
/**
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,155 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class MillAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getSourceCard();
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (!targetAI(ai, sa, false)) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")) && source.getSVar("X").startsWith("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard =
Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent().getCardsIn(ZoneType.Library).size());
source.setSVar("PayX", Integer.toString(cardsToDiscard));
if (cardsToDiscard <= 0) {
return false;
}
}
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
if (tgt != null) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
return false;
}
final int numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
final List<Card> pLibrary = opp.getCardsIn(ZoneType.Library);
if (pLibrary.isEmpty()) { // deck already empty, no need to mill
if (!mandatory) {
return false;
}
sa.getTargets().add(opp);
return true;
}
if (numCards >= pLibrary.size()) {
// Can Mill out Human's deck? Do it!
sa.getTargets().add(opp);
return true;
}
// Obscure case when you know what your top card is so you might?
// want to mill yourself here
// if (AI wants to mill self)
// sa.getTargets().add(AllZone.getComputerPlayer());
// else
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return targetAI(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!targetAI(aiPlayer, sa, mandatory)) {
return false;
}
final Card source = sa.getSourceCard();
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, aiPlayer), aiPlayer.getOpponent()
.getCardsIn(ZoneType.Library).size());
source.setSVar("PayX", Integer.toString(cardsToDiscard));
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
}

View File

@@ -1,36 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class MustAttackAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI for now. Only for Gideon Jura at this time.
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
// TODO - implement AI
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -1,100 +0,0 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class MustBlockAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI until he/she can make decisions about who to make
// block
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return false;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
// only use on creatures that can attack
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
Card attacker = null;
if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
return false;
}
attacker = cards.get(0);
}
if (attacker == null) {
attacker = source;
}
final Card definedAttacker = attacker;
boolean chance = false;
if (abTgt != null) {
List<Card> list = CardLists.filter(ai.getOpponent().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean tapped = c.isTapped();
c.setTapped(false);
if (!CombatUtil.canBlock(definedAttacker, c)) {
return false;
}
if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) {
return false;
}
if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) {
return false;
}
c.setTapped(tapped);
return true;
}
});
if (list.isEmpty()) {
return false;
}
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
if (blocker == null) {
return false;
}
sa.getTargets().add(blocker);
chance = true;
} else {
return false;
}
return chance;
}
}

View File

@@ -1,38 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class PeekAndRevealAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (sa instanceof AbilityStatic) {
return false;
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
AbilitySub subAb = sa.getSubAbility();
return subAb != null && subAb.getAi().chkDrawbackWithSubs(player, subAb);
}
}

View File

@@ -1,75 +0,0 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentCreatureAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
Game game = aiPlayer.getGame();
if ("ZeroToughness".equals(logic)) {
// If Creature has Zero Toughness, make sure some static ability is in play
// That will grant a toughness bonus
final List<Card> list = aiPlayer.getCardsIn(ZoneType.Battlefield);
if (!Iterables.any(list, Predicates.or(CardPredicates.nameEquals("Glorious Anthem"),
CardPredicates.nameEquals("Gaea's Anthem")))) {
return false;
}
// TODO See if card ETB will survive after Static Effects
/*
List<Card> cards = game.getCardsIn(ZoneType.Battlefield);
for(Card c : cards) {
ArrayList<StaticAbility> statics = c.getStaticAbilities();
for(StaticAbility s : statics) {
final Map<String, String> stabMap = s.getMapParams();
if (!stabMap.get("Mode").equals("Continuous")) {
continue;
}
final String affected = stabMap.get("Affected");
if (affected == null) {
continue;
}
}
}
*/
}
// Wait for Main2 if possible
if (game.getPhaseHandler().is(PhaseType.MAIN1)
&& !ComputerUtil.castPermanentInMain1(aiPlayer, sa)) {
return false;
}
// AI shouldn't be retricted all that much for Creatures for now
return true;
}
}

View File

@@ -1,35 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentNoncreatureAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
if ("DontCast".equals(logic)) {
return false;
}
// Wait for Main2 if possible
if (aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)
&& !ComputerUtil.castPermanentInMain1(aiPlayer, sa)) {
return false;
}
// AI shouldn't be retricted all that much for Creatures for now
return true;
}
}

View File

@@ -1,146 +0,0 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class PhasesAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// This still needs to be fleshed out
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getSourceCard();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> tgtCards;
if (tgt == null) {
tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (tgtCards.contains(source)) {
// Protect it from something
} else {
// Card def = tgtCards.get(0);
// Phase this out if it might attack me, or before it can be
// declared as a blocker
}
return false;
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
return mandatory;
}
if (phasesPrefTargeting(tgt, sa, mandatory)) {
return true;
} else if (mandatory) {
// not enough preferred targets, but mandatory so keep going:
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
}
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
boolean randomReturn = true;
if (tgt == null) {
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
/**
* <p>
* phasesPrefTargeting.
* </p>
*
* @param tgt
* a {@link forge.game.spellability.TargetRestrictions} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean phasesPrefTargeting(final TargetRestrictions tgt, final SpellAbility sa,
final boolean mandatory) {
// Card source = sa.getSourceCard();
// List<Card> phaseList =
// AllZoneUtil.getCardsIn(Zone.Battlefield).getTargetableCards(source)
// .getValidCards(tgt.getValidTgts(), source.getController(), source);
// List<Card> aiPhaseList =
// phaseList.getController(AllZone.getComputerPlayer());
// If Something in the Phase List might die from a bad combat, or a
// spell on the stack save it
// List<Card> humanPhaseList =
// phaseList.getController(AllZone.getHumanPlayer());
// If something in the Human List is causing issues, phase it out
return false;
}
/**
* <p>
* phasesUnpreferredTargeting.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean phasesUnpreferredTargeting(final Game game, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source), sa);
return false;
}
}

View File

@@ -1,133 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class PlayAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Random r = MyRandom.getRandom();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
// don't use this as a response
if (!ai.getGame().getStack().isEmpty()) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getRestrictions().getNumberTurnActivations());
List<Card> cards;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
ZoneType zone = tgt.getZone().get(0);
cards = CardLists.getValidCards(ai.getGame().getCardsIn(zone), tgt.getValidTgts(), ai, source);
if (cards.isEmpty()) {
return false;
}
sa.getTargets().add(ComputerUtilCard.getBestAI(cards));
} else if (!sa.hasParam("Valid")) {
cards = new ArrayList<Card>(AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa));
if (cards.isEmpty()) {
return false;
}
}
return chance;
}
/**
* <p>
* doTriggerAINoCost
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// as called from PlayEffect:173
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (SpellAbility s : c.getBasicSpells()) {
Spell spell = (Spell) s;
s.setActivatingPlayer(ai);
// timing restrictions still apply
if (s.getRestrictions().checkTimingRestrictions(c, s) && spell.canPlayFromEffectAI(ai, false, true)) {
return true;
}
}
return false;
}
});
return ComputerUtilCard.getBestAI(tgtCards);
}
}

View File

@@ -1,88 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class PoisonAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
// int humanPoison = AllZone.getHumanPlayer().getPoisonCounters();
// int humanLife = AllZone.getHumanPlayer().getLife();
// int aiPoison = AllZone.getComputerPlayer().getPoisonCounters();
// TODO handle proper calculation of X values based on Cost and what
// would be paid
// final int amount =
// AbilityFactory.calculateAmount(af.getHostCard(),
// amountStr, sa);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 1, null)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
}
// Don't use poison before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.getTargets().add(ai.getOpponent());
} else {
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa);
for (final Player p : players) {
if (!mandatory && p == ai && (p.getPoisonCounters() > p.getOpponent().getPoisonCounters())) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,79 +0,0 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class PowerExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card c1 = null;
Card c2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getSourceCard());
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
CardLists.sortByPowerAsc(list);
c1 = list.isEmpty() ? null : list.get(0);
if (sa.hasParam("Defined")) {
c2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0);
} else if (tgt.getMinTargets(sa.getSourceCard(), sa) > 1) {
List<Card> list2 = ai.getCardsIn(ZoneType.Battlefield);
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getSourceCard());
CardLists.sortByPowerAsc(list2);
Collections.reverse(list2);
c2 = list2.isEmpty() ? null : list2.get(0);
sa.getTargets().add(c2);
}
if (c1 == null || c2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
sa.getTargets().add(c1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return canPlayAI(aiPlayer, sa);
}
return true;
}
}

View File

@@ -1,370 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class ProtectAi extends SpellAbilityAi {
private static boolean hasProtectionFrom(final Card card, final String color) {
final ArrayList<String> onlyColors = new ArrayList<String>(MagicColor.Constant.ONLY_COLORS);
// make sure we have a valid color
if (!onlyColors.contains(color)) {
return false;
}
final String protection = "Protection from " + color;
return card.hasKeyword(protection);
}
private static boolean hasProtectionFromAny(final Card card, final Iterable<String> colors) {
boolean protect = false;
for (final String color : colors) {
protect |= hasProtectionFrom(card, color);
}
return protect;
}
private static boolean hasProtectionFromAll(final Card card, final Iterable<String> colors) {
boolean protect = true;
boolean isEmpty = true;
for (final String color : colors) {
protect &= hasProtectionFrom(card, color);
isEmpty = false;
}
return protect && !isEmpty;
}
/**
* <p>
* getProtectCreatures.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @return a {@link forge.CardList} object.
*/
private static List<Card> getProtectCreatures(final Player ai, final SpellAbility sa) {
final List<String> gains = ProtectEffect.getProtectionList(sa);
final Game game = ai.getGame();
final Combat combat = game.getCombat();
List<Card> list = ai.getCreaturesInPlay();
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
// Don't add duplicate protections
if (hasProtectionFromAll(c, gains)) {
return false;
}
// will the creature attack (only relevant for sorcery speed)?
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& ComputerUtilCard.doesCreatureAttackAI(ai, c)) {
return true;
}
if( combat != null ) {
// is the creature blocking and unable to destroy the attacker
// or would be destroyed itself?
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
return true;
}
// is the creature in blocked and the blocker would survive
// TODO Potential NPE here if no blockers are actually left
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& combat.isAttacking(c) && combat.isBlocked(c)
&& ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) {
return true;
}
}
return false;
}
});
return list;
} // getProtectCreatures()
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
final Game game = ai.getGame();
// if there is no target and host card isn't in play, don't activate
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
// Phase Restrictions
if (game.getStack().isEmpty() && game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE)) {
// Instant-speed protections should not be cast outside of combat
// when the stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
} else if (!game.getStack().isEmpty()) {
// TODO protection something only if the top thing on the stack will
// kill it via damage or destroy
return false;
}
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
if (cards.size() == 0) {
return false;
}
/*
* // when this happens we need to expand AI to consider if its ok
* for everything? for (Card card : cards) { // TODO if AI doesn't
* control Card and Pump is a Curse, than maybe use?
*
* }
*/
} else {
return protectTgtAI(ai, sa, false);
}
return false;
} // protectPlayAI()
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list = getProtectCreatures(ai, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
/*
* TODO - What this should probably do is if it's time for instants and
* abilities after Human declares attackers, determine desired
* protection before assigning blockers.
*
* The other time we want protection is if I'm targeted by a damage or
* destroy spell on the stack
*
* Or, add protection (to make it unblockable) when Compy is attacking.
*/
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getSourceCard());
}
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getSourceCard());
}
}
}
if (list.isEmpty()) {
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
}
// Don't target cards that will die.
list = ComputerUtil.getSafeTargets(ai, sa, list);
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return protectMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // protectTgtAI()
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
final TargetRestrictions tgt = sa.getTargetRestrictions();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
List<Card> pref = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
}
});
final List<Card> pref2 = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
}
});
final List<Card> forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getSourceCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref2.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref2, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref2);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref2, sa, true);
}
pref2.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
if (forced.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // protectMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return protectTgtAI(ai, sa, mandatory);
}
return true;
} // protectTriggerAI
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card host = sa.getSourceCard();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (host.isCreature()) {
// TODO
}
} else {
return protectTgtAI(ai, sa, false);
}
return true;
} // protectDrawbackAI()
}

View File

@@ -1,48 +0,0 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ProtectAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
// if there is no target and host card isn't in play, don't activate
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
return false;
} // protectAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,456 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostTapType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class PumpAi extends PumpAiBase {
private static boolean hasTapCost(final Cost cost, final Card source) {
if (cost == null) {
return true;
}
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostTapType) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost cost = sa.getPayCosts();
final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler();
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getSourceCard(), 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, sa.getSourceCard())) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, sa.getSourceCard())) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, sa.getSourceCard())) {
return false;
}
if (game.getStack().isEmpty() && hasTapCost(cost, sa.getSourceCard())) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && ph.isPlayerTurn(ai)) {
return false;
}
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai.getOpponent())) {
return false;
}
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Phase Restrictions
if (game.getStack().isEmpty() && ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
if (!sa.isCurse() && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
} else if (!game.getStack().isEmpty()) {
if (!keywords.contains("Shroud") && !keywords.contains("Hexproof")) {
return false;
}
}
final SpellAbilityRestriction restrict = sa.getRestrictions();
final int activations = restrict.getNumberTurnActivations();
final int sacActivations = restrict.getActivationNumberSacrifice();
// don't risk sacrificing a creature just to pump it
if ((sacActivations != -1) && (activations >= (sacActivations - 1))) {
return false;
}
final Card source = sa.getSourceCard();
if (source.getSVar("X").equals("Count$xPaid")) {
source.setSVar("PayX", "");
}
int defense;
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
defense = xPay;
if (numDefense.equals("-X")) {
defense = -xPay;
}
} else {
defense = AbilityUtils.calculateAmount(sa.getSourceCard(), numDefense, sa);
}
int attack;
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final String toPay = source.getSVar("PayX");
if (toPay.equals("")) {
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
attack = xPay;
} else {
attack = Integer.parseInt(toPay);
}
} else {
attack = AbilityUtils.calculateAmount(sa.getSourceCard(), numAttack, sa);
}
if ((numDefense.contains("X") && defense == 0)
|| (numAttack.contains("X") && attack == 0)) {
return false;
}
//Untargeted
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(),
sa.getParam("Defined"), sa);
if (cards.size() == 0) {
return false;
}
// when this happens we need to expand AI to consider if its ok for
// everything?
for (final Card card : cards) {
if (sa.isCurse()) {
if (!card.getController().isOpponentOf(ai)) {
return false;
}
if (!containsUsefulKeyword(ai, keywords, card, sa, attack)) {
continue;
}
return true;
}
if (!card.getController().isOpponentOf(ai) && shouldPumpCard(ai, sa, card, defense, attack, keywords)) {
return true;
}
}
return false;
}
//Targeted
if (!this.pumpTgtAI(ai, sa, defense, attack, false)) {
return false;
}
return true;
} // pumpPlayAI()
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory) {
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
final Game game = ai.getGame();
final Card source = sa.getSourceCard();
if (!mandatory
&& !sa.isTrigger()
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& !(sa.isCurse() && defense < 0)
&& !this.containsNonCombatKeyword(keywords)
&& !sa.hasParam("UntilYourNextTurn")) {
return false;
}
final Player opp = ai.getOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list = new ArrayList<Card>();
if (sa.hasParam("AILogic")) {
if (sa.getParam("AILogic").equals("HighestPower")) {
list = CardLists.getValidCards(CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source);
list = CardLists.getTargetableCards(list, sa);
CardLists.sortByPowerDesc(list);
if (!list.isEmpty()) {
sa.getTargets().add(list.get(0));
return true;
} else {
return false;
}
} else {
return false;
}
} else if (sa.isCurse()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
list = this.getCurseCreatures(ai, sa, defense, attack, keywords);
} else {
if (!tgt.canTgtCreature()) {
ZoneType zone = tgt.getZone().get(0);
list = game.getCardsIn(zone);
} else {
list = this.getPumpCreatures(ai, sa, defense, attack, keywords);
}
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
}
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source);
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getSourceCard());
}
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().isPlayerTurn(opp)) {
list.remove(sa.getSourceCard());
}
}
}
if (list.isEmpty()) {
return mandatory && this.pumpMandatoryTarget(ai, sa, mandatory);
}
if (!sa.isCurse()) {
// Don't target cards that will die.
list = ComputerUtil.getSafeTargets(ai, sa, list);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return this.pumpMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // pumpTgtAI()
private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getOpponent();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
list = CardLists.getTargetableCards(list, sa);
if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
List<Card> pref;
List<Card> forced;
final Card source = sa.getSourceCard();
if (sa.isCurse()) {
pref = CardLists.filterControlledBy(list, opp);
forced = CardLists.filterControlledBy(list, ai);
} else {
pref = CardLists.filterControlledBy(list, ai);
forced = CardLists.filterControlledBy(list, opp);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").isEmpty()) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
if (forced.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(forced, "Creature").isEmpty()) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
sa.resetTargets();
return false;
}
return true;
} // pumpMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
int defense;
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
defense = xPay;
} else {
defense = AbilityUtils.calculateAmount(sa.getSourceCard(), numDefense, sa);
}
int attack;
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final String toPay = source.getSVar("PayX");
if (toPay.equals("")) {
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
attack = xPay;
} else {
attack = Integer.parseInt(toPay);
}
} else {
attack = AbilityUtils.calculateAmount(sa.getSourceCard(), numAttack, sa);
}
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return this.pumpTgtAI(ai, sa, defense, attack, mandatory);
}
return true;
} // pumpTriggerAI
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card source = sa.getSourceCard();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : "";
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
int defense;
if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
defense = Integer.parseInt(source.getSVar("PayX"));
} else {
defense = AbilityUtils.calculateAmount(sa.getSourceCard(), numDefense, sa);
}
int attack;
if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
if (source.getSVar("PayX").equals("")) {
// X is not set yet
final int xPay = ComputerUtilMana.determineLeftoverMana(sa.getRootAbility(), ai);
source.setSVar("PayX", Integer.toString(xPay));
attack = xPay;
} else {
attack = Integer.parseInt(source.getSVar("PayX"));
}
} else {
attack = AbilityUtils.calculateAmount(sa.getSourceCard(), numAttack, sa);
}
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (source.isCreature()) {
if (!source.hasKeyword("Indestructible")
&& ((source.getNetDefense() + defense) <= source.getDamage())) {
return false;
}
if ((source.getNetDefense() + defense) <= 0) {
return false;
}
}
} else {
//Targeted
if (!this.pumpTgtAI(ai, sa, defense, attack, false)) {
return false;
}
}
return true;
} // pumpDrawbackAI()
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO Add logic here if necessary but I think the AI won't cast
//the spell in the first place if it would curse its own creature
//and the pump isn't mandatory
return true;
}
}

View File

@@ -1,597 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.phase.Untap;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public abstract class PumpAiBase extends SpellAbilityAi {
public boolean containsUsefulKeyword(final Player ai, final List<String> keywords, final Card card, final SpellAbility sa, final int attack) {
for (final String keyword : keywords) {
if (!sa.isCurse() && isUsefulPumpKeyword(ai, keyword, card, sa, attack)) {
return true;
}
if (sa.isCurse() && isUsefulCurseKeyword(ai, keyword, card, sa)) {
return true;
}
}
return false;
}
/**
* Checks if is useful keyword.
*
* @param keyword
* the keyword
* @param card
* the card
* @param sa SpellAbility
* @return true, if is useful keyword
*/
public boolean isUsefulCurseKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
final Player human = ai.getOpponent();
//int attack = getNumAttack(sa);
//int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
} else if (keyword.equals("Defender") || keyword.endsWith("CARDNAME can't attack.")) {
if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human)
|| (card.getNetCombatDamage() <= 0)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
if (sa.hasParam("UntilYourNextTurn")) {
if (CombatUtil.canAttack(card, human) || CombatUtil.canBlock(card, true)) {
return true;
}
return false;
}
if (ph.isPlayerTurn(human)) {
if (!CombatUtil.canAttack(card, human)
|| (card.getNetCombatDamage() <= 0)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
if (ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1)) {
return false;
}
List<Card> attackers = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isCreature() && CombatUtil.canAttack(c, human));
}
});
if (!CombatUtil.canBlockAtLeastOne(card, attackers)) {
return false;
}
}
} else if (keyword.endsWith("CARDNAME can't block.")) {
if (ph.isPlayerTurn(human) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1)) {
return false;
}
List<Card> attackers = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isCreature() && CombatUtil.canAttack(c, human));
}
});
if (!CombatUtil.canBlockAtLeastOne(card, attackers)) {
return false;
}
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
if (ph.getPhase().isBefore(PhaseType.MAIN2) || card.isUntapped() || ph.isPlayerTurn(human)
|| !Untap.canUntap(card)) {
return false;
}
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|| card.getNetCombatDamage() <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1)
|| CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) {
return false;
}
if (ph.isPlayerTurn(human) && (combat == null || !combat.isAttacking(card) || card.getNetCombatDamage() <= 0)) {
return false;
}
} else if (keyword.endsWith("CARDNAME attacks each turn if able.")) {
if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human) || !CombatUtil.canBeBlocked(card, ai.getOpponent())
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else if (keyword.endsWith("CARDNAME can't be regenerated.")) {
if (!card.getShield().isEmpty()) {
return true;
}
if (card.hasKeyword("If CARDNAME would be destroyed, regenerate it.") && combat != null
&& (combat.isBlocked(card) || combat.isBlocking(card))) {
return true;
}
return false;
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
return false; //too complex
}
return true;
}
/**
* Checks if is useful keyword.
*
* @param keyword
* the keyword
* @param card
* the card
* @param sa SpellAbility
* @return true, if is useful keyword
*/
public boolean isUsefulPumpKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa, final int attack) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ai.getOpponent();
final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
}
final boolean evasive = (keyword.endsWith("Unblockable") || keyword.endsWith("Fear")
|| keyword.endsWith("Intimidate") || keyword.endsWith("Shadow")
|| keyword.startsWith("CantBeBlockedBy"));
// give evasive keywords to creatures that can or do attack
if (evasive) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.endsWith("Flying")) {
if (ph.isPlayerTurn(opp)
&& ph.getPhase() == PhaseType.COMBAT_DECLARE_ATTACKERS
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
&& !card.hasKeyword("Reach")
&& CombatUtil.canBlock(card)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true;
}
Predicate<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
Predicates.not(flyingOrReach))) {
return false;
}
} else if (keyword.endsWith("Horsemanship")) {
if (ph.isPlayerTurn(opp)
&& ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Horsemanship").isEmpty()
&& CombatUtil.canBlock(card)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true;
}
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
"Horsemanship").isEmpty()) {
return false;
}
} else if (keyword.endsWith("Haste")) {
if (!card.hasSickness() || ph.isPlayerTurn(opp) || card.isTapped()
|| newPower <= 0
|| card.hasKeyword("CARDNAME can attack as though it had haste.")
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| !CombatUtil.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.endsWith("Indestructible")) {
return true;
} else if (keyword.endsWith("Deathtouch")) {
if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
List<Card> attackers = combat.getAttackers();
for (Card attacker : attackers) {
if (CombatUtil.canBlock(attacker, card, combat)
&& !ComputerUtilCombat.canDestroyAttacker(ai, attacker, card, combat, false)) {
return true;
}
}
} else if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& CombatUtil.canAttack(card, opp)) {
List<Card> blockers = opp.getCreaturesInPlay();
for (Card blocker : blockers) {
if (CombatUtil.canBlock(card, blocker, combat)
&& !ComputerUtilCombat.canDestroyBlocker(ai, blocker, card, combat, false)) {
return true;
}
}
}
return false;
} else if (keyword.equals("Bushido")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| opp.getCreaturesInPlay().isEmpty()
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.equals("First Strike")) {
if (ph.isPlayerTurn(ai) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
&& newPower > 0
&& ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
&& !CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return true;
}
if (combat != null && combat.isBlocking(card) && !combat.getAttackersBlockedBy(card).isEmpty()) {
Card attacker = combat.getAttackersBlockedBy(card).get(0);
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, card, combat, true)
&& ComputerUtilCombat.canDestroyAttacker(ai, attacker, card, combat, false))
return true;
if (ComputerUtilCombat.canDestroyBlocker(ai, card, attacker, combat, true)
&& !ComputerUtilCombat.canDestroyBlocker(ai, card, attacker, combat, false))
return true;
}
return false;
} else if (keyword.equals("Double Strike")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| newPower <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
} else if (keyword.startsWith("Rampage")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) {
return false;
}
} else if (keyword.startsWith("Flanking")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
"Flanking").isEmpty()) {
return false;
}
} else if (keyword.startsWith("Trample")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| !CombatUtil.canBeBlocked(card, opp)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 1
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.equals("Infect")) {
if (newPower <= 0) {
return false;
}
if (combat != null && combat.isBlocking(card)) {
return true;
}
if ((ph.isPlayerTurn(opp))
|| !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
} else if (keyword.endsWith("Wither")) {
if (newPower <= 0) {
return false;
}
return combat != null && ( combat.isBlocking(card) || (combat.isAttacking(card) && combat.isBlocked(card)) );
} else if (keyword.equals("Lifelink")) {
if (newPower <= 0) {
return false;
}
return combat != null && ( combat.isAttacking(card) || combat.isBlocking(card) );
} else if (keyword.equals("Vigilance")) {
if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(opp.getCreaturesInPlay(), "Defender").size() < 1) {
return false;
}
} else if (keyword.equals("Reach")) {
if (ph.isPlayerTurn(ai)
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|| card.hasKeyword("Flying")
|| !CombatUtil.canBlock(card)) {
return false;
}
} else if (keyword.endsWith("CARDNAME can block an additional creature.")) {
if (ph.isPlayerTurn(ai)
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
int canBlockNum = 1 + card.getKeywordAmount("CARDNAME can block an additional creature.") +
card.getKeywordAmount("CARDNAME can block an additional ninety-nine creatures.") * 99;
int possibleBlockNum = 0;
for (Card attacker : game.getCombat().getAttackers()) {
if (CombatUtil.canBlock(attacker, card)) {
possibleBlockNum++;
if (possibleBlockNum > canBlockNum) {
break;
}
}
}
if (possibleBlockNum <= canBlockNum) {
return false;
}
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(card)) {
return false;
}
} else if (keyword.equals("Islandwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.equals("Swampwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.equals("Mountainwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.equals("Forestwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false;
}
} else if (keyword.endsWith("CARDNAME can attack as though it didn't have defender.")) {
if (!ph.isPlayerTurn(ai) || !card.hasKeyword("Defender")
|| ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)
|| card.isTapped() || newPower <= 0) {
return false;
}
}
return true;
}
protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack,
final List<String> keywords) {
final Game game = ai.getGame();
final PhaseHandler phase = game.getPhaseHandler();
final Combat combat = phase.getCombat();
if (!c.canBeTargetedBy(sa)) {
return false;
}
if ((c.getNetDefense() + defense) <= 0) {
return false;
}
if (containsUsefulKeyword(ai, keywords, c, sa, attack)) {
return true;
}
// 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;
}
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;
}
}
}
// is the creature blocking and unable to destroy the attacker
// or would be destroyed itself?
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && combat.isBlocking(c)) {
if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
return true;
}
List<Card> blockedBy = combat.getAttackersBlockedBy(c);
// For now, Only care the first creature blocked by a card.
// TODO Add in better BlockAdditional support
if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(ai, blockedBy.get(0), combat)) {
return true;
}
}
// is the creature unblocked and the spell will pump its power?
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& combat.isAttacking(c) && combat.isUnblocked(c) && attack > 0) {
return true;
}
// is the creature blocked and the blocker would survive
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && attack > 0
&& combat.isAttacking(c)
&& combat.isBlocked(c)
&& combat.getBlockers(c) != null
&& !combat.getBlockers(c).isEmpty()
&& !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) {
return true;
}
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS, ai.getOpponent()) && combat.isBlocking(c) && defense > 0 ) {
// if the life of the computer is in danger, try to pump blockers blocking Tramplers
List<Card> 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 false;
}
/**
* <p>
* getPumpCreatures.
* </p>
*
* @return a {@link forge.CardList} object.
*/
protected List<Card> getPumpCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List<String> keywords) {
List<Card> list = ai.getCreaturesInPlay();
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return shouldPumpCard(ai, sa, c, defense, attack, keywords);
}
});
return list;
} // getPumpCreatures()
/**
* <p>
* getCurseCreatures.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param defense
* a int.
* @param attack
* a int.
* @return a {@link forge.CardList} object.
*/
protected List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List<String> keywords) {
List<Card> list = ai.getOpponent().getCreaturesInPlay();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty()) {
return list;
}
if (defense < 0) { // with spells that give -X/-X, compi will try to destroy a creature
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getNetDefense() <= -defense) {
return true; // can kill indestructible creatures
}
return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible"));
}
}); // leaves all creatures that will be destroyed
} // -X/-X end
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
// spells that give -X/0
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
if (isMyTurn) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
// TODO: Curse creatures that will block AI's creatures, if AI is going to attack.
list = new ArrayList<Card>();
} else {
list = new ArrayList<Card>();
}
} else {
// Human active, only curse attacking creatures
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c)) {
return false;
}
if (c.getNetAttack() > 0 && ai.getLife() < 5) {
return true;
}
//Don't waste a -7/-0 spell on a 1/1 creature
if (c.getNetAttack() + attack > -2 || c.getNetAttack() > 3) {
return true;
}
return false;
}
});
} else {
list = new ArrayList<Card>();
}
}
} // -X/0 end
else {
final boolean addsKeywords = keywords.size() > 0;
if (addsKeywords) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return containsUsefulKeyword(ai, keywords, c, sa, attack);
}
});
} else if (sa.hasParam("NumAtt") || sa.hasParam("NumDef")) {
// X is zero
list = new ArrayList<Card>();
}
}
return list;
} // getCurseCreatures()
protected boolean containsNonCombatKeyword(final List<String> keywords) {
for (final String keyword : keywords) {
// since most keywords are combat relevant check for those that are
// not
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
return true;
}
}
return false;
}
}

View File

@@ -1,167 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class PumpAllAi extends PumpAiBase {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
String valid = "";
final Card source = sa.getSourceCard();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final int power = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
final Player opp = ai.getOpponent();
List<Card> comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
List<Card> human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
sa.resetTargets();
sa.getTargets().add(opp);
comp = new ArrayList<Card>();
}
if (sa.hasParam("IsCurse")) {
if (defense < 0) { // try to destroy creatures
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getNetDefense() <= -defense) {
return true; // can kill indestructible creatures
}
return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible"));
}
}); // leaves all creatures that will be destroyed
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getNetDefense() <= -defense) {
return true; // can kill indestructible creatures
}
return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible"));
}
}); // leaves all creatures that will be destroyed
} // -X/-X end
else if (power < 0) { // -X/-0
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
int totalPower = 0;
for (Card c : human) {
if (combat == null || !combat.isAttacking(c)) {
continue;
}
totalPower += Math.min(c.getNetAttack(), power * -1);
if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS && combat.isUnblocked(c)) {
if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), combat)) {
return true;
}
totalPower += Math.min(c.getNetAttack(), power * -1);
}
if (totalPower >= power * -2) {
return true;
}
}
return false;
} // -X/-0 end
if (comp.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
return true;
}
// evaluate both lists and pass only if human creatures are more
// valuable
if ((ComputerUtilCard.evaluateCreatureList(comp) + 200) >= ComputerUtilCard.evaluateCreatureList(human)) {
return false;
}
return true;
} // end Curse
// don't use non curse PumpAll after Combat_Begin until AI is improved
if (phase.isBefore(PhaseType.MAIN1)) {
return false;
} else if (phase.isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return ComputerUtil.activateForCost(sa, ai);
}
// only count creatures that can attack
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) {
return false;
}
if (phase == PhaseType.COMBAT_DECLARE_ATTACKERS && combat.isAttacking(c)) {
return true;
}
if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) {
return true;
}
return false;
}
});
if (comp.size() <= human.size()) {
return false;
}
if (comp.size() <= 1 && (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || sa.isSpell())) {
return false;
}
return true;
} // pumpAllCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,44 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
// ability is targeted
sa.resetTargets();
Player opp = ai.getOpponent();
final boolean canTgtHuman = opp.canBeTargetedBy(sa);
if (!canTgtHuman) {
return false;
} else {
sa.getTargets().add(opp);
}
} else {
// if it's just defined, no big deal
}
return false;
}
}

View File

@@ -1,244 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* <p>
* AbilityFactory_Regenerate class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class RegenerateAi extends SpellAbilityAi {
// Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate
// target creature.
// http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate
// **************************************************************
// ********************* Regenerate ****************************
// **************************************************************
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
final Cost abCost = sa.getPayCosts();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false;
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, hostCard)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
if (!game.getStack().isEmpty()) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Card c : list) {
if (objects.contains(c)) {
chance = true;
}
}
} else {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false;
for (final Card c : list) {
if (c.getShield().isEmpty()) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat);
}
}
chance = flag;
} else { // if nothing on the stack, and it's not declare
// blockers. no need to regen
return false;
}
}
} else {
sa.resetTargets();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.size() == 0) {
return false;
}
if (!game.getStack().isEmpty()) {
// check stack for something on the stack will kill anything i
// control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
final List<Card> threatenedTargets = new ArrayList<Card>();
for (final Card c : targetables) {
if (objects.contains(c) && c.getShield().isEmpty()) {
threatenedTargets.add(c);
}
}
if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to regenerate
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true;
}
} else {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants);
for (final Card c : combatants) {
if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c);
chance = true;
break;
}
}
}
}
if (sa.getTargets().isEmpty()) {
return false;
}
}
return chance;
} // regenerateCanPlayAI
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If there's no target on the trigger, just say yes.
chance = true;
} else {
chance = regenMandatoryTarget(ai, sa, mandatory);
}
return chance;
}
private static boolean regenMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card hostCard = sa.getSourceCard();
final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
if (targetables.size() == 0) {
return false;
}
if (!mandatory && (compTargetables.size() == 0)) {
return false;
}
if (compTargetables.size() > 0) {
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) {
if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c);
return true;
}
}
}
// TODO see if something on the stack is about to kill something i
// can target
// choose my best X without regen
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
for (final Card c : combatants) {
if (c.getShield().isEmpty()) {
sa.getTargets().add(c);
return true;
}
}
sa.getTargets().add(combatants.get(0));
return true;
} else {
CardLists.sortByCmcDesc(compTargetables);
for (final Card c : compTargetables) {
if (c.getShield().isEmpty()) {
sa.getTargets().add(c);
return true;
}
}
sa.getTargets().add(compTargetables.get(0));
return true;
}
}
sa.getTargets().add(ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true));
return true;
}
}

View File

@@ -1,94 +0,0 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class RegenerateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
boolean chance = false;
final Cost abCost = sa.getPayCosts();
final Game game = ai.getGame();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, hostCard, 4, null)) {
return false;
}
}
// filter AIs battlefield by what I can target
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard);
list = CardLists.filter(list, CardPredicates.isController(ai));
if (list.size() == 0) {
return false;
}
int numSaved = 0;
if (!game.getStack().isEmpty()) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Card c : list) {
if (objects.contains(c) && c.getShield().isEmpty()) {
numSaved++;
}
}
} else {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
final List<Card> combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES);
final Combat combat = game.getCombat();
for (final Card c : combatants) {
if (c.getShield().isEmpty() && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
numSaved++;
}
}
}
}
if (numSaved > 1) {
chance = true;
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
return chance;
}
}

View File

@@ -1,36 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class RemoveFromCombatAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI for now. Only for Gideon Jura at this time.
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
// TODO - implement AI
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -1,47 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class RepeatAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getOpponent();
if (tgt != null) {
if (!opp.canBeTargetedBy(sa)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO add logic to have computer make better choice (ArsenalNut)
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// setup subability to repeat
final SpellAbility repeat = AbilityFactory.getAbility(sa.getSourceCard().getSVar(sa.getParam("RepeatSubAbility")), sa.getSourceCard());
if (repeat == null) {
return false;
}
repeat.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) repeat).setParent(sa);
return repeat.doTrigger(mandatory, ai);
}
}

View File

@@ -1,103 +0,0 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* TODO: Write javadoc for this type.
*
*/
public class RepeatEachAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
if ("CloneMyTokens".equals(logic)) {
if (CardLists.filter(aiPlayer.getCreaturesInPlay(), Presets.TOKEN).size() < 2) {
return false;
}
} else if ("CloneAllTokens".equals(logic)) {
final Player opp = aiPlayer.getOpponent();
List<Card> humTokenCreats = CardLists.filter(opp.getCreaturesInPlay(), Presets.TOKEN);
List<Card> compTokenCreats = CardLists.filter(aiPlayer.getCreaturesInPlay(), Presets.TOKEN);
if (compTokenCreats.size() <= humTokenCreats.size()) {
return false;
}
} else if ("DoubleCounters".equals(logic)) {
// TODO Improve this logic, double Planeswalker counters first, then +1/+1 on Useful creatures
// Then Charge Counters, then -1/-1 on Opposing Creatures
List<Card> perms = new ArrayList<Card>(aiPlayer.getCardsIn(ZoneType.Battlefield));
perms = CardLists.filter(CardLists.getTargetableCards(perms, sa), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasCounters();
}
});
if (perms.isEmpty()) {
return false;
}
CardLists.shuffle(perms);
sa.setTargetCard(perms.get(0));
} else if ("RemoveAllCounters".equals(logic)) {
// Break Dark Depths
List<Card> depthsList = aiPlayer.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.getCounters(CounterType.ICE) >= 3;
}
});
if (depthsList.size() > 0) {
sa.getTargets().add(depthsList.get(0));
return true;
}
// Get rid of Planeswalkers:
List<Card> list = new ArrayList<Card>(aiPlayer.getOpponent().getCardsIn(ZoneType.Battlefield));
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.isPlaneswalker() && (crd.getCounters(CounterType.LOYALTY) >= 5);
}
});
if (list.isEmpty()) {
return false;
}
sa.getTargets().add(list.get(0));
} else if ("BalanceLands".equals(logic)) {
if (CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() >= 5) {
return false;
}
List<Player> opponents = aiPlayer.getOpponents();
for(Player opp : opponents) {
if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() < 4) {
return false;
}
}
}
// TODO Add some normal AI variability here
return true;
}
}

View File

@@ -1,33 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class RestartGameAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// The only card that uses this is Karn Liberated
// TODO Add Logic, check if AI is losing game state, or life
// TODO Add Logic, check if any good cards will be available to be returned
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// This trigger AI is completely unused, but return true just in case
return true;
}
}

View File

@@ -1,66 +0,0 @@
package forge.ai.ability;
import java.util.Random;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class RevealAi extends RevealAiBase {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
// we can reuse this function here...
final boolean bFlag = revealHandTargetAI(ai, sa/*, true, false*/);
if (!bFlag) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.playReusable(ai, sa)) {
randomReturn = true;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (!revealHandTargetAI(ai, sa/*, false, mandatory*/)) {
return false;
}
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More