- AI will now predict battle cry boni.

This commit is contained in:
jendave
2011-08-06 22:13:37 +00:00
parent 1877ff7e62
commit cae9ebdef2
5 changed files with 123 additions and 73 deletions

View File

@@ -601,7 +601,7 @@ public class CombatUtil {
{
int damage = attacker.getNetCombatDamage();
int sum = 0;
damage += predictPowerBonusOfAttacker(attacker,null);
damage += predictPowerBonusOfAttacker(attacker,null,null);
if (!attacker.hasKeyword("Infect")) {
sum = attacked.predictDamage(damage, attacker, true);
if (attacker.hasKeyword("Double Strike")) sum += attacked.predictDamage(damage, attacker, true);
@@ -614,7 +614,7 @@ public class CombatUtil {
{
int damage = attacker.getNetCombatDamage();
int poison = 0;
damage += predictPowerBonusOfAttacker(attacker,null);
damage += predictPowerBonusOfAttacker(attacker, null, null);
if (attacker.hasKeyword("Infect")) {
poison += attacked.predictDamage(damage, attacker, true);
if (attacker.hasKeyword("Double Strike")) poison += attacked.predictDamage(damage, attacker, true);
@@ -761,7 +761,7 @@ public class CombatUtil {
// This calculates the amount of damage a blocker in a blockgang can take from the attacker (for trampling attackers)
public static int shieldDamage(Card attacker, Card defender) {
if (!canDestroyBlocker(defender,attacker)) return 100;
if (!canDestroyBlocker(defender,attacker, null)) return 100;
int flankingMagnitude = 0;
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
@@ -796,7 +796,7 @@ public class CombatUtil {
CardList blockers = AllZone.Combat.getBlockers(attacker);
for (Card defender:blockers) {
if(CombatUtil.canDestroyAttacker(attacker, defender) &&
if(CombatUtil.canDestroyAttacker(attacker, defender, AllZone.Combat) &&
!(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")))
return true;
}
@@ -805,20 +805,24 @@ public class CombatUtil {
}
//Will this trigger trigger?
public static boolean combatTriggerWillTrigger(Card attacker, Card defender, Trigger trigger) {
public static boolean combatTriggerWillTrigger(Card attacker, Card defender, Trigger trigger, Combat combat) {
HashMap<String,String> trigParams = trigger.getMapParams();
boolean willTrigger = false;
Card source = trigger.getHostCard();
if (combat == null) combat = AllZone.Combat;
if (!trigger.zonesCheck()) return false;
if (!trigger.requirementsCheck()) return false;
if (trigParams.get("Mode").equals("Attacks")) {
willTrigger = true;
if (attacker.isAttacking()) return false; //The trigger should have triggered already
if(trigParams.containsKey("ValidCard"))
if(!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","), source))
if(attacker.isAttacking()) return false; //The trigger should have triggered already
if(trigParams.containsKey("ValidCard")) {
if(!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","), source)
&& !(combat.isAttacking(source) &&
trigger.matchesValid(source, trigParams.get("ValidCard").split(","), source)))
return false;
}
}
// defender == null means unblocked
@@ -876,14 +880,14 @@ public class CombatUtil {
HashMap<String,String> trigParams = trigger.getMapParams();
Card source = trigger.getHostCard();
if(combatTriggerWillTrigger(attacker, defender, trigger) && trigParams.containsKey("Execute")) {
if(combatTriggerWillTrigger(attacker, defender, trigger, null) && trigParams.containsKey("Execute")) {
String ability = source.getSVar(trigParams.get("Execute"));
AbilityFactory AF = new AbilityFactory();
HashMap<String,String> abilityParams = AF.getMapParams(ability, source);
if (abilityParams.containsKey("AB")) {
if (abilityParams.get("AB").equals("Pump"))
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt"))
if (AbilityFactory.getDefinedCards(source, trigParams.get("Defined"), null).contains(defender))
if (AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null).contains(defender))
if (abilityParams.containsKey("NumAtt")){
String att = abilityParams.get("NumAtt");
if (att.startsWith("+"))
@@ -911,14 +915,14 @@ public class CombatUtil {
HashMap<String,String> trigParams = trigger.getMapParams();
Card source = trigger.getHostCard();
if(combatTriggerWillTrigger(attacker, defender, trigger) && trigParams.containsKey("Execute")) {
if(combatTriggerWillTrigger(attacker, defender, trigger, null) && trigParams.containsKey("Execute")) {
String ability = source.getSVar(trigParams.get("Execute"));
AbilityFactory AF = new AbilityFactory();
HashMap<String,String> abilityParams = AF.getMapParams(ability, source);
if (abilityParams.containsKey("AB")) {
if (abilityParams.get("AB").equals("Pump"))
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt"))
if (AbilityFactory.getDefinedCards(source, trigParams.get("Defined"), null).contains(defender))
if (AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null).contains(defender))
if (abilityParams.containsKey("NumDef")) {
String def = abilityParams.get("NumDef");
if (def.startsWith("+"))
@@ -932,7 +936,7 @@ public class CombatUtil {
}
//Predict the Power bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities)
public static int predictPowerBonusOfAttacker(Card attacker, Card defender) {
public static int predictPowerBonusOfAttacker(Card attacker, Card defender, Combat combat) {
int power = 0;
power += attacker.getKeywordMagnitude("Bushido");
@@ -952,26 +956,38 @@ public class CombatUtil {
HashMap<String,String> trigParams = trigger.getMapParams();
Card source = trigger.getHostCard();
if(combatTriggerWillTrigger(attacker, defender, trigger) && trigParams.containsKey("Execute")) {
if(combatTriggerWillTrigger(attacker, defender, trigger, combat) && trigParams.containsKey("Execute")) {
String ability = source.getSVar(trigParams.get("Execute"));
AbilityFactory AF = new AbilityFactory();
HashMap<String,String> abilityParams = AF.getMapParams(ability, source);
if (abilityParams.containsKey("AB")) {
if (abilityParams.containsKey("AB")) {
boolean isValid = false;
//Pump
if (abilityParams.get("AB").equals("Pump"))
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt"))
if (AbilityFactory.getDefinedCards(source, trigParams.get("Defined"), null).contains(attacker))
if (abilityParams.containsKey("NumAtt")){
String att = abilityParams.get("NumAtt");
if (att.startsWith("+"))
att = att.substring(1);
try {
power += Integer.parseInt(att);
}
catch(NumberFormatException nfe) {
//can't parse the number (X for example)
power += 0;
}
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt")) //not targeted
if (AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null).contains(attacker))
isValid = true;
//PumpAll
if (abilityParams.get("AB").equals("PumpAll") && abilityParams.containsKey("ValidCards"))
if (attacker.isValidCard(abilityParams.get("ValidCards").split(","), source.getController(), source)
|| attacker.isValidCard(abilityParams.get("ValidCards").replace("attacking+", "").split(",")
, source.getController(), source))
isValid = true;
if (abilityParams.containsKey("NumAtt") && isValid){
String att = abilityParams.get("NumAtt");
if (att.startsWith("+"))
att = att.substring(1);
try {
power += Integer.parseInt(att);
}
catch(NumberFormatException nfe) {
//can't parse the number (X for example)
power += 0;
}
}
}
}
}
@@ -979,7 +995,7 @@ public class CombatUtil {
}
//Predict the Toughness bonus of the blocker if blocking the attacker (Flanking, Bushido and other triggered abilities)
public static int predictToughnessBonusOfAttacker(Card attacker, Card defender) {
public static int predictToughnessBonusOfAttacker(Card attacker, Card defender, Combat combat) {
int toughness = 0;
toughness += attacker.getKeywordMagnitude("Bushido");
@@ -990,15 +1006,27 @@ public class CombatUtil {
HashMap<String,String> trigParams = trigger.getMapParams();
Card source = trigger.getHostCard();
if(combatTriggerWillTrigger(attacker, defender, trigger) && trigParams.containsKey("Execute")) {
if(combatTriggerWillTrigger(attacker, defender, trigger, combat) && trigParams.containsKey("Execute")) {
String ability = source.getSVar(trigParams.get("Execute"));
AbilityFactory AF = new AbilityFactory();
HashMap<String,String> abilityParams = AF.getMapParams(ability, source);
if (abilityParams.containsKey("AB")) {
boolean isValid = false;
//Pump
if (abilityParams.get("AB").equals("Pump"))
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt"))
if (AbilityFactory.getDefinedCards(source, trigParams.get("Defined"), null).contains(attacker))
if (abilityParams.containsKey("NumDef")) {
if (!abilityParams.containsKey("ValidTgts") && !abilityParams.containsKey("Tgt")) //not targeted
if (AbilityFactory.getDefinedCards(source, abilityParams.get("Defined"), null).contains(attacker))
isValid = true;
//PumpAll
if (abilityParams.get("AB").equals("PumpAll") && abilityParams.containsKey("ValidCards"))
if (attacker.isValidCard(abilityParams.get("ValidCards").split(","), source.getController(), source)
|| attacker.isValidCard(abilityParams.get("ValidCards").replace("attacking+", "").split(",")
, source.getController(), source))
isValid = true;
if (abilityParams.containsKey("NumDef") && isValid){
String def = abilityParams.get("NumDef");
if (def.startsWith("+"))
def = def.substring(1);
@@ -1017,7 +1045,7 @@ public class CombatUtil {
}
//can the blocker destroy the attacker?
public static boolean canDestroyAttacker(Card attacker, Card defender) {
public static boolean canDestroyAttacker(Card attacker, Card defender, Combat combat) {
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return false;
@@ -1039,10 +1067,10 @@ public class CombatUtil {
//int attBushidoMagnitude = attacker.getKeywordMagnitude("Bushido");
int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender);
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender);
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat);
if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) {
defenderDamage = defender.getNetDefense() + predictToughnessBonusOfBlocker(attacker, defender);
attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender);
attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender, combat);
}
// consider Damage Prevention/Replacement
@@ -1050,7 +1078,7 @@ public class CombatUtil {
attackerDamage = defender.predictDamage(attackerDamage, attacker, true);
int defenderLife = defender.getKillDamage() + predictToughnessBonusOfBlocker(attacker, defender);
int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender);
int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender, combat);
if(defender.getKeyword().contains("Double Strike") ) {
if(defender.getKeyword().contains("Deathtouch") && defenderDamage > 0) return true;
@@ -1088,14 +1116,14 @@ public class CombatUtil {
public static boolean blockerWouldBeDestroyed(Card blocker) {
Card attacker = AllZone.Combat.getAttackerBlockedBy(blocker);
if(canDestroyBlocker(blocker, attacker) &&
if(canDestroyBlocker(blocker, attacker, AllZone.Combat) &&
!(attacker.getKeyword().contains("Wither") || attacker.getKeyword().contains("Infect")))
return true;
return false;
}
//can the attacker destroy this blocker?
public static boolean canDestroyBlocker(Card defender, Card attacker) {
public static boolean canDestroyBlocker(Card defender, Card attacker, Combat combat) {
int flankingMagnitude = 0;
if(attacker.getKeyword().contains("Flanking") && !defender.getKeyword().contains("Flanking")) {
@@ -1112,10 +1140,10 @@ public class CombatUtil {
if(attacker.getName().equals("Sylvan Basilisk") && !defender.getKeyword().contains("Indestructible")) return true;
int defenderDamage = defender.getNetAttack() + predictPowerBonusOfBlocker(attacker, defender);
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender);
int attackerDamage = attacker.getNetAttack() + predictPowerBonusOfAttacker(attacker, defender, combat);
if (AllZoneUtil.isCardInPlay("Doran, the Siege Tower")) {
defenderDamage = defender.getNetDefense() + predictToughnessBonusOfBlocker(attacker, defender);
attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender);
attackerDamage = attacker.getNetDefense() + predictToughnessBonusOfAttacker(attacker, defender, combat);
}
// consider Damage Prevention/Replacement
@@ -1123,7 +1151,7 @@ public class CombatUtil {
attackerDamage = defender.predictDamage(attackerDamage, attacker, true);
int defenderLife = defender.getKillDamage() + predictToughnessBonusOfBlocker(attacker, defender);
int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender);
int attackerLife = attacker.getKillDamage() + predictToughnessBonusOfAttacker(attacker, defender, combat);
if(attacker.getKeyword().contains("Double Strike") ) {
if(attacker.getKeyword().contains("Deathtouch") && attackerDamage > 0) return true;

View File

@@ -1,9 +1,11 @@
package forge;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import forge.card.cardFactory.CardFactoryUtil;
import forge.card.trigger.Trigger;
//doesHumanAttackAndWin() uses the global variable AllZone.ComputerPlayer
public class ComputerUtil_Attack2 {
@@ -41,7 +43,29 @@ public class ComputerUtil_Attack2 {
blockers = getPossibleBlockers(possibleBlockers, attackers);
this.blockerLife = blockerLife;
}//constructor
public CardList sortAttackers(CardList in)
{
CardList list = new CardList();
//Cards with triggers should come first (for Battle Cry)
for(Card attacker:in) {
ArrayList<Trigger> registeredTriggers = AllZone.TriggerHandler.getRegisteredTriggers();
for(Trigger trigger : registeredTriggers)
{
HashMap<String,String> trigParams = trigger.getMapParams();
if (trigParams.get("Mode").equals("Attacks") && trigger.getHostCard().equals(attacker))
list.add(attacker);
}
}
for(Card attacker:in) {
if(!list.contains(attacker)) list.add(attacker);
}
return list;
}//sortAttackers()
public CardList getPossibleAttackers(CardList in)
{
CardList list = new CardList(in.toArray());
@@ -358,7 +382,8 @@ public class ComputerUtil_Attack2 {
aiAggression = 5; // attack at all costs
}else if((playerLifeToDamageRatio < 2 && ratioDiff >= 0) || ratioDiff > 3 || (ratioDiff > 0 && outNumber > 0)){
aiAggression = 3; // attack expecting to kill creatures or damage player.
}else if(ratioDiff >= 0 || ratioDiff + outNumber >= -1){ // at 0 ratio expect to potentially gain an advantage by attacking first
}else if(ratioDiff >= 0 || ratioDiff + outNumber >= -1){
// at 0 ratio expect to potentially gain an advantage by attacking first
// if the ai has a slight advantage
// or the ai has a significant advantage numerically but only a slight disadvantage damage/life
aiAggression = 2; // attack expecting to destroy creatures/be unblockable
@@ -412,15 +437,12 @@ public class ComputerUtil_Attack2 {
else
{
System.out.println("Normal attack");
//so the biggest creature will usually attack
//I think this works, not sure, may have to change it
//sortNonFlyingFirst has to be done first, because it reverses everything
CardListUtil.sortNonFlyingFirst(attackersLeft);
CardListUtil.sortAttackLowFirst(attackersLeft);
attackersLeft = notNeededAsBlockers(attackersLeft, combat);
System.out.println(attackersLeft.size());
System.out.println(attackersLeft.size());
attackersLeft = sortAttackers(attackersLeft);
for(int i = 0; i < attackersLeft.size(); i++)
{
Card attacker = attackersLeft.get(i);
@@ -428,7 +450,7 @@ public class ComputerUtil_Attack2 {
if (!attacker.hasFirstStrike() && !attacker.hasDoubleStrike())
totalFirstStrikeBlockPower = CombatUtil.getTotalFirstStrikeBlockPower(attacker, AllZone.HumanPlayer);
if ( shouldAttack(attacker,blockers, combat) && (totalFirstStrikeBlockPower < attacker.getKillDamage() || aiAggression == 5)
if (shouldAttack(attacker,blockers,combat) && (totalFirstStrikeBlockPower < attacker.getKillDamage() || aiAggression == 5)
&& CombatUtil.canAttack(attacker, combat))
combat.addAttacker(attacker);
}
@@ -474,7 +496,7 @@ public class ComputerUtil_Attack2 {
for (Card defender:defenders) {
if(CombatUtil.canBlock(attacker, defender)){ //, combat )) {
canBeBlocked = true;
if(CombatUtil.canDestroyAttacker(attacker, defender)) {
if(CombatUtil.canDestroyAttacker(attacker, defender, combat)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower value. We don't want to attack only to lose value
if(CardFactoryUtil.evaluateCreature(defender) <= CardFactoryUtil.evaluateCreature(attacker)){
@@ -482,7 +504,7 @@ public class ComputerUtil_Attack2 {
}
}
// see if this attacking creature can destroy this defender, if not record that it can't kill everything
if(!CombatUtil.canDestroyBlocker(defender, attacker)){
if(!CombatUtil.canDestroyBlocker(defender, attacker, combat)){
canKillAll = false;
if(defender.getKeyword().contains("Wither") || defender.getKeyword().contains("Infect")){
canKillAllDangerous = false; // there is a dangerous creature that can survive an attack from this creature

View File

@@ -28,22 +28,22 @@ public class ComputerUtil_Block2
}
//finds blockers that won't be destroyed
private static CardList getSafeBlockers(Card attacker, CardList blockersLeft) {
private static CardList getSafeBlockers(Card attacker, CardList blockersLeft, Combat combat) {
CardList blockers = new CardList();
for(Card b : blockersLeft) {
if(!CombatUtil.canDestroyBlocker(b,attacker)) blockers.add(b);
if(!CombatUtil.canDestroyBlocker(b,attacker, combat)) blockers.add(b);
}
return blockers;
}
//finds blockers that destroy the attacker
private static CardList getKillingBlockers(Card attacker, CardList blockersLeft) {
private static CardList getKillingBlockers(Card attacker, CardList blockersLeft, Combat combat) {
CardList blockers = new CardList();
for(Card b : blockersLeft) {
if(CombatUtil.canDestroyAttacker(attacker,b)) blockers.add(b);
if(CombatUtil.canDestroyAttacker(attacker,b,combat)) blockers.add(b);
}
return blockers;
@@ -102,12 +102,12 @@ public class ComputerUtil_Block2
CardList blockers = getPossibleBlockers(attacker, blockersLeft, combat);
CardList safeBlockers = getSafeBlockers(attacker, blockers);
CardList safeBlockers = getSafeBlockers(attacker, blockers, combat);
CardList killingBlockers = new CardList();
if(safeBlockers.size() > 0) {
// 1.Blockers that can destroy the attacker but won't get destroyed
killingBlockers = getKillingBlockers(attacker, safeBlockers);
killingBlockers = getKillingBlockers(attacker, safeBlockers, combat);
if(killingBlockers.size() > 0) blocker = CardFactoryUtil.AI_getWorstCreature(killingBlockers);
// 2.Blockers that won't get destroyed
@@ -117,7 +117,7 @@ public class ComputerUtil_Block2
}
} // no safe blockers
else {
killingBlockers = getKillingBlockers(attacker, blockers);
killingBlockers = getKillingBlockers(attacker, blockers, combat);
if(killingBlockers.size() > 0) {
// 3.Blockers that can destroy the attacker and are worth less
Card worst = CardFactoryUtil.AI_getWorstCreature(killingBlockers);
@@ -183,7 +183,7 @@ public class ComputerUtil_Block2
for(Card attacker : attackersLeft) {
killingBlockers =
getKillingBlockers(attacker, getPossibleBlockers(attacker, blockersLeft, combat));
getKillingBlockers(attacker, getPossibleBlockers(attacker, blockersLeft, combat), combat);
if(killingBlockers.size() > 0 && CombatUtil.lifeInDanger(combat)) {
Card blocker = CardFactoryUtil.AI_getWorstCreature(killingBlockers);
combat.addBlocker(attacker, blocker);
@@ -254,7 +254,7 @@ public class ComputerUtil_Block2
blockers = getPossibleBlockers(attacker, blockersLeft, combat);
//Try to use safe blockers first
safeBlockers = getSafeBlockers(attacker, blockers);
safeBlockers = getSafeBlockers(attacker, blockers, combat);
for(Card blocker : safeBlockers) {
//Add an additional blocker if the current blockers are not enough and the new one would deal additional damage
if(attacker.getKillDamage() > CombatUtil.totalDamageOfBlockers(attacker,combat.getBlockers(attacker))
@@ -358,8 +358,8 @@ public class ComputerUtil_Block2
if (CombatUtil.lifeInDanger(combat)) 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 (CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersAgainstTrample(combat);
if (!CombatUtil.lifeInDanger(combat)) combat = makeGangBlocks(combat);
if (!CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersToKill(combat);
combat = makeGangBlocks(combat);
combat = reinforceBlockersToKill(combat);
}
//== 3. If the AI life would be in serious danger make an even safer approach ==
@@ -370,9 +370,9 @@ public class ComputerUtil_Block2
if (!CombatUtil.lifeInDanger(combat)) combat = makeGoodBlocks(combat);
//Reinforce blockers blocking attackers with trample if life is still in danger
if (CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersAgainstTrample(combat);
if (!CombatUtil.lifeInDanger(combat)) combat = makeGangBlocks(combat);
combat = makeGangBlocks(combat);
//Support blockers not destroying the attacker with more blockers to try to kill the attacker
if (!CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersToKill(combat);
combat = reinforceBlockersToKill(combat);
}
// assign blockers that have to block

View File

@@ -91,8 +91,8 @@ public class Computer_Race
CardList c = blockList;
for(int i = 0; i < c.size(); i++)
if(CombatUtil.canDestroyAttacker(attacker, c.get(i)) &&
(! CombatUtil.canDestroyBlocker(c.get(i), attacker)))
if(CombatUtil.canDestroyAttacker(attacker, c.get(i), null) &&
(! CombatUtil.canDestroyBlocker(c.get(i), attacker, null)))
return c.get(i);
return null;
@@ -105,7 +105,7 @@ public class Computer_Race
CardList c = blockList;
for(int i = 0; i < c.size(); i++)
if(CombatUtil.canDestroyAttacker(attacker, c.get(i)))
if(CombatUtil.canDestroyAttacker(attacker, c.get(i), null))
return c.get(i);
return null;
@@ -118,7 +118,7 @@ public class Computer_Race
CardList c = blockList;
for(int i = 0; i < c.size(); i++)
if(! CombatUtil.canDestroyBlocker(c.get(i), attacker))
if(! CombatUtil.canDestroyBlocker(c.get(i), attacker, null))
return c.get(i);
return null;

View File

@@ -300,7 +300,7 @@ public class RunTest
Card card = cf.getCard("Sylvan Basilisk", null);
Card card2 = cf.getCard("Exalted Angel", null);
check("121a", !CombatUtil.canDestroyAttacker(card, card2));
check("121a", !CombatUtil.canDestroyAttacker(card, card2, null));
}
{
check("122", CardUtil.getConvertedManaCost("0") == 0);