mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
the great move
This commit is contained in:
@@ -18,5 +18,10 @@
|
||||
<artifactId>forge-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode</groupId>
|
||||
<artifactId>minlog</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
1021
forge-game/src/main/java/forge/ai/AiAttackController.java
Normal file
1021
forge-game/src/main/java/forge/ai/AiAttackController.java
Normal file
File diff suppressed because it is too large
Load Diff
809
forge-game/src/main/java/forge/ai/AiBlockController.java
Normal file
809
forge-game/src/main/java/forge/ai/AiBlockController.java
Normal file
@@ -0,0 +1,809 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
1111
forge-game/src/main/java/forge/ai/AiController.java
Normal file
1111
forge-game/src/main/java/forge/ai/AiController.java
Normal file
File diff suppressed because it is too large
Load Diff
609
forge-game/src/main/java/forge/ai/AiCostDecision.java
Normal file
609
forge-game/src/main/java/forge/ai/AiCostDecision.java
Normal file
@@ -0,0 +1,609 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
194
forge-game/src/main/java/forge/ai/AiProfileUtil.java
Normal file
194
forge-game/src/main/java/forge/ai/AiProfileUtil.java
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
46
forge-game/src/main/java/forge/ai/AiProps.java
Normal file
46
forge-game/src/main/java/forge/ai/AiProps.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* AI personality profile settings identifiers, and their default values.
|
||||
* When this class is instantiated, these enum values are used
|
||||
* in a map that is populated with the current AI profile settings
|
||||
* from the text file.
|
||||
*/
|
||||
public enum AiProps { /** */
|
||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||
MULLIGAN_THRESHOLD ("5"), /** */
|
||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
|
||||
|
||||
private final String strDefaultVal;
|
||||
|
||||
/** @param s0   {@link java.lang.String} */
|
||||
AiProps(final String s0) {
|
||||
this.strDefaultVal = s0;
|
||||
}
|
||||
|
||||
/** @return {@link java.lang.String} */
|
||||
public String getDefault() {
|
||||
return strDefaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
1860
forge-game/src/main/java/forge/ai/ComputerUtil.java
Normal file
1860
forge-game/src/main/java/forge/ai/ComputerUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
894
forge-game/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
894
forge-game/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
@@ -0,0 +1,894 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
2084
forge-game/src/main/java/forge/ai/ComputerUtilCombat.java
Normal file
2084
forge-game/src/main/java/forge/ai/ComputerUtilCombat.java
Normal file
File diff suppressed because it is too large
Load Diff
418
forge-game/src/main/java/forge/ai/ComputerUtilCost.java
Normal file
418
forge-game/src/main/java/forge/ai/ComputerUtilCost.java
Normal file
@@ -0,0 +1,418 @@
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
781
forge-game/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
781
forge-game/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
@@ -0,0 +1,781 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
175
forge-game/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
175
forge-game/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
@@ -0,0 +1,175 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
18
forge-game/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
18
forge-game/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
80
forge-game/src/main/java/forge/ai/ability/AddTurnAi.java
Normal file
80
forge-game/src/main/java/forge/ai/ability/AddTurnAi.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
16
forge-game/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
16
forge-game/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
189
forge-game/src/main/java/forge/ai/ability/AnimateAi.java
Normal file
189
forge-game/src/main/java/forge/ai/ability/AnimateAi.java
Normal file
@@ -0,0 +1,189 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
19
forge-game/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
19
forge-game/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
1245
forge-game/src/main/java/forge/ai/ability/AttachAi.java
Normal file
1245
forge-game/src/main/java/forge/ai/ability/AttachAi.java
Normal file
File diff suppressed because it is too large
Load Diff
52
forge-game/src/main/java/forge/ai/ability/BalanceAi.java
Normal file
52
forge-game/src/main/java/forge/ai/ability/BalanceAi.java
Normal file
@@ -0,0 +1,52 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
58
forge-game/src/main/java/forge/ai/ability/BondAi.java
Normal file
58
forge-game/src/main/java/forge/ai/ability/BondAi.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
24
forge-game/src/main/java/forge/ai/ability/CannotPlayAi.java
Normal file
24
forge-game/src/main/java/forge/ai/ability/CannotPlayAi.java
Normal file
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
1448
forge-game/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
1448
forge-game/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
File diff suppressed because it is too large
Load Diff
270
forge-game/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
Normal file
270
forge-game/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
Normal file
@@ -0,0 +1,270 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
96
forge-game/src/main/java/forge/ai/ability/CharmAi.java
Normal file
96
forge-game/src/main/java/forge/ai/ability/CharmAi.java
Normal file
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
189
forge-game/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
189
forge-game/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
@@ -0,0 +1,189 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
24
forge-game/src/main/java/forge/ai/ability/ChooseColorAi.java
Normal file
24
forge-game/src/main/java/forge/ai/ability/ChooseColorAi.java
Normal file
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
188
forge-game/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
188
forge-game/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
@@ -0,0 +1,188 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
33
forge-game/src/main/java/forge/ai/ability/ChooseTypeAi.java
Normal file
33
forge-game/src/main/java/forge/ai/ability/ChooseTypeAi.java
Normal file
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
36
forge-game/src/main/java/forge/ai/ability/ClashAi.java
Normal file
36
forge-game/src/main/java/forge/ai/ability/ClashAi.java
Normal file
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
155
forge-game/src/main/java/forge/ai/ability/CloneAi.java
Normal file
155
forge-game/src/main/java/forge/ai/ability/CloneAi.java
Normal file
@@ -0,0 +1,155 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
229
forge-game/src/main/java/forge/ai/ability/ControlGainAi.java
Normal file
229
forge-game/src/main/java/forge/ai/ability/ControlGainAi.java
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
128
forge-game/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
128
forge-game/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
@@ -0,0 +1,128 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
172
forge-game/src/main/java/forge/ai/ability/CounterAi.java
Normal file
172
forge-game/src/main/java/forge/ai/ability/CounterAi.java
Normal file
@@ -0,0 +1,172 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
109
forge-game/src/main/java/forge/ai/ability/CountersAi.java
Normal file
109
forge-game/src/main/java/forge/ai/ability/CountersAi.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
133
forge-game/src/main/java/forge/ai/ability/CountersMoveAi.java
Normal file
133
forge-game/src/main/java/forge/ai/ability/CountersMoveAi.java
Normal file
@@ -0,0 +1,133 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
403
forge-game/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
403
forge-game/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
@@ -0,0 +1,403 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
144
forge-game/src/main/java/forge/ai/ability/CountersPutAllAi.java
Normal file
144
forge-game/src/main/java/forge/ai/ability/CountersPutAllAi.java
Normal file
@@ -0,0 +1,144 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
106
forge-game/src/main/java/forge/ai/ability/CountersRemoveAi.java
Normal file
106
forge-game/src/main/java/forge/ai/ability/CountersRemoveAi.java
Normal file
@@ -0,0 +1,106 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
66
forge-game/src/main/java/forge/ai/ability/DamageAiBase.java
Normal file
66
forge-game/src/main/java/forge/ai/ability/DamageAiBase.java
Normal file
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
241
forge-game/src/main/java/forge/ai/ability/DamageAllAi.java
Normal file
241
forge-game/src/main/java/forge/ai/ability/DamageAllAi.java
Normal file
@@ -0,0 +1,241 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
522
forge-game/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
522
forge-game/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
@@ -0,0 +1,522 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
43
forge-game/src/main/java/forge/ai/ability/DamageEachAi.java
Normal file
43
forge-game/src/main/java/forge/ai/ability/DamageEachAi.java
Normal file
@@ -0,0 +1,43 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
228
forge-game/src/main/java/forge/ai/ability/DamagePreventAi.java
Normal file
228
forge-game/src/main/java/forge/ai/ability/DamagePreventAi.java
Normal file
@@ -0,0 +1,228 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
299
forge-game/src/main/java/forge/ai/ability/DebuffAi.java
Normal file
299
forge-game/src/main/java/forge/ai/ability/DebuffAi.java
Normal file
@@ -0,0 +1,299 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
65
forge-game/src/main/java/forge/ai/ability/DebuffAllAi.java
Normal file
65
forge-game/src/main/java/forge/ai/ability/DebuffAllAi.java
Normal file
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
267
forge-game/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
267
forge-game/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
@@ -0,0 +1,267 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
143
forge-game/src/main/java/forge/ai/ability/DestroyAllAi.java
Normal file
143
forge-game/src/main/java/forge/ai/ability/DestroyAllAi.java
Normal file
@@ -0,0 +1,143 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
102
forge-game/src/main/java/forge/ai/ability/DigAi.java
Normal file
102
forge-game/src/main/java/forge/ai/ability/DigAi.java
Normal file
@@ -0,0 +1,102 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
117
forge-game/src/main/java/forge/ai/ability/DigUntilAi.java
Normal file
117
forge-game/src/main/java/forge/ai/ability/DigUntilAi.java
Normal file
@@ -0,0 +1,117 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
178
forge-game/src/main/java/forge/ai/ability/DiscardAi.java
Normal file
178
forge-game/src/main/java/forge/ai/ability/DiscardAi.java
Normal file
@@ -0,0 +1,178 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
91
forge-game/src/main/java/forge/ai/ability/DrainManaAi.java
Normal file
91
forge-game/src/main/java/forge/ai/ability/DrainManaAi.java
Normal file
@@ -0,0 +1,91 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
272
forge-game/src/main/java/forge/ai/ability/DrawAi.java
Normal file
272
forge-game/src/main/java/forge/ai/ability/DrawAi.java
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
164
forge-game/src/main/java/forge/ai/ability/EffectAi.java
Normal file
164
forge-game/src/main/java/forge/ai/ability/EffectAi.java
Normal file
@@ -0,0 +1,164 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
101
forge-game/src/main/java/forge/ai/ability/EncodeAi.java
Normal file
101
forge-game/src/main/java/forge/ai/ability/EncodeAi.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
29
forge-game/src/main/java/forge/ai/ability/EndTurnAi.java
Normal file
29
forge-game/src/main/java/forge/ai/ability/EndTurnAi.java
Normal file
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
139
forge-game/src/main/java/forge/ai/ability/FightAi.java
Normal file
139
forge-game/src/main/java/forge/ai/ability/FightAi.java
Normal file
@@ -0,0 +1,139 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
27
forge-game/src/main/java/forge/ai/ability/FlipACoinAi.java
Normal file
27
forge-game/src/main/java/forge/ai/ability/FlipACoinAi.java
Normal file
@@ -0,0 +1,27 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
69
forge-game/src/main/java/forge/ai/ability/FogAi.java
Normal file
69
forge-game/src/main/java/forge/ai/ability/FogAi.java
Normal file
@@ -0,0 +1,69 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
49
forge-game/src/main/java/forge/ai/ability/GameLossAi.java
Normal file
49
forge-game/src/main/java/forge/ai/ability/GameLossAi.java
Normal file
@@ -0,0 +1,49 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
32
forge-game/src/main/java/forge/ai/ability/GameWinAi.java
Normal file
32
forge-game/src/main/java/forge/ai/ability/GameWinAi.java
Normal file
@@ -0,0 +1,32 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
30
forge-game/src/main/java/forge/ai/ability/HauntAi.java
Normal file
30
forge-game/src/main/java/forge/ai/ability/HauntAi.java
Normal file
@@ -0,0 +1,30 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
165
forge-game/src/main/java/forge/ai/ability/LifeGainAi.java
Normal file
165
forge-game/src/main/java/forge/ai/ability/LifeGainAi.java
Normal file
@@ -0,0 +1,165 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
170
forge-game/src/main/java/forge/ai/ability/LifeLoseAi.java
Normal file
170
forge-game/src/main/java/forge/ai/ability/LifeLoseAi.java
Normal file
@@ -0,0 +1,170 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
152
forge-game/src/main/java/forge/ai/ability/LifeSetAi.java
Normal file
152
forge-game/src/main/java/forge/ai/ability/LifeSetAi.java
Normal file
@@ -0,0 +1,152 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
35
forge-game/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
35
forge-game/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
155
forge-game/src/main/java/forge/ai/ability/MillAi.java
Normal file
155
forge-game/src/main/java/forge/ai/ability/MillAi.java
Normal file
@@ -0,0 +1,155 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
36
forge-game/src/main/java/forge/ai/ability/MustAttackAi.java
Normal file
36
forge-game/src/main/java/forge/ai/ability/MustAttackAi.java
Normal file
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
100
forge-game/src/main/java/forge/ai/ability/MustBlockAi.java
Normal file
100
forge-game/src/main/java/forge/ai/ability/MustBlockAi.java
Normal file
@@ -0,0 +1,100 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
146
forge-game/src/main/java/forge/ai/ability/PhasesAi.java
Normal file
146
forge-game/src/main/java/forge/ai/ability/PhasesAi.java
Normal file
@@ -0,0 +1,146 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
133
forge-game/src/main/java/forge/ai/ability/PlayAi.java
Normal file
133
forge-game/src/main/java/forge/ai/ability/PlayAi.java
Normal file
@@ -0,0 +1,133 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
88
forge-game/src/main/java/forge/ai/ability/PoisonAi.java
Normal file
88
forge-game/src/main/java/forge/ai/ability/PoisonAi.java
Normal file
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
370
forge-game/src/main/java/forge/ai/ability/ProtectAi.java
Normal file
370
forge-game/src/main/java/forge/ai/ability/ProtectAi.java
Normal file
@@ -0,0 +1,370 @@
|
||||
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()
|
||||
|
||||
}
|
||||
48
forge-game/src/main/java/forge/ai/ability/ProtectAllAi.java
Normal file
48
forge-game/src/main/java/forge/ai/ability/ProtectAllAi.java
Normal file
@@ -0,0 +1,48 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
456
forge-game/src/main/java/forge/ai/ability/PumpAi.java
Normal file
456
forge-game/src/main/java/forge/ai/ability/PumpAi.java
Normal file
@@ -0,0 +1,456 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
597
forge-game/src/main/java/forge/ai/ability/PumpAiBase.java
Normal file
597
forge-game/src/main/java/forge/ai/ability/PumpAiBase.java
Normal file
@@ -0,0 +1,597 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
167
forge-game/src/main/java/forge/ai/ability/PumpAllAi.java
Normal file
167
forge-game/src/main/java/forge/ai/ability/PumpAllAi.java
Normal file
@@ -0,0 +1,167 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
244
forge-game/src/main/java/forge/ai/ability/RegenerateAi.java
Normal file
244
forge-game/src/main/java/forge/ai/ability/RegenerateAi.java
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
47
forge-game/src/main/java/forge/ai/ability/RepeatAi.java
Normal file
47
forge-game/src/main/java/forge/ai/ability/RepeatAi.java
Normal file
@@ -0,0 +1,47 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
103
forge-game/src/main/java/forge/ai/ability/RepeatEachAi.java
Normal file
103
forge-game/src/main/java/forge/ai/ability/RepeatEachAi.java
Normal file
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user