Merge branch 'simulation' into 'master'

Simulation fixes!

See merge request core-developers/forge!5890
This commit is contained in:
Michael Kamensky
2021-11-29 03:56:52 +00:00
23 changed files with 240 additions and 206 deletions

View File

@@ -358,7 +358,7 @@ public class AiAttackController {
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() { opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetCombatDamage() > 0; return c.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
} }
}); });
for (final Card c : this.myList) { for (final Card c : this.myList) {
@@ -422,7 +422,7 @@ public class AiAttackController {
int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus; int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) { if (finestHour) {
// For Finest Hour, one creature could attack and get the bonus TWICE // For Finest Hour, one creature could attack and get the bonus TWICE
humanBasePower = humanBasePower + humanExaltedBonus; humanBasePower += humanExaltedBonus;
} }
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
: humanBasePower; : humanBasePower;
@@ -470,7 +470,7 @@ public class AiAttackController {
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) { if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
return true; return true;
} }
return ai.canReceiveCounters(CounterEnumType.POISON) && ai.getPoisonCounters() + totalPoison > 9; return ai.getPoisonCounters() + totalPoison > 9 && ai.canReceiveCounters(CounterEnumType.POISON);
} }
private boolean doAssault(final Player ai) { private boolean doAssault(final Player ai) {
@@ -672,9 +672,9 @@ public class AiAttackController {
* *
* @return a {@link forge.game.combat.Combat} object. * @return a {@link forge.game.combat.Combat} object.
*/ */
public final void declareAttackers(final Combat combat) { public final int declareAttackers(final Combat combat) {
if (this.attackers.isEmpty()) { if (this.attackers.isEmpty()) {
return; return aiAggression;
} }
// Aggro options // Aggro options
@@ -686,12 +686,14 @@ public class AiAttackController {
boolean predictEvasion = false; boolean predictEvasion = false;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO); if (!aic.usesSimulation()) {
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE); playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT); chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA); tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE); extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION); tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
}
} }
final boolean bAssault = doAssault(ai); final boolean bAssault = doAssault(ai);
@@ -714,7 +716,7 @@ public class AiAttackController {
if (attackMax == 0) { if (attackMax == 0) {
// can't attack anymore // can't attack anymore
return; return aiAggression;
} }
// Attackers that don't really have a choice // Attackers that don't really have a choice
@@ -751,7 +753,7 @@ public class AiAttackController {
} }
} }
if (attackersLeft.isEmpty()) { if (attackersLeft.isEmpty()) {
return; return aiAggression;
} }
} }
@@ -761,7 +763,7 @@ public class AiAttackController {
} }
// Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily // Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily
if (!doRevengeOfRavensAttackLogic(ai, defender, attackersLeft, numForcedAttackers, attackMax)) { if (!doRevengeOfRavensAttackLogic(ai, defender, attackersLeft, numForcedAttackers, attackMax)) {
return; return aiAggression;
} }
if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else
@@ -771,14 +773,14 @@ public class AiAttackController {
for (Card attacker : attackersLeft) { for (Card attacker : attackersLeft) {
// reached max, breakup // reached max, breakup
if (attackMax != -1 && combat.getAttackers().size() >= attackMax) if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
return; return aiAggression;
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) { if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
// no more creatures to attack // no more creatures to attack
return; return aiAggression;
} }
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures) // Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
@@ -816,7 +818,7 @@ public class AiAttackController {
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) { if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
return; return aiAggression;
} }
} }
} }
@@ -835,7 +837,7 @@ public class AiAttackController {
} }
} }
// no more creatures to attack // no more creatures to attack
return; return aiAggression;
} }
// ******************* // *******************
@@ -877,7 +879,7 @@ public class AiAttackController {
for (final Card pCard : categorizedOppList) { for (final Card pCard : categorizedOppList) {
// if the creature can attack next turn add it to counter attackers list // if the creature can attack next turn add it to counter attackers list
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) { if (pCard.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(pCard)) {
nextTurnAttackers.add(pCard); nextTurnAttackers.add(pCard);
candidateCounterAttackDamage += pCard.getNetCombatDamage(); candidateCounterAttackDamage += pCard.getNetCombatDamage();
humanForces += 1; // player forces they might use to attack humanForces += 1; // player forces they might use to attack
@@ -1108,6 +1110,8 @@ public class AiAttackController {
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} }
} }
return aiAggression;
} }
/** /**
@@ -1133,8 +1137,7 @@ public class AiAttackController {
int numberOfPossibleBlockers = 0; int numberOfPossibleBlockers = 0;
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking? // Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if ((attacker.hasSVar("NonCombatPriority")) if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) {
&& (!attacker.hasKeyword(Keyword.VIGILANCE))) {
// For each level of priority, enemy has to have life as much as the creature's power // For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks. // so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking. // the lower the priroity, the less willing the AI is to use the creature for attacking.
@@ -1159,8 +1162,7 @@ public class AiAttackController {
} }
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// contains only the defender's blockers that can actually block the attacker // contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() { CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
@@ -1318,7 +1320,7 @@ public class AiAttackController {
return false; // don't attack return false; // don't attack
} }
public static List<Card> exertAttackers(List<Card> attackers) { public static List<Card> exertAttackers(List<Card> attackers, int aggression) {
List<Card> exerters = Lists.newArrayList(); List<Card> exerters = Lists.newArrayList();
for (Card c : attackers) { for (Card c : attackers) {
boolean shouldExert = false; boolean shouldExert = false;
@@ -1362,9 +1364,14 @@ public class AiAttackController {
continue; continue;
} }
if (!shouldExert) {
// TODO Improve when the AI wants to use Exert powers
shouldExert = aggression > 3;
}
// A specific AI condition for Exert: if specified on the card, the AI will always // A specific AI condition for Exert: if specified on the card, the AI will always
// exert creatures that meet this condition // exert creatures that meet this condition
if (c.hasSVar("AIExertCondition")) { if (!shouldExert && c.hasSVar("AIExertCondition")) {
if (!c.getSVar("AIExertCondition").isEmpty()) { if (!c.getSVar("AIExertCondition").isEmpty()) {
final String needsToExert = c.getSVar("AIExertCondition"); final String needsToExert = c.getSVar("AIExertCondition");
String sVar = needsToExert.split(" ")[0]; String sVar = needsToExert.split(" ")[0];
@@ -1379,11 +1386,6 @@ public class AiAttackController {
} }
} }
if (!shouldExert && MyRandom.getRandom().nextBoolean()) {
// TODO Improve when the AI wants to use Exert powers
shouldExert = true;
}
if (shouldExert) { if (shouldExert) {
exerters.add(c); exerters.add(c);
} }

View File

@@ -457,7 +457,7 @@ public class AiBlockController {
return false; return false;
} }
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat); final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker) || randomTrade; return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
} }
}); });
if (usableBlockers.size() < 2) { if (usableBlockers.size() < 2) {
@@ -854,7 +854,7 @@ public class AiBlockController {
} }
private void makeChumpBlocksToSavePW(Combat combat) { private void makeChumpBlocksToSavePW(Combat combat) {
if (ComputerUtilCombat.lifeInDanger(ai, combat) || ai.getLife() <= ai.getStartingLife() / 5) { if (ai.getLife() <= ai.getStartingLife() / 5 || ComputerUtilCombat.lifeInDanger(ai, combat)) {
// most likely not worth trying to protect planeswalkers when at threateningly low life or in // most likely not worth trying to protect planeswalkers when at threateningly low life or in
// dangerous combat which threatens lethal or severe damage to face // dangerous combat which threatens lethal or severe damage to face
return; return;
@@ -1145,7 +1145,7 @@ public class AiBlockController {
// check to see if it's possible to defend a Planeswalker under attack with a chump block, // check to see if it's possible to defend a Planeswalker under attack with a chump block,
// unless life is low enough to be more worried about saving preserving the life total // unless life is low enough to be more worried about saving preserving the life total
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ai.getController().isAI()) {
makeChumpBlocksToSavePW(combat); makeChumpBlocksToSavePW(combat);
} }
@@ -1206,7 +1206,7 @@ public class AiBlockController {
final CardCollection result = new CardCollection(); final CardCollection result = new CardCollection();
boolean newBlockerIsAdded = false; boolean newBlockerIsAdded = false;
// The new blocker comes right after this one // The new blocker comes right after this one
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1)); final Card newBlockerRightAfter = newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1);
if (newBlockerRightAfter == null if (newBlockerRightAfter == null
&& damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) { && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
result.add(blocker); result.add(blocker);
@@ -1274,16 +1274,19 @@ public class AiBlockController {
int oppCreatureCount = 0; int oppCreatureCount = 0;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK); // simulation must get same results or it may crash
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS); if (!aic.usesSimulation()) {
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT); enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK); randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK); randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM); minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE); maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL); chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER); maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE);
chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER); maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL);
chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
}
} }
if (!enableRandomTrades) { if (!enableRandomTrades) {

View File

@@ -88,6 +88,7 @@ public class AiController {
private boolean cheatShuffle; private boolean cheatShuffle;
private boolean useSimulation; private boolean useSimulation;
private SpellAbilityPicker simPicker; private SpellAbilityPicker simPicker;
private int lastAttackAggression;
public boolean canCheatShuffle() { public boolean canCheatShuffle() {
return cheatShuffle; return cheatShuffle;
@@ -104,6 +105,10 @@ public class AiController {
this.useSimulation = value; this.useSimulation = value;
} }
public int getAttackAggression() {
return lastAttackAggression;
}
public SpellAbilityPicker getSimulationPicker() { public SpellAbilityPicker getSimulationPicker() {
return simPicker; return simPicker;
} }
@@ -1445,7 +1450,7 @@ public class AiController {
public void declareAttackers(Player attacker, Combat combat) { public void declareAttackers(Player attacker, Combat combat) {
// 12/2/10(sol) the decision making here has moved to getAttackers() // 12/2/10(sol) the decision making here has moved to getAttackers()
AiAttackController aiAtk = new AiAttackController(attacker); AiAttackController aiAtk = new AiAttackController(attacker);
aiAtk.declareAttackers(combat); lastAttackAggression = aiAtk.declareAttackers(combat);
// if invalid: just try an attack declaration that we know to be legal // if invalid: just try an attack declaration that we know to be legal
if (!CombatUtil.validateAttackers(combat)) { if (!CombatUtil.validateAttackers(combat)) {

View File

@@ -1483,6 +1483,7 @@ public class ComputerUtil {
if (sa.getApi() != ApiType.DealDamage) { if (sa.getApi() != ApiType.DealDamage) {
continue; continue;
} }
sa.setActivatingPlayer(ai);
final String numDam = sa.getParam("NumDmg"); final String numDam = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), numDam, sa); int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), numDam, sa);
if (dmg <= damage) { if (dmg <= damage) {

View File

@@ -109,7 +109,7 @@ public class ComputerUtilCard {
// get biggest Artifact // get biggest Artifact
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc); return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
} }
/** /**
* Returns the best Planeswalker from a given list * Returns the best Planeswalker from a given list
* @param list list of cards to evaluate * @param list list of cards to evaluate
@@ -211,7 +211,7 @@ public class ComputerUtilCard {
} }
}); });
} }
// get biggest Enchantment // get biggest Enchantment
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc); return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
} }
@@ -229,7 +229,7 @@ public class ComputerUtilCard {
if (land.isEmpty()) { if (land.isEmpty()) {
return null; return null;
} }
// prefer to target non basic lands // prefer to target non basic lands
final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS)); final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
@@ -237,7 +237,7 @@ public class ComputerUtilCard {
// TODO - Rank non basics? // TODO - Rank non basics?
return Aggregates.random(nbLand); return Aggregates.random(nbLand);
} }
// if no non-basic lands, target the least represented basic land type // if no non-basic lands, target the least represented basic land type
String sminBL = ""; String sminBL = "";
int iminBL = Integer.MAX_VALUE; int iminBL = Integer.MAX_VALUE;
@@ -257,13 +257,14 @@ public class ComputerUtilCard {
} }
return land.get(0); return land.get(0);
} }
final List<Card> bLand = CardLists.getType(land, sminBL); final List<Card> bLand = CardLists.getType(land, sminBL);
for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) { for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) {
return ut; return ut;
} }
// TODO potentially risky if simulation mode currently able to reach this from triggers
return Aggregates.random(bLand); // random tapped land of least represented type return Aggregates.random(bLand); // random tapped land of least represented type
} }
@@ -348,7 +349,7 @@ public class ComputerUtilCard {
if (Iterables.isEmpty(all)) { if (Iterables.isEmpty(all)) {
return null; return null;
} }
// get cheapest card: // get cheapest card:
Card cheapest = null; Card cheapest = null;
@@ -357,7 +358,7 @@ public class ComputerUtilCard {
cheapest = c; cheapest = c;
} }
} }
return cheapest; return cheapest;
} }
@@ -475,12 +476,12 @@ public class ComputerUtilCard {
if (Iterables.isEmpty(list)) { if (Iterables.isEmpty(list)) {
return null; return null;
} }
final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS); final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS);
if (biasEnch && hasEnchantmants) { if (biasEnch && hasEnchantmants) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false); return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false);
} }
final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS); final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS);
if (biasArt && hasArtifacts) { if (biasArt && hasArtifacts) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false); return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false);
@@ -489,17 +490,17 @@ public class ComputerUtilCard {
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) { if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS)); return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
} }
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES); final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
if (biasCreature && hasCreatures) { if (biasCreature && hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES)); return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
} }
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS); List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (lands.size() > 6) { if (lands.size() > 6) {
return getWorstLand(lands); return getWorstLand(lands);
} }
if (hasEnchantmants || hasArtifacts) { if (hasEnchantmants || hasArtifacts) {
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() { final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
@Override @Override
@@ -509,11 +510,11 @@ public class ComputerUtilCard {
})); }));
return getCheapestPermanentAI(ae, null, false); return getCheapestPermanentAI(ae, null, false);
} }
if (hasCreatures) { if (hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES)); return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
} }
// Planeswalkers fall through to here, lands will fall through if there aren't very many // Planeswalkers fall through to here, lands will fall through if there aren't very many
return getCheapestPermanentAI(list, null, false); return getCheapestPermanentAI(list, null, false);
} }
@@ -644,7 +645,7 @@ public class ComputerUtilCard {
} }
return false; return false;
} }
/** /**
* Create a mock combat where ai is being attacked and returns the list of likely blockers. * Create a mock combat where ai is being attacked and returns the list of likely blockers.
* @param ai blocking player * @param ai blocking player
@@ -675,7 +676,7 @@ public class ComputerUtilCard {
} }
return combat.getAllBlockers(); return combat.getAllBlockers();
} }
/** /**
* Decide if a creature is going to be used as a blocker. * Decide if a creature is going to be used as a blocker.
* @param ai controller of creature * @param ai controller of creature
@@ -701,7 +702,7 @@ public class ComputerUtilCard {
aiBlk.assignBlockersGivenAttackers(combat, attackers); aiBlk.assignBlockersGivenAttackers(combat, attackers);
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat); return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
} }
/** /**
* getMostExpensivePermanentAI. * getMostExpensivePermanentAI.
* *
@@ -711,24 +712,24 @@ public class ComputerUtilCard {
*/ */
public static Card getMostExpensivePermanentAI(final Iterable<Card> all) { public static Card getMostExpensivePermanentAI(final Iterable<Card> all) {
Card biggest = null; Card biggest = null;
int bigCMC = -1; int bigCMC = -1;
for (final Card card : all) { for (final Card card : all) {
// TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield) // TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield)
int curCMC = card.getCMC(); int curCMC = card.getCMC();
// Add all cost of all auras with the same controller // Add all cost of all auras with the same controller
if (card.isEnchanted()) { if (card.isEnchanted()) {
final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController()); final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size(); curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
} }
if (curCMC >= bigCMC) { if (curCMC >= bigCMC) {
bigCMC = curCMC; bigCMC = curCMC;
biggest = card; biggest = card;
} }
} }
return biggest; return biggest;
} }
@@ -736,21 +737,21 @@ public class ComputerUtilCard {
if (list.size() == 0) { if (list.size() == 0) {
return ""; return "";
} }
final Map<String, Integer> map = Maps.newHashMap(); final Map<String, Integer> map = Maps.newHashMap();
for (final Card c : list) { for (final Card c : list) {
final String name = c.getName(); final String name = c.getName();
Integer currentCnt = map.get(name); Integer currentCnt = map.get(name);
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt)); map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
} // for }
int max = 0; int max = 0;
String maxName = ""; String maxName = "";
for (final Entry<String, Integer> entry : map.entrySet()) { for (final Entry<String, Integer> entry : map.entrySet()) {
final String type = entry.getKey(); final String type = entry.getKey();
if (max < entry.getValue()) { if (max < entry.getValue()) {
max = entry.getValue(); max = entry.getValue();
maxName = type; maxName = type;
@@ -857,7 +858,7 @@ public class ComputerUtilCard {
int max = 0; int max = 0;
String maxType = ""; String maxType = "";
for (final Entry<String, Integer> entry : typesInDeck.entrySet()) { for (final Entry<String, Integer> entry : typesInDeck.entrySet()) {
final String type = entry.getKey(); final String type = entry.getKey();
@@ -866,7 +867,7 @@ public class ComputerUtilCard {
maxType = type; maxType = type;
} }
} }
return maxType; return maxType;
} }
@@ -918,7 +919,7 @@ public class ComputerUtilCard {
return o2.getValue() - o1.getValue(); return o2.getValue() - o1.getValue();
} }
}); });
// will this part be once dropped? // will this part be once dropped?
List<String> result = new ArrayList<>(cntColors); List<String> result = new ArrayList<>(cntColors);
for (Pair<Byte, Integer> idx : map) { // fetch color names in the same order for (Pair<Byte, Integer> idx : map) { // fetch color names in the same order
@@ -948,7 +949,7 @@ public class ComputerUtilCard {
Player opp = ai.getWeakestOpponent(); Player opp = ai.getWeakestOpponent();
if (sa.hasParam("AILogic")) { if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic"); final String logic = sa.getParam("AILogic");
if (logic.equals("MostProminentInHumanDeck")) { if (logic.equals("MostProminentInHumanDeck")) {
chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices)); chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
} }
@@ -1047,7 +1048,7 @@ public class ComputerUtilCard {
} }
return chosen; return chosen;
} }
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) { public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
final Player ai = sa.getActivatingPlayer(); final Player ai = sa.getActivatingPlayer();
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
@@ -1058,16 +1059,16 @@ public class ComputerUtilCard {
final int costRemoval = sa.getHostCard().getCMC(); final int costRemoval = sa.getHostCard().getCMC();
final int costTarget = c.getCMC(); final int costTarget = c.getCMC();
if (!sa.isSpell()) { if (!sa.isSpell()) {
return true; return true;
} }
//Check for cards that profit from spells - for example Prowess or Threshold //Check for cards that profit from spells - for example Prowess or Threshold
if (phaseType == PhaseType.MAIN1 && ComputerUtil.castSpellInMain1(ai, sa)) { if (phaseType == PhaseType.MAIN1 && ComputerUtil.castSpellInMain1(ai, sa)) {
return true; return true;
} }
//interrupt 1: Check whether a possible blocker will be killed for the AI to make a bigger attack //interrupt 1: Check whether a possible blocker will be killed for the AI to make a bigger attack
if (ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai) && c.isCreature()) { if (ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai) && c.isCreature()) {
AiAttackController aiAtk = new AiAttackController(ai); AiAttackController aiAtk = new AiAttackController(ai);
@@ -1107,7 +1108,7 @@ public class ComputerUtilCard {
} }
} }
} }
// interrupt 3: two for one = good // interrupt 3: two for one = good
if (c.isEnchanted()) { if (c.isEnchanted()) {
boolean myEnchants = false; boolean myEnchants = false;
@@ -1121,7 +1122,7 @@ public class ComputerUtilCard {
return true; //card advantage > tempo return true; //card advantage > tempo
} }
} }
//interrupt 4: opponent pumping target (only works if the pump target is the chosen best target to begin with) //interrupt 4: opponent pumping target (only works if the pump target is the chosen best target to begin with)
final MagicStack stack = game.getStack(); final MagicStack stack = game.getStack();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
@@ -1130,7 +1131,7 @@ public class ComputerUtilCard {
return true; return true;
} }
} }
//burn and curse spells //burn and curse spells
float valueBurn = 0; float valueBurn = 0;
if (dmg > 0) { if (dmg > 0) {
@@ -1146,7 +1147,7 @@ public class ComputerUtilCard {
return true; return true;
} }
} }
//evaluate tempo gain //evaluate tempo gain
float valueTempo = Math.max(0.1f * costTarget / costRemoval, valueBurn); float valueTempo = Math.max(0.1f * costTarget / costRemoval, valueBurn);
if (c.isEquipped()) { if (c.isEquipped()) {
@@ -1177,7 +1178,7 @@ public class ComputerUtilCard {
if (valueTempo >= 0.8 && ph.getPhase().isBefore(PhaseType.COMBAT_END)) { if (valueTempo >= 0.8 && ph.getPhase().isBefore(PhaseType.COMBAT_END)) {
return true; return true;
} }
//evaluate threat of targeted card //evaluate threat of targeted card
float threat = 0; float threat = 0;
if (c.isCreature()) { if (c.isCreature()) {
@@ -1260,7 +1261,7 @@ public class ComputerUtilCard {
if (!c.getManaAbilities().isEmpty()) { if (!c.getManaAbilities().isEmpty()) {
threat += 0.5f * costTarget / opp.getLandsInPlay().size(); //set back opponent's mana threat += 0.5f * costTarget / opp.getLandsInPlay().size(); //set back opponent's mana
} }
final float valueNow = Math.max(valueTempo, threat); final float valueNow = Math.max(valueTempo, threat);
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
return false; return false;
@@ -1296,13 +1297,17 @@ public class ComputerUtilCard {
boolean combatTrick = false; boolean combatTrick = false;
boolean holdCombatTricks = false; boolean holdCombatTricks = false;
int chanceToHoldCombatTricks = -1; int chanceToHoldCombatTricks = -1;
boolean simAI = false;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK); simAI = aic.usesSimulation();
chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK); if (!simAI) {
holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
}
} }
if (!c.canBeTargetedBy(sa)) { if (!c.canBeTargetedBy(sa)) {
return false; return false;
} }
@@ -1349,7 +1354,7 @@ public class ComputerUtilCard {
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords); Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
List<Card> oppCreatures = opp.getCreaturesInPlay(); List<Card> oppCreatures = opp.getCreaturesInPlay();
float chance = 0; float chance = 0;
//create and buff attackers //create and buff attackers
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai) && opp.getLife() > 0) { if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai) && opp.getLife() > 0) {
//1. become attacker for whatever reason //1. become attacker for whatever reason
@@ -1385,7 +1390,7 @@ public class ComputerUtilCard {
} }
} }
} }
//2. grant haste //2. grant haste
if (keywords.contains("Haste") && c.hasSickness() && !c.isTapped()) { if (keywords.contains("Haste") && c.hasSickness() && !c.isTapped()) {
double nonCombatChance = 0.0f; double nonCombatChance = 0.0f;
@@ -1405,7 +1410,7 @@ public class ComputerUtilCard {
} }
chance += nonCombatChance + combatChance; chance += nonCombatChance + combatChance;
} }
//3. grant evasive //3. grant evasive
if (Iterables.any(oppCreatures, CardPredicates.possibleBlockers(c))) { if (Iterables.any(oppCreatures, CardPredicates.possibleBlockers(c))) {
if (!Iterables.any(oppCreatures, CardPredicates.possibleBlockers(pumped)) if (!Iterables.any(oppCreatures, CardPredicates.possibleBlockers(pumped))
@@ -1414,7 +1419,7 @@ public class ComputerUtilCard {
} }
} }
} }
//combat trickery //combat trickery
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
//clunky code because ComputerUtilCombat.combatantWouldBeDestroyed() does not work for this sort of artificial combat //clunky code because ComputerUtilCombat.combatantWouldBeDestroyed() does not work for this sort of artificial combat
@@ -1444,7 +1449,7 @@ public class ComputerUtilCard {
pumpedWillDie = true; pumpedWillDie = true;
} }
} }
//1. save combatant //1. save combatant
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie
&& !c.hasKeyword(Keyword.INDESTRUCTIBLE)) { && !c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
@@ -1452,7 +1457,7 @@ public class ComputerUtilCard {
// does not check for Indestructible when computing lethal damage // does not check for Indestructible when computing lethal damage
return true; return true;
} }
//2. kill combatant //2. kill combatant
boolean survivor = false; boolean survivor = false;
for (Card o : opposing) { for (Card o : opposing) {
@@ -1477,7 +1482,7 @@ public class ComputerUtilCard {
} }
} }
} }
//3. buff attacker //3. buff attacker
if (combat.isAttacking(c) && opp.getLife() > 0) { if (combat.isAttacking(c) && opp.getLife() > 0) {
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true); int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
@@ -1551,7 +1556,7 @@ public class ComputerUtilCard {
} }
chance += value; chance += value;
} }
//4. lifelink //4. lifelink
if (ai.canGainLife() && ai.getLife() > 0 && !c.hasKeyword(Keyword.LIFELINK) && keywords.contains("Lifelink") if (ai.canGainLife() && ai.getLife() > 0 && !c.hasKeyword(Keyword.LIFELINK) && keywords.contains("Lifelink")
&& (combat.isAttacking(c) || combat.isBlocking(c))) { && (combat.isAttacking(c) || combat.isBlocking(c))) {
@@ -1560,7 +1565,7 @@ public class ComputerUtilCard {
//and trample damage (if any) //and trample damage (if any)
chance += 1.0f * dmg / ai.getLife(); chance += 1.0f * dmg / ai.getLife();
} }
//5. if the life of the computer is in danger, try to pump blockers blocking Tramplers //5. if the life of the computer is in danger, try to pump blockers blocking Tramplers
if (combat.isBlocking(c) && toughness > 0 ) { if (combat.isBlocking(c) && toughness > 0 ) {
List<Card> blockedBy = combat.getAttackersBlockedBy(c); List<Card> blockedBy = combat.getAttackersBlockedBy(c);
@@ -1629,7 +1634,7 @@ public class ComputerUtilCard {
} }
} }
return MyRandom.getRandom().nextFloat() < chance; return simAI || MyRandom.getRandom().nextFloat() < chance;
} }
/** /**
@@ -1710,7 +1715,7 @@ public class ComputerUtilCard {
applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
return pumped; return pumped;
} }
/** /**
* Applies static continuous Power/Toughness effects to a (virtual) creature. * Applies static continuous Power/Toughness effects to a (virtual) creature.
* @param game game instance to work with * @param game game instance to work with
@@ -1757,7 +1762,7 @@ public class ComputerUtilCard {
} }
} }
} }
/** /**
* Evaluate if the ability can save a target against removal * Evaluate if the ability can save a target against removal
* @param ai casting player * @param ai casting player

View File

@@ -160,7 +160,7 @@ public class ComputerUtilCombat {
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return CombatUtil.canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike()); return (c.hasFirstStrike() || c.hasDoubleStrike()) && CombatUtil.canBlock(att, c);
} }
}); });
@@ -324,12 +324,12 @@ public class ComputerUtilCombat {
for (final Card attacker : attackers) { for (final Card attacker : attackers) {
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
if ((blockers.size() == 0) if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage " || attacker.hasKeyword("You may have CARDNAME assign its combat damage "
+ "as though it weren't blocked.")) { + "as though it weren't blocked.")) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (getAttack(attacker) > totalShieldDamage(attacker, blockers))) { && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
if (!attacker.hasKeyword(Keyword.INFECT)) { if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers); damage += getAttack(attacker) - totalShieldDamage(attacker, blockers);
} }
@@ -366,12 +366,12 @@ public class ComputerUtilCombat {
for (final Card attacker : attackers) { for (final Card attacker : attackers) {
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
if ((blockers.size() == 0) if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage" || attacker.hasKeyword("You may have CARDNAME assign its combat damage"
+ " as though it weren't blocked.")) { + " as though it weren't blocked.")) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (getAttack(attacker) > totalShieldDamage(attacker, blockers))) { && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers); int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (attacker.hasKeyword(Keyword.INFECT)) { if (attacker.hasKeyword(Keyword.INFECT)) {
poison += trampleDamage; poison += trampleDamage;
@@ -431,7 +431,7 @@ public class ComputerUtilCombat {
// check for creatures that must be blocked // check for creatures that must be blocked
final List<Card> attackers = combat.getAttackersOf(ai); final List<Card> attackers = combat.getAttackersOf(ai);
final List<Card> threateningCommanders = getLifeThreateningCommanders(ai,combat); final List<Card> threateningCommanders = getLifeThreateningCommanders(ai, combat);
for (final Card attacker : attackers) { for (final Card attacker : attackers) {
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
@@ -617,7 +617,7 @@ public class ComputerUtilCombat {
if (flankingMagnitude >= defender.getNetToughness()) { if (flankingMagnitude >= defender.getNetToughness()) {
return 0; return 0;
} }
if (flankingMagnitude >= (defender.getNetToughness() - defender.getDamage()) if (flankingMagnitude >= defender.getNetToughness() - defender.getDamage()
&& !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) { && !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0; return 0;
} }
@@ -1660,7 +1660,7 @@ public class ComputerUtilCombat {
*/ */
public static boolean combatantCantBeDestroyed(Player ai, final Card combatant) { public static boolean combatantCantBeDestroyed(Player ai, final Card combatant) {
// either indestructible or may regenerate // either indestructible or may regenerate
if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, combatant))) { if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, combatant)) {
return true; return true;
} }
@@ -1715,7 +1715,7 @@ public class ComputerUtilCombat {
if (flankingMagnitude >= blocker.getNetToughness()) { if (flankingMagnitude >= blocker.getNetToughness()) {
return false; return false;
} }
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage())) if (flankingMagnitude >= blocker.getNetToughness() - blocker.getDamage()
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) { && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return false; return false;
} }

