From 01992c62e619461c1dd6077a0d33d4d94fa2fffc Mon Sep 17 00:00:00 2001 From: pfirpfel Date: Wed, 21 Oct 2020 14:43:51 +0200 Subject: [PATCH] Add initial new themed chaos draft functionality Add new randomizer --- .../src/main/java/forge/card/CardEdition.java | 5 + .../item/generation/ChaosBoosterSupplier.java | 17 +- .../main/java/forge/util/BagRandomizer.java | 68 +++++++ forge-gui/res/blockdata/chaosdraftthemes.txt | 5 + forge-gui/res/languages/de-DE.properties | 1 + forge-gui/res/languages/en-US.properties | 1 + .../main/java/forge/limited/BoosterDraft.java | 51 +++--- .../java/forge/limited/ThemedChaosDraft.java | 172 ++++++++++++++++++ .../src/main/java/forge/model/FModel.java | 7 + 9 files changed, 292 insertions(+), 35 deletions(-) create mode 100644 forge-core/src/main/java/forge/util/BagRandomizer.java create mode 100644 forge-gui/res/blockdata/chaosdraftthemes.txt create mode 100644 forge-gui/src/main/java/forge/limited/ThemedChaosDraft.java diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index c69b165d995..bc3d1854a28 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -124,6 +124,7 @@ public final class CardEdition implements Comparable { // immutable private boolean smallSetOverride = false; private String boosterMustContain = ""; private String boosterReplaceSlotFromPrintSheet = ""; + private String[] chaosDraftThemes = new String[0]; private boolean doublePickToStartRound = false; private final CardInSet[] cards; private final Map tokenNormalized; @@ -195,6 +196,7 @@ public final class CardEdition implements Comparable { // immutable public boolean getDoublePickToStartRound() { return doublePickToStartRound; } public String getBoosterMustContain() { return boosterMustContain; } public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; } + public String[] getChaosDraftThemes() { return chaosDraftThemes; } public CardInSet[] getCards() { return cards; } public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others @@ -385,6 +387,9 @@ public final class CardEdition implements Comparable { // immutable res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature res.boosterReplaceSlotFromPrintSheet = section.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card + + res.chaosDraftThemes = section.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names + return res; } diff --git a/forge-core/src/main/java/forge/item/generation/ChaosBoosterSupplier.java b/forge-core/src/main/java/forge/item/generation/ChaosBoosterSupplier.java index a6dc60d6d83..215174ee822 100644 --- a/forge-core/src/main/java/forge/item/generation/ChaosBoosterSupplier.java +++ b/forge-core/src/main/java/forge/item/generation/ChaosBoosterSupplier.java @@ -1,25 +1,26 @@ package forge.item.generation; +import com.google.common.collect.Iterables; import forge.card.CardEdition; import forge.item.BoosterPack; import forge.item.PaperCard; +import forge.util.BagRandomizer; import java.util.List; public class ChaosBoosterSupplier implements IUnOpenedProduct { - private List sets; + private BagRandomizer randomizer; - public ChaosBoosterSupplier(List sets) { - this.sets = sets; + public ChaosBoosterSupplier(Iterable sets) throws Exception { + if (Iterables.size(sets) <= 0) { + throw new Exception("At least one set needed to generate chaos draft!"); + } + randomizer = new BagRandomizer<>(sets); } @Override public List get() { - if (sets.size() == 0) { - System.out.println("No chaos boosters left to supply."); - return null; - } - final CardEdition set = sets.remove(0); + final CardEdition set = randomizer.getNextItem(); final BoosterPack pack = new BoosterPack(set.getCode(), set.getBoosterTemplate()); return pack.getCards(); } diff --git a/forge-core/src/main/java/forge/util/BagRandomizer.java b/forge-core/src/main/java/forge/util/BagRandomizer.java new file mode 100644 index 00000000000..d59b3207e3b --- /dev/null +++ b/forge-core/src/main/java/forge/util/BagRandomizer.java @@ -0,0 +1,68 @@ +package forge.util; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; + +/** + * Data structure that allows random draws from a set number of items, + * where all items are returned once before the first will be retrieved. + * The bag will be shuffled after each time all items have been returned. + * @param an object + */ +public class BagRandomizer implements Iterable{ + private static Random random = new SecureRandom(); + + private T[] bag; + private int currentPosition = 0; + + public BagRandomizer(T[] items) { + bag = items; + } + + public BagRandomizer(Iterable items) { + ArrayList list = new ArrayList<>(); + for (T item : items) { + list.add(item); + } + bag = (T[]) list.toArray(); + } + + public T getNextItem() { + // reset bag if last position is reached + if (currentPosition >= bag.length) { + shuffleBag(); + currentPosition = 0; + } + return bag[currentPosition++]; + } + + private void shuffleBag() { + int n = bag.length; + for (int i = 0; i < n; i++) { + int r = (int) (random.nextDouble() * (i + 1)); + T swap = bag[r]; + bag[r] = bag[i]; + bag[i] = swap; + } + } + + @Override + public Iterator iterator() { + return new BagRandomizerIterator(); + } + + private class BagRandomizerIterator implements Iterator { + + @Override + public boolean hasNext() { + return bag.length > 0; + } + + @Override + public T next() { + return (T) BagRandomizer.this.getNextItem(); + } + } +} diff --git a/forge-gui/res/blockdata/chaosdraftthemes.txt b/forge-gui/res/blockdata/chaosdraftthemes.txt new file mode 100644 index 00000000000..b85cbd86f2d --- /dev/null +++ b/forge-gui/res/blockdata/chaosdraftthemes.txt @@ -0,0 +1,5 @@ +# Order, Tag, Label +1, DEFAULT, All sets (default) +2, CORE_SET, Core Sets +2, MASTER_SET, Masters Sets +3, RAVNICA, Ravnica (Plane) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index a2e1662921d..cee314236db 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1929,6 +1929,7 @@ lblPlanarDeckZone=Weltendeck lblNoneZone=Keine #BoosterDraft.java lblChooseBlock=Wähle Block +lblChooseChaosTheme=Wähle ein Chaos-Draft-Thema lblBlockNotContainSetCombinations={0} enthält keine Set-Auswahl. lblChooseSetCombination=Treffe Set-Auswahl lblNotFoundCustomDraftFiles=Keine angepaßte Draft-Datei gefunden. diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 5c9b42477b9..f58b045ed8c 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1929,6 +1929,7 @@ lblPlanarDeckZone=planardeck lblNoneZone=none #BoosterDraft.java lblChooseBlock=Choose Block +lblChooseChaosTheme=Choose Chaos Draft Theme lblBlockNotContainSetCombinations={0} does not contain any set combinations. lblChooseSetCombination=Choose Set Combination lblNotFoundCustomDraftFiles=No custom draft files found. diff --git a/forge-gui/src/main/java/forge/limited/BoosterDraft.java b/forge-gui/src/main/java/forge/limited/BoosterDraft.java index a3ffdf73ebe..2f6672df9c1 100644 --- a/forge-gui/src/main/java/forge/limited/BoosterDraft.java +++ b/forge-gui/src/main/java/forge/limited/BoosterDraft.java @@ -20,7 +20,6 @@ package forge.limited; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import forge.StaticData; import forge.card.CardEdition; import forge.deck.CardPool; @@ -41,7 +40,6 @@ import forge.util.gui.SGuiChoose; import forge.util.gui.SOptionPane; import forge.util.storage.IStorage; import forge.util.Localizer; -import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.ArrayUtils; import java.io.File; @@ -181,33 +179,32 @@ public class BoosterDraft implements IBoosterDraft { break; case Chaos: + + final List themes = new ArrayList<>(); + final IStorage themeStorage = FModel.getThemedChaosDrafts(); + + for (final ThemedChaosDraft theme : themeStorage) { + themes.add(theme); + } + + // TODO test oneOrNone behaviour + final ThemedChaosDraft theme = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblChooseChaosTheme"), themes); + if (theme == null) { + return false; + } + + final Predicate themeFilter = theme.getEditionFilter(); + final CardEdition.Collection allEditions = StaticData.instance().getEditions(); - final Iterable chaosDraftEditions = Iterables.filter(allEditions.getOrderedEditions(), new Predicate() { - @Override - public boolean apply(final CardEdition cardEdition) { - boolean isExpansion = cardEdition.getType().equals(CardEdition.Type.EXPANSION); - boolean isCoreSet = cardEdition.getType().equals(CardEdition.Type.CORE); - boolean isReprintSet = cardEdition.getType().equals(CardEdition.Type.REPRINT); - if (isExpansion || isCoreSet || isReprintSet) { - // Only allow sets with 15 cards in booster packs - if (cardEdition.hasBoosterTemplate()) { - final List> slots = cardEdition.getBoosterTemplate().getSlots(); - int boosterSize = 0; - for (Pair slot : slots) { - boosterSize += slot.getRight(); - } - return boosterSize == 15; - } - } - return false; - } - }); + final Iterable chaosDraftEditions = Iterables.filter(allEditions.getOrderedEditions(), themeFilter); - // Randomize order of sets - List shuffled = Lists.newArrayList(chaosDraftEditions); - Collections.shuffle(shuffled); - - final Supplier> ChaosDraftSupplier = new ChaosBoosterSupplier(shuffled); + final Supplier> ChaosDraftSupplier; + try{ + ChaosDraftSupplier = new ChaosBoosterSupplier(chaosDraftEditions); + } catch(Exception e) { + System.out.println(e.getMessage()); + return false; + } for (int i = 0; i < 3; i++) { this.product.add(ChaosDraftSupplier); diff --git a/forge-gui/src/main/java/forge/limited/ThemedChaosDraft.java b/forge-gui/src/main/java/forge/limited/ThemedChaosDraft.java new file mode 100644 index 00000000000..4c4cd8338b0 --- /dev/null +++ b/forge-gui/src/main/java/forge/limited/ThemedChaosDraft.java @@ -0,0 +1,172 @@ +package forge.limited; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; + +import forge.card.CardEdition; +import forge.util.TextUtil; +import forge.util.storage.StorageReaderFile; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; + +/** + * Themed chaos draft allows limiting the pool of available random boosters for a draft to a certain theme. + */ +public class ThemedChaosDraft implements Comparable { + private final String tag; + private final String label; + private final int orderNumber; + + /** + * @param tag Tag name used in edition files. + * @param label Label used in user interface. + * @param orderNumber Number used to order entries in user interface. + */ + public ThemedChaosDraft(String tag, String label, int orderNumber) { + this.tag = tag; + this.label = label; + this.orderNumber = orderNumber; + } + + /** + * @return theme tag + */ + public String getTag() { return tag; } + + /** + * @return theme label + */ + public String getLabel() { return label; } + + /** + * @return theme order number + */ + public int getOrderNumber() { return orderNumber; } + + public Predicate getEditionFilter() { + if (!tag.equals("DEFAULT")) { + System.out.println("Return themed filter for " + tag); // TODO remove + return themedFilter; + } + System.out.println("Return default filter"); // TODO remove + return DEFAULT_FILTER; + } + + private final Predicate themedFilter = new Predicate() { + @Override + public boolean apply(final CardEdition cardEdition) { + String[] themes = cardEdition.getChaosDraftThemes(); + for (String theme : themes) { + if (tag.equals(theme)) return true; + } + return false; + } + }; + + private static final Predicate DEFAULT_FILTER = new Predicate() { + @Override + public boolean apply(final CardEdition cardEdition) { + boolean isExpansion = cardEdition.getType().equals(CardEdition.Type.EXPANSION); + boolean isCoreSet = cardEdition.getType().equals(CardEdition.Type.CORE); + boolean isReprintSet = cardEdition.getType().equals(CardEdition.Type.REPRINT); + if (isExpansion || isCoreSet || isReprintSet) { + // Only allow sets with 15 cards in booster packs + if (cardEdition.hasBoosterTemplate()) { + final List> slots = cardEdition.getBoosterTemplate().getSlots(); + int boosterSize = 0; + for (Pair slot : slots) { + boosterSize += slot.getRight(); + } + return boosterSize == 15; + } + } + return false; + } + }; + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.label; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.tag == null) ? 0 : this.tag.hashCode()); + result = (prime * result) + ((this.label == null) ? 0 : this.label.hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(ThemedChaosDraft other) { + int order = this.orderNumber - other.orderNumber; + if (order != 0) return order; + return this.label.compareTo(other.label); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + + final ThemedChaosDraft other = (ThemedChaosDraft) obj; + if (!this.label.equals(other.label)) { + return false; + } + if (!this.tag.equals(other.tag)) { + return false; + } + return true; + } + + public static final Function FN_GET_TAG = new Function() { + @Override + public String apply(ThemedChaosDraft themedChaosBooster) { + return themedChaosBooster.getTag(); + } + }; + + public static class Reader extends StorageReaderFile { + public Reader(String pathname) { + super(pathname, ThemedChaosDraft.FN_GET_TAG); + } + + @Override + protected ThemedChaosDraft read(String line, int idx) { + final String[] sParts = TextUtil.splitWithParenthesis(line, ',', 3); + int orderNumber = Integer.parseInt(sParts[0].trim(), 10); + String tag = sParts[1].trim(); + String label = sParts[2].trim(); + return new ThemedChaosDraft(tag, label, orderNumber); + } + } +} diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index fe3dc41129f..1c1a621b8c1 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -37,6 +37,7 @@ import forge.gauntlet.GauntletData; import forge.interfaces.IProgressBar; import forge.itemmanager.ItemManagerConfig; import forge.limited.GauntletMini; +import forge.limited.ThemedChaosDraft; import forge.planarconquest.ConquestController; import forge.planarconquest.ConquestPlane; import forge.planarconquest.ConquestPreferences; @@ -91,6 +92,7 @@ public final class FModel { private static IStorage blocks; private static IStorage fantasyBlocks; + private static IStorage themedChaosDrafts; private static IStorage planes; private static IStorage worlds; private static GameFormat.Collection formats; @@ -187,6 +189,7 @@ public final class FModel { questPreferences = new QuestPreferences(); conquestPreferences = new ConquestPreferences(); fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); + themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); @@ -383,6 +386,10 @@ public final class FModel { return fantasyBlocks; } + public static IStorage getThemedChaosDrafts() { + return themedChaosDrafts; + } + public static TournamentData getTournamentData() { return tournamentData; } public static void setTournamentData(TournamentData tournamentData) { FModel.tournamentData = tournamentData; }