Fixes for Mana.enough and Mana.needed. For #8153 (#8663)

Fixes to Mana.enough:

mana of any color (ManaType.Any) was being used to pay for colourless mana.
Fixes for Mana.needed:

mana of any color (ManaType.Any) was being used to pay for colourless mana.
calculation for generic mana remaining was using min(0, available) instead of max(0, available) meaning that leftover mana of other types was never used to pay for any leftover generic costs.
Other:

Added tests for both .needed and .enough.
Moved tests some tests from ManaUtilTest to ManaTest
Simplified Mana.subtractCosts by calling Mana.substract first to make use of common functionality.
Added more documentation
Added tests for both .needed
Added more tests for .enough to cover the changes with colourless mana.
This commit is contained in:
Alex Vasile 2022-06-01 08:13:37 -06:00 committed by GitHub
parent 16914632c4
commit e679574a15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 293 additions and 196 deletions

View file

@ -3,9 +3,7 @@ package org.mage.test.utils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.mana.BasicManaAbility;
@ -15,7 +13,6 @@ import mage.abilities.mana.RedManaAbility;
import mage.abilities.mana.WhiteManaAbility;
import mage.cards.Card;
import mage.cards.repository.CardRepository;
import mage.util.CardUtil;
import mage.util.ManaUtil;
import org.junit.Assert;
import org.junit.Test;
@ -27,42 +24,42 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class ManaUtilTest extends CardTestPlayerBase {
@Test
public void test() {
testManaToPayVsLand("{R}", "Blood Crypt", 2, 1); // should use {R}
testManaToPayVsLand("{1}{R}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{R}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{2}{R}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{R}{R}{B}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{R}{G}{W}{W}{U}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
public void testAutoPay() {
testManaToPayVsLand("{R}", "Blood Crypt", 2, 1); // should use {R}
testManaToPayVsLand("{1}{R}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{R}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{2}{R}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{R}{R}{B}{B}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{R}{G}{W}{W}{U}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{R}{R}{G}{W}{W}{U}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{R}{R}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{G}{W}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{1}{G}{W}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{2}{G}{W}{U}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{3}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{R}{R}", "Blood Crypt", 2, RedManaAbility.class); // should use {R}
testManaToPayVsLand("{G}{W}", "Blood Crypt", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{1}{G}{W}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{2}{G}{W}{U}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{3}", "Blood Crypt", 2, 1); // should use any but auto choose it
testManaToPayVsLand("{R}{R}{G}{W}{W}{U}", "Watery Grave", 2, 1); // should use {U}
testManaToPayVsLand("{R}{R}{G}{W}{W}", "Steam Vents", 2, 1); // should use {R}
testManaToPayVsLand("{R}{R}{G}{B}{U}", "Temple Garden", 2, 1); // should use {G}
testManaToPayVsLand("{W}{W}{G}{B}{U}", "Sacred Foundry", 2, 1); // should use {W}
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Overgrown Tomb", 2, BlackManaAbility.class); // should use {B}
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Swamp", 1, BlackManaAbility.class);
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Plains", 1, WhiteManaAbility.class);
testManaToPayVsLand("{R}{R}{G}{W}{W}{U}", "Watery Grave", 2, 1); // should use {U}
testManaToPayVsLand("{R}{R}{G}{W}{W}", "Steam Vents", 2, 1); // should use {R}
testManaToPayVsLand("{R}{R}{G}{B}{U}", "Temple Garden", 2, 1); // should use {G}
testManaToPayVsLand("{W}{W}{G}{B}{U}", "Sacred Foundry", 2, 1); // should use {W}
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Overgrown Tomb", 2, BlackManaAbility.class); // should use {B}
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Swamp", 1, BlackManaAbility.class);
testManaToPayVsLand("{W}{W}{R}{B}{U}", "Plains", 1, WhiteManaAbility.class);
testManaToPayVsLand("{1}{R}", "Cavern of Souls", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{2}", "Cavern of Souls", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{1}{R}", "Cavern of Souls", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{2}", "Cavern of Souls", 2, 2); // can't auto choose to pay
testManaToPayVsLand("{2}", "Eldrazi Temple", 2, 2); // can't auto choose to pay
// hybrid mana
testManaToPayVsLand("{W/R}{W/R}{W/R}", "Sacred Foundry", 2, 1); // auto choose for hybrid mana: choose any
testManaToPayVsLand("{R}{W/R}", "Sacred Foundry", 2, RedManaAbility.class); // auto choose for hybrid mana: we should choose {R}
testManaToPayVsLand("{G}{W/R}", "Sacred Foundry", 2, 1); // auto choose for hybrid mana: choose any
testManaToPayVsLand("{G}{W/R}{W}", "Sacred Foundry", 2, WhiteManaAbility.class); // auto choose for hybrid mana: choose {W}
testManaToPayVsLand("{W/B}{W/B}", "Swamp", 1, BlackManaAbility.class);
testManaToPayVsLand("{W/R}{W/R}{W/R}", "Sacred Foundry", 2, 1); // auto choose for hybrid mana: choose any
testManaToPayVsLand("{R}{W/R}", "Sacred Foundry", 2, RedManaAbility.class); // auto choose for hybrid mana: we should choose {R}
testManaToPayVsLand("{G}{W/R}", "Sacred Foundry", 2, 1); // auto choose for hybrid mana: choose any
testManaToPayVsLand("{G}{W/R}{W}", "Sacred Foundry", 2, WhiteManaAbility.class); // auto choose for hybrid mana: choose {W}
testManaToPayVsLand("{W/B}{W/B}", "Swamp", 1, BlackManaAbility.class);
testManaToPayVsLand("{R}", "Glimmervoid", 1, 1);
testManaToPayVsLand("{R}{1}", "Glimmervoid", 1, 1);
testManaToPayVsLand("{R}", "Glimmervoid", 1, 1);
testManaToPayVsLand("{R}{1}", "Glimmervoid", 1, 1);
// we can't auto choose here:
// let say we auto choose {R}, then we have to use it to pay for {R} not {W/R} (as {W/R} is more generic cost)
@ -75,100 +72,24 @@ public class ManaUtilTest extends CardTestPlayerBase {
testManaToPayVsLand("{W/R}{R/G}", "Sacred Foundry", 2, 2); // can't auto choose to pay
}
/**
* Mana.condenseManaCostString is used to simplify the String representation of a mana cost to make it more readable.
*/
@Test
public void testManaCondensing() {
Assert.assertEquals("{5}{W}", ManaUtil.condenseManaCostString(("{1}{1}{1}{2}{W}")));
Assert.assertEquals("{4}{B}{B}", ManaUtil.condenseManaCostString("{2}{B}{2}{B}"));
Assert.assertEquals("{6}{R}{R}{R}{U}", ManaUtil.condenseManaCostString("{R}{1}{R}{2}{R}{3}{U}"));
Assert.assertEquals("{5}{B}{U}{W}", ManaUtil.condenseManaCostString("{1}{B}{W}{4}{U}"));
Assert.assertEquals("{8}{B}{G}{G}{U}", ManaUtil.condenseManaCostString("{1}{G}{1}{2}{3}{G}{B}{U}{1}"));
Assert.assertEquals("{3}{R}{U}", ManaUtil.condenseManaCostString("{3}{R}{U}"));
Assert.assertEquals("{10}", ManaUtil.condenseManaCostString("{1}{2}{3}{4}"));
Assert.assertEquals("{B}{G}{R}{U}{W}", ManaUtil.condenseManaCostString("{B}{G}{R}{U}{W}"));
Assert.assertEquals("{R}{R}", ManaUtil.condenseManaCostString("{R}{R}"));
Assert.assertEquals("{U}", ManaUtil.condenseManaCostString("{U}"));
Assert.assertEquals("{2}", ManaUtil.condenseManaCostString("{2}"));
Assert.assertEquals("", ManaUtil.condenseManaCostString("{}"));
Assert.assertEquals("{5}{C}{R}{R}{R}{U}", ManaUtil.condenseManaCostString("{R}{C}{R}{2}{R}{3}{U}"));
}
/**
* Mana.enough is used to check if a spell can be cast with an given amount
* of avalable mana
*/
@Test
public void testManaEnough() {
testManaAvailEnough("{G}", 1, "", true);
testManaAvailEnough("{G}", 0, "{G}", true);
testManaAvailEnough("{R}", 0, "{G}", false);
testManaAvailEnough("{B}", 0, "{G}", false);
testManaAvailEnough("{U}", 0, "{G}", false);
testManaAvailEnough("{W}", 0, "{G}", false);
testManaAvailEnough("{R}", 1, "", true);
testManaAvailEnough("{R}", 0, "{R}", true);
testManaAvailEnough("{G}", 0, "{R}", false);
testManaAvailEnough("{B}", 0, "{R}", false);
testManaAvailEnough("{U}", 0, "{R}", false);
testManaAvailEnough("{W}", 0, "{R}", false);
testManaAvailEnough("{U}{B}{W}{G}{R}", 4, "{R}", true);
testManaAvailEnough("{U}{B}{W}{G}{R}", 3, "{R}{B}", true);
testManaAvailEnough("{U}{U}{U}{G}{G}{2}", 2, "{U}{U}{G}{R}{B}", true);
testManaAvailEnough("{2}{U}{U}", 0, "{U}{U}{U}{U}", true);
testManaAvailEnough("{2}{U}{U}", 0, "{4}", false);
testManaAvailEnough("{2}{U}{U}", 0, "{B}{B}{4}", false);
testManaAvailEnough("{G}", 0, "{G/W}", true);
testManaAvailEnough("{G}{W}", 0, "{G/W}{G/W}", true);
testManaAvailEnough("{W}{W}", 0, "{G/W}{G/W}", true);
testManaAvailEnough("{G}{G}", 0, "{G/W}{G/W}", true);
}
/**
* Mana.enough is used to check if a spell can be cast with an given amount
* of avalable mana
*/
@Test
public void testManaIncrease() {
// cost - reduction - rest
testManaReduction("{G}{G}", "{G}", "{G}");
testManaReduction("{1}{G}{G}", "{G}", "{1}{G}");
testManaReduction("{B}{B}", "{B}", "{B}");
testManaReduction("{1}{B}{B}", "{B}", "{1}{B}");
testManaReduction("{W}{W}", "{W}", "{W}");
testManaReduction("{1}{W}{W}", "{W}", "{1}{W}");
testManaReduction("{U}{U}", "{U}", "{U}");
testManaReduction("{1}{U}{U}", "{U}", "{1}{U}");
testManaReduction("{R}{R}", "{R}", "{R}");
testManaReduction("{1}{R}{R}", "{R}", "{1}{R}");
testManaReduction("{R}{G}{B}{U}{W}", "{R}{G}{B}{U}{W}", "{0}");
// Hybrid Mana
testManaReduction("{2/B}{2/B}{2/B}", "{B}{B}", "{2/B}");
testManaReduction("{2/B}{2/B}{2/B}", "{B}{B}{B}", "{0}");
testManaReduction("{2/W}{2/W}{2/W}", "{W}{W}", "{2/W}");
testManaReduction("{2/W}{2/W}{2/W}", "{W}{W}{W}", "{0}");
testManaReduction("{G/B}{G/B}{G/B}", "{B}{G}{B}", "{0}");
}
/**
* Checks if a given mana reduction left the expected amount of mana costs
*
* @param manaCostsToPay
* @param availablyAny
* @param available
* @param expected
*/
private void testManaReduction(String manaCostsToPay, String manaToReduce, String restMana) {
SpellAbility spellAbility = new SpellAbility(new ManaCostsImpl(manaCostsToPay), "Test");
CardUtil.adjustCost(spellAbility, new ManaCostsImpl(manaToReduce), true);
Assert.assertTrue("The mana cost to pay " + manaCostsToPay + " reduced by " + manaToReduce + " should left " + restMana + " but the rest was " + spellAbility.getManaCostsToPay().getText(), spellAbility.getManaCostsToPay().getText().equals(restMana));
Assert.assertEquals("{5}{W}", ManaUtil.condenseManaCostString("{1}{1}{1}{2}{W}"));
Assert.assertEquals("{4}{B}{B}", ManaUtil.condenseManaCostString("{2}{B}{2}{B}"));
Assert.assertEquals("{6}{R}{R}{R}{U}", ManaUtil.condenseManaCostString("{R}{1}{R}{2}{R}{3}{U}"));
Assert.assertEquals("{5}{B}{U}{W}", ManaUtil.condenseManaCostString("{1}{B}{W}{4}{U}"));
Assert.assertEquals("{8}{B}{G}{G}{U}", ManaUtil.condenseManaCostString("{1}{G}{1}{2}{3}{G}{B}{U}{1}"));
Assert.assertEquals("{3}{R}{U}", ManaUtil.condenseManaCostString("{3}{R}{U}"));
Assert.assertEquals("{10}", ManaUtil.condenseManaCostString("{1}{2}{3}{4}"));
Assert.assertEquals("{B}{G}{R}{U}{W}", ManaUtil.condenseManaCostString("{B}{G}{R}{U}{W}"));
Assert.assertEquals("{R}{R}", ManaUtil.condenseManaCostString("{R}{R}"));
Assert.assertEquals("{U}", ManaUtil.condenseManaCostString("{U}"));
Assert.assertEquals("{2}", ManaUtil.condenseManaCostString("{2}"));
Assert.assertEquals("", ManaUtil.condenseManaCostString("{}"));
Assert.assertEquals("{5}{C}{R}{R}{R}{U}", ManaUtil.condenseManaCostString("{R}{C}{R}{2}{R}{3}{U}"));
}
/**
@ -184,7 +105,7 @@ public class ManaUtilTest extends CardTestPlayerBase {
* should be returned after optimization.
*/
private void testManaToPayVsLand(String manaToPay, String landName, int expected1, int expected2) {
ManaCost unpaid = new ManaCostsImpl(manaToPay);
ManaCost unpaid = new ManaCostsImpl<>(manaToPay);
Card card = CardRepository.instance.findCard(landName).getCard();
Assert.assertNotNull(card);
@ -206,13 +127,13 @@ public class ManaUtilTest extends CardTestPlayerBase {
* We get all mana abilities, then try to auto pay and compare to expected1
* and expected2 params.
*
* @param manaToPay Mana that should be paid using land.
* @param landName Land to use as mana producer.
* @param expected1 The amount of mana abilities the land should have.
* @param expectedChosen
* @param manaToPay Mana that should be paid using land.
* @param landName Land to use as mana producer.
* @param expected1 The amount of mana abilities the land should have.
* @param expectedChosen The mana ability expected to be chosen.
*/
private void testManaToPayVsLand(String manaToPay, String landName, int expected1, Class<? extends BasicManaAbility> expectedChosen) {
ManaCost unpaid = new ManaCostsImpl(manaToPay);
ManaCost unpaid = new ManaCostsImpl<>(manaToPay);
Card card = CardRepository.instance.findCard(landName).getCard();
Assert.assertNotNull(card);
@ -225,31 +146,11 @@ public class ManaUtilTest extends CardTestPlayerBase {
Assert.assertTrue("Wrong mana ability has been chosen", expectedChosen.isInstance(ability));
}
/**
* Checks if the given available Mana is enough to pay a given mana cost
*
* @param manaCostsToPay
* @param availablyAny
* @param available
* @param expected
*/
private void testManaAvailEnough(String manaCostsToPay, int availablyAny, String available, boolean expected) {
ManaCost unpaid = new ManaCostsImpl(manaCostsToPay);
ManaCost costAvailable = new ManaCostsImpl(available);
Mana manaAvailable = costAvailable.getMana();
manaAvailable.setAny(availablyAny);
if (expected) {
Assert.assertTrue("The available Mana " + costAvailable.getText() + " should be enough to pay the costs " + unpaid.getText(), unpaid.getMana().enough(manaAvailable));
} else {
Assert.assertFalse("The available Mana " + costAvailable.getText() + " shouldn't be enough to pay the costs " + unpaid.getText(), unpaid.getMana().enough(manaAvailable));
}
}
/**
* Extracts mana abilities from the card.
*
* @param card Card to extract mana abilities from.
* @return
* @param card Card to extract mana abilities from.
* @return Map between the UUID of each ability on the card and the ability.
*/
private Map<UUID, ActivatedManaAbilityImpl> getManaAbilities(Card card) {
Map<UUID, ActivatedManaAbilityImpl> useableAbilities = new LinkedHashMap<>();