- Smarter and more concise mechanism for deciding whether to play a shockland untapped or tapped.

This commit is contained in:
Agetian
2015-10-03 15:09:34 +00:00
parent 4819316fbb
commit c685fc5919
2 changed files with 24 additions and 69 deletions

View File

@@ -1942,66 +1942,26 @@ public class ComputerUtil {
return bestBoardPosition;
}
public static boolean hasGoodTargetForAura(final Player ai, final Card aura) {
// This is currently used by ComputerUtilCost.willPayUnlessCost to determine if there's a viable target for a spell
// that can be paid for with an untapped shockland.
if (ai == null || aura == null) {
public static boolean hasReasonToPlayCardThisTurn(final Player ai, final Card c) {
if (ai == null || c == null) {
return false;
}
if (!(ai.getController() instanceof PlayerControllerAi)) {
System.err.println("Unexpected behavior: ComputerUtil::hasGoodTargetforAura called with the non-AI player as a parameter.");
System.err.println("Unexpected behavior: ComputerUtil::getReasonToPlayCard called with the non-AI player as a parameter.");
return false;
}
if (aura.getFirstAttachSpell() == null) {
// Something went majorly wrong here, should never happen
System.err.println("Unexpected behavior: first attach spell for Aura card " + aura.getName() + " was null in ComputerUtil::hasGoodTargetForAura.");
return false;
}
boolean hasTarget = false;
boolean aiHasTargets = false, oppHasTargets = false;
aiHasTargets = !CardLists.filter(ai.getAllCards(), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty();
for (Player p : ai.getOpponents()) {
if (!CardLists.filter(p.getAllCards(), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty()) {
oppHasTargets = true;
break;
for (SpellAbility sa : c.getAllPossibleAbilities(ai, true)) {
if (sa.getApi() == ApiType.Counter) {
// return true for counterspells so that the AI can take into account that it may need to cast it later in the opponent's turn
return true;
}
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(sa);
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
return true;
}
}
boolean isCurse = false;
for (SpellAbility ability : aura.getAllSpellAbilities()) {
if (ability.isCurse() || (ability.hasParam("AILogic") && ability.getParam("AILogic").equals("Curse"))) {
isCurse = true;
break;
}
}
if (isCurse && oppHasTargets) {
hasTarget = true;
} else if (!isCurse && aiHasTargets) {
hasTarget = true;
}
return hasTarget;
}
public static boolean hasReasonToPlaySaThisTurn(final Player ai, final SpellAbility sa) {
// This is currently used by ComputerUtilCost.willPayUnlessCost to determine if there's a viable reason to cast a spell
// that can be paid for with an untapped shockland.
if (ai == null || sa == null) {
return false;
}
if (!(ai.getController() instanceof PlayerControllerAi)) {
System.err.println("Unexpected behavior: ComputerUtil::hasReasonToPlaySaThisTurn called with the non-AI player as a parameter.");
return false;
}
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(sa);
boolean willPlay = decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2;
return willPlay;
return false;
}
}

View File

@@ -7,6 +7,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.cost.*;
@@ -417,25 +418,19 @@ public class ComputerUtilCost {
}
} else if (shockland) {
if (payer.getLife() > 3 && payer.canPayLife(2)) {
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
int cmc = c.getCMC();
// if the new land size would equal the CMC of a card in AIs hand, consider playing it untapped,
// otherwise don't bother running other checks
if (landsize != c.getCMC()) {
continue;
}
// try to determine in the AI is actually planning to play a spell ability from the card
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
// try to determine if the mana shards provided by the lands would be applicable to pay the mana cost
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
// try to determine that there is a valid target for a spell (very basic, consider expanding)
boolean requirementsMet = true;
if (c.isAura()) {
// for auras, make sure that a good enough target exists
requirementsMet = ComputerUtil.hasGoodTargetForAura(payer, c);
} else if (c.isSorcery()) {
// for sorceries with one mode, consider actually checking for a reason for the AI to decide to play the spell ability
if (c.getAllSpellAbilities().size() == 1) {
SpellAbility ability = c.getFirstSpellAbility();
requirementsMet = ComputerUtil.hasReasonToPlaySaThisTurn(payer, ability);
}
}
if (landsize == cmc && canPay && requirementsMet) {
if (canPay && willPlay) {
return true;
}
}
@@ -492,15 +487,15 @@ public class ComputerUtilCost {
}
public static Set<String> getAvailableManaColors(Player ai, List<Card> additionalLands) {
CardCollection lands = ai.getLandsInPlay();
CardCollection cardsToConsider = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Presets.UNTAPPED);
Set<String> colorsAvailable = new HashSet<>();
if (additionalLands != null) {
GameActionUtil.grantBasicLandsManaAbilities(additionalLands);
lands.addAll(additionalLands);
cardsToConsider.addAll(additionalLands);
}
for (Card c : lands) {
for (Card c : cardsToConsider) {
for (SpellAbility sa : c.getManaAbilities()) {
colorsAvailable.add(sa.getManaPart().getOrigProduced());
}