diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java index 619a25fc72..2f854c85e4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java @@ -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 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 getManaAbilities(Card card) { Map useableAbilities = new LinkedHashMap<>(); diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index 03d80ef053..74b6f02b93 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -76,7 +76,7 @@ public class Mana implements Comparable, Serializable, Copyable { * Copy constructor. Creates a {@link Mana} object from existing * {@link Mana} * - * @param mana object to create copy from + * @param mana object to create copy from. */ public Mana(final Mana mana) { Objects.requireNonNull(mana, "The passed in mana can not be null"); @@ -129,6 +129,11 @@ public class Mana implements Comparable, Serializable, Copyable { } } + /** + * Creates a {@link Mana} object of one mana of the passed {@link ManaType}. + * + * @param manaType The type of mana to set to one. + */ public Mana(final ManaType manaType) { this(); Objects.requireNonNull(manaType, "The passed in ManaType can not be null"); @@ -159,6 +164,12 @@ public class Mana implements Comparable, Serializable, Copyable { } } + /** + * Creates a {@link Mana} object of #num mana of the passed {@link ManaType}. + * + * @param manaType The type of mana to set. + * @param num The number of mana available of the passed ManaType. + **/ public Mana(final ManaType manaType, int num) { this(); Objects.requireNonNull(manaType, "The passed in ManaType can not be null"); @@ -311,7 +322,7 @@ public class Mana implements Comparable, Serializable, Copyable { /** * Increases the given mana by one. * - * @param manaType + * @param manaType the type of mana to increase by one. */ public void increase(ManaType manaType) { switch (manaType) { @@ -411,18 +422,12 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param mana mana values to subtract * @throws ArithmeticException thrown if there is not enough available - * colored mana to pay the generic cost + * mana to pay the generic cost */ public void subtractCost(final Mana mana) throws ArithmeticException { - white = CardUtil.overflowDec(white, mana.white); - blue = CardUtil.overflowDec(blue, mana.blue); - black = CardUtil.overflowDec(black, mana.black); - red = CardUtil.overflowDec(red, mana.red); - green = CardUtil.overflowDec(green, mana.green); - generic = CardUtil.overflowDec(generic, mana.generic); - colorless = CardUtil.overflowDec(colorless, mana.colorless); - any = CardUtil.overflowDec(any, mana.any); + this.subtract(mana); + // Handle any unpaid generic mana costs while (generic < 0) { int oldColorless = generic; if (white > 0) { @@ -666,17 +671,32 @@ public class Mana implements Comparable, Serializable, Copyable { } /** - * Returns if there is enough available mana to pay the mana provided by the - * passed in {@link Mana} object. + * Returns if the cost (this) can be paid by the mana provided by the passed in {@link Mana} object. * - * @param cost the cost to compare too. - * @return if there is enough available mana to pay. + * @param avail The mana to compare too. + * @return boolean indicating if there is enough available mana to pay. */ - public boolean enough(final Mana cost) { - Mana compare = cost.copy(); + public boolean enough(final Mana avail) { + Mana compare = avail.copy(); + + // Subtract the mana cost (this) from the mana available (compare). + // This will only subtract like mana types from one another (e.g. green from green, coloreless from colorless). compare.subtract(this); + + // A negative value for compare.X means that mana of type X from the cost could not be paid by mana + // of the same kind from the available mana. + // Check each of the types, and see if there is enough mana of any color left to pay for the colors. + + if (compare.colorless < 0) { // Put first to shortcut the calculations + // Colorless mana can only be paid by colorless mana. + // If there's a negative value, then there's nothing else that can be used to pay for it. + return false; + } if (compare.white < 0) { compare.any = CardUtil.overflowInc(compare.any, compare.white); + // A negatice value means that there was more mana of the given type required than there was mana of any + // color to pay for it. + // So, there is not enough mana to pay the avail. if (compare.any < 0) { return false; } @@ -710,13 +730,6 @@ public class Mana implements Comparable, Serializable, Copyable { } compare.green = 0; } - if (compare.colorless < 0) { - compare.any = CardUtil.overflowInc(compare.any, compare.colorless); - if (compare.any < 0) { - return false; - } - compare.colorless = 0; - } if (compare.generic < 0) { compare.generic = CardUtil.overflowInc(compare.generic, compare.white); compare.generic = CardUtil.overflowInc(compare.generic, compare.blue); @@ -731,16 +744,27 @@ public class Mana implements Comparable, Serializable, Copyable { } /** - * Returns the total mana needed to meet the passed in {@link Mana} object. + * Returns the total mana needed to meet the cost of this given the available mana passed in + * as a {@link Mana} object. * - * @param cost the mana cost - * @return the total mana needed to meet the passes in {@link Mana} object. + * Used by the AI to calculate what mana it needs to obtain for a spell to become playable. + * + * @param avail the mana available to pay the cost + * @return the total mana needed to pay this given the available mana passed in as a {@link Mana} object. */ - public Mana needed(final Mana cost) { - Mana compare = cost.copy(); + public Mana needed(final Mana avail) { + Mana compare = avail.copy(); + + // Subtract the mana cost (this) from the mana available (compare). + // This will only subtract like mana types from one another (e.g. green from green, coloreless from colorless). compare.subtract(this); + + // A negative value for compare.X means that mana of type X from the cost could not be paid by mana + // of the same kind from the available mana (this). if (compare.white < 0 && compare.any > 0) { + // Calculate how much of the unpaid colored mana can be covered by mana of any color int diff = Math.min(compare.any, Math.abs(compare.white)); + // Make the payment compare.any = CardUtil.overflowDec(compare.any, diff); compare.white = CardUtil.overflowInc(compare.white, diff); } @@ -764,25 +788,30 @@ public class Mana implements Comparable, Serializable, Copyable { compare.any = CardUtil.overflowDec(compare.any, diff); compare.green = CardUtil.overflowInc(compare.green, diff); } - if (compare.colorless < 0 && compare.any > 0) { - int diff = Math.min(compare.any, Math.abs(compare.colorless)); - compare.any = CardUtil.overflowDec(compare.any, diff); - compare.colorless = CardUtil.overflowInc(compare.colorless, diff); - } + + // Colorless mana can only be paid by colorless sources, so a check for it is not performed. + if (compare.generic < 0) { + // Calculate total leftover mana available to pay for generic costs int remaining = 0; - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.white)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.blue)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.black)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.red)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.green)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.colorless)); - remaining = CardUtil.overflowInc(remaining, Math.min(0, compare.any)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.white)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.blue)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.black)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.red)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.green)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.colorless)); + remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.any)); + if (remaining > 0) { + // Calculate how much of the unpaid generic cost can be paid by the leftover mana int diff = Math.min(remaining, Math.abs(compare.generic)); + // Make the payment compare.generic = CardUtil.overflowInc(compare.generic, diff); } } + + // Create the mana object holding the mana needed to pay the cost + // If the value in compare is positive it means that there's excess mana of that type, and no more is needed. Mana needed = new Mana(); if (compare.white < 0) { needed.white = CardUtil.overflowDec(needed.white, compare.white); diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index 5276cafc9d..3d7e6fe55b 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -449,9 +449,14 @@ public final class ManaUtil { } /** - * Converts a collection of mana symbols into a single condensed string e.g. - * {1}{1}{1}{1}{1}{W} = {5}{W} {2}{B}{2}{B}{2}{B} = {6}{B}{B}{B} - * {1}{2}{R}{U}{1}{1} = {5}{R}{U} {B}{G}{R} = {B}{G}{R} + * Converts a collection of mana symbols into a single condensed string e.g: + * {1}{1}{1}{1}{1}{W} = {5}{W} + * {2}{B}{2}{B}{2}{B} = {6}{B}{B}{B} + * {1}{2}{R}{U}{1}{1} = {5}{R}{U} + * {B}{G}{R} = {B}{G}{R} + * + * @param rawCost the uncondensed version of the mana String. + * @return the condensed version of the mana String. */ public static String condenseManaCostString(String rawCost) { int total = 0; diff --git a/Mage/src/test/java/mage/ManaTest.java b/Mage/src/test/java/mage/ManaTest.java index df388fcff5..a3b61dd1b2 100644 --- a/Mage/src/test/java/mage/ManaTest.java +++ b/Mage/src/test/java/mage/ManaTest.java @@ -1,8 +1,13 @@ package mage; +import mage.abilities.SpellAbility; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; import mage.filter.FilterMana; +import mage.util.CardUtil; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -11,7 +16,7 @@ import static org.junit.Assert.*; /** - * Custom unit tests for {link Mana}. + * Custom unit tests for {@link Mana}. * * @author githubpoixen@github.com */ @@ -686,4 +691,161 @@ public class ManaTest { assertEquals(Integer.MAX_VALUE, mana.getGeneric()); assertEquals(Integer.MAX_VALUE, mana.getAny()); } + + /** + * Mana.enough is used to check if a spell can be cast with an given amount + * of avalable mana + */ + @Test + public void testManaEnough() { + assertAvailableManaEnough("{G}", 1, "", true); + assertAvailableManaEnough("{G}", 0, "{G}", true); + assertAvailableManaEnough("{R}", 0, "{G}", false); + assertAvailableManaEnough("{B}", 0, "{G}", false); + assertAvailableManaEnough("{U}", 0, "{G}", false); + assertAvailableManaEnough("{W}", 0, "{G}", false); + assertAvailableManaEnough("{W}", 0, "{C}", false); + + assertAvailableManaEnough("{R}", 1, "", true); + assertAvailableManaEnough("{R}", 0, "{R}", true); + assertAvailableManaEnough("{G}", 0, "{R}", false); + assertAvailableManaEnough("{B}", 0, "{R}", false); + assertAvailableManaEnough("{U}", 0, "{R}", false); + assertAvailableManaEnough("{W}", 0, "{R}", false); + + assertAvailableManaEnough("{U}{B}{W}{G}{R}", 4, "{R}", true); + assertAvailableManaEnough("{U}{B}{W}{G}{R}", 3, "{R}{B}", true); + + assertAvailableManaEnough("{U}{U}{U}{G}{G}{2}", 2, "{U}{U}{G}{R}{B}", true); + + assertAvailableManaEnough("{2}{U}{U}", 0, "{U}{U}{U}{U}", true); + assertAvailableManaEnough("{2}{U}{U}", 0, "{4}", false); + assertAvailableManaEnough("{2}{U}{U}", 0, "{B}{B}{4}", false); + + assertAvailableManaEnough("{G}", 0, "{G/W}", true); + assertAvailableManaEnough("{G}{W}", 0, "{G/W}{G/W}", true); + assertAvailableManaEnough("{W}{W}", 0, "{G/W}{G/W}", true); + assertAvailableManaEnough("{G}{G}", 0, "{G/W}{G/W}", true); + + assertAvailableManaEnough("{C}", 1, "", false); + assertAvailableManaEnough("{C}", 0, "{C}", true); + assertAvailableManaEnough("{C}", 0, "{G}", false); + assertAvailableManaEnough("{C}", 0, "{R}", false); + assertAvailableManaEnough("{C}", 0, "{B}", false); + assertAvailableManaEnough("{C}", 0, "{W}", false); + assertAvailableManaEnough("{C}", 0, "{U}", false); + } + + /** + * Mana.enough is used to check if a spell can be cast with an given amount + * of avalable mana + */ + @Test + public void testManaReduction() { + // cost - reduction - rest + assertManaReduction("{G}{G}", "{G}", "{G}"); + assertManaReduction("{1}{G}{G}", "{G}", "{1}{G}"); + assertManaReduction("{B}{B}", "{B}", "{B}"); + assertManaReduction("{1}{B}{B}", "{B}", "{1}{B}"); + assertManaReduction("{W}{W}", "{W}", "{W}"); + assertManaReduction("{1}{W}{W}", "{W}", "{1}{W}"); + assertManaReduction("{U}{U}", "{U}", "{U}"); + assertManaReduction("{1}{U}{U}", "{U}", "{1}{U}"); + assertManaReduction("{R}{R}", "{R}", "{R}"); + assertManaReduction("{1}{R}{R}", "{R}", "{1}{R}"); + + assertManaReduction("{R}{G}{B}{U}{W}", "{R}{G}{B}{U}{W}", "{0}"); + + // Hybrid Mana + assertManaReduction("{2/B}{2/B}{2/B}", "{B}{B}", "{2/B}"); + assertManaReduction("{2/B}{2/B}{2/B}", "{B}{B}{B}", "{0}"); + assertManaReduction("{2/W}{2/W}{2/W}", "{W}{W}", "{2/W}"); + assertManaReduction("{2/W}{2/W}{2/W}", "{W}{W}{W}", "{0}"); + + assertManaReduction("{G/B}{G/B}{G/B}", "{B}{G}{B}", "{0}"); + } + + /** + * Mana.needed is used by the AI to know how much mana it needs in order to be able to play a card. + */ + @Test + public void should() { + // TODO: How does it handle generic and any. + // How *should* it handle them? + testManaNeeded( + new Mana(ManaType.COLORLESS, 1), // Available + new Mana(ManaType.COLORLESS, 2), // Cost + new Mana(ManaType.COLORLESS, 1) // Needed + ); + testManaNeeded( + new Mana(ManaType.RED, 1), // Avaiable + new Mana(ManaType.GENERIC, 1), // Cost + new Mana() // Needed + ); + testManaNeeded( + new Mana(ManaType.COLORLESS, 1), // Avaiable + new Mana(ManaType.GENERIC, 1), // Cost + new Mana() // Needed + ); + testManaNeeded( + new Mana(), // Available + new Mana(2, 0, 0, 0, 0, 2, 0, 0), // Cost + new Mana(2, 0, 0, 0, 0, 2, 0, 0) // Needed + ); + } + + /** + * Checks if the mana needed calculations produces the expected needed mana amount. + * + * @param available The mana currently available. + * @param cost The mana needed for a cost. + * @param neededExpected The mana expected to be required to pay the cost. + */ + private void testManaNeeded(Mana available, Mana cost, Mana neededExpected) { + Mana neededActual = cost.needed(available); + Assert.assertTrue( + "The mana needed to pay " + cost + " given " + available + + " should have been " + neededExpected + " but was calculate to be " + neededActual, + neededActual.equalManaValue(neededExpected) + ); + } + + /** + * Checks if the given available Mana is enough to pay a given mana cost + * + * @param manaCostsToPay The mana cost that needs to be paid. + * @param availablyAny The amount of generic mana available. + * @param available The colored and colorless mana available. + * @param expected boolean indicating if the available mana is expected to cover the mana cost. + */ + private void assertAvailableManaEnough(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)); + } + } + + /** + * Checks if a given mana reduction left the expected amount of mana costs + * + * @param manaCostsToPay The mana cost before reductions are applied. + * @param manaToReduce The amount and types of many to reduced the cost by. + * @param restMana The expected amount of mana left + */ + private void assertManaReduction(String manaCostsToPay, String manaToReduce, String restMana) { + SpellAbility spellAbility = new SpellAbility(new ManaCostsImpl<>(manaCostsToPay), "Test"); + CardUtil.adjustCost(spellAbility, new ManaCostsImpl<>(manaToReduce), true); + Assert.assertEquals( + "The mana cost to pay " + manaCostsToPay + " reduced by " + manaToReduce + + " should left " + restMana + " but the rest was " + spellAbility.getManaCostsToPay(), + spellAbility.getManaCostsToPay().getText(), + restMana + ); + } + }