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 4d34fe1df8..e499847ea0 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 @@ -26,13 +26,13 @@ public class ManaUtilTest extends CardTestPlayerBase { @Test public void test() { testManaToPayVsLand("{R}", "Blood Crypt", 2, 1); // should use {R} - testManaToPayVsLand("{1}{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, 1); // should use {R} - testManaToPayVsLand("{R}{R}{G}{W}{W}{U}", "Blood Crypt", 2, 1); // should use {R} - testManaToPayVsLand("{R}{R}", "Blood Crypt", 2, 1); // should use {R} + 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 @@ -60,6 +60,8 @@ public class ManaUtilTest extends CardTestPlayerBase { // we can pay {W/R}{W}{R} by using Sacred Foundry and choosing {W} then using two Mountains // but if we auto choose {R} then we won't be able to pay the cost at all testManaToPayVsLand("{W/R}{W}{R}", "Sacred Foundry", 2, 2); + + testManaToPayVsLand("{W/R}{R/G}", "Sacred Foundry", 2, 2); // can't auto choose to pay } /** diff --git a/Mage/src/mage/abilities/costs/mana/HybridManaCost.java b/Mage/src/mage/abilities/costs/mana/HybridManaCost.java index 985490cfb5..0658d9e68c 100644 --- a/Mage/src/mage/abilities/costs/mana/HybridManaCost.java +++ b/Mage/src/mage/abilities/costs/mana/HybridManaCost.java @@ -129,4 +129,12 @@ public class HybridManaCost extends ManaCostImpl { public boolean containsColor(ColoredManaSymbol coloredManaSymbol) { return mana1.equals(coloredManaSymbol) || mana2.equals(coloredManaSymbol); } + + public ColoredManaSymbol getMana1() { + return mana1; + } + + public ColoredManaSymbol getMana2() { + return mana2; + } } diff --git a/Mage/src/mage/util/ManaUtil.java b/Mage/src/mage/util/ManaUtil.java index 59b938c6d5..8b3f8e133b 100644 --- a/Mage/src/mage/util/ManaUtil.java +++ b/Mage/src/mage/util/ManaUtil.java @@ -1,16 +1,15 @@ package mage.util; -import java.util.LinkedHashMap; -import java.util.UUID; import mage.Mana; +import mage.ManaSymbol; import mage.abilities.costs.mana.ManaCost; -import mage.abilities.mana.BasicManaAbility; -import mage.abilities.mana.BlackManaAbility; -import mage.abilities.mana.BlueManaAbility; -import mage.abilities.mana.GreenManaAbility; -import mage.abilities.mana.ManaAbility; -import mage.abilities.mana.RedManaAbility; -import mage.abilities.mana.WhiteManaAbility; +import mage.abilities.costs.mana.ManaSymbols; +import mage.abilities.mana.*; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.UUID; /** * @author noxx @@ -42,63 +41,292 @@ public class ManaUtil { * @return List of mana abilities permanent may produce and are reasonable for unpaid mana */ public static LinkedHashMap tryToAutoPay(ManaCost unpaid, LinkedHashMap useableAbilities) { + + // first check if we have only basic mana abilities + for (ManaAbility ability : useableAbilities.values()) { + if (!(ability instanceof BasicManaAbility)) { + // return map as-is without any modification + return useableAbilities; + } + } + + if (unpaid != null) { - Mana mana = unpaid.getMana(); - // first check if we have only basic mana abilities - for (ManaAbility ability : useableAbilities.values()) { - if (!(ability instanceof BasicManaAbility)) { - // return map as-is without any modification - return useableAbilities; - } - } - int countColorfull = 0; - int countColorless = 0; - ManaAbility chosenManaAbility = null; - for (ManaAbility ability : useableAbilities.values()) { - if (ability instanceof RedManaAbility && mana.contains(Mana.RedMana)) { - chosenManaAbility = ability; - countColorfull++; - } - if (ability instanceof BlackManaAbility && mana.contains(Mana.BlackMana)) { - chosenManaAbility = ability; - countColorfull++; - } - if (ability instanceof BlueManaAbility && mana.contains(Mana.BlueMana)) { - chosenManaAbility = ability; - countColorfull++; - } - if (ability instanceof WhiteManaAbility && mana.contains(Mana.WhiteMana)) { - chosenManaAbility = ability; - countColorfull++; - } - if (ability instanceof GreenManaAbility && mana.contains(Mana.GreenMana)) { - chosenManaAbility = ability; - countColorfull++; - } - } + ManaSymbols symbols = ManaSymbols.buildFromManaCost(unpaid); + Mana unpaidMana = unpaid.getMana(); - if (countColorfull == 0) { // seems there is no colorful mana we can use - // try to pay {1} - if (mana.getColorless() > 0) { - // use first - return replace(useableAbilities, useableAbilities.values().iterator().next()); - } - - // return map as-is without any modification - return useableAbilities; + if (!symbols.isEmpty()) { + return getManaAbilitiesUsingManaSymbols(useableAbilities, symbols, unpaidMana); + } else { + return getManaAbilitiesUsingMana(unpaid, useableAbilities); } - - if (countColorfull > 1) { // we can't auto choose as there are variant of mana payment - // return map as-is without any modification - return useableAbilities; - } - - return replace(useableAbilities, chosenManaAbility); } return useableAbilities; } + private static LinkedHashMap getManaAbilitiesUsingManaSymbols(LinkedHashMap useableAbilities, ManaSymbols symbols, Mana unpaidMana) { + Set countColored = new HashSet<>(); + + ManaAbility chosenManaAbility = null; + ManaAbility chosenManaAbilityForHybrid; + for (ManaAbility ability : useableAbilities.values()) { + chosenManaAbility = getManaAbility(symbols, countColored, chosenManaAbility, ability); + + chosenManaAbilityForHybrid = checkRedMana(symbols, countColored, ability); + chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; + checkBlackMana(symbols, countColored, ability); + chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; + checkBlueMana(symbols, countColored, ability); + chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; + checkWhiteMana(symbols, countColored, ability); + chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; + checkGreenMana(symbols, countColored, ability); + chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; + } + + if (countColored.size() == 0) { // seems there is no colorful mana we can pay for + // try to pay {1} + if (unpaidMana.getColorless() > 0) { + // use any (lets choose first) + return replace(useableAbilities, useableAbilities.values().iterator().next()); + } + + // return map as-is without any modification + return useableAbilities; + } + + if (countColored.size() > 1) { + // we may try to pay for hybrid mana symbol + Set temp = new HashSet<>(); + temp.addAll(countColored); + for (ManaSymbol manaSymbol : countColored) { + // idea: if we have {W/R} symbol then we can remove it if symbols contain {W} or {R} + // but only if it doesn't contain both of them + if (manaSymbol.isHybrid()) { + boolean found1 = countColored.contains(manaSymbol.getManaSymbol1()); + boolean found2 = countColored.contains(manaSymbol.getManaSymbol2()); + if (found1 && !found2) { + temp.remove(manaSymbol); + } else if (!found1 && found2) { + temp.remove(manaSymbol); + } + } + } + + // we got another chance for auto pay + if (temp.size() == 1) { + for (ManaAbility ability : useableAbilities.values()) { + chosenManaAbility = getManaAbility(symbols, countColored, chosenManaAbility, ability); + } + return replace(useableAbilities, chosenManaAbility); + } + + // we can't auto choose as there are variants of mana payment + // return map as-is without any modification + return useableAbilities; + } + + return replace(useableAbilities, chosenManaAbility); + } + + private static ManaAbility getManaAbility(ManaSymbols symbols, Set countColored, ManaAbility chosenManaAbility, ManaAbility ability) { + if (ability instanceof RedManaAbility && symbols.contains(ManaSymbol.R)) { + chosenManaAbility = ability; + countColored.add(ManaSymbol.R); + } + if (ability instanceof BlackManaAbility && symbols.contains(ManaSymbol.B)) { + chosenManaAbility = ability; + countColored.add(ManaSymbol.B); + } + if (ability instanceof BlueManaAbility && symbols.contains(ManaSymbol.U)) { + chosenManaAbility = ability; + countColored.add(ManaSymbol.U); + } + if (ability instanceof WhiteManaAbility && symbols.contains(ManaSymbol.W)) { + chosenManaAbility = ability; + countColored.add(ManaSymbol.W); + } + if (ability instanceof GreenManaAbility && symbols.contains(ManaSymbol.G)) { + chosenManaAbility = ability; + countColored.add(ManaSymbol.G); + } + return chosenManaAbility; + } + + /** + * Counts DIFFERENT hybrid mana symbols. + * + * @param symbols + * @return + */ + private static int countUniqueHybridSymbols(Set symbols) { + int count = 0; + for (ManaSymbol symbol : symbols) { + if (symbol.isHybrid()) { + count++; + } + } + return count; + } + + private static ManaAbility checkBlackMana(ManaSymbols symbols, Set countColored, ManaAbility ability) { + ManaAbility chosenManaAbilityForHybrid = null; + if (ability instanceof BlackManaAbility) { + if (symbols.contains(ManaSymbol.HYBRID_BR)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_BR); + } else if (symbols.contains(ManaSymbol.HYBRID_BG)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_BG); + } else if (symbols.contains(ManaSymbol.HYBRID_UB)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_UB); + } else if (symbols.contains(ManaSymbol.HYBRID_WB)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_WB); + } + } + + return chosenManaAbilityForHybrid; + } + + private static ManaAbility checkRedMana(ManaSymbols symbols, Set countColored, ManaAbility ability) { + ManaAbility chosenManaAbilityForHybrid = null; + if (ability instanceof RedManaAbility) { + if (symbols.contains(ManaSymbol.HYBRID_BR)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_BR); + } else if (symbols.contains(ManaSymbol.HYBRID_RG)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_RG); + } else if (symbols.contains(ManaSymbol.HYBRID_RW)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_RW); + } else if (symbols.contains(ManaSymbol.HYBRID_UR)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_UR); + } + } + return chosenManaAbilityForHybrid; + } + + private static ManaAbility checkBlueMana(ManaSymbols symbols, Set countColored, ManaAbility ability) { + ManaAbility chosenManaAbilityForHybrid = null; + if (ability instanceof BlueManaAbility) { + if (symbols.contains(ManaSymbol.HYBRID_UB)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_UB); + } else if (symbols.contains(ManaSymbol.HYBRID_UR)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_UR); + } else if (symbols.contains(ManaSymbol.HYBRID_WU)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_WU); + } else if (symbols.contains(ManaSymbol.HYBRID_GU)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_GU); + } + } + return chosenManaAbilityForHybrid; + } + + private static ManaAbility checkWhiteMana(ManaSymbols symbols, Set countColored, ManaAbility ability) { + ManaAbility chosenManaAbilityForHybrid = null; + if (ability instanceof WhiteManaAbility) { + if (symbols.contains(ManaSymbol.HYBRID_WU)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_WU); + } else if (symbols.contains(ManaSymbol.HYBRID_WB)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_WB); + } else if (symbols.contains(ManaSymbol.HYBRID_GW)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_GW); + } else if (symbols.contains(ManaSymbol.HYBRID_RW)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_RW); + } + } + return chosenManaAbilityForHybrid; + } + + private static ManaAbility checkGreenMana(ManaSymbols symbols, Set countColored, ManaAbility ability) { + ManaAbility chosenManaAbilityForHybrid = null; + if (ability instanceof GreenManaAbility) { + if (symbols.contains(ManaSymbol.HYBRID_GW)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_GW); + } else if (symbols.contains(ManaSymbol.HYBRID_GU)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_GU); + } else if (symbols.contains(ManaSymbol.HYBRID_BG)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_BG); + } else if (symbols.contains(ManaSymbol.HYBRID_RG)) { + chosenManaAbilityForHybrid = ability; + countColored.add(ManaSymbol.HYBRID_RG); + } + } + return chosenManaAbilityForHybrid; + } + + /** + * This is old method that uses unpaid mana to filter out some abilities. + * The only disadvantage is that it can't handle hybrid mana correctly. + * + * @param unpaid + * @param useableAbilities + * @return + */ + private static LinkedHashMap getManaAbilitiesUsingMana(ManaCost unpaid, LinkedHashMap useableAbilities) { + Mana mana = unpaid.getMana(); + + int countColorfull = 0; + int countColorless = 0; + ManaAbility chosenManaAbility = null; + for (ManaAbility ability : useableAbilities.values()) { + if (ability instanceof RedManaAbility && mana.contains(Mana.RedMana)) { + chosenManaAbility = ability; + countColorfull++; + } + if (ability instanceof BlackManaAbility && mana.contains(Mana.BlackMana)) { + chosenManaAbility = ability; + countColorfull++; + } + if (ability instanceof BlueManaAbility && mana.contains(Mana.BlueMana)) { + chosenManaAbility = ability; + countColorfull++; + } + if (ability instanceof WhiteManaAbility && mana.contains(Mana.WhiteMana)) { + chosenManaAbility = ability; + countColorfull++; + } + if (ability instanceof GreenManaAbility && mana.contains(Mana.GreenMana)) { + chosenManaAbility = ability; + countColorfull++; + } + } + + if (countColorfull == 0) { // seems there is no colorful mana we can use + // try to pay {1} + if (mana.getColorless() > 0) { + // use any (lets choose first) + return replace(useableAbilities, useableAbilities.values().iterator().next()); + } + + // return map as-is without any modification + return useableAbilities; + } + + if (countColorfull > 1) { // we can't auto choose as there are variants of mana payment + // return map as-is without any modification + return useableAbilities; + } + + return replace(useableAbilities, chosenManaAbility); + } + private static LinkedHashMap replace(LinkedHashMap useableAbilities, ManaAbility chosenManaAbility) { // modify the map with the chosen mana ability useableAbilities.clear();