LandEvaluator for AI

This commit is contained in:
Anthony Calosa
2023-02-21 21:21:39 +08:00
parent 5852942e54
commit e2c3908df6
2 changed files with 236 additions and 209 deletions

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -75,7 +75,7 @@ import java.util.*;
* <p> * <p>
* AiController class. * AiController class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id$ * @version $Id$
*/ */
@@ -108,6 +108,7 @@ public class AiController {
public boolean usesSimulation() { public boolean usesSimulation() {
return this.useSimulation; return this.useSimulation;
} }
public void setUseSimulation(boolean value) { public void setUseSimulation(boolean value) {
this.useSimulation = value; this.useSimulation = value;
} }
@@ -153,7 +154,7 @@ public class AiController {
private List<SpellAbility> getPossibleETBCounters() { private List<SpellAbility> getPossibleETBCounters() {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library); CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
all.addAll(player.getCardsIn(ZoneType.Exile)); all.addAll(player.getCardsIn(ZoneType.Exile));
all.addAll(player.getCardsIn(ZoneType.Graveyard)); all.addAll(player.getCardsIn(ZoneType.Graveyard));
if (!ccvPlayerLibrary.isEmpty()) { if (!ccvPlayerLibrary.isEmpty()) {
@@ -175,7 +176,7 @@ public class AiController {
} }
return spellAbilities; return spellAbilities;
} }
// look for cards on the battlefield that should prevent the AI from using that spellability // look for cards on the battlefield that should prevent the AI from using that spellability
private boolean checkCurseEffects(final SpellAbility sa) { private boolean checkCurseEffects(final SpellAbility sa) {
CardCollectionView ccvGameBattlefield = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.hasSVar("AICurseEffect")); CardCollectionView ccvGameBattlefield = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.hasSVar("AICurseEffect"));
@@ -194,7 +195,7 @@ public class AiController {
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host) } else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
&& host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) { && host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) {
return true; return true;
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) { } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
String hostName = host.getName(); String hostName = host.getName();
for (Card card : ccvGameBattlefield) { for (Card card : ccvGameBattlefield) {
if (!card.isToken() && card.sharesNameWith(host)) { if (!card.isToken() && card.sharesNameWith(host)) {
@@ -217,6 +218,7 @@ public class AiController {
card.setCastSA(null); card.setCastSA(null);
return result; return result;
} }
private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility sa, final ApiType api) { private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility sa, final ApiType api) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
@@ -301,7 +303,7 @@ public class AiController {
String s = validCard.split("kicked ")[1]; String s = validCard.split("kicked ")[1];
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) { } else if (!sa.isKicked()) {
continue; continue;
} }
} }
@@ -476,6 +478,8 @@ public class AiController {
// If nothing is done here, proceeds to the default land picking strategy // If nothing is done here, proceeds to the default land picking strategy
} }
return ComputerUtilCard.getBestLandToPlayAI(landList);
/*
//Skip reflected lands. //Skip reflected lands.
CardCollection unreflectedLands = new CardCollection(landList); CardCollection unreflectedLands = new CardCollection(landList);
for (Card l : landList) { for (Card l : landList) {
@@ -574,7 +578,7 @@ public class AiController {
landList = CardLists.filter(landList, Predicates.not(CardPredicates.Presets.BASIC_LANDS)); landList = CardLists.filter(landList, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
} }
} }
return landList.get(0); return landList.get(0);*/
} }
// if return true, go to next phase // if return true, go to next phase
@@ -600,7 +604,7 @@ public class AiController {
} else { } else {
// Compare bestSA with this SA // Compare bestSA with this SA
final int restrictionLevel = ComputerUtil.counterSpellRestriction(player, currentSA); final int restrictionLevel = ComputerUtil.counterSpellRestriction(player, currentSA);
if (restrictionLevel > bestRestriction) { if (restrictionLevel > bestRestriction) {
bestRestriction = restrictionLevel; bestRestriction = restrictionLevel;
bestSA = currentSA; bestSA = currentSA;
@@ -617,20 +621,20 @@ public class AiController {
public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) { public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
return predictSpellToCastInMain2(exceptSA, true); return predictSpellToCastInMain2(exceptSA, true);
} }
private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) { private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) {
if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) { if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
return null; return null;
} }
final CardCollectionView cards = handOnly ? player.getCardsIn(ZoneType.Hand) : final CardCollectionView cards = handOnly ? player.getCardsIn(ZoneType.Hand) :
ComputerUtilAbility.getAvailableCards(game, player); ComputerUtilAbility.getAvailableCards(game, player);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player); List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
try { try {
Collections.sort(all, saComparator); // put best spells first Collections.sort(all, saComparator); // put best spells first
} } catch (IllegalArgumentException ex) {
catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
@@ -638,7 +642,7 @@ public class AiController {
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
ApiType saApi = sa.getApi(); ApiType saApi = sa.getApi();
if (saApi == ApiType.Counter || saApi == exceptSA) { if (saApi == ApiType.Counter || saApi == exceptSA) {
continue; continue;
} }
@@ -657,12 +661,15 @@ public class AiController {
public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) { public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) {
return reserveManaSources(sa, null, false, true, exceptForSa); return reserveManaSources(sa, null, false, true, exceptForSa);
} }
public boolean reserveManaSources(SpellAbility sa) { public boolean reserveManaSources(SpellAbility sa) {
return reserveManaSources(sa, PhaseType.MAIN2, false, false, null); return reserveManaSources(sa, PhaseType.MAIN2, false, false, null);
} }
public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) { public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
return reserveManaSources(sa, phaseType, enemy, true, null); return reserveManaSources(sa, phaseType, enemy, true, null);
} }
public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy, boolean forNextSpell, SpellAbility exceptForThisSa) { public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy, boolean forNextSpell, SpellAbility exceptForThisSa) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player); CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
@@ -784,8 +791,9 @@ public class AiController {
return AiPlayDecision.CantAfford; return AiPlayDecision.CantAfford;
} }
} }
SpellAbilityAi topAI = new SpellAbilityAi() {}; SpellAbilityAi topAI = new SpellAbilityAi() {
if (!topAI.willPayCosts(player, sa , wardCost, host)) { };
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
return AiPlayDecision.CostNotAcceptable; return AiPlayDecision.CostNotAcceptable;
} }
} }
@@ -899,7 +907,7 @@ public class AiController {
return AiPlayDecision.AnotherTime; return AiPlayDecision.AnotherTime;
} }
if (sa instanceof SpellPermanent) { if (sa instanceof SpellPermanent) {
return canPlayFromEffectAI((SpellPermanent)sa, false, true); return canPlayFromEffectAI((SpellPermanent) sa, false, true);
} }
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) { if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) {
@@ -1037,7 +1045,7 @@ public class AiController {
} else if (a1 > 0 && b1 == 0 && ApiType.Mana != b.getApi()) { } else if (a1 > 0 && b1 == 0 && ApiType.Mana != b.getApi()) {
return 1; return 1;
} }
if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) { if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) {
return -1; return -1;
} else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) { } else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) {
@@ -1060,7 +1068,7 @@ public class AiController {
return b1 - a1; return b1 - a1;
} }
private int getSpellAbilityPriority(SpellAbility sa) { private int getSpellAbilityPriority(SpellAbility sa) {
int p = 0; int p = 0;
Card source = sa.getHostCard(); Card source = sa.getHostCard();
@@ -1142,6 +1150,7 @@ public class AiController {
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) { public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) {
return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY); return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY);
} }
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) { public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
boolean noFiltering = sa != null && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere boolean noFiltering = sa != null && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
@@ -1193,7 +1202,9 @@ public class AiController {
} }
} }
for (Card c : inHand) { for (Card c : inHand) {
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; } if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) {
continue;
}
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) { if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) {
discards.add(c); discards.add(c);
} }
@@ -1232,8 +1243,7 @@ public class AiController {
discardList.add(prefCard); discardList.add(prefCard);
validCards.remove(prefCard); validCards.remove(prefCard);
count++; count++;
} } else {
else {
break; break;
} }
} }
@@ -1248,16 +1258,15 @@ public class AiController {
final int numLandsInPlay = CardLists.count(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA); final int numLandsInPlay = CardLists.count(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
final CardCollection landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS); final CardCollection landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS);
final int numLandsInHand = landsInHand.size(); final int numLandsInHand = landsInHand.size();
// Discard a land // Discard a land
boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0) boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0)
|| (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5); || (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5);
if (canDiscardLands) { if (canDiscardLands) {
discardList.add(landsInHand.get(0)); discardList.add(landsInHand.get(0));
validCards.remove(landsInHand.get(0)); validCards.remove(landsInHand.get(0));
} } else { // Discard other stuff
else { // Discard other stuff
CardLists.sortByCmcDesc(validCards); CardLists.sortByCmcDesc(validCards);
int numLandsAvailable = numLandsInPlay; int numLandsAvailable = numLandsInPlay;
if (numLandsInHand > 0) { if (numLandsInHand > 0) {
@@ -1346,7 +1355,7 @@ public class AiController {
return false; return false;
default: default:
return false; return false;
} }
return false; return false;
} }
@@ -1448,7 +1457,9 @@ public class AiController {
} }
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) { private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
if (sa == null) { return null; } if (sa == null) {
return null;
}
final List<SpellAbility> abilities = Lists.newArrayList(); final List<SpellAbility> abilities = Lists.newArrayList();
abilities.add(sa); abilities.add(sa);
@@ -1470,17 +1481,17 @@ public class AiController {
} }
CardCollection playBeforeLand = CardLists.filter( CardCollection playBeforeLand = CardLists.filter(
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop") player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
); );
if (!playBeforeLand.isEmpty()) { if (!playBeforeLand.isEmpty()) {
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList( SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
); );
if (wantToPlayBeforeLand != null) { if (wantToPlayBeforeLand != null) {
return singleSpellAbilityList(wantToPlayBeforeLand); return singleSpellAbilityList(wantToPlayBeforeLand);
} }
} }
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
if (landsWannaPlay != null) { if (landsWannaPlay != null) {
landsWannaPlay = filterLandsToPlay(landsWannaPlay); landsWannaPlay = filterLandsToPlay(landsWannaPlay);
@@ -1672,7 +1683,7 @@ public class AiController {
if (!game.getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards)); SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards));
if (counter != null) return counter; if (counter != null) return counter;
SpellAbility counterETB = chooseSpellAbilityToPlayFromList(getPossibleETBCounters(), false); SpellAbility counterETB = chooseSpellAbilityToPlayFromList(getPossibleETBCounters(), false);
if (counterETB != null) if (counterETB != null)
return counterETB; return counterETB;
@@ -1705,8 +1716,7 @@ public class AiController {
try { try {
Collections.sort(all, saComparator); // put best spells first Collections.sort(all, saComparator); // put best spells first
} } catch (IllegalArgumentException ex) {
catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
@@ -1755,7 +1765,7 @@ public class AiController {
public CardCollection chooseCardsToDelve(int genericCost, CardCollection grave) { public CardCollection chooseCardsToDelve(int genericCost, CardCollection grave) {
CardCollection toExile = new CardCollection(); CardCollection toExile = new CardCollection();
int numToExile = Math.min(grave.size(), genericCost); int numToExile = Math.min(grave.size(), genericCost);
for (int i = 0; i < numToExile; i++) { for (int i = 0; i < numToExile; i++) {
Card chosen = null; Card chosen = null;
for (final Card c : grave) { // Exile noncreatures first in for (final Card c : grave) { // Exile noncreatures first in
@@ -1783,7 +1793,7 @@ public class AiController {
public boolean doTrigger(SpellAbility spell, boolean mandatory) { public boolean doTrigger(SpellAbility spell, boolean mandatory) {
if (spell instanceof WrappedAbility) if (spell instanceof WrappedAbility)
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory); return doTrigger(((WrappedAbility) spell).getWrappedAbility(), mandatory);
if (spell.getApi() != null) if (spell.getApi() != null)
return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory); return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory);
if (spell.getPayCosts() == Cost.Zero && !spell.usesTargeting()) { if (spell.getPayCosts() == Cost.Zero && !spell.usesTargeting()) {
@@ -1929,7 +1939,7 @@ public class AiController {
} else if ("LowestLoseLife".equals(logic)) { } else if ("LowestLoseLife".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1; return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
} else if ("HighestLoseLife".equals(logic)) { } else if ("HighestLoseLife".equals(logic)) {
return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1); return Math.min(player.getLife() - 1, MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1);
} else if ("HighestGetCounter".equals(logic)) { } else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3); return MyRandom.getRandom().nextInt(3);
} else if (sa.hasSVar("EnergyToPay")) { } else if (sa.hasSVar("EnergyToPay")) {
@@ -1949,8 +1959,7 @@ public class AiController {
} }
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) { public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
switch(sa.getApi()) switch (sa.getApi()) {
{
case SetLife: // Reverse the Sands case SetLife: // Reverse the Sands
if (relatedPlayer.equals(sa.getHostCard().getController())) { if (relatedPlayer.equals(sa.getHostCard().getController())) {
return Collections.max(options); return Collections.max(options);
@@ -2076,14 +2085,13 @@ public class AiController {
// this is where the computer cheats // this is where the computer cheats
// changes AllZone.getComputerPlayer().getZone(Zone.Library) // changes AllZone.getComputerPlayer().getZone(Zone.Library)
/** /**
* <p> * <p>
* smoothComputerManaCurve. * smoothComputerManaCurve.
* </p> * </p>
* *
* @param in * @param in an array of {@link forge.game.card.Card} objects.
* an array of {@link forge.game.card.Card} objects.
* @return an array of {@link forge.game.card.Card} objects. * @return an array of {@link forge.game.card.Card} objects.
*/ */
public CardCollectionView cheatShuffle(CardCollectionView in) { public CardCollectionView cheatShuffle(CardCollectionView in) {
@@ -2111,7 +2119,7 @@ public class AiController {
library.add(8, land.get(2)); library.add(8, land.get(2));
library.add(9, land.get(3)); library.add(9, land.get(3));
library.add(10, land.get(4)); library.add(10, land.get(4));
library.add(12, land.get(5)); library.add(12, land.get(5));
library.add(15, land.get(6)); library.add(15, land.get(6));
} catch (final IndexOutOfBoundsException e) { } catch (final IndexOutOfBoundsException e) {
@@ -2183,7 +2191,7 @@ public class AiController {
} }
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa, public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa,
CardCollection fetchList, Player player2, Player decider) { CardCollection fetchList, Player player2, Player decider) {
if (useSimulation) { if (useSimulation) {
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
} }

View File

@@ -9,6 +9,8 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import com.google.common.base.Function;
import forge.ai.simulation.GameStateEvaluator;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.MutablePair;
@@ -84,7 +86,7 @@ public class ComputerUtilCard {
* Sorts a List<Card> by "best" using the EvaluateCreature function. * Sorts a List<Card> by "best" using the EvaluateCreature function.
* the best creatures will be first in the list. * the best creatures will be first in the list.
* </p> * </p>
* *
* @param list * @param list
*/ */
public static void sortByEvaluateCreature(final CardCollection list) { public static void sortByEvaluateCreature(final CardCollection list) {
@@ -92,11 +94,12 @@ public class ComputerUtilCard {
} }
// The AI doesn't really pick the best artifact, just the most expensive. // The AI doesn't really pick the best artifact, just the most expensive.
/** /**
* <p> * <p>
* getBestArtifactAI. * getBestArtifactAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -111,6 +114,7 @@ public class ComputerUtilCard {
/** /**
* 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
* @return best Planeswalker * @return best Planeswalker
*/ */
@@ -125,6 +129,7 @@ public class ComputerUtilCard {
/** /**
* Returns the worst Planeswalker from a given list * Returns the worst Planeswalker from a given list
*
* @param list list of cards to evaluate * @param list list of cards to evaluate
* @return best Planeswalker * @return best Planeswalker
*/ */
@@ -188,16 +193,15 @@ public class ComputerUtilCard {
} }
// The AI doesn't really pick the best enchantment, just the most expensive. // The AI doesn't really pick the best enchantment, just the most expensive.
/** /**
* <p> * <p>
* getBestEnchantmentAI. * getBestEnchantmentAI.
* </p> * </p>
* *
* @param list * @param list
* @param spell * @param spell a {@link forge.game.card.Card} object.
* a {@link forge.game.card.Card} object. * @param targeted a boolean.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) { public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
@@ -219,7 +223,7 @@ public class ComputerUtilCard {
* <p> * <p>
* getBestLandAI. * getBestLandAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -231,7 +235,7 @@ public class ComputerUtilCard {
// 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));
if (!nbLand.isEmpty()) { if (!nbLand.isEmpty()) {
// TODO - Rank non basics? // TODO - Rank non basics?
return Aggregates.random(nbLand); return Aggregates.random(nbLand);
@@ -267,7 +271,7 @@ public class ComputerUtilCard {
* <p> * <p>
* getWorstLand. * getWorstLand.
* </p> * </p>
* *
* @param lands * @param lands
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -324,12 +328,10 @@ public class ComputerUtilCard {
* <p> * <p>
* getCheapestPermanentAI. * getCheapestPermanentAI.
* </p> * </p>
* *
* @param all * @param all
* @param spell * @param spell a {@link forge.game.card.Card} object.
* a {@link forge.game.card.Card} object. * @param targeted a boolean.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
public static Card getCheapestPermanentAI(Iterable<Card> all, final SpellAbility spell, final boolean targeted) { public static Card getCheapestPermanentAI(Iterable<Card> all, final SpellAbility spell, final boolean targeted) {
@@ -347,7 +349,7 @@ public class ComputerUtilCard {
// get cheapest card: // get cheapest card:
Card cheapest = null; Card cheapest = null;
for (Card c : all) { for (Card c : all) {
if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) { if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c; cheapest = c;
@@ -358,11 +360,12 @@ public class ComputerUtilCard {
} }
// returns null if list.size() == 0 // returns null if list.size() == 0
/** /**
* <p> * <p>
* getBestAI. * getBestAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -380,9 +383,8 @@ public class ComputerUtilCard {
/** /**
* getBestCreatureAI. * getBestCreatureAI.
* *
* @param list * @param list the list
* the list
* @return the card * @return the card
*/ */
public static Card getBestCreatureAI(final Iterable<Card> list) { public static Card getBestCreatureAI(final Iterable<Card> list) {
@@ -392,11 +394,24 @@ public class ComputerUtilCard {
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator); return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator);
} }
/**
* getBestLandToPlayAI.
*
* @param list the list
* @return the card
*/
public static Card getBestLandToPlayAI(final Iterable<Card> list) {
if (Iterables.size(list) == 1) {
return Iterables.get(list, 0);
}
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.LANDS), ComputerUtilCard.landEvaluator);
}
/** /**
* <p> * <p>
* getWorstCreatureAI. * getWorstCreatureAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -408,11 +423,12 @@ public class ComputerUtilCard {
} }
// This selection rates tokens higher // This selection rates tokens higher
/** /**
* <p> * <p>
* getBestCreatureToBounceAI. * getBestCreatureToBounceAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -438,7 +454,7 @@ public class ComputerUtilCard {
// For ability of Oracle en-Vec, return the first card that are going to attack next turn // For ability of Oracle en-Vec, return the first card that are going to attack next turn
public static Card getBestCreatureToAttackNextTurnAI(final Player aiPlayer, final Iterable<Card> list) { public static Card getBestCreatureToAttackNextTurnAI(final Player aiPlayer, final Iterable<Card> list) {
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); AiController aic = ((PlayerControllerAi) aiPlayer.getController()).getAi();
for (final Card card : list) { for (final Card card : list) {
if (aic.getPredictedCombatNextTurn().isAttacking(card)) { if (aic.getPredictedCombatNextTurn().isAttacking(card)) {
return card; return card;
@@ -451,7 +467,7 @@ public class ComputerUtilCard {
* <p> * <p>
* getWorstAI. * getWorstAI.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
@@ -463,20 +479,16 @@ public class ComputerUtilCard {
* <p> * <p>
* getWorstPermanentAI. * getWorstPermanentAI.
* </p> * </p>
* *
* @param list * @param list
* @param biasEnch * @param biasEnch a boolean.
* a boolean. * @param biasLand a boolean.
* @param biasLand * @param biasArt a boolean.
* a boolean. * @param biasCreature a boolean.
* @param biasArt
* a boolean.
* @param biasCreature
* a boolean.
* @return a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object.
*/ */
public static Card getWorstPermanentAI(final Iterable<Card> list, final boolean biasEnch, final boolean biasLand, public static Card getWorstPermanentAI(final Iterable<Card> list, final boolean biasEnch, final boolean biasLand,
final boolean biasArt, final boolean biasCreature) { final boolean biasArt, final boolean biasCreature) {
if (Iterables.isEmpty(list)) { if (Iterables.isEmpty(list)) {
return null; return null;
} }
@@ -486,7 +498,7 @@ public class ComputerUtilCard {
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);
} }
@@ -557,14 +569,14 @@ public class ComputerUtilCard {
}; };
private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator(); private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
private static final LandEvaluator landEvaluator = new LandEvaluator();
/** /**
* <p> * <p>
* evaluateCreature. * evaluateCreature.
* </p> * </p>
* *
* @param c * @param c a {@link forge.game.card.Card} object.
* a {@link forge.game.card.Card} object.
* @return a int. * @return a int.
*/ */
public static int evaluateCreature(final Card c) { public static int evaluateCreature(final Card c) {
@@ -603,14 +615,15 @@ public class ComputerUtilCard {
} }
public static boolean doesCreatureAttackAI(final Player aiPlayer, final Card card) { public static boolean doesCreatureAttackAI(final Player aiPlayer, final Card card) {
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); AiController aic = ((PlayerControllerAi) aiPlayer.getController()).getAi();
return aic.getPredictedCombat().isAttacking(card); return aic.getPredictedCombat().isAttacking(card);
} }
/** /**
* Extension of doesCreatureAttackAI() for "virtual" creatures that do not actually exist on the battlefield yet * Extension of doesCreatureAttackAI() for "virtual" creatures that do not actually exist on the battlefield yet
* such as unanimated manlands. * such as unanimated manlands.
* @param ai controller of creature *
* @param ai controller of creature
* @param card creature to be evaluated * @param card creature to be evaluated
* @return creature will be attack * @return creature will be attack
*/ */
@@ -622,8 +635,9 @@ public class ComputerUtilCard {
} }
/** /**
* 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
* @param blockers list of additional blockers to be considered * @param blockers list of additional blockers to be considered
* @return list of creatures assigned to block in the simulation * @return list of creatures assigned to block in the simulation
*/ */
@@ -654,7 +668,8 @@ public class ComputerUtilCard {
/** /**
* 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
* @param blocker creature to be evaluated * @param blocker creature to be evaluated
* @return creature will be a blocker * @return creature will be a blocker
*/ */
@@ -664,7 +679,8 @@ public class ComputerUtilCard {
/** /**
* Check if an attacker can be blocked profitably (ie. kill attacker) * Check if an attacker can be blocked profitably (ie. kill attacker)
* @param ai controller of attacking creature *
* @param ai controller of attacking creature
* @param attacker attacking creature to evaluate * @param attacker attacking creature to evaluate
* @return attacker will die * @return attacker will die
*/ */
@@ -708,9 +724,8 @@ public class ComputerUtilCard {
/** /**
* getMostExpensivePermanentAI. * getMostExpensivePermanentAI.
* *
* @param all * @param all the all
* the all
* @return the card * @return the card
*/ */
public static Card getMostExpensivePermanentAI(final Iterable<Card> all) { public static Card getMostExpensivePermanentAI(final Iterable<Card> all) {
@@ -742,7 +757,7 @@ public class ComputerUtilCard {
} }
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);
@@ -901,7 +916,7 @@ public class ComputerUtilCard {
* <p> * <p>
* getMostProminentColor. * getMostProminentColor.
* </p> * </p>
* *
* @param list * @param list
* @return a {@link java.lang.String} object. * @return a {@link java.lang.String} object.
*/ */
@@ -926,22 +941,23 @@ public class ComputerUtilCard {
public static List<String> getColorByProminence(final List<Card> list) { public static List<String> getColorByProminence(final List<Card> list) {
int cntColors = MagicColor.WUBRG.length; int cntColors = MagicColor.WUBRG.length;
final List<Pair<Byte,Integer>> map = new ArrayList<>(); final List<Pair<Byte, Integer>> map = new ArrayList<>();
for (int i = 0; i < cntColors; i++) { for (int i = 0; i < cntColors; i++) {
map.add(MutablePair.of(MagicColor.WUBRG[i], 0)); map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
} }
for (final Card crd : list) { for (final Card crd : list) {
ColorSet color = crd.getColor(); ColorSet color = crd.getColor();
if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1)); if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue() + 1));
if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1)); if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue() + 1));
if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1)); if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue() + 1));
if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue()+1)); if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue() + 1));
if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue()+1)); if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue() + 1));
} }
Collections.sort(map, new Comparator<Pair<Byte,Integer>>() { Collections.sort(map, new Comparator<Pair<Byte, Integer>>() {
@Override public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) { @Override
public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
return o2.getValue() - o1.getValue(); return o2.getValue() - o1.getValue();
} }
}); });
@@ -978,64 +994,52 @@ public class ComputerUtilCard {
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));
} } else if (logic.equals("MostProminentInComputerDeck")) {
else if (logic.equals("MostProminentInComputerDeck")) {
chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices)); chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices));
} } else if (logic.equals("MostProminentDualInComputerDeck")) {
else if (logic.equals("MostProminentDualInComputerDeck")) {
List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai)); List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
chosen.add(prominence.get(0)); chosen.add(prominence.get(0));
chosen.add(prominence.get(1)); chosen.add(prominence.get(1));
} } else if (logic.equals("MostProminentInGame")) {
else if (logic.equals("MostProminentInGame")) {
chosen.add(getMostProminentColor(game.getCardsInGame(), colorChoices)); chosen.add(getMostProminentColor(game.getCardsInGame(), colorChoices));
} } else if (logic.equals("MostProminentHumanCreatures")) {
else if (logic.equals("MostProminentHumanCreatures")) {
CardCollectionView list = opp.getCreaturesInPlay(); CardCollectionView list = opp.getCreaturesInPlay();
if (list.isEmpty()) { if (list.isEmpty()) {
list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES); list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES);
} }
chosen.add(getMostProminentColor(list, colorChoices)); chosen.add(getMostProminentColor(list, colorChoices));
} } else if (logic.equals("MostProminentComputerControls")) {
else if (logic.equals("MostProminentComputerControls")) {
chosen.add(getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices)); chosen.add(getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
} } else if (logic.equals("MostProminentHumanControls")) {
else if (logic.equals("MostProminentHumanControls")) {
chosen.add(getMostProminentColor(opp.getCardsIn(ZoneType.Battlefield), colorChoices)); chosen.add(getMostProminentColor(opp.getCardsIn(ZoneType.Battlefield), colorChoices));
} } else if (logic.equals("MostProminentPermanent")) {
else if (logic.equals("MostProminentPermanent")) {
chosen.add(getMostProminentColor(game.getCardsIn(ZoneType.Battlefield), colorChoices)); chosen.add(getMostProminentColor(game.getCardsIn(ZoneType.Battlefield), colorChoices));
} } else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
chosen.add(getMostProminentColor(game.getCombat().getAttackers(), colorChoices)); chosen.add(getMostProminentColor(game.getCombat().getAttackers(), colorChoices));
} } else if (logic.equals("MostProminentInActivePlayerHand")) {
else if (logic.equals("MostProminentInActivePlayerHand")) {
chosen.add(getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices)); chosen.add(getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices));
} } else if (logic.equals("MostProminentInComputerDeckButGreen")) {
else if (logic.equals("MostProminentInComputerDeckButGreen")) { List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai)); if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
chosen.add(prominence.get(1)); chosen.add(prominence.get(1));
} else { } else {
chosen.add(prominence.get(0)); chosen.add(prominence.get(0));
} }
} } else if (logic.equals("MostExcessOpponentControls")) {
else if (logic.equals("MostExcessOpponentControls")) { int maxExcess = 0;
int maxExcess = 0; String bestColor = Constant.GREEN;
String bestColor = Constant.GREEN; for (byte color : MagicColor.WUBRG) {
for (byte color : MagicColor.WUBRG) { CardCollectionView ailist = ai.getColoredCardsInPlay(color);
CardCollectionView ailist = ai.getColoredCardsInPlay(color); CardCollectionView opplist = opp.getColoredCardsInPlay(color);
CardCollectionView opplist = opp.getColoredCardsInPlay(color);
int excess = evaluatePermanentList(opplist) - evaluatePermanentList(ailist); int excess = evaluatePermanentList(opplist) - evaluatePermanentList(ailist);
if (excess > maxExcess) { if (excess > maxExcess) {
maxExcess = excess; maxExcess = excess;
bestColor = MagicColor.toLongString(color); bestColor = MagicColor.toLongString(color);
} }
} }
chosen.add(bestColor); chosen.add(bestColor);
} } else if (logic.equals("MostProminentKeywordInComputerDeck")) {
else if (logic.equals("MostProminentKeywordInComputerDeck")) {
CardCollectionView list = ai.getAllCards(); CardCollectionView list = ai.getAllCards();
int m1 = 0; int m1 = 0;
String chosenColor = MagicColor.Constant.WHITE; String chosenColor = MagicColor.Constant.WHITE;
@@ -1048,8 +1052,7 @@ public class ComputerUtilCard {
} }
} }
chosen.add(chosenColor); chosen.add(chosenColor);
} } else if (logic.equals("HighestDevotionToColor")) {
else if (logic.equals("HighestDevotionToColor")) {
int curDevotion = 0; int curDevotion = 0;
String chosenColor = MagicColor.Constant.WHITE; String chosenColor = MagicColor.Constant.WHITE;
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand); CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
@@ -1075,7 +1078,7 @@ public class ComputerUtilCard {
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();
final Game game = ai.getGame(); final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
final PhaseType phaseType = ph.getPhase(); final PhaseType phaseType = ph.getPhase();
@@ -1085,7 +1088,7 @@ public class ComputerUtilCard {
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
@@ -1099,14 +1102,14 @@ public class ComputerUtilCard {
final Combat combat = new Combat(ai); final Combat combat = new Combat(ai);
aiAtk.removeBlocker(c); aiAtk.removeBlocker(c);
aiAtk.declareAttackers(combat); aiAtk.declareAttackers(combat);
if (!combat.getAttackers().isEmpty()) { if (!combat.getAttackers().isEmpty()) {
AiAttackController aiAtk2 = new AiAttackController(ai); AiAttackController aiAtk2 = new AiAttackController(ai);
final Combat combat2 = new Combat(ai); final Combat combat2 = new Combat(ai);
aiAtk2.declareAttackers(combat2); aiAtk2.declareAttackers(combat2);
if (combat.getAttackers().size() > combat2.getAttackers().size()) { if (combat.getAttackers().size() > combat2.getAttackers().size()) {
return true; return true;
} }
} }
} }
// interrupt 2: remove blocker to save my attacker // interrupt 2: remove blocker to save my attacker
@@ -1152,7 +1155,7 @@ public class ComputerUtilCard {
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
final SpellAbility topStack = stack.peekAbility(); final SpellAbility topStack = stack.peekAbility();
if (topStack.getActivatingPlayer().equals(opp) && c.equals(topStack.getTargetCard()) && topStack.isSpell()) { if (topStack.getActivatingPlayer().equals(opp) && c.equals(topStack.getTargetCard()) && topStack.isSpell()) {
return true; return true;
} }
} }
@@ -1168,7 +1171,7 @@ public class ComputerUtilCard {
valueBurn /= 2; //preserve option to burn to the face valueBurn /= 2; //preserve option to burn to the face
} }
if (valueBurn >= 0.8 && phaseType.isBefore(PhaseType.COMBAT_END)) { if (valueBurn >= 0.8 && phaseType.isBefore(PhaseType.COMBAT_END)) {
return true; return true;
} }
} }
@@ -1193,33 +1196,33 @@ public class ComputerUtilCard {
if (c.isLand()) { if (c.isLand()) {
valueTempo += 0.5f / opp.getLandsInPlay().size(); //set back opponent's mana valueTempo += 0.5f / opp.getLandsInPlay().size(); //set back opponent's mana
if ("Land".equals(sa.getParam("ValidTgts")) && ph.getPhase().isAfter(PhaseType.COMBAT_END)) { if ("Land".equals(sa.getParam("ValidTgts")) && ph.getPhase().isAfter(PhaseType.COMBAT_END)) {
valueTempo += 0.5; // especially when nothing else can be targeted valueTempo += 0.5; // especially when nothing else can be targeted
} }
} }
if (!ph.isPlayerTurn(ai) && ph.getPhase().equals(PhaseType.END_OF_TURN)) { if (!ph.isPlayerTurn(ai) && ph.getPhase().equals(PhaseType.END_OF_TURN)) {
valueTempo *= 2; //prefer to cast at opponent EOT valueTempo *= 2; //prefer to cast at opponent EOT
} }
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()) {
// the base value for evaluate creature is 100 // the base value for evaluate creature is 100
threat += (-1 + 1.0f * evaluateCreature(c) / 100) / costRemoval; threat += (-1 + 1.0f * evaluateCreature(c) / 100) / costRemoval;
if (ai.getLife() > 0 && ComputerUtilCombat.canAttackNextTurn(c)) { if (ai.getLife() > 0 && ComputerUtilCombat.canAttackNextTurn(c)) {
Combat combat = game.getCombat(); Combat combat = game.getCombat();
threat += 1.0f * ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) / ai.getLife(); threat += 1.0f * ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) / ai.getLife();
//TODO:add threat from triggers and other abilities (ie. Master of Cruelties) //TODO:add threat from triggers and other abilities (ie. Master of Cruelties)
} }
if (ph.isPlayerTurn(ai) && phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (ph.isPlayerTurn(ai) && phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
threat *= 0.1f; threat *= 0.1f;
} }
if (!ph.isPlayerTurn(ai) && if (!ph.isPlayerTurn(ai) &&
(phaseType.isBefore(PhaseType.COMBAT_BEGIN) || phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) { (phaseType.isBefore(PhaseType.COMBAT_BEGIN) || phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
threat *= 0.1f; threat *= 0.1f;
} }
} else if (c.isPlaneswalker()) { } else if (c.isPlaneswalker()) {
threat = 1; threat = 1;
} else if (aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) { } else if (aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) {
@@ -1296,20 +1299,22 @@ public class ComputerUtilCard {
/** /**
* Decides if the "pump" is worthwhile * Decides if the "pump" is worthwhile
* @param ai casting player *
* @param sa Pump* or CounterPut* * @param ai casting player
* @param c target of sa * @param sa Pump* or CounterPut*
* @param c target of sa
* @param toughness +T * @param toughness +T
* @param power +P * @param power +P
* @param keywords additional keywords from sa (only for Pump) * @param keywords additional keywords from sa (only for Pump)
* @return * @return
*/ */
public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness, public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness,
final int power, final List<String> keywords) { final int power, final List<String> keywords) {
return shouldPumpCard(ai, sa, c, toughness, power, keywords, false); return shouldPumpCard(ai, sa, c, toughness, power, keywords, false);
} }
public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness, public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness,
final int power, final List<String> keywords, boolean immediately) { final int power, final List<String> keywords, boolean immediately) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final PhaseHandler phase = game.getPhaseHandler(); final PhaseHandler phase = game.getPhaseHandler();
final Combat combat = phase.getCombat(); final Combat combat = phase.getCombat();
@@ -1452,7 +1457,9 @@ public class ComputerUtilCard {
boolean pumpedWillDie = false; boolean pumpedWillDie = false;
final boolean isAttacking = combat.isAttacking(c); final boolean isAttacking = combat.isAttacking(c);
if ((isBerserk && isAttacking) || loseCardAtEOT) { pumpedWillDie = true; } if ((isBerserk && isAttacking) || loseCardAtEOT) {
pumpedWillDie = true;
}
if (isAttacking) { if (isAttacking) {
pumpedCombat.addAttacker(pumped, opp); pumpedCombat.addAttacker(pumped, opp);
@@ -1475,7 +1482,7 @@ public class ComputerUtilCard {
} }
//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)) {
// hack because attackerWouldBeDestroyed() // hack because attackerWouldBeDestroyed()
// does not check for Indestructible when computing lethal damage // does not check for Indestructible when computing lethal damage
@@ -1492,8 +1499,8 @@ public class ComputerUtilCard {
} }
if (survivor) { if (survivor) {
for (Card o : opposing) { for (Card o : opposing) {
if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat) if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat)
&& !(o.hasSVar("SacMe") && Integer.parseInt(o.getSVar("SacMe")) > 2)) { && !(o.hasSVar("SacMe") && Integer.parseInt(o.getSVar("SacMe")) > 2)) {
if (isAttacking) { if (isAttacking) {
if (ComputerUtilCombat.blockerWouldBeDestroyed(opp, o, pumpedCombat)) { if (ComputerUtilCombat.blockerWouldBeDestroyed(opp, o, pumpedCombat)) {
return true; return true;
@@ -1526,9 +1533,9 @@ public class ComputerUtilCard {
dmg = 0; dmg = 0;
} }
if (c.hasKeyword(Keyword.TRAMPLE) || keywords.contains("Trample")) { if (c.hasKeyword(Keyword.TRAMPLE) || keywords.contains("Trample")) {
for (Card b : combat.getBlockers(c)) { for (Card b : combat.getBlockers(c)) {
pumpedDmg -= ComputerUtilCombat.getDamageToKill(b, false); pumpedDmg -= ComputerUtilCombat.getDamageToKill(b, false);
} }
} else { } else {
pumpedDmg = 0; pumpedDmg = 0;
} }
@@ -1615,7 +1622,7 @@ public class ComputerUtilCard {
// if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose // if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose
// (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration) // (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration)
if (ai.getController() instanceof PlayerControllerAi) { if (ai.getController() instanceof PlayerControllerAi) {
boolean aggr = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY) boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY)
|| sa.hasParam("AtEOT"); || sa.hasParam("AtEOT");
if (!aggr) { if (!aggr) {
return false; return false;
@@ -1635,44 +1642,45 @@ public class ComputerUtilCard {
boolean isHeldCombatTrick = combatTrick && wantToHoldTrick; boolean isHeldCombatTrick = combatTrick && wantToHoldTrick;
if (isHeldCombatTrick) { if (isHeldCombatTrick) {
if (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.TRICK_ATTACKERS)) { if (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.TRICK_ATTACKERS)) {
// Attempt to hold combat tricks until blockers are declared, and try to lure the opponent into blocking // Attempt to hold combat tricks until blockers are declared, and try to lure the opponent into blocking
// (The AI will only do it for one attacker at the moment, otherwise it risks running his attackers into // (The AI will only do it for one attacker at the moment, otherwise it risks running his attackers into
// an army of opposing blockers with only one combat trick in hand) // an army of opposing blockers with only one combat trick in hand)
// Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use // Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use
// the combat trick // the combat trick
boolean reserved = false; boolean reserved = false;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
reserved = ((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false); reserved = ((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false);
// Only proceed with this if we could actually reserve mana // Only proceed with this if we could actually reserve mana
if (reserved) { if (reserved) {
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.MANDATORY_ATTACKERS); AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.TRICK_ATTACKERS); AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.TRICK_ATTACKERS);
return false; return false;
} }
} }
} else { } else {
// Don't try to mix "lure" and "precast" paradigms for combat tricks, since that creates issues with // Don't try to mix "lure" and "precast" paradigms for combat tricks, since that creates issues with
// the AI overextending the attack // the AI overextending the attack
return false; return false;
} }
} }
return simAI || MyRandom.getRandom().nextFloat() < chance; return simAI || MyRandom.getRandom().nextFloat() < chance;
} }
/** /**
* Apply "pump" ability and return modified creature * Apply "pump" ability and return modified creature
* @param ai casting player *
* @param sa Pump* or CounterPut* * @param ai casting player
* @param c target of sa * @param sa Pump* or CounterPut*
* @param c target of sa
* @param toughness +T * @param toughness +T
* @param power +P * @param power +P
* @param keywords additional keywords from sa (only for Pump) * @param keywords additional keywords from sa (only for Pump)
* @return * @return
*/ */
public static Card getPumpedCreature(final Player ai, final SpellAbility sa, public static Card getPumpedCreature(final Player ai, final SpellAbility sa,
final Card c, int toughness, int power, final List<String> keywords) { final Card c, int toughness, int power, final List<String> keywords) {
Card pumped = CardFactory.copyCard(c, false); Card pumped = CardFactory.copyCard(c, false);
pumped.setSickness(c.hasSickness()); pumped.setSickness(c.hasSickness());
final long timestamp = c.getGame().getNextTimestamp(); final long timestamp = c.getGame().getNextTimestamp();
@@ -1740,8 +1748,9 @@ public class ComputerUtilCard {
/** /**
* 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 vCard creature to work with * @param game game instance to work with
* @param vCard creature to work with
* @param exclude list of cards to exclude when considering ability sources, accepts null * @param exclude list of cards to exclude when considering ability sources, accepts null
*/ */
public static void applyStaticContPT(final Game game, Card vCard, final CardCollectionView exclude) { public static void applyStaticContPT(final Game game, Card vCard, final CardCollectionView exclude) {
@@ -1787,6 +1796,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
* @param sa Pump* or CounterPut* * @param sa Pump* or CounterPut*
* @return * @return
@@ -1889,7 +1899,7 @@ public class ComputerUtilCard {
int priorityRemovalThreshold = 0; int priorityRemovalThreshold = 0;
int lifeInDanger = 5; int lifeInDanger = 5;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
enablePriorityRemoval = aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); enablePriorityRemoval = aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE);
priorityRemovalThreshold = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD); priorityRemovalThreshold = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD);
priorityRemovalOnlyInDanger = aic.getBooleanProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR); priorityRemovalOnlyInDanger = aic.getBooleanProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR);
@@ -1973,7 +1983,7 @@ public class ComputerUtilCard {
if (needsToPlay.equalsIgnoreCase("WillAttack")) { if (needsToPlay.equalsIgnoreCase("WillAttack")) {
if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ? return doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ?
AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects; AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects;
} else { } else {
return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc. return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc.
} }
@@ -2083,10 +2093,19 @@ public class ComputerUtilCard {
public static boolean isCardRemAIDeck(final Card card) { public static boolean isCardRemAIDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemAIDecks(); return card.getRules() != null && card.getRules().getAiHints().getRemAIDecks();
} }
public static boolean isCardRemRandomDeck(final Card card) { public static boolean isCardRemRandomDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks(); return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks();
} }
public static boolean isCardRemNonCommanderDeck(final Card card) { public static boolean isCardRemNonCommanderDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks(); return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks();
} }
static class LandEvaluator implements Function<Card, Integer> {
@Override
public Integer apply(Card card) {
return GameStateEvaluator.evaluateLand(card);
}
}
} }