Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ public static int getConvergeCount(final SpellAbility sa, final Player ai) {
return 0;
}

/**
* Announce X on a converge or sunburst card, where its only job is to buy colors: the least X
* that still reaches the most of them. Returns the number of colors that will be paid.
*/
public static int setXForBestConverge(final SpellAbility sa, final Player ai, final int maxX) {
int bestX = 0;
int bestColors = 0;
for (int i = 0; i <= maxX; i++) {
sa.setXManaCostPaid(i);
int colors = getConvergeCount(sa, ai);
if (colors > bestColors) {
bestColors = colors;
bestX = i;
}
}
sa.setXManaCostPaid(bestX);
return bestColors;
}

// Does not check if mana sources can be used right now, just checks for potential chance.
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
if (ai == null || sa == null)
Expand Down
12 changes: 1 addition & 11 deletions forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,7 @@ protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa
if (mana.countX() > 0) {
final int xPay = ComputerUtilCost.setMaxXValue(sa, ai, false);
if (source.hasConverge()) {
int nColors = ComputerUtilMana.getConvergeCount(sa, ai);
for (int i = 1; i <= xPay; i++) {
sa.setXManaCostPaid(i);
int newColors = ComputerUtilMana.getConvergeCount(sa, ai);
if (newColors > nColors) {
nColors = newColors;
} else {
sa.setXManaCostPaid(i - 1);
break;
}
}
ComputerUtilMana.setXForBestConverge(sa, ai, xPay);
} else if (xPay <= 0) {
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
}
Expand Down
8 changes: 8 additions & 0 deletions forge-ai/src/main/java/forge/ai/ability/TokenAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa,
return pwPlus || sa.getSubAbility() != null;
}

// A converge card counts its colors in a second SVar, because X is already the mana cost -
// so X is not the token count and the block below will not announce it, but it is still
// what buys the colors that count (e.g. Sweep the Skies)
if (!tokenHasX && source.hasConverge() && "Count$xPaid".equals(sa.getSVar("X"))) {
ComputerUtilMana.setXForBestConverge(sa, ai,
ComputerUtilCost.setMaxXValue(sa, ai, sa.isTrigger()));
}

// X-cost spells
if (tokenHasX) {
int x = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package forge.ai.ability;

import forge.ai.AITest;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellApiToAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import org.testng.annotations.Test;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;

/**
* On a converge or sunburst card X only buys colors, so the AI announces the least X that reaches
* the most of them - whether the count lands on the card as counters or as the size of the effect.
*/
public class ConvergeXCastTest extends AITest {

@Test
public void sunburstCountsEveryColourItCouldPayFor() {
Game game = newGame();
Player ai = game.getPlayers().get(1);
fiveColours(ai);
addCardToZone("Engineered Explosives", ai, ZoneType.Hand);

settleAndPlay(game, ai);

Card ee = findCardWithName(game, "Engineered Explosives");
assertNotNull("the AI cast it", ee);
assertEquals("sunburst counted all five colours", 5,
ee.getCounters(CounterEnumType.CHARGE));
}

/**
* Sweep the Skies counts its colors in Y, so X is not the token count and is easy to leave
* unannounced - but it is still what buys the colors. Asserted on the announced X rather than
* on a played turn, because TokenAi gates token spells behind a random roll.
*/
@Test
public void convergeSizesAnEffectTheCostDoesNotName() {
Game game = newGame();
Player ai = game.getPlayers().get(1);
fiveColours(ai);
addCard("Island", ai);
Card sweep = addCardToZone("Sweep the Skies", ai, ZoneType.Hand);

game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai);
game.getAction().checkStateEffects(true);

SpellAbility sa = sweep.getFirstSpellAbility();
sa.setActivatingPlayer(ai);
SpellApiToAi.Converter.get(sa.getApi()).canPlayWithSubs(ai, sa);

assertEquals("X paid for the four extra colours", 4, sa.getXManaCostPaid().intValue());
assertEquals("so all five are spent", 5, ComputerUtilMana.getConvergeCount(sa, ai));
}

private Game newGame() {
Game game = initAndCreateGame();
game.getPlayers().get(0).setTeam(1);
game.getPlayers().get(1).setTeam(0);
return game;
}

private void fiveColours(Player p) {
addCard("Plains", p);
addCard("Island", p);
addCard("Swamp", p);
addCard("Mountain", p);
addCard("Forest", p);
}

private void settleAndPlay(Game game, Player ai) {
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai);
game.getAction().checkStateEffects(true);
gameLoopUntilNextPhase(game);
}
}
1 change: 0 additions & 1 deletion forge-gui/res/cardsfolder/e/engineered_explosives.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ A:AB$ DestroyAll | Cost$ 2 Sac<1/CARDNAME> | ValidCards$ Permanent.nonLand+cmcEQ
SVar:X:Count$xPaid
SVar:Y:Count$CardCounters.CHARGE
SVar:NonStackingEffect:True
AI:RemoveDeck:All

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the reasoning for this was zero X since that's another regression and more like AI unable to coordinate which one is actually a smart choice

DeckHints:Ability$Proliferate
Oracle:Sunburst (This enters with a charge counter on it for each color of mana spent to cast it.)\n{2}, Sacrifice Engineered Explosives: Destroy each nonland permanent with mana value equal to the number of charge counters on Engineered Explosives.