diff --git a/.gitattributes b/.gitattributes index 02ff9c557a0..9610e66c068 100644 --- a/.gitattributes +++ b/.gitattributes @@ -189,6 +189,7 @@ forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java -text forge-core/src/main/java/forge/deck/generation/DeckGeneratorMonoColor.java -text forge-core/src/main/java/forge/deck/generation/package-info.java svneol=native#text/plain forge-core/src/main/java/forge/deck/io/DeckFileHeader.java -text +forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java -text forge-core/src/main/java/forge/deck/io/DeckSerializer.java -text forge-core/src/main/java/forge/deck/io/DeckStorage.java -text forge-core/src/main/java/forge/deck/io/OldDeckFileFormatException.java -text @@ -15450,7 +15451,6 @@ forge-gui/src/main/java/forge/control/InputQueue.java svneol=native#text/plain forge-gui/src/main/java/forge/control/KeyboardShortcuts.java -text forge-gui/src/main/java/forge/control/RestartUtil.java -text forge-gui/src/main/java/forge/control/package-info.java -text -forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java -text forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java -text forge-gui/src/main/java/forge/deck/io/DeckPreferences.java -text forge-gui/src/main/java/forge/deck/io/OldDeckParser.java -text @@ -16024,9 +16024,15 @@ forge-m-base/src/forge/assets/FSkinTexture.java -text forge-m-base/src/forge/assets/FTextureImage.java -text forge-m-base/src/forge/assets/ImageCache.java -text forge-m-base/src/forge/assets/ImageLoader.java -text +forge-m-base/src/forge/deck/io/OldDeckParser.java -text forge-m-base/src/forge/error/BugReporter.java -text forge-m-base/src/forge/error/ExceptionHandler.java -text +forge-m-base/src/forge/limited/CustomLimited.java -text +forge-m-base/src/forge/model/CardBlock.java -text +forge-m-base/src/forge/model/CardCollections.java -text forge-m-base/src/forge/model/FModel.java -text +forge-m-base/src/forge/model/MetaSet.java -text +forge-m-base/src/forge/model/UnOpenedMeta.java -text forge-m-base/src/forge/player/LobbyPlayerHuman.java -text forge-m-base/src/forge/player/PlayerControllerHuman.java -text forge-m-base/src/forge/screens/FScreen.java -text @@ -16060,6 +16066,7 @@ forge-m-base/src/forge/toolbox/FOptionPane.java -text forge-m-base/src/forge/toolbox/FOverlay.java -text forge-m-base/src/forge/toolbox/FProgressBar.java -text forge-m-base/src/forge/toolbox/FScrollPane.java -text +forge-m-base/src/forge/toolbox/GuiChoose.java -text forge-m-base/src/forge/utils/Constants.java -text forge-m-base/src/forge/utils/ForgePreferences.java -text forge-m-base/src/forge/utils/ForgeProfileProperties.java -text diff --git a/forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java b/forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java similarity index 100% rename from forge-gui/src/main/java/forge/deck/io/DeckGroupSerializer.java rename to forge-core/src/main/java/forge/deck/io/DeckGroupSerializer.java diff --git a/forge-m-base/src/forge/deck/io/OldDeckParser.java b/forge-m-base/src/forge/deck/io/OldDeckParser.java new file mode 100644 index 00000000000..f160cb438b5 --- /dev/null +++ b/forge-m-base/src/forge/deck/io/OldDeckParser.java @@ -0,0 +1,275 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.deck.io; + +import forge.deck.Deck; +import forge.deck.DeckGroup; +import forge.toolbox.FOptionPane; +import forge.util.FileSection; +import forge.util.FileUtil; +import forge.util.storage.IStorage; +import forge.utils.Constants; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +/** + * TODO: Write javadoc for this type. + * + */ +public class OldDeckParser { + + /** Constant BDKFileFilter. */ + public static final FilenameFilter BDK_FILE_FILTER = new FilenameFilter() { + @Override + public boolean accept(final File dir, final String name) { + return name.endsWith(".bdk"); + } + }; + + /** + * TODO: Write javadoc for Constructor. + * + * @param file the file + * @param constructed2 the constructed2 + * @param draft2 the draft2 + * @param sealed2 the sealed2 + * @param cube2 the cube2 + */ + public OldDeckParser(final IStorage constructed2, final IStorage draft2, + final IStorage sealed2, final IStorage cube2) { + this.deckDir = new File(Constants.DECK_BASE_DIR); + this.sealed = sealed2; + this.constructed = constructed2; + this.cube = cube2; + this.draft = draft2; + } + + /** + * Gets the sealed. + * + * @return the sealed + */ + protected final IStorage getSealed() { + return this.sealed; + } + + /** + * Gets the constructed. + * + * @return the constructed + */ + protected final IStorage getConstructed() { + return this.constructed; + } + + /** + * Gets the draft. + * + * @return the draft + */ + protected final IStorage getDraft() { + return this.draft; + } + + /** + * Gets the cube. + * + * @return the cube + */ + protected final IStorage getCube() { + return this.cube; + } + + /** + * Gets the deck dir. + * + * @return the deck dir + */ + protected final File getDeckDir() { + return this.deckDir; + } + + private final IStorage sealed; + private final IStorage constructed; + private final IStorage draft; + private final IStorage cube; + private final File deckDir; + + /** + * TODO: Write javadoc for this method. + */ + public void tryParse() { + this.convertConstructedAndSealed(); + this.convertDrafts(); + } + + private void convertDrafts() { + for (final File f : this.deckDir.listFiles(OldDeckParser.BDK_FILE_FILTER)) { + boolean gotError = false; + final Deck human = DeckSerializer.fromFile(new File(f, "0.dck")); + final DeckGroup d = new DeckGroup(human.getName()); + d.setHumanDeck(human); + + for (int i = 1; i < DeckGroupSerializer.MAX_DRAFT_PLAYERS; i++) { + final Deck nextAi = DeckSerializer.fromFile(new File(f, i + ".dck")); + if (nextAi == null) { + gotError = true; + break; + } + d.addAiDeck(nextAi); + } + + boolean mayDelete = !gotError; + if (!gotError) { + this.draft.add(d); + } else { + final String msg = String.format("Draft '%s' lacked some decks.%n%nShould it be deleted?"); + mayDelete = FOptionPane.showConfirmDialog(msg, "Draft loading error"); + } + + if (mayDelete) { + for (final File f1 : f.listFiles()) { + f1.delete(); + } + f.delete(); + } + + } + } + + private void convertConstructedAndSealed() { + boolean allowDeleteUnsupportedConstructed = false; + final Map>> sealedDecks = new TreeMap>>( + String.CASE_INSENSITIVE_ORDER); + + for (final File f : this.deckDir.listFiles(DeckStorage.DCK_FILE_FILTER)) { + boolean importedOk = false; + + final List fileLines = FileUtil.readFile(f); + final Map> sections = FileSection.parseSections(fileLines); + final DeckFileHeader dh = DeckSerializer.readDeckMetadata(sections); + String name = dh.getName(); + + if (dh.isCustomPool()) { + try { + this.cube.add(DeckSerializer.fromSections(sections)); + importedOk = true; + } + catch (final NoSuchElementException ex) { + if (!allowDeleteUnsupportedConstructed) { + final String msg = String + .format("Can not convert deck '%s' for some unsupported cards it contains. %n%s%n%nMay Forge delete all such decks?", + name, ex.getMessage()); + allowDeleteUnsupportedConstructed = FOptionPane.showConfirmDialog(msg, "Problem converting decks"); + } + } + if (importedOk || allowDeleteUnsupportedConstructed) { + f.delete(); + } + continue; + } + + switch (dh.getDeckType()) { + case Constructed: + try { + this.constructed.add(DeckSerializer.fromSections(sections)); + importedOk = true; + } catch (final NoSuchElementException ex) { + if (!allowDeleteUnsupportedConstructed) { + final String msg = String + .format("Can not convert deck '%s' for some unsupported cards it contains. %n%s%n%nMay Forge delete all such decks?", + name, ex.getMessage()); + allowDeleteUnsupportedConstructed = FOptionPane.showConfirmDialog(msg, "Problem converting decks"); + } + } + if (importedOk || allowDeleteUnsupportedConstructed) { + f.delete(); + } + break; + + case Limited: + name = name.startsWith("AI_") ? name.replace("AI_", "") : name; + + Pair> stored = sealedDecks.get(name); + if (null == stored) { + stored = ImmutablePair.of(new DeckGroup(name), MutablePair.of((File) null, (File) null)); + } + + final Deck deck = DeckSerializer.fromSections(sections); + if (dh.isIntendedForAi()) { + stored.getLeft().addAiDeck(deck); + stored.getRight().setRight(f); + } else { + stored.getLeft().setHumanDeck(deck); + stored.getRight().setLeft(f); + } + + if ((stored.getLeft().getHumanDeck() != null) && !stored.getLeft().getAiDecks().isEmpty()) { + // have both parts of sealed deck, may convert + this.sealed.add(stored.getLeft()); + stored.getRight().getLeft().delete(); + stored.getRight().getRight().delete(); + + // there stay only orphans + sealedDecks.remove(name); + } else { + sealedDecks.put(name, stored); + } + break; + default: + break; + } + } + + // advise to kill orphaned decks + if (!sealedDecks.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (final Pair> s : sealedDecks.values()) { + final String missingPart = s.getRight().getLeft() == null ? "human" : "computer"; + sb.append(String.format("Sealed deck '%s' has no matching '%s' deck.%n", s.getKey().getName(), + missingPart)); + } + sb.append(System.getProperty("line.separator")); + sb.append("May Forge delete these decks?"); + if (FOptionPane.showConfirmDialog(sb.toString(), "Some of your sealed decks are orphaned")) { + for (final Pair> s : sealedDecks.values()) { + if (s.getRight().getLeft() != null) { + s.getRight().getLeft().delete(); + } + if (s.getRight().getRight() != null) { + s.getRight().getRight().delete(); + } + } + } + } + } + + /** + * @return the deckDir + */ + +} diff --git a/forge-m-base/src/forge/limited/CustomLimited.java b/forge-m-base/src/forge/limited/CustomLimited.java new file mode 100644 index 00000000000..d9cd2003f55 --- /dev/null +++ b/forge-m-base/src/forge/limited/CustomLimited.java @@ -0,0 +1,180 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.limited; + +import forge.card.CardEdition; +import forge.deck.Deck; +import forge.deck.DeckBase; +import forge.item.PaperCard; +import forge.item.SealedProduct; +import forge.model.FModel; +import forge.util.FileSection; +import forge.util.ItemPool; +import forge.util.TextUtil; +import forge.util.storage.IStorage; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * CustomDraft class. + *

