- Better AI for fetching cards.

This commit is contained in:
mcrawford620
2012-07-05 05:45:01 +00:00
parent a63e60d07c
commit 898697a212
8 changed files with 358 additions and 217 deletions

View File

@@ -7,7 +7,6 @@ K:Trample
T:Mode$ Attacks | ValidCard$ Card.Self | DelayedTrigger$ DelTrig | TriggerDescription$ When CARDNAME attacks, sacrifice it at end of combat. T:Mode$ Attacks | ValidCard$ Card.Self | DelayedTrigger$ DelTrig | TriggerDescription$ When CARDNAME attacks, sacrifice it at end of combat.
SVar:DelTrig:Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | Execute$ TrigSacrifice | TriggerDescription$ Sacrifice CARDNAME at end of combat. SVar:DelTrig:Mode$ Phase | Phase$ EndCombat | ValidPlayer$ Player | Execute$ TrigSacrifice | TriggerDescription$ Sacrifice CARDNAME at end of combat.
SVar:TrigSacrifice:AB$ Sacrifice | Cost$ 0 | Defined$ Self SVar:TrigSacrifice:AB$ Sacrifice | Cost$ 0 | Defined$ Self
SVar:RemAIDeck:True
SVar:Rarity:Uncommon SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/crumbling_colossus.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/crumbling_colossus.jpg
SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/204.jpg SetInfo:M12|Uncommon|http://magiccards.info/scans/en/m12/204.jpg

View File

@@ -3,7 +3,6 @@ ManaCost:2 B B
Types:Sorcery Types:Sorcery
Text:no text Text:no text
A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library. A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library.
SVar:RemAIDeck:True
SVar:Rarity:Uncommon SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/diabolic_tutor.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/diabolic_tutor.jpg
SetInfo:8ED|Uncommon|http://magiccards.info/scans/en/8e/128.jpg SetInfo:8ED|Uncommon|http://magiccards.info/scans/en/8e/128.jpg

View File

@@ -3,7 +3,6 @@ ManaCost:G
Types:Instant Types:Instant
Text:no text Text:no text
A:SP$ ChangeZone | Cost$ G | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target card in your graveyard | ValidTgts$ Card.YouCtrl | SpellDescription$ Put target card from your graveyard on top of your library. A:SP$ ChangeZone | Cost$ G | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target card in your graveyard | ValidTgts$ Card.YouCtrl | SpellDescription$ Put target card from your graveyard on top of your library.
SVar:RemAIDeck:True
SVar:Rarity:Common SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/reclaim.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/reclaim.jpg
SetInfo:EXO|Common|http://magiccards.info/scans/en/ex/120.jpg SetInfo:EXO|Common|http://magiccards.info/scans/en/ex/120.jpg

View File