View File

@@ -33,7 +33,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
} }
int power = getEffectivePower(c); int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c); final int toughness = getEffectiveToughness(c);
// TODO replace with ReplacementEffect checks // TODO replace with ReplacementEffect checks
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.") if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.") || c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
@@ -49,7 +49,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (considerCMC) { if (considerCMC) {
value += addValue(c.getCMC() * 5, "cmc"); value += addValue(c.getCMC() * 5, "cmc");
} }
// Evasion keywords // Evasion keywords
if (c.hasKeyword(Keyword.FLYING)) { if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 10, "flying"); value += addValue(power * 10, "flying");
@@ -76,7 +76,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
value += addValue(power * 3, "block-restrict"); value += addValue(power * 3, "block-restrict");
} }
} }
// Other good keywords // Other good keywords
if (power > 0) { if (power > 0) {
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) { if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
@@ -105,7 +105,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage"); value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict"); value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
} }
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido"); value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking"); value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted"); value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
@@ -127,7 +127,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += addValue(3, "shadow-block"); value += addValue(3, "shadow-block");
} }
// Protection // Protection
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) { if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
value += addValue(70, "darksteel"); value += addValue(70, "darksteel");
@@ -161,11 +161,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) { }/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach"); value -= subValue(toughness * 5, "reverse-reach");
}//*/ }//*/
if (c.hasSVar("DestroyWhenDamaged")) { if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg"); value -= subValue((toughness - 1) * 9, "dies-to-dmg");
} }
if (c.hasKeyword("CARDNAME can't attack or block.")) { if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
} }
@@ -185,7 +185,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
} else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) { } else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid"); value -= subValue(10, "echo-unpaid");
} }
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) { if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg"); value -= subValue(20, "upkeep-dmg");
} }
@@ -198,7 +198,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.getSVar("Targeting").equals("Dies")) { if (c.getSVar("Targeting").equals("Dies")) {
value -= subValue(25, "dies"); value -= subValue(25, "dies");
} }
for (final SpellAbility sa : c.getSpellAbilities()) { for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) { if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa); value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
@@ -207,11 +207,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (!c.getManaAbilities().isEmpty()) { if (!c.getManaAbilities().isEmpty()) {
value += addValue(10, "manadork"); value += addValue(10, "manadork");
} }
if (c.isUntapped()) { if (c.isUntapped()) {
value += addValue(1, "untapped"); value += addValue(1, "untapped");
} }
// paired creatures are more valuable because they grant a bonus to the other creature // paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) { if (c.isPaired()) {
value += addValue(14, "paired"); value += addValue(14, "paired");