+ * + * @author Forge + * @version $Id: CustomLimited.java 24769 2014-02-09 13:56:04Z Hellfish $ + */ +public class CustomLimited extends DeckBase { + private final SealedProduct.Template tpl; + + /** + * TODO: Write javadoc for Constructor. + * + * @param name0 the name0 + * @param slots + */ + public CustomLimited(final String name0, List> slots) { + super(name0); + tpl = new SealedProduct.Template(slots); + } + + private static final long serialVersionUID = 7435640939026612173L; + + /** The Num packs. */ + private int numPacks = 3; + + private transient ItemPool cardPool; + + /** The Land set code. */ + private String landSetCode = CardEdition.Predicates.getRandomSetWithAllBasicLands(FModel.getMagicDb().getEditions()).getCode(); + + private boolean singleton; + + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.getName(); + } + + @Override + public String getItemType() { + return "Limited deck"; + } + + /** + * Parses the. + * + * @param dfData the df data + * @param cubes the cubes + * @return the custom limited + */ + public static CustomLimited parse(final List dfData, final IStorage cubes) { + + final FileSection data = FileSection.parse(dfData, ":"); + + List> slots = new ArrayList>(); + String boosterData = data.get("Booster"); + if(StringUtils.isNotEmpty(boosterData)){ + final String[] booster = TextUtil.splitWithParenthesis(boosterData, ','); + for(String slotDesc : booster) { + String[] kv = TextUtil.splitWithParenthesis(slotDesc, ' ', 2); + slots.add(ImmutablePair.of(kv[1], Integer.parseInt(kv[0]))); + } + } else + slots = SealedProduct.Template.genericBooster.getSlots(); + + final CustomLimited cd = new CustomLimited(data.get("Name"), slots); + cd.landSetCode = data.get("LandSetCode"); + cd.numPacks = data.getInt("NumPacks"); + cd.singleton = data.getBoolean("Singleton"); + final Deck deckCube = cubes.get(data.get("DeckFile")); + cd.cardPool = deckCube == null ? ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getUniqueCards(), PaperCard.class) : deckCube.getMain(); + + return cd; + } + + + + /** + * Gets the num packs. + * + * @return the numPacks + */ + public int getNumPacks() { + return this.numPacks; + } + + /** + * Sets the num packs. + * + * @param numPacksIn + * the numPacks to set + */ + public void setNumPacks(final int numPacksIn) { + this.numPacks = numPacksIn; + } + + /** + * Gets the land set code. + * + * @return the landSetCode + */ + public String getLandSetCode() { + return this.landSetCode; + } + + /* + * (non-Javadoc) + * + * @see forge.item.CardCollectionBase#getCardPool() + */ + public ItemPool getCardPool() { + return this.cardPool; + } + + /* + * (non-Javadoc) + * + * @see forge.deck.DeckBase#getInstance(java.lang.String) + */ + @Override + protected DeckBase newInstance(final String name0) { + return new CustomLimited(name0, tpl.getSlots()); + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public SealedProduct.Template getSealedProductTemplate() { + return tpl; + } + + public boolean isSingleton() { + return singleton; + } +} diff --git a/forge-m-base/src/forge/model/CardBlock.java b/forge-m-base/src/forge/model/CardBlock.java new file mode 100644 index 00000000000..7c3951a1517 --- /dev/null +++ b/forge-m-base/src/forge/model/CardBlock.java @@ -0,0 +1,302 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.model; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import forge.card.CardEdition; +import forge.card.IUnOpenedProduct; +import forge.card.UnOpenedProduct; +import forge.item.IPaperCard; +import forge.item.PaperCard; +import forge.util.TextUtil; +import forge.util.storage.StorageReaderFile; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +// import forge.deck.Deck; + +/** + * This is a CardBlock class. + */ +public final class CardBlock implements Comparable { + private static final CardEdition[] EMPTY_SET_ARRAY = new CardEdition[] {}; + + private final int orderNum; + private final String name; + private final CardEdition[] sets; + private final Map metaSets = new TreeMap(); + private final CardEdition landSet; + private final int cntBoostersDraft; + private final int cntBoostersSealed; + private Predicate filter = null; + + /** + * Instantiates a new card block. + * + * @param index + * the index + * @param name + * the name + * @param sets + * the sets + * @param metas + * the included meta-sets + * @param landSet + * the land set + * @param cntBoostersDraft + * the cnt boosters draft + * @param cntBoostersSealed + * the cnt boosters sealed + */ + public CardBlock(final int index, final String name, final List sets, final List metas, + final CardEdition landSet, final int cntBoostersDraft, final int cntBoostersSealed) { + this.orderNum = index; + this.name = name; + this.sets = sets.toArray(CardBlock.EMPTY_SET_ARRAY); + for(MetaSet m : metas) { + this.metaSets.put(m.getCode(), m); + } + this.landSet = landSet; + this.cntBoostersDraft = cntBoostersDraft; + this.cntBoostersSealed = cntBoostersSealed; + } + + /** + * Gets the name. + * + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * Gets the sets. + * + * @return the sets + */ + public CardEdition[] getSets() { + return this.sets; + } + + /** + * Gets the land set. + * + * @return the land set + */ + public CardEdition getLandSet() { + return this.landSet; + } + + /** + * Gets the cnt boosters draft. + * + * @return the cnt boosters draft + */ + public int getCntBoostersDraft() { + return this.cntBoostersDraft; + } + + /** + * Gets the cnt boosters sealed. + * + * @return the cnt boosters sealed + */ + public int getCntBoostersSealed() { + return this.cntBoostersSealed; + } + + /** + * Gets the filter. + * + * @return the filter + */ + public Predicate getFilter() { + if (this.filter == null) { + this.filter = this.buildFilter(); + } + return this.filter; + } + + private Predicate buildFilter() { + final List setCodes = new ArrayList(); + for (final CardEdition set : this.sets) { + setCodes.add(set.getCode()); + } + return IPaperCard.Predicates.printedInSets(setCodes, true); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.landSet == null) ? 0 : this.landSet.hashCode()); + result = (prime * result) + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + /* + * (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 CardBlock other = (CardBlock) obj; + if (!this.landSet.equals(other.landSet)) { + return false; + } + if (!this.name.equals(other.name)) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(final CardBlock o) { + return this.orderNum - o.orderNum; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + if (this.metaSets.isEmpty() && this.sets.length < 1) { + return this.name + " (empty)"; + } else if (this.metaSets.size() + this.getNumberSets() < 2) { + return this.name + " (set)"; + } + return this.name + " (block)"; + } + + public static final Function FN_GET_NAME = new Function() { + + @Override + public String apply(CardBlock arg1) { + return arg1.getName(); + } + }; + + public static class Reader extends StorageReaderFile { + + private final CardEdition.Collection editions; + /** + * TODO: Write javadoc for Constructor. + * @param pathname + * @param keySelector0 + */ + public Reader(String pathname, CardEdition.Collection editions0) { + super(pathname, CardBlock.FN_GET_NAME); + editions = editions0; + } + + /* (non-Javadoc) + * @see forge.util.StorageReaderFile#read(java.lang.String) + */ + @Override + protected CardBlock read(String line, int i) { + final String[] sParts = TextUtil.splitWithParenthesis(line.trim(), ',', 3); + String name = sParts[0]; + + String[] numbers = sParts[1].trim().split("/"); + int draftBoosters = StringUtils.isNumeric(numbers[0]) ? Integer.parseInt(numbers[0]) : 0; + int sealedBoosters = StringUtils.isNumeric(numbers[1]) ? Integer.parseInt(numbers[1]) : 0; + CardEdition landSet = editions.getEditionByCodeOrThrow(numbers[2]); + + List sets = new ArrayList(); + List metas = new ArrayList(); + + String[] setNames = TextUtil.splitWithParenthesis(sParts[2], ' ' ); + for(final String set : setNames ) { + if(set.startsWith("Meta-")) { + String metaSpec = set.substring(5); + boolean noDraft = metaSpec.startsWith("NoDraft-"); + if( noDraft ) metaSpec = metaSpec.substring(8); + metas.add(new MetaSet(metaSpec, noDraft)); + } else { + sets.add(editions.getEditionByCodeOrThrow(set)); + } + } + + return new CardBlock(i+1, name, sets, metas, landSet, draftBoosters, sealedBoosters); + } + + } + + /** + * The number of normal sets in the block. + * + * @return int, number of sets. + */ + public int getNumberSets() { + if (sets == null || sets.length < 1) { + return 0; + } + else { + return sets.length; + } + } + + public Iterable getMetaSetNames() { + return metaSets.keySet(); + } + + public MetaSet getMetaSet(String key) { + return metaSets.get(key); + } + + /** + * Tries to create a booster for the selected meta-set code. + * + * @param code + * String, the MetaSet code + * @return UnOpenedProduct, the created booster. + */ + public IUnOpenedProduct getBooster(final String code) { + MetaSet ms = metaSets.get(code); + return ms == null ? new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(code)) : ms.getBooster(); + } +} diff --git a/forge-m-base/src/forge/model/CardCollections.java b/forge-m-base/src/forge/model/CardCollections.java new file mode 100644 index 00000000000..d11d5f5fd08 --- /dev/null +++ b/forge-m-base/src/forge/model/CardCollections.java @@ -0,0 +1,129 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.model; + +import forge.deck.Deck; +import forge.deck.DeckGroup; +import forge.deck.io.DeckGroupSerializer; +import forge.deck.io.DeckStorage; +import forge.deck.io.OldDeckParser; +import forge.util.storage.IStorage; +import forge.util.storage.StorageImmediatelySerialized; +import forge.utils.Constants; + +import org.apache.commons.lang3.time.StopWatch; + +import java.io.File; + +/** + * Holds editable maps of decks saved to disk. Adding or removing items to(from) + * such map turns into immediate file update + */ +public class CardCollections { + private final IStorage constructed; + private final IStorage draft; + private final IStorage sealed; + private final IStorage cube; + private final IStorage scheme; + private final IStorage plane; + private final IStorage commander; + + /** + * TODO: Write javadoc for Constructor. + * + * @param file the file + */ + public CardCollections() { + StopWatch sw = new StopWatch(); + sw.start(); + this.constructed = new StorageImmediatelySerialized("Constructed decks", new DeckStorage(new File(Constants.DECK_CONSTRUCTED_DIR), true), true); + this.draft = new StorageImmediatelySerialized("Draft deck sets", new DeckGroupSerializer(new File(Constants.DECK_DRAFT_DIR))); + this.sealed = new StorageImmediatelySerialized("Sealed deck sets", new DeckGroupSerializer(new File(Constants.DECK_SEALED_DIR))); + this.cube = new StorageImmediatelySerialized("Cubes", new DeckStorage(new File(Constants.DECK_CUBE_DIR))); + this.scheme = new StorageImmediatelySerialized("Archenemy decks", new DeckStorage(new File(Constants.DECK_SCHEME_DIR))); + this.plane = new StorageImmediatelySerialized("Planechase decks", new DeckStorage(new File(Constants.DECK_PLANE_DIR))); + this.commander = new StorageImmediatelySerialized("Commander decks", new DeckStorage(new File(Constants.DECK_COMMANDER_DIR))); + + sw.stop(); + System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar, %d commander.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size(),commander.size()); +// int sum = constructed.size() + sealed.size() + draft.size() + cube.size() + scheme.size() + plane.size(); +// FSkin.setProgessBarMessage(String.format("Loaded %d decks in %f sec", sum, sw.getTime() / 1000f )); + // remove this after most people have been switched to new layout + final OldDeckParser oldParser = new OldDeckParser(this.constructed, this.draft, this.sealed, this.cube); + oldParser.tryParse(); + } + + /** + * Gets the constructed. + * + * @return the constructed + */ + public final IStorage getConstructed() { + return this.constructed; + } + + /** + * Gets the draft. + * + * @return the draft + */ + public final IStorage getDraft() { + return this.draft; + } + + /** + * Gets the cubes. + * + * @return the cubes + */ + public final IStorage getCubes() { + return this.cube; + } + + /** + * Gets the sealed. + * + * @return the sealed + */ + public IStorage getSealed() { + return this.sealed; + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public IStorage getScheme() { + return this.scheme; + } + + /** + * @return the plane + */ + public IStorage getPlane() { + return plane; + } + + /** + * @return the plane + */ + public IStorage getCommander() { + return commander; + } + +} diff --git a/forge-m-base/src/forge/model/FModel.java b/forge-m-base/src/forge/model/FModel.java index bf1f0269d00..077784fe8b8 100644 --- a/forge-m-base/src/forge/model/FModel.java +++ b/forge-m-base/src/forge/model/FModel.java @@ -25,6 +25,8 @@ import forge.game.GameFormat; import forge.game.card.CardUtil; import forge.toolbox.FProgressBar; import forge.util.FileUtil; +import forge.util.storage.IStorage; +import forge.util.storage.StorageBase; import forge.utils.Constants; import forge.utils.ForgePreferences; @@ -53,15 +55,15 @@ public class FModel { private static ForgePreferences preferences; // Someone should take care of 2 gauntlets here - /*private static GauntletData gauntletData; - private static GauntletMini gauntlet; + //private static GauntletData gauntletData; + //private static GauntletMini gauntlet; - private static QuestController quest; + //private static QuestController quest; private static CardCollections decks; private static IStorage blocks; private static IStorage fantasyBlocks; - private static IStorage worlds;*/ + //private static IStorage worlds; private static GameFormat.Collection formats; public static void initialize(final FProgressBar progressBar) { @@ -138,18 +140,18 @@ public class FModel { } formats = new GameFormat.Collection(new GameFormat.Reader(new File(Constants.BLOCK_DATA_DIR + "formats.txt"))); - /*blocks = new StorageBase("Block definitions", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions())); - questPreferences = new QuestPreferences(); - gauntletData = new GauntletData(); + blocks = new StorageBase("Block definitions", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions())); + //questPreferences = new QuestPreferences(); + //gauntletData = new GauntletData(); fantasyBlocks = new StorageBase("Custom blocks", new CardBlock.Reader(Constants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); - worlds = new StorageBase("Quest worlds", new QuestWorld.Reader(Constants.QUEST_WORLD_DIR + "worlds.txt"));*/ + //worlds = new StorageBase("Quest worlds", new QuestWorld.Reader(Constants.QUEST_WORLD_DIR + "worlds.txt")); loadDynamicGamedata(); progressBar.setDescription("Loading decks"); - /*decks = new CardCollections(); - quest = new QuestController();*/ + decks = new CardCollections(); + //quest = new QuestController(); //preload AI profiles AiProfileUtil.loadAllProfiles(Constants.AI_PROFILE_DIR); @@ -272,23 +274,23 @@ public class FModel { return preferences; } - /*public static IStorage getBlocks() { + public static IStorage getBlocks() { return blocks; } - public static QuestPreferences getQuestPreferences() { + /*public static QuestPreferences getQuestPreferences() { return questPreferences; } public static GauntletData getGauntletData() { return gauntletData; - } + }*/ public static CardCollections getDecks() { return decks; } - public static IStorage getWorlds() { + /*public static IStorage getWorlds() { return worlds; }*/ @@ -296,6 +298,10 @@ public class FModel { return formats; } + public static IStorage getFantasyBlocks() { + return fantasyBlocks; + } + /** * Finalizer, generally should be avoided, but here closes the log file * stream and resets the system output streams. @@ -308,13 +314,9 @@ public class FModel { /*public static void setGauntletData(GauntletData data0) { gauntletData = data0; - } + }*/ - public static IStorage getFantasyBlocks() { - return fantasyBlocks; - } - - public static GauntletMini getGauntletMini() { + /*public static GauntletMini getGauntletMini() { if (gauntlet == null) { gauntlet = new GauntletMini(); } diff --git a/forge-m-base/src/forge/model/MetaSet.java b/forge-m-base/src/forge/model/MetaSet.java new file mode 100644 index 00000000000..b1a12514214 --- /dev/null +++ b/forge-m-base/src/forge/model/MetaSet.java @@ -0,0 +1,214 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package forge.model; + +import com.google.common.base.Predicate; +import forge.card.IUnOpenedProduct; +import forge.card.UnOpenedProduct; +import forge.item.IPaperCard; +import forge.item.PaperCard; +import forge.item.SealedProduct; +import forge.limited.CustomLimited; +import forge.util.FileUtil; + +import java.io.File; +import java.util.List; + +/** + * The class MetaSet. This class is used to define 'special' + * sets within a (fantasy) block, like custom sets (cubes), + * combination sets (sub-blocks), and full cardpool. + * + * NOTE: The format of a MetaSet definition is as follows + * (in a blocks definition file): + * + * "metaX A/B/C" + * + * where "X" is an integer from 0...8 (just like for sets) + * + * "A" is either "cube", "meta", "full", "choose1", "random1", + * "set", "pack", or "combo". + * + * "full" uses all available cards for this booster, just like + * the full cardpool option. The values of "B" and "C" are not + * relevant ("B" is not used at all, and the displayed name is + * always "*FULL"). + * + * "meta" uses a cardpool that is combined from several + * editions. The parameter "B" is a list of 3-letter + * edition codes, separated with commas, e.g. + * "2ED,ARN,ATQ". The parameter "C" is the name + * that is displayed for this meta-booster in the + * set selection menu. + * + * "cube" uses a previously defined custom sealed deck + * deck cube as the cardpool for this booster. The cube + * definition file must be in res/sealed/ and have a + * ".sealed" extension. The related .dck file must be + * in the res/decks/cube directory. + * "B" is the name of the cube definition file without + * the ".sealed" extension, e.g. "juzamjedi". + * "C" is the name that is displayed for this meta-booster + * in the set selection menu. + * + * The new types added after beta 1.2.14: + * + * "choose1": define several metasets in a semicolon-separated (;) + * list in value B, the player will choose one of them. + * + * "random1": like choose1, except that the player will get a + * random pack. + * + * "combo": define several metasets in a semicolon-separated (;) + * list in value B, the booster will be based on the combined + * cardpool of all these. Note that if one of the metasets is + * a "full", the rest are irrelevant. + * + * "booster": generate a single booster based on value B set. (You + * should use this only for combo, choose1 and random1 metasets, + * otherwise use normal Sets instead of MetaSets in the block + * definition!) + * + * "pack": like set, but attempts to generate a starter pack instead + * of a booster. If starter packs are not available for value B set, + * a booster is generated instead. + * + */ +public class MetaSet { + + private enum MetaSetType { + Full("F", "All cards"), + Cube("C", "Cube"), + JoinedSet("J", "Joined set"), + Choose("Select", "Choose from list"), + Combo("All", "Combined booster"), + Random("Any", "Randomly selected"), + Booster("B", "Booster"), + SpecialBooster("S", "Special Booster"), + Pack("T", "Tournament/Starter"); + + private final String shortHand; + public final String descriptiveName; + private MetaSetType(String shortname, String descName) { + shortHand = shortname; + descriptiveName = descName; + } + + public static MetaSetType smartValueOf(String trim) { + for(MetaSetType mt : MetaSetType.values()) { + if( mt.name().equalsIgnoreCase(trim) || mt.shortHand.equalsIgnoreCase(trim)) + return mt; + } + throw new IllegalArgumentException(trim + " not recognized as Meta Set"); + } + } + + private final MetaSetType type; + private final String data; + private final String code; + private final boolean draftable; + // private BoosterGenerator boosterGen; + + /** + * The constructor. A new MetaSet is currently only instantiated in CardBlock.java + * when CardBlock information is read. + * + * @param creationString + * a {@link java.lang.String} object. + */ + public MetaSet(final String creationString, boolean canDraft) { + int idxFirstPar = creationString.indexOf('('); + int idxLastPar = creationString.lastIndexOf(')'); + + draftable = canDraft; + type = MetaSetType.smartValueOf(creationString.substring(0, idxFirstPar).trim()); + data = creationString.substring(idxFirstPar + 1, idxLastPar); + String description = creationString.substring(idxLastPar + 1); + code = description + "\u00A0(" + type.descriptiveName + ")"; // u00A0 (nbsp) will not be equal to simple space + } + + /** + * Return the code. + * + * @return + * String, code + */ + public final String getCode() { + return code; + } + + /** + * + * Attempt to get a booster. + * + * @return UnOpenedProduct, the generated booster. + */ + public IUnOpenedProduct getBooster() { + + switch(type) { + case Full: + return new UnOpenedProduct(SealedProduct.Template.genericBooster); + + case Booster: + return new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(data)); + + case SpecialBooster: + return new UnOpenedProduct(FModel.getMagicDb().getSpecialBoosters().get(data)); + + case Pack: + return new UnOpenedProduct(FModel.getMagicDb().getTournamentPacks().get(data)); + + case JoinedSet: + Predicate predicate = IPaperCard.Predicates.printedInSets(data.split(" ")); + return new UnOpenedProduct(SealedProduct.Template.genericBooster, predicate); + + case Choose: return UnOpenedMeta.choose(data); + case Random: return UnOpenedMeta.random(data); + case Combo: return UnOpenedMeta.selectAll(data); + + case Cube: + final File dFolder = new File("res/sealed/"); + + if (!dFolder.exists()) { + throw new RuntimeException("GenerateSealed : folder not found -- folder is " + dFolder.getAbsolutePath()); + } + + if (!dFolder.isDirectory()) { + throw new RuntimeException("GenerateSealed : not a folder -- " + dFolder.getAbsolutePath()); + } + + List dfData = FileUtil.readFile("res/sealed/" + data + ".sealed"); + final CustomLimited myCube = CustomLimited.parse(dfData, FModel.getDecks().getCubes()); + + SealedProduct.Template fnPick = myCube.getSealedProductTemplate(); + return new UnOpenedProduct(fnPick, myCube.getCardPool()); + + default: return null; + } + } + + @Override + public String toString() { + return code; + } + + public boolean isDraftable() { + return draftable; + } +} diff --git a/forge-m-base/src/forge/model/UnOpenedMeta.java b/forge-m-base/src/forge/model/UnOpenedMeta.java new file mode 100644 index 00000000000..030937ad7b7 --- /dev/null +++ b/forge-m-base/src/forge/model/UnOpenedMeta.java @@ -0,0 +1,126 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package forge.model; + +import forge.card.IUnOpenedProduct; +import forge.item.PaperCard; +import forge.toolbox.GuiChoose; +import forge.util.MyRandom; +import forge.util.TextUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * This type extends UnOpenedProduct to support booster choice or random boosters + * in sealed deck games. See MetaSet.java for further information. + */ + +public class UnOpenedMeta implements IUnOpenedProduct { + + private enum JoinOperation { + RandomOne, + ChooseOne, + SelectAll, + } + + private final ArrayList metaSets; + private final JoinOperation operation; + private final Random generator = MyRandom.getRandom(); + + /** + * Constructor for UnOpenedMeta. + * @param creationString + * String, is parsed for MetaSet info. + * @param choose + * sets the random/choice status. + */ + private UnOpenedMeta(final String creationString, final JoinOperation op) { + metaSets = new ArrayList(); + operation = op; + + for (String m : TextUtil.splitWithParenthesis(creationString, ';')) { + metaSets.add(new MetaSet(m, true)); + } + } + + /** + * Open the booster pack, return contents. + * @return List, list of cards. + */ + @Override + public List get() { + return this.open(true, false); + } + + /** + * Like open, can define whether is human or not. + * @param isHuman + * boolean, is human player? + * @param partialities + * known partialities for the AI. + * @return List, list of cards. + */ + public List open(final boolean isHuman, final boolean allowCancel) { + if (metaSets.isEmpty()) { + throw new RuntimeException("Empty UnOpenedMetaset, cannot generate booster."); + } + + switch (operation) { + case ChooseOne: + if (isHuman) { + final MetaSet ms; + if (allowCancel) { + ms = GuiChoose.oneOrNone("Choose Booster", metaSets); + if (ms == null) { + return null; + } + } + else { + ms = GuiChoose.one("Choose Booster", metaSets); + } + return ms.getBooster().get(); + } + + case RandomOne: // AI should fall though here from the case above + int selected = generator.nextInt(metaSets.size()); + final IUnOpenedProduct newBooster = metaSets.get(selected).getBooster(); + return newBooster.get(); + + case SelectAll: + List allCards = new ArrayList(); + for (MetaSet ms : metaSets) { + allCards.addAll(ms.getBooster().get()); + } + return allCards; + } + throw new IllegalStateException("Got wrong operation type in unopenedMeta - execution should never reach this point"); + } + + public static UnOpenedMeta choose(String desc) { + return new UnOpenedMeta(desc, JoinOperation.ChooseOne); + } + public static UnOpenedMeta random(String desc) { + return new UnOpenedMeta(desc, JoinOperation.RandomOne); + } + public static UnOpenedMeta selectAll(String desc) { + return new UnOpenedMeta(desc, JoinOperation.SelectAll); + } +} diff --git a/forge-m-base/src/forge/player/LobbyPlayerHuman.java b/forge-m-base/src/forge/player/LobbyPlayerHuman.java index 93376807b17..96782faaabc 100644 --- a/forge-m-base/src/forge/player/LobbyPlayerHuman.java +++ b/forge-m-base/src/forge/player/LobbyPlayerHuman.java @@ -1,10 +1,11 @@ package forge.player; -import forge.ai.PlayerControllerAi; import forge.game.Game; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerController; +import forge.model.FModel; +import forge.utils.ForgePreferences.FPref; public class LobbyPlayerHuman extends LobbyPlayer { public LobbyPlayerHuman(String name) { @@ -18,17 +19,18 @@ public class LobbyPlayerHuman extends LobbyPlayer { @Override public PlayerController createControllerFor(Player human) { - return new PlayerControllerAi(human.getGame(), human, this); //TODO new PlayerControllerHuman(human.getGame(), human, this); + return new PlayerControllerHuman(human.getGame(), human, this); } @Override public Player createIngamePlayer(Game game) { //TODO Player player = new Player(/*GuiDisplayUtil.personalizeHuman(*/getName()/*)*/, game); - player.setFirstController(new PlayerControllerAi(game, player, this)/*new PlayerControllerHuman(game, player, this)*/); + player.setFirstController(new PlayerControllerHuman(game, player, this)); - /*if (ForgePreferences.DEV_MODE && Singletons.getModel().getPreferences().getPrefBoolean(FPref.DEV_UNLIMITED_LAND)) { + if (FModel.getPreferences().getPrefBoolean(FPref.DEV_MODE_ENABLED) && + FModel.getPreferences().getPrefBoolean(FPref.DEV_UNLIMITED_LAND)) { player.canCheatPlayUnlimitedLands = true; - }*/ + } return player; } diff --git a/forge-m-base/src/forge/player/PlayerControllerHuman.java b/forge-m-base/src/forge/player/PlayerControllerHuman.java index 2b74bc18f59..ec7f969aed2 100644 --- a/forge-m-base/src/forge/player/PlayerControllerHuman.java +++ b/forge-m-base/src/forge/player/PlayerControllerHuman.java @@ -1,20 +1,16 @@ package forge.player; -/*import com.google.common.base.Predicate; +import com.google.common.base.Predicate; import com.google.common.collect.Multimap; import forge.card.ColorSet; -import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; -import forge.deck.CardPool; import forge.deck.Deck; -import forge.deck.DeckSection; import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; import forge.game.GameType; -import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardShields; import forge.game.card.CounterType; @@ -23,7 +19,6 @@ import forge.game.cost.Cost; import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.mana.Mana; -import forge.game.phase.PhaseType; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -35,25 +30,20 @@ import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetChoices; import forge.game.trigger.Trigger; import forge.game.trigger.WrappedAbility; -import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.PaperCard; -import forge.util.Lang; -import forge.util.TextUtil; - import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.*; -*//** +/** * A prototype for player controller class * * Handles phase skips for now. - *//* + */ public class PlayerControllerHuman extends PlayerController { public PlayerControllerHuman(Game game0, Player p, LobbyPlayer lp) { super(game0, p, lp); @@ -523,4 +513,4 @@ public class PlayerControllerHuman extends PlayerController { // TODO Auto-generated method stub return null; } -}*/ +} diff --git a/forge-m-base/src/forge/toolbox/GuiChoose.java b/forge-m-base/src/forge/toolbox/GuiChoose.java new file mode 100644 index 00000000000..8b4b73ca4a9 --- /dev/null +++ b/forge-m-base/src/forge/toolbox/GuiChoose.java @@ -0,0 +1,346 @@ +package forge.toolbox; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; + +import forge.game.card.Card; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * TODO: Write javadoc for this type. + * + */ +public class GuiChoose { + + /** + * Convenience for getChoices(message, 0, 1, choices). + * + * @param + * is automatically inferred. + * @param message + * a {@link java.lang.String} object. + * @param choices + * a T object. + * @return null if choices is missing, empty, or if the users' choices are + * empty; otherwise, returns the first item in the List returned by + * getChoices. + * @see #getChoices(String, int, int, Object...) + */ + public static T oneOrNone(final String message, final T[] choices) { + if ((choices == null) || (choices.length == 0)) { + return null; + } + final List choice = GuiChoose.getChoices(message, 0, 1, choices); + return choice.isEmpty() ? null : choice.get(0); + } // getChoiceOptional(String,T...) + + public static T oneOrNone(final String message, final Collection choices) { + if ((choices == null) || choices.isEmpty()) { + return null; + } + final List choice = GuiChoose.getChoices(message, 0, 1, choices); + return choice.isEmpty() ? null : choice.get(0); + } // getChoiceOptional(String,T...) + + // returned Object will never be null + /** + *