@@ -43,9 +43,11 @@ import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellAbilityStackInstance;
import forge.card.spellability.SpellPermanent; import forge.card.spellability.SpellPermanent;
import forge.card.spellability.Target; import forge.card.spellability.Target;
import forge.game.phase.Combat;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.ComputerUtil; import forge.game.player.ComputerUtil;
import forge.game.player.ComputerUtilBlock;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -1111,12 +1113,14 @@ public final class AbilityFactoryChangeZone {
} }
// Improve the AI for fetching. // Improve the AI for fetching.
Card c; Card c = null;
if (params.containsKey("AtRandom")) { if (params.containsKey("AtRandom")) {
c = CardUtil.getRandom(fetchList.toArray()); c = CardUtil.getRandom(fetchList.toArray());
} else if (defined) { } else if (defined) {
c = fetchList.get(0); c = fetchList.get(0);
} else { } else {
fetchList.shuffle();
// Save a card as a default, in case we can't find anything suitable.
Card first = fetchList.get(0); Card first = fetchList.get(0);
fetchList = fetchList.filter(new CardListFilter() { fetchList = fetchList.filter(new CardListFilter() {
@Override @Override
@@ -1130,19 +1134,15 @@ public final class AbilityFactoryChangeZone {
} }
}); });
if (type.contains("Basic")) { if (type.contains("Basic")) {
c = AbilityFactoryChangeZone.basicManaFixing(fetchList); c = AbilityFactoryChangeZone.basicManaFixing(fetchList);
} else if (AbilityFactoryChangeZone.areAllBasics(type)) { } else if (AbilityFactoryChangeZone.areAllBasics(type)) {
c = AbilityFactoryChangeZone.basicManaFixing(fetchList, type); c = AbilityFactoryChangeZone.basicManaFixing(fetchList, type);
} else if (fetchList.getNotType("Creature").size() == 0) { } else if (fetchList.getNotType("Creature").size() == 0) {
c = CardFactoryUtil.getBestCreatureAI(fetchList); // if only c = AbilityFactoryChangeZone.chooseCreature(fetchList);
// creatures
// take the
// best
} else if (ZoneType.Battlefield.equals(destination) || ZoneType.Graveyard.equals(destination)) { } else if (ZoneType.Battlefield.equals(destination) || ZoneType.Graveyard.equals(destination)) {
c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false);
} else if (ZoneType.Exile.equals(destination)) { } else if (ZoneType.Exile.equals(destination)) {
// Exiling your own stuff, if Exiling opponents stuff choose // Exiling your own stuff, if Exiling opponents stuff choose best
// best
if (destZone.getPlayer().isHuman()) { if (destZone.getPlayer().isHuman()) {
c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false); c = CardFactoryUtil.getMostExpensivePermanentAI(fetchList, sa, false);
} else { } else {
@@ -1153,13 +1153,49 @@ public final class AbilityFactoryChangeZone {
if (origin.contains(ZoneType.Library) && !fetchList.getNotName(card.getName()).isEmpty()) { if (origin.contains(ZoneType.Library) && !fetchList.getNotName(card.getName()).isEmpty()) {
fetchList = fetchList.getNotName(card.getName()); fetchList = fetchList.getNotName(card.getName());
} }
fetchList.shuffle(); Player ai = AllZone.getComputerPlayer();
c = fetchList.get(0); // Does AI need a land?
CardList hand = ai.getCardsIn(ZoneType.Hand);
System.out.println("Lands in hand = " + hand.filter(CardListFilter.LANDS).size() + ", on battlefield = " + ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size());
if (hand.filter(CardListFilter.LANDS).size() == 0 && ai.getCardsIn(ZoneType.Battlefield).filter(CardListFilter.LANDS).size() < 4) {
boolean canCastSomething = false;
for (Card cardInHand : hand) {
canCastSomething |= ComputerUtil.payManaCost(cardInHand.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false);
}
if (!canCastSomething) {
System.out.println("Pulling a land as there are none in hand, less than 4 on the board, and nothing in hand is castable.");
c = basicManaFixing(fetchList);
}
}
if (c == null) {
System.out.println("Don't need a land or none available; trying for a creature.");
fetchList = fetchList.getNotType("Land");
// Prefer to pull a creature, generally more useful for AI.
c = chooseCreature(fetchList.getType("Creature"));
}
if (c == null) { // Could not find a creature.
if (ai.getLife() <= 5) { // Desperate?
// Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardListUtil.sortByMostExpensive(fetchList);
for (Card potentialCard : fetchList) {
if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) {
c = potentialCard;
break;
}
}
} else {
// Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
c = CardFactoryUtil.getBestAI(fetchList);
}
}
} }
if (c == null) { if (c == null) {
c = first; c = first;
} }
} }
System.out.println("Chose " + c.toString());
fetched.add(c); fetched.add(c);
fetchList.remove(c); fetchList.remove(c);
@@ -1336,6 +1372,38 @@ public final class AbilityFactoryChangeZone {
return true; return true;
} }
/**
* Some logic for picking a creature card from a list.
* @param list
* @return Card
*/
private static Card chooseCreature(CardList list) {
Card card = null;
Combat combat = new Combat();
combat.initiatePossibleDefenders(AllZone.getComputerPlayer());
CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer());
for (Card att : attackers) {
combat.addAttacker(att);
}
combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer()));
System.out.println("Life would remain = " + CombatUtil.lifeThatWouldRemain(combat));
if (CombatUtil.lifeInDanger(combat)) {
// need something AI can cast now
CardListUtil.sortByEvaluateCreature(list);
for (Card c : list) {
if (ComputerUtil.payManaCost(c.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) {
card = c;
break;
}
}
} else {
// not urgent, get the largest creature possible
card = CardFactoryUtil.getBestCreatureAI(list);
}
return card;
}
// ************************************************************************************* // *************************************************************************************
// **************** Known Origin (Battlefield/Graveyard/Exile) // **************** Known Origin (Battlefield/Graveyard/Exile)
@@ -1508,6 +1576,7 @@ public final class AbilityFactoryChangeZone {
CardList list = AllZoneUtil.getCardsIn(origin); CardList list = AllZoneUtil.getCardsIn(origin);
list = list.getValidCards(tgt.getValidTgts(), AllZone.getComputerPlayer(), source); list = list.getValidCards(tgt.getValidTgts(), AllZone.getComputerPlayer(), source);
list = list.getNotName(source.getName()); // Don't get the same card back.
if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) { if (list.size() < tgt.getMinTargets(sa.getSourceCard(), sa)) {
return false; return false;
@@ -1523,7 +1592,6 @@ public final class AbilityFactoryChangeZone {
aiPermanents = aiPermanents.filter(new CardListFilter() { aiPermanents = aiPermanents.filter(new CardListFilter() {
@Override @Override
public boolean addCard(final Card c) { public boolean addCard(final Card c) {
System.out.println("Not Changing Zone");
return !c.getSVar("Targeting").equals("Dies"); return !c.getSVar("Targeting").equals("Dies");
} }
}); });
@@ -1574,15 +1642,8 @@ public final class AbilityFactoryChangeZone {
} }
// counters TODO check good and // counters TODO check good and
// bad counters // bad counters
return SpellPermanent.checkETBEffects(c, null, null); // checks // checks only if there is a dangerous ETB effect
// only return SpellPermanent.checkETBEffects(c, null, null);
// if
// there
// is
// a
// dangerous
// ETB
// effect
} }
}); });
if (!aiPermanents.isEmpty()) { if (!aiPermanents.isEmpty()) {
@@ -1666,11 +1727,34 @@ public final class AbilityFactoryChangeZone {
} else { } else {
choice = mostExpensive; choice = mostExpensive;
} }
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
CardList nonLands = list.getNotType("Land");
// Prefer to pull a creature, generally more useful for AI.
choice = chooseCreature(nonLands.getType("Creature"));
if (choice == null) { // Could not find a creature.
if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate?
// Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardListUtil.sortByMostExpensive(nonLands);
for (Card potentialCard : nonLands) {
if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) {
choice = potentialCard;
break;
}
}
} else {
// Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
choice = CardFactoryUtil.getBestAI(nonLands);
}
}
if (choice == null) {
// No creatures or spells?
list.shuffle();
choice = list.get(0);
}
} else { } else {
// TODO AI needs more improvement to it's retrieval (reuse choice = CardFactoryUtil.getBestAI(list);
// some code from spReturn here)
list.shuffle();
choice = list.get(0);
} }
} }
if (choice == null) { // can't find anything left if (choice == null) { // can't find anything left
@@ -1680,7 +1764,9 @@ public final class AbilityFactoryChangeZone {
} }
return false; return false;
} else { } else {
// TODO is this good enough? for up to amounts? if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false;
}
break; break;
} }
} }
@@ -1691,7 +1777,7 @@ public final class AbilityFactoryChangeZone {
return true; return true;
} }
/** /**
* <p> * <p>
* changeKnownUnpreferredTarget. * changeKnownUnpreferredTarget.
@@ -1754,11 +1840,34 @@ public final class AbilityFactoryChangeZone {
choice = CardFactoryUtil.getBestCreatureToBounceAI(list); choice = CardFactoryUtil.getBestCreatureToBounceAI(list);
} else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) { } else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) {
choice = CardFactoryUtil.getMostExpensivePermanentAI(list, sa, false); choice = CardFactoryUtil.getMostExpensivePermanentAI(list, sa, false);
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
CardList nonLands = list.getNotType("Land");
// Prefer to pull a creature, generally more useful for AI.
choice = chooseCreature(nonLands.getType("Creature"));
if (choice == null) { // Could not find a creature.
if (AllZone.getComputerPlayer().getLife() <= 5) { // Desperate?
// Get something AI can cast soon.
System.out.println("5 Life or less, trying to find something castable.");
CardListUtil.sortByMostExpensive(nonLands);
for (Card potentialCard : nonLands) {
if (ComputerUtil.payManaCost(potentialCard.getFirstSpellAbility(), AllZone.getComputerPlayer(), true, 0, false)) {
choice = potentialCard;
break;
}
}
} else {
// Get the best card in there.
System.out.println("No creature and lots of life, finding something good.");
choice = CardFactoryUtil.getBestAI(nonLands);
}
}
if (choice == null) {
// No creatures or spells?
list.shuffle();
choice = list.get(0);
}
} else { } else {
// TODO AI needs more improvement to it's retrieval (reuse choice = CardFactoryUtil.getBestAI(list);
// some code from spReturn here)
list.shuffle();
choice = list.get(0);
} }
} }
if (choice == null) { // can't find anything left if (choice == null) { // can't find anything left
@@ -1766,7 +1875,9 @@ public final class AbilityFactoryChangeZone {
tgt.resetTargets(); tgt.resetTargets();
return false; return false;
} else { } else {
// TODO is this good enough? for up to amounts? if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false;
}
break; break;
} }
} }

View File

@@ -36,12 +36,9 @@ import forge.card.spellability.AbilitySub;
import forge.card.spellability.Spell; import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target; import forge.card.spellability.Target;
import forge.game.phase.Combat;
import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.ComputerUtil; import forge.game.player.ComputerUtil;
import forge.game.player.ComputerUtilBlock;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiUtils; import forge.gui.GuiUtils;
@@ -1018,7 +1015,7 @@ public class AbilityFactoryPermanentState {
} }
return false; return false;
} else { } else {
if (!tapCastLessThanMax(source)) { if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false; return false;
} }
break; break;
@@ -1044,7 +1041,7 @@ public class AbilityFactoryPermanentState {
} }
return false; return false;
} else { } else {
if (!tapCastLessThanMax(source)) { if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false; return false;
} }
break; break;
@@ -1058,31 +1055,6 @@ public class AbilityFactoryPermanentState {
return true; return true;
} }
/**
* Is it OK to cast this for less than the Max Targets?
* @param source
*/
private static boolean tapCastLessThanMax(final Card source) {
boolean ret = true;
if (source.getManaCost().countX() > 0) {
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
} else {
// Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat();
combat.initiatePossibleDefenders(AllZone.getComputerPlayer());
CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer());
for (Card att : attackers) {
combat.addAttacker(att);
}
combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer()));
if (!CombatUtil.lifeInDanger(combat)) {
// Otherwise, return false. Do not play now.
ret = false;
}
}
return ret;
}
/** /**
* <p> * <p>
* tapUnpreferredTargeting. * tapUnpreferredTargeting.
@@ -1168,7 +1140,7 @@ public class AbilityFactoryPermanentState {
} }
return false; return false;
} else { } else {
if (!tapCastLessThanMax(source)) { if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false; return false;
} }
break; break;
@@ -1189,7 +1161,7 @@ public class AbilityFactoryPermanentState {
} }
return false; return false;
} else { } else {
if (!tapCastLessThanMax(source)) { if (!ComputerUtil.shouldCastLessThanMax(source)) {
return false; return false;
} }
break; break;

View File

@@ -266,7 +266,7 @@ public class CardFactoryInstants {
public boolean canPlayAI() { public boolean canPlayAI() {
final CardList graveList = AllZone.getHumanPlayer().getCardsIn(ZoneType.Graveyard); final CardList graveList = AllZone.getHumanPlayer().getCardsIn(ZoneType.Graveyard);
final int maxX = ComputerUtil.getAvailableMana().size() - 1; final int maxX = ComputerUtil.getAvailableMana(true).size() - 1;
return (maxX >= 3) && (graveList.size() > 0); return (maxX >= 3) && (graveList.size() > 0);
} }
}; };

View File

@@ -393,7 +393,7 @@ public class CardFactorySorceries {
// the computer will at least destroy 2 more human creatures // the computer will at least destroy 2 more human creatures
return ((computer.size() < (human.size() - 1)) || ((AllZone.getComputerPlayer().getLife() < 7) && !human return ((computer.size() < (human.size() - 1)) || ((AllZone.getComputerPlayer().getLife() < 7) && !human
.isEmpty())) && (ComputerUtil.getAvailableMana().size() >= 7); .isEmpty())) && (ComputerUtil.getAvailableMana(true).size() >= 7);
} }
}; // SpellAbility }; // SpellAbility
@@ -905,7 +905,7 @@ public class CardFactorySorceries {
@Override @Override
public boolean canPlayAI() { public boolean canPlayAI() {
final int maxX = ComputerUtil.getAvailableMana().size() - 1; final int maxX = ComputerUtil.getAvailableMana(true).size() - 1;
final int humanLife = AllZone.getHumanPlayer().getLife(); final int humanLife = AllZone.getHumanPlayer().getLife();
if (maxX >= humanLife) { if (maxX >= humanLife) {
targetPlayers.add(AllZone.getHumanPlayer()); targetPlayers.add(AllZone.getHumanPlayer());

View File

@@ -125,7 +125,7 @@ public class ComputerUtil {
if (sa instanceof AbilityStatic) { if (sa instanceof AbilityStatic) {
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0)) { if (cost == null && ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true)) {
sa.resolve(); sa.resolve();
} else { } else {
final CostPayment pay = new CostPayment(cost, sa); final CostPayment pay = new CostPayment(cost, sa);
@@ -473,7 +473,7 @@ public class ComputerUtil {
* @return a boolean. * @return a boolean.
*/ */
public static boolean canPayCost(final SpellAbility sa, final Player player) { public static boolean canPayCost(final SpellAbility sa, final Player player) {
if (!ComputerUtil.payManaCost(sa, player, true, 0)) { if (!ComputerUtil.payManaCost(sa, player, true, 0, true)) {
return false; return false;
} }
@@ -510,7 +510,7 @@ public class ComputerUtil {
int xMana = 0; int xMana = 0;
for (int i = 1; i < 99; i++) { for (int i = 1; i < 99; i++) {
if (!ComputerUtil.payManaCost(sa, player, true, i)) { if (!ComputerUtil.payManaCost(sa, player, true, i, true)) {
break; break;
} }
xMana = i; xMana = i;
@@ -563,7 +563,7 @@ public class ComputerUtil {
* a {@link forge.card.spellability.SpellAbility} object. * a {@link forge.card.spellability.SpellAbility} object.
*/ */
public static void payManaCost(final SpellAbility sa) { public static void payManaCost(final SpellAbility sa) {
ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0); ComputerUtil.payManaCost(sa, AllZone.getComputerPlayer(), false, 0, true);
} }
/** /**
@@ -579,67 +579,17 @@ public class ComputerUtil {
* (is for canPayCost, if true does not change the game state) * (is for canPayCost, if true does not change the game state)
* @param extraMana * @param extraMana
* a int. * a int.
* @param checkPlayable
* should we check if playable? use for hypothetical "can AI play this"
* @return a boolean. * @return a boolean.
* @since 1.0.15 * @since 1.0.15
*/ */
public static boolean payManaCost(final SpellAbility sa, final Player player, final boolean test, public static boolean payManaCost(final SpellAbility sa, final Player player, final boolean test,
final int extraMana) { final int extraMana, boolean checkPlayable) {
final String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost(); ManaCost cost = calculateManaCost(sa, test, extraMana);
ManaCost cost = new ManaCost(mana);
cost = Singletons.getModel().getGameAction().getSpellCostChange(sa, cost);
final ManaPool manapool = player.getManaPool(); final ManaPool manapool = player.getManaPool();
final Card card = sa.getSourceCard();
// Tack xMana Payments into mana here if X is a set value
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0)) {
int manaToAdd = 0;
if (test && (extraMana > 0)) {
manaToAdd = extraMana * cost.getXcounter();
} else {
// For Count$xPaid set PayX in the AFs then use that here
// Else calculate it as appropriate.
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
if (!card.getSVar(xSvar).equals("")) {
if (xSvar.equals("PayX")) {
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
} else {
manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
}
}
cost.increaseColorlessMana(manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd);
}
}
// Make mana needed to avoid negative effect a mandatory cost for the AI
if (card.getSVar("ManaNeededToAvoidNegativeEffect") != "") {
final String[] negEffects = card.getSVar("ManaNeededToAvoidNegativeEffect").split(",");
int amountAdded = 0;
for (int nStr = 0; nStr < negEffects.length; nStr++) {
// convert long color strings to short color strings
if (negEffects[nStr].length() > 1) {
negEffects[nStr] = InputPayManaCostUtil.getShortColorString(negEffects[nStr]);
}
// make mana mandatory for AI
if (!cost.isColor(negEffects[nStr])) {
cost.combineManaCost(negEffects[nStr]);
amountAdded++;
}
}
cost.setManaNeededToAvoidNegativeEffect(negEffects);
// TODO: should it be an error condition if amountAdded is greater
// than the colorless in the original cost? (ArsenalNut - 120102)
// adjust colorless amount to account for added mana
cost.decreaseColorlessMana(amountAdded);
}
cost = manapool.payManaFromPool(sa, cost); cost = manapool.payManaFromPool(sa, cost);
if (cost.isPaid()) { if (cost.isPaid()) {
@@ -649,82 +599,12 @@ public class ComputerUtil {
} }
// get map of mana abilities // get map of mana abilities
final HashMap<String, ArrayList<AbilityMana>> manaAbilityMap = ComputerUtil.mapManaSources(player); final HashMap<String, ArrayList<AbilityMana>> manaAbilityMap = ComputerUtil.mapManaSources(player, checkPlayable);
// initialize ArrayList list for mana needed // initialize ArrayList list for mana needed
final ArrayList<ArrayList<AbilityMana>> partSources = new ArrayList<ArrayList<AbilityMana>>(); final ArrayList<ArrayList<AbilityMana>> partSources = new ArrayList<ArrayList<AbilityMana>>();
final ArrayList<Integer> partPriority = new ArrayList<Integer>(); final ArrayList<Integer> partPriority = new ArrayList<Integer>();
final String[] costParts = cost.toString().replace("X ", "").replace("P", "").split(" "); final String[] costParts = cost.toString().replace("X ", "").replace("P", "").split(" ");
Boolean foundAllSources = true; Boolean foundAllSources = findManaSources(manaAbilityMap, partSources, partPriority, costParts);
if (manaAbilityMap.isEmpty()) {
foundAllSources = false;
} else {
final String[] shortColors = { "W", "U", "B", "R", "G" };
// loop over cost parts
for (int nPart = 0; nPart < costParts.length; nPart++) {
final ArrayList<AbilityMana> srcFound = new ArrayList<AbilityMana>();
// Test for:
// 1) Colorless
// 2) Split e.g. 2/G
// 3) Hybrid e.g. U/G
// defaults to single short color
if (costParts[nPart].matches("[0-9]+")) { // Colorless
srcFound.addAll(manaAbilityMap.get("1"));
} else if (costParts[nPart].contains("2/")) { // Split
final String colorKey = costParts[nPart].replace("2/", "");
// add specified color sources first
if (manaAbilityMap.containsKey(colorKey)) {
srcFound.addAll(manaAbilityMap.get(colorKey));
}
// add other available colors
for (final String color : shortColors) {
if (!colorKey.contains(color)) {
// Is source available?
if (manaAbilityMap.containsKey(color)) {
srcFound.addAll(manaAbilityMap.get(color));
}
}
}
} else if (costParts[nPart].length() > 1) { // Hybrid
final String firstColor = costParts[nPart].substring(0, 1);
final String secondColor = costParts[nPart].substring(2);
final Boolean foundFirst = manaAbilityMap.containsKey(firstColor);
final Boolean foundSecond = manaAbilityMap.containsKey(secondColor);
if (foundFirst || foundSecond) {
if (!foundFirst) {
srcFound.addAll(manaAbilityMap.get(secondColor));
} else if (!foundSecond) {
srcFound.addAll(manaAbilityMap.get(firstColor));
} else if (manaAbilityMap.get(firstColor).size() > manaAbilityMap.get(secondColor).size()) {
srcFound.addAll(manaAbilityMap.get(firstColor));
srcFound.addAll(manaAbilityMap.get(secondColor));
} else {
srcFound.addAll(manaAbilityMap.get(secondColor));
srcFound.addAll(manaAbilityMap.get(firstColor));
}
}
} else { // single color
if (manaAbilityMap.containsKey(costParts[nPart])) {
srcFound.addAll(manaAbilityMap.get(costParts[nPart]));
}
}
// add sources to array lists
partSources.add(nPart, srcFound);
// add to sorted priority list
if (srcFound.size() > 0) {
int i;
for (i = 0; i < partPriority.size(); i++) {
if (srcFound.size() <= partSources.get(i).size()) {
break;
}
}
partPriority.add(i, nPart);
} else {
foundAllSources = false;
break;
}
}
}
if (!foundAllSources) { if (!foundAllSources) {
if (!test) { if (!test) {
// real payment should not arrive here // real payment should not arrive here
@@ -746,8 +626,7 @@ public class ComputerUtil {
final int nPart = partPriority.get(nPriority); final int nPart = partPriority.get(nPriority);
final ArrayList<AbilityMana> manaAbilities = partSources.get(nPart); final ArrayList<AbilityMana> manaAbilities = partSources.get(nPart);
final ManaCost costPart = new ManaCost(costParts[nPart]); final ManaCost costPart = new ManaCost(costParts[nPart]);
// Loop over mana abilities that can be used to current mana cost // Loop over mana abilities that can be used to current mana cost part
// part
for (final AbilityMana m : manaAbilities) { for (final AbilityMana m : manaAbilities) {
final Card sourceCard = m.getSourceCard(); final Card sourceCard = m.getSourceCard();
@@ -758,13 +637,12 @@ public class ComputerUtil {
// Check if AI can still play this mana ability // Check if AI can still play this mana ability
m.setActivatingPlayer(player); m.setActivatingPlayer(player);
// if the AI can't pay the additional costs skip the mana // if the AI can't pay the additional costs skip the mana ability
// ability if (m.getPayCosts() != null && checkPlayable) {
if (m.getPayCosts() != null) {
if (!ComputerUtil.canPayAdditionalCosts(m, player)) { if (!ComputerUtil.canPayAdditionalCosts(m, player)) {
continue; continue;
} }
} else if (sourceCard.isTapped()) { } else if (sourceCard.isTapped() && checkPlayable) {
continue; continue;
} }
@@ -874,6 +752,157 @@ public class ComputerUtil {
} // payManaCost() } // payManaCost()
/**
* Find all mana sources.
* @param manaAbilityMap
* @param partSources
* @param partPriority
* @param costParts
* @param foundAllSources
* @return Were all mana sources found?
*/
private static Boolean findManaSources(final HashMap<String, ArrayList<AbilityMana>> manaAbilityMap,
final ArrayList<ArrayList<AbilityMana>> partSources, final ArrayList<Integer> partPriority,
final String[] costParts) {
final String[] shortColors = { "W", "U", "B", "R", "G" };
Boolean foundAllSources;
if (manaAbilityMap.isEmpty()) {
foundAllSources = false;
} else {
foundAllSources = true;
// loop over cost parts
for (int nPart = 0; nPart < costParts.length; nPart++) {
final ArrayList<AbilityMana> srcFound = new ArrayList<AbilityMana>();
// Test for:
// 1) Colorless
// 2) Split e.g. 2/G
// 3) Hybrid e.g. U/G
// defaults to single short color
if (costParts[nPart].matches("[0-9]+")) { // Colorless
srcFound.addAll(manaAbilityMap.get("1"));
} else if (costParts[nPart].contains("2/")) { // Split
final String colorKey = costParts[nPart].replace("2/", "");
// add specified color sources first
if (manaAbilityMap.containsKey(colorKey)) {
srcFound.addAll(manaAbilityMap.get(colorKey));
}
// add other available colors
for (final String color : shortColors) {
if (!colorKey.contains(color)) {
// Is source available?
if (manaAbilityMap.containsKey(color)) {
srcFound.addAll(manaAbilityMap.get(color));
}
}
}
} else if (costParts[nPart].length() > 1) { // Hybrid
final String firstColor = costParts[nPart].substring(0, 1);
final String secondColor = costParts[nPart].substring(2);
final Boolean foundFirst = manaAbilityMap.containsKey(firstColor);
final Boolean foundSecond = manaAbilityMap.containsKey(secondColor);
if (foundFirst || foundSecond) {
if (!foundFirst) {
srcFound.addAll(manaAbilityMap.get(secondColor));
} else if (!foundSecond) {
srcFound.addAll(manaAbilityMap.get(firstColor));
} else if (manaAbilityMap.get(firstColor).size() > manaAbilityMap.get(secondColor).size()) {
srcFound.addAll(manaAbilityMap.get(firstColor));
srcFound.addAll(manaAbilityMap.get(secondColor));
} else {
srcFound.addAll(manaAbilityMap.get(secondColor));
srcFound.addAll(manaAbilityMap.get(firstColor));
}
}
} else { // single color
if (manaAbilityMap.containsKey(costParts[nPart])) {
srcFound.addAll(manaAbilityMap.get(costParts[nPart]));
}
}
// add sources to array lists
partSources.add(nPart, srcFound);
// add to sorted priority list
if (srcFound.size() > 0) {
int i;
for (i = 0; i < partPriority.size(); i++) {
if (srcFound.size() <= partSources.get(i).size()) {
break;
}
}
partPriority.add(i, nPart);
} else {
foundAllSources = false;
break;
}
}
}
return foundAllSources;
}
/**
* Calculate the ManaCost for the given SpellAbility.
* @param sa
* @param test
* @param extraMana
* @return ManaCost
*/
private static ManaCost calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
final String mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost();
ManaCost cost = new ManaCost(mana);
cost = Singletons.getModel().getGameAction().getSpellCostChange(sa, cost);
final Card card = sa.getSourceCard();
// Tack xMana Payments into mana here if X is a set value
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0)) {
int manaToAdd = 0;
if (test && (extraMana > 0)) {
manaToAdd = extraMana * cost.getXcounter();
} else {
// For Count$xPaid set PayX in the AFs then use that here
// Else calculate it as appropriate.
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
if (!card.getSVar(xSvar).equals("")) {
if (xSvar.equals("PayX")) {
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
} else {
manaToAdd = AbilityFactory.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
}
}
cost.increaseColorlessMana(manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd);
}
}
// Make mana needed to avoid negative effect a mandatory cost for the AI
if (card.getSVar("ManaNeededToAvoidNegativeEffect") != "") {
final String[] negEffects = card.getSVar("ManaNeededToAvoidNegativeEffect").split(",");
int amountAdded = 0;
for (int nStr = 0; nStr < negEffects.length; nStr++) {
// convert long color strings to short color strings
if (negEffects[nStr].length() > 1) {
negEffects[nStr] = InputPayManaCostUtil.getShortColorString(negEffects[nStr]);
}
// make mana mandatory for AI
if (!cost.isColor(negEffects[nStr])) {
cost.combineManaCost(negEffects[nStr]);
amountAdded++;
}
}
cost.setManaNeededToAvoidNegativeEffect(negEffects);
// TODO: should it be an error condition if amountAdded is greater
// than the colorless in the original cost? (ArsenalNut - 120102)
// adjust colorless amount to account for added mana
cost.decreaseColorlessMana(amountAdded);
}
return cost;
}
/** /**
* <p> * <p>
* getProduceableColors. * getProduceableColors.
@@ -914,11 +943,12 @@ public class ComputerUtil {
* <p> * <p>
* getAvailableMana. * getAvailableMana.
* </p> * </p>
* @param checkPlayable
* *
* @return a {@link forge.CardList} object. * @return a {@link forge.CardList} object.
*/ */
public static CardList getAvailableMana() { public static CardList getAvailableMana(boolean checkPlayable) {
return ComputerUtil.getAvailableMana(AllZone.getComputerPlayer()); return ComputerUtil.getAvailableMana(AllZone.getComputerPlayer(), checkPlayable);
} // getAvailableMana() } // getAvailableMana()
// gets available mana sources and sorts them // gets available mana sources and sorts them
@@ -929,21 +959,26 @@ public class ComputerUtil {
* *
* @param player * @param player
* a {@link forge.game.player.Player} object. * a {@link forge.game.player.Player} object.
* @param checkPlayable
* @return a {@link forge.CardList} object. * @return a {@link forge.CardList} object.
*/ */
public static CardList getAvailableMana(final Player player) { public static CardList getAvailableMana(final Player player, final boolean checkPlayable) {
final CardList list = player.getCardsIn(ZoneType.Battlefield); final CardList list = player.getCardsIn(ZoneType.Battlefield);
final CardList manaSources = list.filter(new CardListFilter() { final CardList manaSources = list.filter(new CardListFilter() {
@Override @Override
public boolean addCard(final Card c) { public boolean addCard(final Card c) {
for (final AbilityMana am : c.getAIPlayableMana()) { if (checkPlayable) {
am.setActivatingPlayer(player); for (final AbilityMana am : c.getAIPlayableMana()) {
if (am.canPlay()) { am.setActivatingPlayer(player);
return true; if (am.canPlay()) {
return true;
}
} }
return false;
} else {
return true;
} }
return false;
} }
}); // CardListFilter }); // CardListFilter
@@ -1098,9 +1133,10 @@ public class ComputerUtil {
* *
* @param player * @param player
* a {@link forge.game.player.Player} object. * a {@link forge.game.player.Player} object.
* @param checkPlayable TODO
* @return HashMap<String, CardList> * @return HashMap<String, CardList>
*/ */
public static HashMap<String, ArrayList<AbilityMana>> mapManaSources(final Player player) { public static HashMap<String, ArrayList<AbilityMana>> mapManaSources(final Player player, boolean checkPlayable) {
final HashMap<String, ArrayList<AbilityMana>> manaMap = new HashMap<String, ArrayList<AbilityMana>>(); final HashMap<String, ArrayList<AbilityMana>> manaMap = new HashMap<String, ArrayList<AbilityMana>>();
final ArrayList<AbilityMana> whiteSources = new ArrayList<AbilityMana>(); final ArrayList<AbilityMana> whiteSources = new ArrayList<AbilityMana>();
@@ -1112,7 +1148,7 @@ public class ComputerUtil {
final ArrayList<AbilityMana> snowSources = new ArrayList<AbilityMana>(); final ArrayList<AbilityMana> snowSources = new ArrayList<AbilityMana>();
// Get list of current available mana sources // Get list of current available mana sources
final CardList manaSources = ComputerUtil.getAvailableMana(); final CardList manaSources = ComputerUtil.getAvailableMana(checkPlayable);
// Loop over all mana sources // Loop over all mana sources
for (int i = 0; i < manaSources.size(); i++) { for (int i = 0; i < manaSources.size(); i++) {
@@ -1122,7 +1158,7 @@ public class ComputerUtil {
// Loop over all mana abilities for a source // Loop over all mana abilities for a source
for (final AbilityMana m : manaAbilities) { for (final AbilityMana m : manaAbilities) {
m.setActivatingPlayer(AllZone.getComputerPlayer()); m.setActivatingPlayer(AllZone.getComputerPlayer());
if (!m.canPlay()) { if (!m.canPlay() && checkPlayable) {
continue; continue;
} }
@@ -1999,4 +2035,29 @@ public class ComputerUtil {
} }
return false; return false;
} }
/**
* Is it OK to cast this for less than the Max Targets?
* @param source
*/
public static boolean shouldCastLessThanMax(final Card source) {
boolean ret = true;
if (source.getManaCost().countX() > 0) {
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
} else {
// Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat();
combat.initiatePossibleDefenders(AllZone.getComputerPlayer());
CardList attackers = AllZoneUtil.getCreaturesInPlay(AllZone.getHumanPlayer());
for (Card att : attackers) {
combat.addAttacker(att);
}
combat = ComputerUtilBlock.getBlockers(combat, AllZoneUtil.getCreaturesInPlay(AllZone.getComputerPlayer()));
if (!CombatUtil.lifeInDanger(combat)) {
// Otherwise, return false. Do not play now.
ret = false;
}
}
return ret;
}
} }