diff --git a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java index 00ce3e7af75..8b517db7ee2 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java @@ -87,7 +87,7 @@ public class AddLandDialog extends MageDialog { landSetNames.add(expansionInfo.getName()); } if (landSetNames.isEmpty()) { - throw new IllegalArgumentException("No set with basic land was found"); + throw new IllegalArgumentException("No set with basic land was found (possible memory problems, need client restart)"); } if (landSetNames.size() > 1) { landSetNames.add(""); @@ -477,6 +477,7 @@ public class AddLandDialog extends MageDialog { }//GEN-LAST:event_btnSetFastSearchActionPerformed private void autoAddLands() { + // suggest lands amount for deck without lands int deckSize = ((Number) spnDeckSize.getValue()).intValue(); int[] lands = DeckBuildUtils.landCountSuggestion(deckSize, deck.getMaindeckCards()); spnPlains.setValue(lands[0]); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckAutoLandsTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckAutoLandsTest.java new file mode 100644 index 00000000000..35c08188042 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckAutoLandsTest.java @@ -0,0 +1,166 @@ +package org.mage.test.serverside.deck; + +import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLists; +import mage.game.GameException; +import mage.util.DeckBuildUtils; +import mage.util.TournamentUtil; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.MageTestPlayerBase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Testing lands suggestion and adding. It used in: + * - client side: for auto-lands suggest button + * - server side: for AI and invalid deck's timeout + * + * @author JayDi85 + */ +public class DeckAutoLandsTest extends MageTestPlayerBase { + + @Test + public void test_LandsSuggest_OneColor() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Grizzly Bears", "129", "S99", 40) + )); + assertSuggestedLands("one color", deck, 60, 0, 0, 0, 0, 20); + } + + @Test + public void test_LandsSuggest_TwoColors() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Arbalest Engineers", "206", "BRO", 40) + )); + assertSuggestedLands("two colors", deck, 60, 0, 0, 0, 10, 10); + } + + @Test + public void test_LandsSuggest_ThreeColors() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Adun Oakenshield", "216", "LEG", 40) + )); + assertSuggestedLands("tree colors", deck, 60, 0, 0, 7, 7, 6); + } + + @Test + public void test_LandsSuggest_FiveColors() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Atogatog", "286", "ODY", 40) + )); + assertSuggestedLands("five colors", deck, 60, 4, 4, 4, 4, 4); + } + + @Test + public void test_LandsSuggest_NoColors() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Abstruse Archaic", "712", "CMM", 40) + )); + assertSuggestedLands("no colors", deck, 60, 4, 4, 4, 4, 4); + } + + @Test + public void test_LandsSuggest_NoNeedLands() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Grizzly Bears", "129", "S99", 40) + )); + assertSuggestedLands("no need lands - 39", deck, 39, 0, 0, 0, 0, 0); + assertSuggestedLands("no need lands - 40", deck, 40, 0, 0, 0, 0, 0); + assertSuggestedLands("no need lands - 41", deck, 41, 0, 0, 0, 0, 1); + } + + private Deck prepareDeck(List cards) { + DeckCardLists source = new DeckCardLists(); + source.getCards().addAll(cards); + Deck deck = null; + try { + deck = Deck.load(source, true); + } catch (GameException e) { + Assert.fail("Can't prepare deck: " + cards); + } + return deck; + } + + private void assertSuggestedLands( + String info, + Deck deck, + int needTotal, + int needPlains, + int needIslands, + int needSwamps, + int needMountains, + int needForests) { + int[] lands = DeckBuildUtils.landCountSuggestion(needTotal, deck.getMaindeckCards()); + List current = new ArrayList<>(Arrays.asList(lands[0], lands[1], lands[2], lands[3], lands[4])); + List need = new ArrayList<>(Arrays.asList(needPlains, needIslands, needSwamps, needMountains, needForests)); + Assert.assertTrue(info + " - wrong deck size", deck.getMaindeckCards().size() + current.stream().mapToInt(x -> x).sum() >= needTotal); + Assert.assertEquals(info + " - wrong lands count (WUBRG)", need, current); + } + + @Test + public void test_PossibleSets_OneCompatibleSet() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Grizzly Bears", "129", "S99", 1) + )); + assertPossibleSets("one compatible set", deck, Arrays.asList("S99")); + } + + @Test + public void test_PossibleSets_MakeSureNoSnowLands() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Grizzly Bears", "129", "S99", 1), + new DeckCardInfo("Abominable Treefolk", "194", "MH1", 1) // MH1 with snow lands + )); + assertPossibleSets("no snow lands", deck, Arrays.asList("S99")); + } + + @Test + public void test_PossibleSets_MultipleCompatibleSets() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Grizzly Bears", "129", "S99", 1), + new DeckCardInfo("Akki Raider", "92", "BOK", 1), // BOK without lands, but with boosters + new DeckCardInfo("Grizzly Bears", "169", "POR", 1), + new DeckCardInfo("Aggravated Assault", "25", "MP2", 1) // MP2 without lands and boosters + )); + assertPossibleSets("multiple compatible sets", deck, Arrays.asList("POR", "S99")); + } + + @Test + public void test_PossibleSets_CompatibleBlocks() { + // BOK from Kamigawa block, so it must look at: + // * CHK - Champions of Kamigawa - has lands + // * SOK - Saviors of Kamigawa - no lands + // * BOK - Betrayers of Kamigawa - no lands + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Akki Raider", "92", "BOK", 1), // BOK without lands, but with boosters + new DeckCardInfo("Aggravated Assault", "25", "MP2", 1) // MP2 without lands and boosters + )); + assertPossibleSets("compatible block", deck, Arrays.asList("CHK")); + } + + @Test + public void test_PossibleSets_NoCompatibleSetsOrBlocks() { + Deck deck = prepareDeck(Arrays.asList( + new DeckCardInfo("Amulet of Kroog", "36", "ATQ", 1) // ATQ without lands + )); + // must find 2 random sets + List possibleSets1 = TournamentUtil.getLandSetCodeForDeckSets(deck.getExpansionSetCodes()).stream().sorted().collect(Collectors.toList()); + List possibleSets2 = TournamentUtil.getLandSetCodeForDeckSets(deck.getExpansionSetCodes()).stream().sorted().collect(Collectors.toList()); + Assert.assertEquals("must find 1 random set, try 1", 1, possibleSets1.size()); + Assert.assertEquals("must find 1 random set, try 2", 1, possibleSets2.size()); + Assert.assertNotEquals("must find random sets, try 3", possibleSets1.get(0), possibleSets2.get(0)); + } + + private void assertPossibleSets( + String info, + Deck deck, + List needSets) { + List possibleSets = TournamentUtil.getLandSetCodeForDeckSets(deck.getExpansionSetCodes()).stream().sorted().collect(Collectors.toList()); + Assert.assertEquals(info + " - wrong possible sets", needSets, possibleSets); + } +} diff --git a/Mage/src/main/java/mage/util/DeckBuildUtils.java b/Mage/src/main/java/mage/util/DeckBuildUtils.java index c7bae4d8175..20d264666ce 100644 --- a/Mage/src/main/java/mage/util/DeckBuildUtils.java +++ b/Mage/src/main/java/mage/util/DeckBuildUtils.java @@ -6,12 +6,12 @@ import java.util.Set; public final class DeckBuildUtils { + /** + * Returns the number of basic lands suggested to complete a deck + * as an array of five ints: plains, islands, swamps, mountains, forests + * Total number of lands always sufficient to reach deckSize + */ public static int[] landCountSuggestion(int deckSize, Set deckList) { - /* - Returns the number of basic lands suggested to complete a deck - as an array of five ints: plains, islands, swamps, mountains, forests - Total number of lands always sufficient to reach deckSize - */ int plains = 0, islands = 0, swamps = 0, mountains = 0, forests = 0; int landsNeeded = deckSize - deckList.size(); if (landsNeeded > 0) { @@ -51,9 +51,4 @@ public final class DeckBuildUtils { } return new int[] {plains, islands, swamps, mountains, forests}; } - - // Hide constructor - not to be instantiated - private DeckBuildUtils() { - } - } diff --git a/Mage/src/main/java/mage/util/TournamentUtil.java b/Mage/src/main/java/mage/util/TournamentUtil.java index 165c59d9d04..f9e3d0025ae 100644 --- a/Mage/src/main/java/mage/util/TournamentUtil.java +++ b/Mage/src/main/java/mage/util/TournamentUtil.java @@ -15,14 +15,12 @@ public final class TournamentUtil { /** * Tries to calculate the most appropriate sets to add basic lands for cards of a deck * - * @param setCodesDeck - * @return setCode for lands + * @param setCodesDeck all sets in current deck */ - public static Set getLandSetCodeForDeckSets(Collection setCodesDeck) { - Set landSetCodes = new HashSet<>(); - // decide from which sets basic lands are taken from + + // from deck's sets for (String setCode : setCodesDeck) { ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode); if (expansionInfo.hasBasicLands() && !CardRepository.haveSnowLands(setCode)) { @@ -30,7 +28,7 @@ public final class TournamentUtil { } } - // if sets have no basic land, take land from block + // from deck's blocks if (landSetCodes.isEmpty()) { for (String setCode : setCodesDeck) { ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode); @@ -42,10 +40,9 @@ public final class TournamentUtil { } } } - // if still no set with lands found, take one by random + + // from random if (landSetCodes.isEmpty()) { - // if sets have no basic lands and also it has no parent or parent has no lands get last set with lands - // select a set with basic lands by random List basicLandSets = ExpansionRepository.instance.getSetsWithBasicLandsByReleaseDate() .stream() .filter(exp -> !CardRepository.haveSnowLands(exp.getCode())) @@ -56,7 +53,7 @@ public final class TournamentUtil { } if (landSetCodes.isEmpty()) { - throw new IllegalArgumentException("No set with basic land was found"); + throw new IllegalArgumentException("No set with basic land was found (possible memory problems, need server restart)"); } return landSetCodes; }