+ * getChoice. + *

+ * + * @param + * a T object. + * @param message + * a {@link java.lang.String} object. + * @param choices + * a T object. + * @return a T object. + */ + public static T one(final String message, final T[] choices) { + final List choice = GuiChoose.getChoices(message, 1, 1, choices); + assert choice.size() == 1; + return choice.get(0); + } + + public static T one(final String message, final Collection choices) { + if (choices == null || choices.isEmpty()) + return null; + if( choices.size() == 1) + return Iterables.getFirst(choices, null); + + final List choice = GuiChoose.getChoices(message, 1, 1, choices); + assert choice.size() == 1; + return choice.get(0); + } + + public static List noneOrMany(final String message, final Collection choices) { + return GuiChoose.getChoices(message, 0, choices.size(), choices, null, null); + } + + // Nothing to choose here. Code uses this to just reveal one or more items + public static void reveal(final String message, final T item) { + List items = new ArrayList(); + items.add(item); + reveal(message, items); + } + public static void reveal(final String message, final T[] items) { + GuiChoose.getChoices(message, -1, -1, items); + } + public static void reveal(final String message, final Collection items) { + GuiChoose.getChoices(message, -1, -1, items); + } + + // Get Integer in range + public static Integer getInteger(final String message) { + return getInteger(message, 0, Integer.MAX_VALUE); + } + public static Integer getInteger(final String message, int min) { + return getInteger(message, min, Integer.MAX_VALUE); + } + public static Integer getInteger(final String message, int min, int max) { + if (max <= min) { return min; } //just return min if max <= min + + //force cutting off after 100 numbers at most + if (max == Integer.MAX_VALUE) { + return getInteger(message, min, max, min + 99); + } + int count = max - min + 1; + if (count > 100) { + return getInteger(message, min, max, min + 99); + } + + final Integer[] choices = new Integer[count]; + for (int i = 0; i < count; i++) { + choices[i] = Integer.valueOf(i + min); + } + return GuiChoose.oneOrNone(message, choices); + } + public static Integer getInteger(final String message, int min, int max, int cutoff) { + if (max <= min || cutoff < min) { return min; } //just return min if max <= min or cutoff < min + + if (cutoff >= max) { //fallback to regular integer prompt if cutoff at or after max + return getInteger(message, min, max); + } + + List choices = new ArrayList(); + for (int i = min; i <= cutoff; i++) { + choices.add(Integer.valueOf(i)); + } + choices.add("Other..."); + + Object choice = GuiChoose.oneOrNone(message, choices); + if (choice instanceof Integer || choice == null) { + return (Integer)choice; + } + + //if Other option picked, prompt for number input + String prompt = "Enter a number"; + if (min != Integer.MIN_VALUE) { + if (max != Integer.MAX_VALUE) { + prompt += " between " + min + " and " + max; + } + else { + prompt += " greater than or equal to " + min; + } + } + else if (max != Integer.MAX_VALUE) { + prompt += " less than or equal to " + max; + } + prompt += ":"; + + while (true) { + String str = FOptionPane.showInputDialog(prompt, message); + if (str == null) { return null; } // that is 'cancel' + + if (StringUtils.isNumeric(str)) { + Integer val = Integer.valueOf(str); + if (val >= min && val <= max) { + return val; + } + } + } + } + + // returned Object will never be null + public static List getChoices(final String message, final int min, final int max, final T[] choices) { + return getChoices(message, min, max, Arrays.asList(choices), null, null); + } + + public static List getChoices(final String message, final int min, final int max, final Collection choices) { + return getChoices(message, min, max, choices, null, null); + } + + public static List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + /*if (choices == null || choices.isEmpty()) { + if (min == 0) { + return new ArrayList(); + } + throw new RuntimeException("choice required from empty list"); + } + + Callable> showChoice = new Callable>() { + @Override + public List call() { + ListChooser c = new ListChooser(message, min, max, choices, display); + final JList list = c.getLstChoices(); + list.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(final ListSelectionEvent ev) { + if (list.getSelectedValue() instanceof Card) { + Card card = (Card) list.getSelectedValue(); + if (card.isFaceDown() && FControl.mayShowCard(card)) { + CMatchUI.SINGLETON_INSTANCE.setCard(card, true); + } + else { + CMatchUI.SINGLETON_INSTANCE.setCard(card); + } + + GuiUtils.clearPanelSelections(); + GuiUtils.setPanelSelection(card); + } + if (list.getSelectedValue() instanceof InventoryItem) { + CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue()); + } + } + }); + + if (selected != null) { + c.show(selected); + } + else { + c.show(); + } + + GuiUtils.clearPanelSelections(); + return c.getSelectedValues(); + } + }; + + FutureTask> future = new FutureTask>(showChoice); + FThreads.invokeInEdtAndWait(future); + try { + return future.get(); + } catch (Exception e) { // should be no exception here + e.printStackTrace(); + }*/ + return null; + } + + public static List many(final String title, final String topCaption, int cnt, final List sourceChoices, Card referenceCard) { + return order(title, topCaption, cnt, cnt, sourceChoices, null, referenceCard, false); + } + + public static List many(final String title, final String topCaption, int min, int max, final List sourceChoices, Card referenceCard) { + int m2 = min >= 0 ? sourceChoices.size() - min : -1; + int m1 = max >= 0 ? sourceChoices.size() - max : -1; + return order(title, topCaption, m1, m2, sourceChoices, null, referenceCard, false); + } + + public static List order(final String title, final String top, final List sourceChoices, Card referenceCard) { + return order(title, top, 0, 0, sourceChoices, null, referenceCard, false); + } + + public static > List sideboard(List sideboard, List deck) { + Collections.sort(deck); + Collections.sort(sideboard); + return order("Sideboard", "Main Deck", -1, -1, sideboard, deck, null, true); + } + + private static List order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax, + final List sourceChoices, final List destChoices, final Card referenceCard, final boolean sideboardingMode) { + // An input box for handling the order of choices. + + /*Callable> callable = new Callable>() { + @Override + public List call() throws Exception { + DualListBox dual = new DualListBox(remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices); + dual.setSecondColumnLabelText(top); + + dual.setSideboardMode(sideboardingMode); + + dual.setTitle(title); + dual.pack(); + dual.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + if (referenceCard != null) { + CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard); + // MARKED FOR UPDATE + } + dual.setVisible(true); + + List objects = dual.getOrderedList(); + + dual.dispose(); + GuiUtils.clearPanelSelections(); + return objects; + } + }; + + FutureTask> ft = new FutureTask>(callable); + FThreads.invokeInEdtAndWait(ft); + try { + return ft.get(); + } catch (Exception e) { // we have waited enough + e.printStackTrace(); + }*/ + return null; + } + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static T sortedOneOrNone(final String message, final T[] choices, Comparator comparer) { + if ((choices == null) || (choices.length == 0)) { + return null; + } + final List choice = GuiChoose.sortedGetChoices(message, 0, 1, choices, comparer); + return choice.isEmpty() ? null : choice.get(0); + } // getChoiceOptional(String,T...) + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static T sortedOneOrNone(final String message, final List choices, Comparator comparer) { + if ((choices == null) || choices.isEmpty()) { + return null; + } + final List choice = GuiChoose.sortedGetChoices(message, 0, 1, choices, comparer); + return choice.isEmpty() ? null : choice.get(0); + } // getChoiceOptional(String,T...) + + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static T sortedOne(final String message, final T[] choices, Comparator comparer) { + final List choice = GuiChoose.sortedGetChoices(message, 1, 1, choices, comparer); + assert choice.size() == 1; + return choice.get(0); + } // getChoice() + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static T sortedOne(final String message, final List choices, Comparator comparer) { + if ((choices == null) || (choices.size() == 0)) { + return null; + } + final List choice = GuiChoose.sortedGetChoices(message, 1, 1, choices, comparer); + assert choice.size() == 1; + return choice.get(0); + } + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static List sortedNoneOrMany(final String message, final List choices, Comparator comparer) { + return GuiChoose.sortedGetChoices(message, 0, choices.size(), choices, comparer); + } + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static List sortedGetChoices(final String message, final int min, final int max, final T[] choices, Comparator comparer) { + // You may create a copy of source array if callers expect the collection to be unchanged + Arrays.sort(choices, comparer); + return getChoices(message, min, max, choices); + } + + // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine + public static List sortedGetChoices(final String message, final int min, final int max, final List choices, Comparator comparer) { + // You may create a copy of source list if callers expect the collection to be unchanged + Collections.sort(choices, comparer); + return getChoices(message, min, max, choices); + } + +} +