From 3205cc867f402e694970a629b11c5277827f070e Mon Sep 17 00:00:00 2001 From: magenoxx Date: Thu, 14 Mar 2013 18:31:31 +0400 Subject: [PATCH] Code and tests for Issue#156: Make smarter dual lands mana choose --- .../src/mage/player/human/HumanPlayer.java | 8 +- .../org/mage/test/utils/ManaUtilTest.java | 86 +++++++++++++++ Mage/src/mage/util/ManaUtil.java | 104 ++++++++++++++++++ 3 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java create mode 100644 Mage/src/mage/util/ManaUtil.java diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index e90cdac9888..df93a9982a6 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -61,6 +61,7 @@ import mage.target.TargetPermanent; import mage.target.common.TargetAttackingCreature; import mage.target.common.TargetCreatureOrPlayer; import mage.target.common.TargetDefender; +import mage.util.ManaUtil; import org.apache.log4j.Logger; import java.io.Serializable; @@ -454,7 +455,7 @@ public class HumanPlayer extends PlayerImpl { if (response.getBoolean() != null) { return false; } else if (response.getUUID() != null) { - playManaAbilities(game); + playManaAbilities(unpaid, game); } else if (response.getString() != null && response.getString().equals("special")) { if (unpaid instanceof ManaCostsImpl) { ManaCostsImpl costs = (ManaCostsImpl) unpaid; @@ -483,12 +484,12 @@ public class HumanPlayer extends PlayerImpl { game.informPlayers(getName() + " payed " + cost.getPayment().count() + " for " + cost.getText()); cost.setPaid(); } else if (response.getUUID() != null) { - playManaAbilities(game); + playManaAbilities(null, game); } return true; } - protected void playManaAbilities(Game game) { + protected void playManaAbilities(ManaCost unpaid, Game game) { updateGameStatePriority("playManaAbilities", game); MageObject object = game.getObject(response.getUUID()); if (object == null) return; @@ -497,6 +498,7 @@ public class HumanPlayer extends PlayerImpl { if (zone != null) { useableAbilities = getUseableManaAbilities(object, zone, game); if (useableAbilities != null && useableAbilities.size() > 0) { + useableAbilities = ManaUtil.tryToAutoPay(unpaid, useableAbilities); activateAbility(useableAbilities, game); } } 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 new file mode 100644 index 00000000000..14129118545 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java @@ -0,0 +1,86 @@ +package org.mage.test.utils; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.mana.ManaAbility; +import mage.cards.Card; +import mage.cards.repository.CardRepository; +import mage.util.ManaUtil; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.UUID; + +/** + * @author noxx + */ +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("{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("{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, 1); // should use {B} + + 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 + } + + /** + * Common way to test ManaUtil.tryToAutoPay + * + * 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 expected2 The amount of mana abilities that ManaUtil.tryToAutoPay should be returned after optimization. + */ + private void testManaToPayVsLand(String manaToPay, String landName, int expected1, int expected2) { + ManaCost unpaid = new ManaCostsImpl(manaToPay); + Card card = CardRepository.instance.findCard(landName).getCard(); + Assert.assertNotNull(card); + + HashMap useableAbilities = getManaAbilities(card); + Assert.assertEquals(expected1, useableAbilities.size()); + + useableAbilities = ManaUtil.tryToAutoPay(unpaid, (LinkedHashMap)useableAbilities); + Assert.assertEquals(expected2, useableAbilities.size()); + } + + /** + * Extracts mana abilities from the card. + * + * @param card Card to extract mana abilities from. + * @return + */ + private HashMap getManaAbilities(Card card) { + HashMap useableAbilities = new LinkedHashMap(); + for (Ability ability: card.getAbilities()) { + if (ability instanceof ManaAbility) { + ability.newId(); // we need to assign id manually as we are not in game + useableAbilities.put(ability.getId(), (ManaAbility)ability); + } + } + return useableAbilities; + } +} diff --git a/Mage/src/mage/util/ManaUtil.java b/Mage/src/mage/util/ManaUtil.java new file mode 100644 index 00000000000..6d01efaad86 --- /dev/null +++ b/Mage/src/mage/util/ManaUtil.java @@ -0,0 +1,104 @@ +package mage.util; + +import mage.Mana; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.mana.*; + +import java.util.LinkedHashMap; +import java.util.UUID; + +/** + * @author noxx + */ +public class ManaUtil { + + private ManaUtil() {} + + /** + * In case the choice of mana to be produced is obvious, let's discard all other abilities. + * + * Example: + * Pay {W}{R} + * + * Land produces {W} or {G}. + * + * No need to ask what player wants to choose. + * {W} mana ability should be left only. + * + * But we CAN do auto choice only in case we have basic mana abilities. + * Example: + * we should pay {1} and we have Cavern of Souls that can produce {1} or any mana of creature type choice. + * We can't simply auto choose {1} as the second mana ability also makes spell uncounterable. + * + * In case we can't auto choose we'll simply return the useableAbilities map back to caller without any modification. + * + * @param unpaid Mana we need to pay. Can be null (it is for X costs now). + * @param useableAbilities List of mana abilities permanent may produce + * @return List of mana abilities permanent may produce and are reasonable for unpaid mana + */ + public static LinkedHashMap tryToAutoPay(ManaCost unpaid, LinkedHashMap 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++; + } + } + + 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 (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 replace(LinkedHashMap useableAbilities, ManaAbility chosenManaAbility) { + // modify the map with the chosen mana ability + useableAbilities.clear(); + useableAbilities.put(chosenManaAbility.getId(), chosenManaAbility); + + return useableAbilities; + } +}