diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/PartyCountTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/PartyCountTest.java new file mode 100644 index 00000000000..1a18557d7d6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/dynamicvalue/PartyCountTest.java @@ -0,0 +1,127 @@ +package org.mage.test.cards.dynamicvalue; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class PartyCountTest extends CardTestPlayerBase { + + private static final String shpd = "Shepherd of Heroes"; + private static final String skrmshr = "Aven Skirmisher"; + private static final String dncstr = "Dromoka Dunecaster"; + private static final String ddgr = "Goldmeadow Dodger"; + + @Test + public void testSingleMember() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.HAND, playerA, shpd); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 22); + } + + @Test + public void testTwoMembers() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.HAND, playerA, skrmshr); + addCard(Zone.HAND, playerA, shpd); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, skrmshr); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 24); + } + + @Test + public void testTwoMembers2() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + addCard(Zone.HAND, playerA, skrmshr, 2); + addCard(Zone.HAND, playerA, shpd); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, skrmshr); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, skrmshr); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 24); + } + + @Test + public void testThreeMembers() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + addCard(Zone.HAND, playerA, skrmshr); + addCard(Zone.HAND, playerA, dncstr); + addCard(Zone.HAND, playerA, shpd); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, skrmshr); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, dncstr); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 26); + } + + private void makeCreature(String name, SubType... subTypes) { + addCustomCardWithAbility(name, playerA, null, null, CardType.CREATURE, "{1}", Zone.BATTLEFIELD, subTypes); + } + + @Test + public void testOddCombos() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.HAND, playerA, shpd); + makeCreature("crtA", SubType.ROGUE, SubType.WIZARD, SubType.WARRIOR); + makeCreature("crtB", SubType.ROGUE, SubType.CLERIC); + makeCreature("crtC", SubType.CLERIC, SubType.WIZARD); + makeCreature("crtD", SubType.WARRIOR, SubType.WIZARD); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 28); + } + + @Test + public void testChangelings() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Impostor of the Sixth Pride", 4); + addCard(Zone.HAND, playerA, shpd); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, shpd); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 28); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 4889220447e..55ddf6c13dd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -1,5 +1,6 @@ package org.mage.test.serverside.base; +import mage.MageInt; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; @@ -381,8 +382,15 @@ public abstract class MageTestPlayerBase { protected void addCustomCardWithAbility(String customName, TestPlayer controllerPlayer, Ability ability, SpellAbility spellAbility, CardType cardType, String spellCost, Zone putAtZone) { + addCustomCardWithAbility(customName, controllerPlayer, ability, spellAbility, cardType, spellCost, putAtZone, null); + } + + protected void addCustomCardWithAbility(String customName, TestPlayer controllerPlayer, Ability ability, SpellAbility spellAbility, + CardType cardType, String spellCost, Zone putAtZone, SubType... additionalSubTypes) { CustomTestCard.clearCustomAbilities(customName); CustomTestCard.addCustomAbility(customName, spellAbility, ability); + CustomTestCard.clearAdditionalSubtypes(customName); + CustomTestCard.addAdditionalSubtypes(customName, additionalSubTypes); CardSetInfo testSet = new CardSetInfo(customName, "custom", "123", Rarity.COMMON); PermanentCard card = new PermanentCard(new CustomTestCard(controllerPlayer.getId(), testSet, cardType, spellCost), controllerPlayer.getId(), currentGame); @@ -414,6 +422,7 @@ class CustomTestCard extends CardImpl { static private final Map> abilitiesList = new HashMap<>(); // card name -> abilities static private final Map spellAbilitiesList = new HashMap<>(); // card name -> spell ability + static private final Map> subTypesList = new HashMap<>(); // card name -> additional subtypes static void addCustomAbility(String cardName, SpellAbility spellAbility, Ability ability) { if (!abilitiesList.containsKey(cardName)) { @@ -430,6 +439,16 @@ class CustomTestCard extends CardImpl { spellAbilitiesList.remove(cardName); } + static void addAdditionalSubtypes(String cardName, SubType... subtypes) { + if(subtypes!=null) { + subTypesList.computeIfAbsent(cardName, s -> new HashSet<>()).addAll(Arrays.asList(subtypes.clone())); + } + } + + static void clearAdditionalSubtypes(String cardName) { + subTypesList.remove(cardName); + } + CustomTestCard(UUID ownerId, CardSetInfo setInfo, CardType cardType, String spellCost) { super(ownerId, setInfo, new CardType[]{cardType}, spellCost); @@ -443,6 +462,17 @@ class CustomTestCard extends CardImpl { this.addAbility(ability.copy()); } } + + Set subTypeSet = subTypesList.get(setInfo.getName()); + if (subTypeSet != null) { + for (SubType subType : subTypeSet) { + this.subtype.add(subType); + } + } + if (cardType == CardType.CREATURE) { + this.power = new MageInt(1); + this.toughness = new MageInt(1); + } } private CustomTestCard(final CustomTestCard card) { diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/PartyCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/PartyCount.java index c2e03fcbef0..5b9c893577e 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/PartyCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/PartyCount.java @@ -4,7 +4,9 @@ import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.constants.SubType; -import mage.filter.StaticFilters; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -16,11 +18,94 @@ import java.util.stream.Collectors; */ public enum PartyCount implements DynamicValue { instance; + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.or( + SubType.CLERIC.getPredicate(), + SubType.ROGUE.getPredicate(), + SubType.WARRIOR.getPredicate(), + SubType.WIZARD.getPredicate() + )); + } + + private static final List partyTypes = Arrays.asList( + SubType.CLERIC, + SubType.ROGUE, + SubType.WARRIOR, + SubType.WIZARD + ); + + private static boolean attemptRearrange(SubType subType, UUID uuid, Set creatureTypes, Map subTypeUUIDMap, Map> creatureTypesMap) { + UUID uuid1 = subTypeUUIDMap.get(subType); + if (uuid1 == null) { + return false; + } + Set creatureTypes1 = creatureTypesMap.get(uuid1); + for (SubType subType1 : creatureTypes1) { + if (subType == subType1) { + continue; + } + if (!subTypeUUIDMap.containsKey(subType1)) { + subTypeUUIDMap.put(subType, uuid); + subTypeUUIDMap.put(subType1, uuid1); + return true; + } + return attemptRearrange(subType1, uuid1, creatureTypes, subTypeUUIDMap, creatureTypesMap); + } + return false; + } + + private static Set makeSet(Permanent permanent, Game game) { + Set subTypeSet = new HashSet<>(); + for (SubType subType : partyTypes) { + if (permanent.hasSubtype(subType, game)) { + subTypeSet.add(subType); + } + } + return subTypeSet; + } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - // TODO: implement this (in separate branch for now) - return 0; + Map> creatureTypesMap = new HashMap<>(); + game.getBattlefield() + .getActivePermanents( + filter, sourceAbility.getControllerId(), sourceAbility.getSourceId(), game + ).stream() + .forEach(permanent -> creatureTypesMap.put(permanent.getId(), makeSet(permanent, game))); + if (creatureTypesMap.size() < 2) { + return creatureTypesMap.size(); + } + Set availableTypes = creatureTypesMap + .values() + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + if (creatureTypesMap.size() == 2) { + return Math.min(2, availableTypes.size()); + } + Map subTypeUUIDMap = new HashMap<>(); + for (Map.Entry> entry : creatureTypesMap.entrySet()) { + for (SubType subType : entry.getValue()) { + if (!subTypeUUIDMap.containsKey(subType)) { + subTypeUUIDMap.put(subType, entry.getKey()); + break; + } + } + if (subTypeUUIDMap.size() >= availableTypes.size()) { + return subTypeUUIDMap.size(); + } else if (subTypeUUIDMap.containsValue(entry.getKey())) { + continue; + } else { + for (SubType subType : entry.getValue()) { + if (attemptRearrange(subType, entry.getKey(), entry.getValue(), subTypeUUIDMap, creatureTypesMap)) { + break; + } + } + } + } + return subTypeUUIDMap.keySet().size(); } @Override