View File

@@ -330,7 +330,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public List<Card> exertAttackers(List<Card> attackers) { public List<Card> exertAttackers(List<Card> attackers) {
return AiAttackController.exertAttackers(attackers); return AiAttackController.exertAttackers(attackers, brains.getAttackAggression());
} }
@Override @Override

View File

@@ -916,7 +916,7 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate<Card>() { prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetPower() > 0; return c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
} }
}); });
} }
@@ -1278,7 +1278,7 @@ public class AttachAi extends SpellAbilityAi {
if (!c.isCreature()) { if (!c.isCreature()) {
return true; return true;
} }
return ComputerUtilCombat.canAttackNextTurn(c) && powerBonus + c.getNetPower() > 0; return powerBonus + c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
} }
}); });
} }
@@ -1588,8 +1588,8 @@ public class AttachAi extends SpellAbilityAi {
if (evasive) { if (evasive) {
return card.getNetCombatDamage() + powerBonus > 0 return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card) && canBeBlocked
&& canBeBlocked; && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.equals("Haste")) { } else if (keyword.equals("Haste")) {
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped() return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
&& card.getNetCombatDamage() + powerBonus > 0 && card.getNetCombatDamage() + powerBonus > 0
@@ -1609,8 +1609,8 @@ public class AttachAi extends SpellAbilityAi {
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true)); && (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
} else if (keyword.startsWith("Flanking")) { } else if (keyword.startsWith("Flanking")) {
return card.getNetCombatDamage() + powerBonus > 0 return card.getNetCombatDamage() + powerBonus > 0
&& ComputerUtilCombat.canAttackNextTurn(card) && canBeBlocked
&& canBeBlocked; && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.startsWith("Bushido")) { } else if (keyword.startsWith("Bushido")) {
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card)) return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|| CombatUtil.canBlock(card, true); || CombatUtil.canBlock(card, true);
@@ -1652,9 +1652,9 @@ public class AttachAi extends SpellAbilityAi {
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender") if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|| keyword.endsWith("CARDNAME can't attack or block.")) { || keyword.endsWith("CARDNAME can't attack or block.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1; return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) { } else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty(); return !ai.getCreaturesInPlay().isEmpty() && ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME can't block.")) { } else if (keyword.endsWith("CARDNAME can't block.")) {
return CombatUtil.canBlock(card, true); return CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) { } else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
@@ -1665,10 +1665,10 @@ public class AttachAi extends SpellAbilityAi {
} }
return false; return false;
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) { } else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1; return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.") } else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { || keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2; return card.getNetCombatDamage() >= 2 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) { } else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
return !card.isUntapped(); return !card.isUntapped();
} }

View File

@@ -8,8 +8,11 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.ai.AIOption;
import forge.ai.LobbyPlayerAi; import forge.ai.LobbyPlayerAi;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -54,19 +57,19 @@ public class GameCopier {
public GameCopier(Game origGame) { public GameCopier(Game origGame) {
this.origGame = origGame; this.origGame = origGame;
} }
public Game getOriginalGame() { public Game getOriginalGame() {
return origGame; return origGame;
} }
public Game getCopiedGame() { public Game getCopiedGame() {
return gameObjectMap.getGame(); return gameObjectMap.getGame();
} }
public Game makeCopy() { public Game makeCopy() {
return makeCopy(null); return makeCopy(null, null);
} }
public Game makeCopy(PhaseType advanceToPhase) { public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers(); List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
List<RegisteredPlayer> newPlayers = new ArrayList<>(); List<RegisteredPlayer> newPlayers = new ArrayList<>();
for (RegisteredPlayer p : origPlayers) { for (RegisteredPlayer p : origPlayers) {
@@ -82,8 +85,7 @@ public class GameCopier {
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++) for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++)
newPlayer.addSpellCastThisTurn(); newPlayer.addSpellCastThisTurn();
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++) newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
newPlayer.addLandPlayedThisTurn();
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn()); newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn()); newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
@@ -99,7 +101,7 @@ public class GameCopier {
for (Player p : newGame.getPlayers()) { for (Player p : newGame.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false);
} }
copyGameState(newGame); copyGameState(newGame);
for (Player p : newGame.getPlayers()) { for (Player p : newGame.getPlayers()) {
@@ -138,20 +140,27 @@ public class GameCopier {
effect.removeMapped(gameObjectMap); effect.removeMapped(gameObjectMap);
} }
if (origPhaseHandler.getCombat() != null) {
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
}
newGame.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated newGame.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
newGame.getTriggerHandler().resetActiveTriggers(); newGame.getTriggerHandler().resetActiveTriggers();
if (GameSimulator.COPY_STACK) if (GameSimulator.COPY_STACK)
copyStack(origGame, newGame, gameObjectMap); copyStack(origGame, newGame, gameObjectMap);
if (origPhaseHandler.getCombat() != null) { // TODO update thisTurnCast
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
}
if (advanceToPhase != null) { if (advanceToPhase != null) {
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase); newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, new Runnable() {
@Override
public void run() {
GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent());
}
});
} }
return newGame; return newGame;
} }
@@ -185,7 +194,8 @@ public class GameCopier {
RegisteredPlayer clone = new RegisteredPlayer(p.getDeck()); RegisteredPlayer clone = new RegisteredPlayer(p.getDeck());
LobbyPlayer lp = p.getPlayer(); LobbyPlayer lp = p.getPlayer();
if (!(lp instanceof LobbyPlayerAi)) { if (!(lp instanceof LobbyPlayerAi)) {
lp = new LobbyPlayerAi(p.getPlayer().getName(), null); // TODO should probably also override them if they're normal AI
lp = new LobbyPlayerAi(p.getPlayer().getName(), Sets.newHashSet(AIOption.USE_SIMULATION));
} }
clone.setPlayer(lp); clone.setPlayer(lp);
return clone; return clone;
@@ -197,6 +207,7 @@ public class GameCopier {
for (Card card : origGame.getCardsIn(zone)) { for (Card card : origGame.getCardsIn(zone)) {
addCard(newGame, zone, card); addCard(newGame, zone, card);
} }
// TODO CardsAddedThisTurn is now messed up
} }
gameObjectMap = new CopiedGameObjectMap(newGame); gameObjectMap = new CopiedGameObjectMap(newGame);
@@ -271,14 +282,17 @@ public class GameCopier {
cardMap.put(c, newCard); cardMap.put(c, newCard);
Player zoneOwner = owner; Player zoneOwner = owner;
// everything the CreatureEvaluator checks must be set here
if (zone == ZoneType.Battlefield) { if (zone == ZoneType.Battlefield) {
// TODO: Controllers' list with timestamps should be copied. // TODO: Controllers' list with timestamps should be copied.
zoneOwner = playerMap.get(c.getController()); zoneOwner = playerMap.get(c.getController());
newCard.setController(zoneOwner, 0); newCard.setController(zoneOwner, 0);
newCard.setCameUnderControlSinceLastUpkeep(c.cameUnderControlSinceLastUpkeep());
newCard.setPTTable(c.getSetPTTable()); newCard.setPTTable(c.getSetPTTable());
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable()); newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
newCard.setPTBoost(c.getPTBoostTable()); newCard.setPTBoost(c.getPTBoostTable());
newCard.setDamage(c.getDamage()); newCard.setDamage(c.getDamage());
@@ -290,9 +304,11 @@ public class GameCopier {
newCard.setChangedCardKeywords(c.getChangedCardKeywords()); newCard.setChangedCardKeywords(c.getChangedCardKeywords());
newCard.setChangedCardNames(c.getChangedCardNames()); newCard.setChangedCardNames(c.getChangedCardNames());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such? for (Table.Cell<Long, Long, List<String>> kw : c.getHiddenExtrinsicKeywordsTable().cellSet()) {
//for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) newCard.addHiddenExtrinsicKeywords(kw.getRowKey(), kw.getColumnKey(), kw.getValue());
// newCard.addHiddenExtrinsicKeyword(kw); }
newCard.updateKeywordsCache(newCard.getCurrentState());
if (c.isTapped()) { if (c.isTapped()) {
newCard.setTapped(true); newCard.setTapped(true);
} }
@@ -358,7 +374,7 @@ public class GameCopier {
zoneOwner.getZone(zone).add(newCard); zoneOwner.getZone(zone).add(newCard);
} }
} }
private static SpellAbility findSAInCard(SpellAbility sa, Card c) { private static SpellAbility findSAInCard(SpellAbility sa, Card c) {
String saDesc = sa.getDescription(); String saDesc = sa.getDescription();
for (SpellAbility cardSa : c.getAllSpellAbilities()) { for (SpellAbility cardSa : c.getAllSpellAbilities()) {
@@ -386,7 +402,7 @@ public class GameCopier {
return find(o); return find(o);
} }
} }
public GameObject find(GameObject o) { public GameObject find(GameObject o) {
GameObject result = cardMap.get(o); GameObject result = cardMap.get(o);
if (result != null) if (result != null)

View File

@@ -31,11 +31,11 @@ public class GameSimulator {
public GameSimulator(SimulationController controller, Game origGame, Player origAiPlayer, PhaseType advanceToPhase) { public GameSimulator(SimulationController controller, Game origGame, Player origAiPlayer, PhaseType advanceToPhase) {
this.controller = controller; this.controller = controller;
copier = new GameCopier(origGame); copier = new GameCopier(origGame);
simGame = copier.makeCopy(advanceToPhase); simGame = copier.makeCopy(advanceToPhase, origAiPlayer);
aiPlayer = (Player) copier.find(origAiPlayer); aiPlayer = (Player) copier.find(origAiPlayer);
eval = new GameStateEvaluator(); eval = new GameStateEvaluator();
origLines = new ArrayList<>(); origLines = new ArrayList<>();
debugLines = origLines; debugLines = origLines;
@@ -74,6 +74,8 @@ public class GameSimulator {
eval.getScoreForGameState(origGame, origAiPlayer); eval.getScoreForGameState(origGame, origAiPlayer);
// Print debug info. // Print debug info.
printDiff(origLines, simLines); printDiff(origLines, simLines);
// make sure it gets printed
System.out.flush();
throw new RuntimeException("Game copy error. See diff output above for details."); throw new RuntimeException("Game copy error. See diff output above for details.");
} }
eval.setDebugging(false); eval.setDebugging(false);
@@ -83,7 +85,7 @@ public class GameSimulator {
this.interceptor = interceptor; this.interceptor = interceptor;
((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor); ((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor);
} }
private void printDiff(List<String> lines1, List<String> lines2) { private void printDiff(List<String> lines1, List<String> lines2) {
int i = 0; int i = 0;
int j = 0; int j = 0;
@@ -117,11 +119,11 @@ public class GameSimulator {
if (debugPrint) { if (debugPrint) {
System.out.println(str); System.out.println(str);
} }
if (debugLines!=null) { if (debugLines != null) {
debugLines.add(str); debugLines.add(str);
} }
} }
private SpellAbility findSaInSimGame(SpellAbility sa) { private SpellAbility findSaInSimGame(SpellAbility sa) {
// is already an ability from sim game // is already an ability from sim game
if (sa.getHostCard().getGame().equals(this.simGame)) { if (sa.getHostCard().getGame().equals(this.simGame)) {
@@ -230,8 +232,10 @@ public class GameSimulator {
return score; return score;
} }
private static void resolveStack(final Game game, final Player opponent) { public static void resolveStack(final Game game, final Player opponent) {
// TODO: This needs to set an AI controller for all opponents, in case of multiplayer. // TODO: This needs to set an AI controller for all opponents, in case of multiplayer.
PlayerControllerAi sim = new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer());
sim.setUseSimulation(true);
opponent.runWithController(new Runnable() { opponent.runWithController(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -259,9 +263,9 @@ public class GameSimulator {
// Continue until stack is empty. // Continue until stack is empty.
} }
} }
}, new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer())); }, sim);
} }
public Game getSimulatedGameState() { public Game getSimulatedGameState() {
return simGame; return simGame;
} }
@@ -269,7 +273,7 @@ public class GameSimulator {
public Score getScoreForOrigGame() { public Score getScoreForOrigGame() {
return origScore; return origScore;
} }
public GameCopier getGameCopier() { public GameCopier getGameCopier() {
return copier; return copier;
} }

View File

@@ -15,16 +15,16 @@ public class GameStateEvaluator {
public void setDebugging(boolean debugging) { public void setDebugging(boolean debugging) {
this.debugging = debugging; this.debugging = debugging;
} }
private static void debugPrint(String s) { private static void debugPrint(String s) {
GameSimulator.debugPrint(s); GameSimulator.debugPrint(s);
} }
private static class CombatSimResult { private static class CombatSimResult {
public GameCopier copier; public GameCopier copier;
public Game gameCopy; public Game gameCopy;
} }
private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame) { private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame, final Player aiPlayer) {
PhaseType phase = evalGame.getPhaseHandler().getPhase(); PhaseType phase = evalGame.getPhaseHandler().getPhase();
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) { if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
return null; return null;
@@ -38,7 +38,12 @@ public class GameStateEvaluator {
} }
GameCopier copier = new GameCopier(evalGame); GameCopier copier = new GameCopier(evalGame);
Game gameCopy = copier.makeCopy(); Game gameCopy = copier.makeCopy();
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE); gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, new Runnable() {
@Override
public void run() {
GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent());
}
});
CombatSimResult result = new CombatSimResult(); CombatSimResult result = new CombatSimResult();
result.copier = copier; result.copier = copier;
result.gameCopy = gameCopy; result.gameCopy = gameCopy;
@@ -61,13 +66,13 @@ public class GameStateEvaluator {
return new Score(Integer.MIN_VALUE); return new Score(Integer.MIN_VALUE);
} }
public Score getScoreForGameState(Game game, Player aiPlayer) { public Score getScoreForGameState(Game game, Player aiPlayer) {
if (game.isGameOver()) { if (game.isGameOver()) {
return getScoreForGameOver(game, aiPlayer); return getScoreForGameOver(game, aiPlayer);
} }
CombatSimResult result = simulateUpcomingCombatThisTurn(game); CombatSimResult result = simulateUpcomingCombatThisTurn(game, aiPlayer);
if (result != null) { if (result != null) {
Player aiPlayerCopy = (Player) result.copier.find(aiPlayer); Player aiPlayerCopy = (Player) result.copier.find(aiPlayer);
if (result.gameCopy.isGameOver()) { if (result.gameCopy.isGameOver()) {
@@ -138,7 +143,7 @@ public class GameStateEvaluator {
debugPrint("Score = " + score); debugPrint("Score = " + score);
return new Score(score, summonSickScore); return new Score(score, summonSickScore);
} }
public int evalCard(Game game, Player aiPlayer, Card c) { public int evalCard(Game game, Player aiPlayer, Card c) {
// TODO: These should be based on other considerations - e.g. in relation to opponents state. // TODO: These should be based on other considerations - e.g. in relation to opponents state.
if (c.isCreature()) { if (c.isCreature()) {
@@ -178,12 +183,12 @@ public class GameStateEvaluator {
this.value = value; this.value = value;
this.summonSickValue = value; this.summonSickValue = value;
} }
public Score(int value, int summonSickValue) { public Score(int value, int summonSickValue) {
this.value = value; this.value = value;
this.summonSickValue = summonSickValue; this.summonSickValue = summonSickValue;
} }
public boolean equals(Score other) { public boolean equals(Score other) {
if (other == null) if (other == null)
return false; return false;

View File

@@ -22,7 +22,7 @@ public class Plan {
this.decisions = decisions; this.decisions = decisions;
this.finalScore = finalScore; this.finalScore = finalScore;
} }
public Score getFinalScore() { public Score getFinalScore() {
return finalScore; return finalScore;
} }
@@ -112,14 +112,14 @@ public class Plan {
this.prevDecision = prevDecision; this.prevDecision = prevDecision;
this.saRef = saRef; this.saRef = saRef;
} }
public Decision(Score initialScore, Decision prevDecision, MultiTargetSelector.Targets targets) { public Decision(Score initialScore, Decision prevDecision, MultiTargetSelector.Targets targets) {
this.initialScore = initialScore; this.initialScore = initialScore;
this.prevDecision = prevDecision; this.prevDecision = prevDecision;
this.saRef = null; this.saRef = null;
this.targets = targets; this.targets = targets;
} }
public Decision(Score initialScore, Decision prevDecision, Card choice) { public Decision(Score initialScore, Decision prevDecision, Card choice) {
this.initialScore = initialScore; this.initialScore = initialScore;
this.prevDecision = prevDecision; this.prevDecision = prevDecision;

View File

@@ -48,30 +48,30 @@ public class SimulationController {
private int getRecursionDepth() { private int getRecursionDepth() {
return scoreStack.size() - 1; return scoreStack.size() - 1;
} }
public boolean shouldRecurse() { public boolean shouldRecurse() {
return bestScore.value != Integer.MAX_VALUE && getRecursionDepth() < MAX_DEPTH; return bestScore.value != Integer.MAX_VALUE && getRecursionDepth() < MAX_DEPTH;
} }
private Plan.Decision getLastDecision() { private Plan.Decision getLastDecision() {
if (currentStack.isEmpty()) { if (currentStack.isEmpty()) {
return null; return null;
} }
return currentStack.get(currentStack.size() - 1); return currentStack.get(currentStack.size() - 1);
} }
private Score getCurrentScore() { private Score getCurrentScore() {
return scoreStack.get(scoreStack.size() - 1); return scoreStack.get(scoreStack.size() - 1);
} }
public void evaluateSpellAbility(List<SpellAbility> saList, int saIndex) { public void evaluateSpellAbility(List<SpellAbility> saList, int saIndex) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), new Plan.SpellAbilityRef(saList, saIndex))); currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), new Plan.SpellAbilityRef(saList, saIndex)));
} }
public void evaluateCardChoice(Card choice) { public void evaluateCardChoice(Card choice) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), choice)); currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), choice));
} }
public void evaluateChosenModes(int[] chosenModes, String modesStr) { public void evaluateChosenModes(int[] chosenModes, String modesStr) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), chosenModes, modesStr)); currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), chosenModes, modesStr));
} }
@@ -87,11 +87,11 @@ public class SimulationController {
} }
currentStack.remove(currentStack.size() - 1); currentStack.remove(currentStack.size() - 1);
} }
public Score getBestScore() { public Score getBestScore() {
return bestScore; return bestScore;
} }
public Plan getBestPlan() { public Plan getBestPlan() {
if (!currentStack.isEmpty()) { if (!currentStack.isEmpty()) {
throw new RuntimeException("getBestPlan() expects currentStack to be empty!"); throw new RuntimeException("getBestPlan() expects currentStack to be empty!");

View File

@@ -101,7 +101,7 @@ public class SpellAbilityPicker {
} }
public SpellAbility chooseSpellAbilityToPlay(SimulationController controller) { public SpellAbility chooseSpellAbilityToPlay(SimulationController controller) {
printOutput = (controller == null); printOutput = controller == null;
// Pass if top of stack is owned by me. // Pass if top of stack is owned by me.
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) { if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
@@ -338,7 +338,7 @@ public class SpellAbilityPicker {
return AiPlayDecision.CantPlaySa; return AiPlayDecision.CantPlaySa;
} }
// Note: Can'tjust check condition on the top ability, because it may have // Note: Can't just check condition on the top ability, because it may have
// sub-abilities without conditions (e.g. wild slash's main ability has a // sub-abilities without conditions (e.g. wild slash's main ability has a
// main ability with conditions but the burn sub-ability has none). // main ability with conditions but the burn sub-ability has none).
if (!atLeastOneConditionMet(sa)) { if (!atLeastOneConditionMet(sa)) {

View File

@@ -56,11 +56,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) { if (sa.hasParam("RememberObjects")) {
for (final String rem : sa.getParam("RememberObjects").split(",")) { for (final String rem : sa.getParam("RememberObjects").split(",")) {
for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) { for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
if (o instanceof SpellAbility) {
// "RememberObjects$ Remembered" don't remember spellability
continue;
}
delTrig.addRemembered(o); delTrig.addRemembered(o);
} }
} }

View File

@@ -81,7 +81,7 @@ public class EffectEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) { if (sa.hasParam("RememberObjects")) {
rememberList = new FCollection<>(); rememberList = new FCollection<>();
for (final String rem : sa.getParam("RememberObjects").split(",")) { for (final String rem : sa.getParam("RememberObjects").split(",")) {
rememberList.addAll(AbilityUtils.getDefinedObjects(hostCard, rem, sa)); rememberList.addAll(AbilityUtils.getDefinedEntities(hostCard, rem, sa));
} }
if (sa.hasParam("ForgetCounter")) { if (sa.hasParam("ForgetCounter")) {

View File

@@ -59,11 +59,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) { if (sa.hasParam("RememberObjects")) {
for (final String rem : sa.getParam("RememberObjects").split(",")) { for (final String rem : sa.getParam("RememberObjects").split(",")) {
for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) { for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
if (o instanceof SpellAbility) {
// "RememberObjects$ Remembered" don't remember spellability
continue;
}
immediateTrig.addRemembered(o); immediateTrig.addRemembered(o);
} }
} }

View File

@@ -3617,7 +3617,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public Iterable<KeywordsChange> getChangedCardKeywordsList() { public Iterable<KeywordsChange> getChangedCardKeywordsList() {
return Iterables.concat( return Iterables.concat(
changedCardKeywordsByText.values(), // Layer 3 changedCardKeywordsByText.values(), // Layer 3
ImmutableList.of(new KeywordsChange(ImmutableList.<String>of(), null, this.hasRemoveIntrinsic())), // Layer 4 ImmutableList.of(new KeywordsChange(ImmutableList.<String>of(), null, this.hasRemoveIntrinsic())), // Layer 4
@@ -3629,7 +3628,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return changedCardKeywords; return changedCardKeywords;
} }
public Table<Long, Long, CardColor> getChangedCardColorsTable() { public Table<Long, Long, CardColor> getChangedCardColorsTable() {
return changedCardColors; return changedCardColors;
} }
@@ -4561,6 +4559,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final Iterable<String> getHiddenExtrinsicKeywords() { public final Iterable<String> getHiddenExtrinsicKeywords() {
return Iterables.concat(this.hiddenExtrinsicKeywords.values()); return Iterables.concat(this.hiddenExtrinsicKeywords.values());
} }
public final Table<Long, Long, List<String>> getHiddenExtrinsicKeywordsTable() {
return hiddenExtrinsicKeywords;
}
public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable<String> keywords) { public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable<String> keywords) {
// TODO if some keywords aren't removed anymore, then no need for extra Array List // TODO if some keywords aren't removed anymore, then no need for extra Array List

View File

@@ -1036,7 +1036,7 @@ public class CombatUtil {
* @return a boolean. * @return a boolean.
*/ */
public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) { public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) {
if ((attacker == null) || (blocker == null)) { if (attacker == null || blocker == null) {
return false; return false;
} }

View File

@@ -1162,11 +1162,17 @@ public class PhaseHandler implements java.io.Serializable {
} }
public final boolean devAdvanceToPhase(PhaseType targetPhase) { public final boolean devAdvanceToPhase(PhaseType targetPhase) {
return devAdvanceToPhase(targetPhase, null);
}
public final boolean devAdvanceToPhase(PhaseType targetPhase, Runnable resolver) {
boolean isTopsy = playerTurn.getAmountOfKeyword("The phases of your turn are reversed.") % 2 == 1; boolean isTopsy = playerTurn.getAmountOfKeyword("The phases of your turn are reversed.") % 2 == 1;
while (phase.isBefore(targetPhase, isTopsy)) { while (phase.isBefore(targetPhase, isTopsy)) {
if (checkStateBasedEffects()) { if (checkStateBasedEffects()) {
return false; return false;
} }
if (resolver != null) {
resolver.run();
}
onPhaseEnd(); onPhaseEnd();
advanceToNextPhase(); advanceToNextPhase();
onPhaseBegin(); onPhaseBegin();

View File

@@ -855,11 +855,7 @@ public final class StaticAbilityContinuous {
if (params.containsKey("TriggerRememberDefined")) { if (params.containsKey("TriggerRememberDefined")) {
String triggerRemembered = (params.get("TriggerRememberDefined")); String triggerRemembered = (params.get("TriggerRememberDefined"));
for (final String rem : triggerRemembered.split(",")) { for (final String rem : triggerRemembered.split(",")) {
for (final Object o : AbilityUtils.getDefinedObjects(hostCard, rem, stAb)) { for (final Object o : AbilityUtils.getDefinedEntities(hostCard, rem, stAb)) {
if (o instanceof SpellAbility) {
// "RememberObjects$ Remembered" don't remember spellability
continue;
}
actualTrigger.addRemembered(o); actualTrigger.addRemembered(o);
} }
} }

View File

@@ -81,11 +81,9 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
public final void add(final Card c) { public final void add(final Card c) {
add(c, null); add(c, null);
} }
public final void add(final Card c, final Integer index) { public final void add(final Card c, final Integer index) {
add(c, index, null); add(c, index, null);
} }
public void add(final Card c, Integer index, final Card latestState) { public void add(final Card c, Integer index, final Card latestState) {
if (index != null && cardList.isEmpty() && index.intValue() > 0) { if (index != null && cardList.isEmpty() && index.intValue() > 0) {
// something went wrong, most likely the method fired when the game was in an unexpected state // something went wrong, most likely the method fired when the game was in an unexpected state