397 lines
14 KiB
Java
397 lines
14 KiB
Java
package magic.utility;
|
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.SortedMap;
|
|
import java.util.TreeMap;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import java.util.zip.CRC32;
|
|
|
|
import org.apache.commons.io.FilenameUtils;
|
|
|
|
import magic.data.CardDefinitions;
|
|
import magic.data.DeckType;
|
|
import magic.exception.InvalidDeckException;
|
|
import magic.model.DuelPlayerConfig;
|
|
import magic.model.MagicCardDefinition;
|
|
import magic.model.MagicColor;
|
|
import magic.model.MagicDeck;
|
|
import magic.model.MagicDeckProfile;
|
|
import magic.model.MagicRandom;
|
|
import magic.utility.MagicFileSystem.DataPath;
|
|
|
|
public class DeckUtils {
|
|
|
|
public static final String DECK_EXTENSION=".dec";
|
|
|
|
private static final String[] CARD_TYPES={"creatures","spells","lands"};
|
|
|
|
public static Path getDecksFolder() {
|
|
return MagicFileSystem.getDataPath(DataPath.DECKS);
|
|
}
|
|
|
|
public static Path getPrebuiltDecksFolder() {
|
|
Path decksPath = getDecksFolder().resolve("prebuilt");
|
|
MagicFileSystem.verifyDirectoryPath(decksPath);
|
|
return decksPath;
|
|
}
|
|
|
|
public static Path getFiremindDecksFolder() {
|
|
Path decksPath = getDecksFolder().resolve("firemind");
|
|
MagicFileSystem.verifyDirectoryPath(decksPath);
|
|
return decksPath;
|
|
}
|
|
|
|
public static void createDeckFolder() {
|
|
final File deckFolderFile = getDecksFolder().toFile();
|
|
if (!deckFolderFile.exists() && !deckFolderFile.mkdir()) {
|
|
System.err.println("WARNING. Unable to create " + getDecksFolder());
|
|
}
|
|
}
|
|
|
|
public static boolean saveDeck(final String filename, final MagicDeck deck) {
|
|
|
|
final List<SortedMap<String,Integer>> cardMaps=new ArrayList<>();
|
|
boolean isSuccessful = true;
|
|
|
|
for (int count=3;count>0;count--) {
|
|
cardMaps.add(new TreeMap<>());
|
|
}
|
|
|
|
for (final MagicCardDefinition cardDefinition : deck) {
|
|
final String name = cardDefinition.getAsciiName();
|
|
final int index;
|
|
if (cardDefinition.isLand()) {
|
|
index=2;
|
|
} else if (cardDefinition.isCreature()) {
|
|
index=0;
|
|
} else {
|
|
index=1;
|
|
}
|
|
final SortedMap<String,Integer> cardMap=cardMaps.get(index);
|
|
final Integer count=cardMap.get(name);
|
|
cardMap.put(name,count==null?Integer.valueOf(1):Integer.valueOf(count+1));
|
|
}
|
|
|
|
//save deck
|
|
try (final BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), UTF_8)) {
|
|
for (int index=0;index<=2;index++) {
|
|
final SortedMap<String,Integer> cardMap=cardMaps.get(index);
|
|
if (!cardMap.isEmpty()) {
|
|
writer.write("# "+cardMap.size()+" "+CARD_TYPES[index]);
|
|
writer.newLine();
|
|
for (final Map.Entry<String,Integer> entry : cardMap.entrySet()) {
|
|
writer.write(entry.getValue()+" "+entry.getKey());
|
|
writer.newLine();
|
|
}
|
|
writer.newLine();
|
|
}
|
|
}
|
|
final String description = deck.getDescription();
|
|
if (description != null) {
|
|
writer.write(">" + description.replaceAll("(\\r|\\n|\\r\\n)", "\\\\n"));
|
|
}
|
|
} catch (final IOException ex) {
|
|
isSuccessful = false;
|
|
System.err.println("Invalid deck : " + deck.getFilename() + " - " + ex.getMessage());
|
|
}
|
|
|
|
return isSuccessful;
|
|
|
|
}
|
|
|
|
/**
|
|
* reads deck file into list of strings where each string represents a line
|
|
* in the file. If getDeckFileContent() generates an IOException then it is
|
|
* an invalid or corrupt deck file but this method should not handle the exception
|
|
* as it is called from multiple locations which may want to handle an invalid
|
|
* file in different ways.
|
|
*
|
|
* @param filename
|
|
* @return
|
|
*/
|
|
private static List<String> getDeckFileContent(final String filename) {
|
|
try {
|
|
return FileIO.toStrList(new File(filename));
|
|
} catch (IOException ex) {
|
|
throw new InvalidDeckException("Invalid deck (\".dec\") file: " + filename, ex);
|
|
}
|
|
}
|
|
|
|
private static long getDeckFileChecksum(final Path deckFilePath) {
|
|
try (
|
|
InputStream fis = new FileInputStream(deckFilePath.toFile());
|
|
InputStream bis = new BufferedInputStream(fis);
|
|
) {
|
|
CRC32 crc = new CRC32();
|
|
int cnt;
|
|
while ((cnt = bis.read()) != -1) {
|
|
crc.update(cnt);
|
|
}
|
|
return crc.getValue();
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(DeckUtils.class.getName()).log(Level.WARNING, null, ex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static long getDeckFileChecksum(String name, DeckType deckType) {
|
|
Path deckPath = DeckType.getDeckFolder(deckType);
|
|
Path deckFile = deckPath.resolve(name + DECK_EXTENSION);
|
|
return deckFile.toFile().exists() ? getDeckFileChecksum(deckFile) : -1;
|
|
}
|
|
|
|
public static long getDeckFileChecksum(MagicDeck aDeck) {
|
|
return getDeckFileChecksum(aDeck.getName(), aDeck.getDeckType());
|
|
}
|
|
|
|
private static boolean isSamePath(Path p1, Path p2) {
|
|
try {
|
|
return Files.isSameFile(p1, p2);
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(DeckUtils.class.getName()).log(Level.SEVERE, null, ex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static DeckType getDeckType(Path deckFilePath) {
|
|
Path deckFolder = Paths.get(FilenameUtils.getFullPath(deckFilePath.toString()));
|
|
if (isSamePath(deckFolder, getPrebuiltDecksFolder())) {
|
|
return DeckType.Preconstructed;
|
|
}
|
|
if (isSamePath(deckFolder, getFiremindDecksFolder())) {
|
|
return DeckType.Firemind;
|
|
}
|
|
if (isSamePath(deckFolder, MagicFileSystem.getDataPath(DataPath.DECKS))) {
|
|
return DeckType.Custom;
|
|
}
|
|
throw new RuntimeException("Unable to determine deck type from " + deckFilePath);
|
|
}
|
|
|
|
/**
|
|
* Loads a deck file into a new MagicDeck instance.
|
|
* <p>
|
|
* @param deckFilePath full path of deck file to load.
|
|
* @return
|
|
*/
|
|
public static MagicDeck loadDeckFromFile(final Path deckFilePath) {
|
|
if (deckFilePath == null || !deckFilePath.toFile().exists()) {
|
|
throw new InvalidDeckException("File " + deckFilePath + " does not exist");
|
|
}
|
|
final List<String> lines = getDeckFileContent(deckFilePath.toString());
|
|
final MagicDeck deck = DeckParser.parseLines(lines);
|
|
deck.setFilename(deckFilePath.getFileName().toString());
|
|
deck.setDeckFileChecksum(getDeckFileChecksum(deckFilePath));
|
|
deck.setDeckType(getDeckType(deckFilePath));
|
|
return deck;
|
|
}
|
|
|
|
public static MagicDeck loadDeckFromFile(String name, DeckType deckType) {
|
|
Path deckPath = DeckType.getDeckFolder(deckType);
|
|
return loadDeckFromFile(deckPath.resolve(name + DECK_EXTENSION));
|
|
}
|
|
|
|
public static void loadAndSetPlayerDeck(final String filename, final DuelPlayerConfig player) {
|
|
|
|
final MagicDeck deck = loadDeckFromFile(Paths.get(filename));
|
|
|
|
if (deck.isValid()) {
|
|
player.setDeck(deck);
|
|
player.setDeckProfile(getDeckProfile(deck));
|
|
} else {
|
|
throw new InvalidDeckException(deck);
|
|
}
|
|
|
|
}
|
|
|
|
private static MagicDeckProfile getDeckProfile(MagicDeck deck) {
|
|
final MagicDeckProfile profile = new MagicDeckProfile(getDeckColor(deck));
|
|
profile.setPreConstructed();
|
|
return profile;
|
|
}
|
|
|
|
private static int[] getDeckColorCount(final MagicDeck deck) {
|
|
final int[] colorCount = new int[MagicColor.NR_COLORS];
|
|
for (MagicCardDefinition cardDef : deck) {
|
|
final int colorFlags = cardDef.getColorFlags();
|
|
for (final MagicColor color : MagicColor.values()) {
|
|
if (color.hasColor(colorFlags)) {
|
|
colorCount[color.ordinal()]++;
|
|
}
|
|
}
|
|
}
|
|
return colorCount;
|
|
}
|
|
|
|
/**
|
|
* Find up to 3 of the most common colors in the deck.
|
|
*/
|
|
public static String getDeckColor(final MagicDeck deck) {
|
|
final int[] colorCount = getDeckColorCount(deck);
|
|
final StringBuilder colorText = new StringBuilder();
|
|
while (colorText.length() < 3) {
|
|
int maximum=0;
|
|
int index=0;
|
|
for (int i = 0; i < colorCount.length; i++) {
|
|
if (colorCount[i] > maximum) {
|
|
maximum = colorCount[i];
|
|
index = i;
|
|
}
|
|
}
|
|
if (maximum == 0) {
|
|
break;
|
|
}
|
|
colorText.append(MagicColor.values()[index].getSymbol());
|
|
colorCount[index]=0;
|
|
}
|
|
return colorText.toString();
|
|
}
|
|
|
|
public static List<File> getDeckFiles() {
|
|
try {
|
|
DeckFileVisitor dfv = new DeckFileVisitor();
|
|
Files.walkFileTree(MagicFileSystem.getDataPath(DataPath.DECKS), Collections.singleton(FOLLOW_LINKS),
|
|
Integer.MAX_VALUE, dfv);
|
|
return dfv.getFiles();
|
|
} catch (IOException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a deck randomly chosen from the "decks" directory.
|
|
* (includes both custom & prebuilt decks).
|
|
*/
|
|
public static void loadRandomDeckFile(final DuelPlayerConfig player) {
|
|
List<File> deckFiles = getDeckFiles();
|
|
if (deckFiles.isEmpty()) {
|
|
// Creates a simple default deck.
|
|
final MagicDeck deck = player.getDeck();
|
|
deck.setFilename("Default.dec");
|
|
final MagicCardDefinition creature = CardDefinitions.getCard("Elite Vanguard");
|
|
final MagicCardDefinition land = CardDefinitions.getCard("Plains");
|
|
for (int count = 24; count > 0; count--) {
|
|
deck.add(creature);
|
|
}
|
|
for (int count = 16; count > 0; count--) {
|
|
deck.add(land);
|
|
}
|
|
player.setDeckProfile(new MagicDeckProfile("w"));
|
|
} else {
|
|
loadAndSetPlayerDeck(deckFiles.get(MagicRandom.nextRNGInt(deckFiles.size())).toString(), player);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts the name of a deck from its filename.
|
|
*/
|
|
public static String getDeckNameFromFilename(final String deckFilename) {
|
|
if (deckFilename.indexOf(DECK_EXTENSION) > 0) {
|
|
return deckFilename.substring(0, deckFilename.lastIndexOf(DECK_EXTENSION));
|
|
} else {
|
|
return deckFilename;
|
|
}
|
|
}
|
|
/**
|
|
* Gets the name of a deck file without the extension.
|
|
*/
|
|
public static String getDeckNameFromFile(final Path deckFile) {
|
|
return getDeckNameFromFilename(deckFile.getFileName().toString());
|
|
}
|
|
|
|
public static MagicCardDefinition getCard(final String name) {
|
|
try {
|
|
return CardDefinitions.getCard(name);
|
|
} catch (final RuntimeException e) {
|
|
final MagicCardDefinition cardDefinition = new MagicCardDefinition();
|
|
cardDefinition.setName(name);
|
|
cardDefinition.setDistinctName(name);
|
|
cardDefinition.setInvalid();
|
|
return cardDefinition;
|
|
}
|
|
}
|
|
|
|
public static List<File> getDecksContainingCard(final MagicCardDefinition cardDef) {
|
|
final List<File> matchingDeckFiles = new ArrayList<>();
|
|
if (cardDef != null) {
|
|
for (File deckFile : getDeckFiles()) {
|
|
try (final BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(deckFile), "UTF-8"))) {
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
if (!line.startsWith("#")) {
|
|
if (line.contains(cardDef.getName())) {
|
|
matchingDeckFiles.add(deckFile);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
}
|
|
return matchingDeckFiles;
|
|
}
|
|
|
|
public static Set<MagicCardDefinition> getDistinctCards(final MagicDeck aDeck) {
|
|
final Set<MagicCardDefinition> distinctCards = new HashSet<>();
|
|
distinctCards.addAll(aDeck);
|
|
return distinctCards;
|
|
}
|
|
|
|
public static Path getDeckPath(MagicDeck deck) {
|
|
Path deckPath = DeckType.getDeckFolder(deck.getDeckType());
|
|
return deckPath.resolve(deck.getName() + DECK_EXTENSION);
|
|
}
|
|
|
|
static Path getPlayerDecksFolder() {
|
|
return MagicFileSystem.getDataPath(MagicFileSystem.DataPath.DECKS);
|
|
}
|
|
|
|
static boolean isValidDeckFolder(Path dir) throws IOException {
|
|
return Files.isSameFile(dir, getPlayerDecksFolder())
|
|
|| Files.isSameFile(dir, getPrebuiltDecksFolder())
|
|
|| Files.isSameFile(dir, getFiremindDecksFolder());
|
|
}
|
|
|
|
/**
|
|
* Returns string as {@code filename}.dec
|
|
*/
|
|
public static String getNormalizedFilename(String filename) {
|
|
return filename.endsWith(DECK_EXTENSION) ? filename : filename + DECK_EXTENSION;
|
|
}
|
|
|
|
/**
|
|
* Searches for a deck file and returns the first matching file
|
|
* in the \decks\* folder structure.
|
|
*/
|
|
public static File findDeckFile(String filename) {
|
|
String target = getNormalizedFilename(filename);
|
|
for (File deckFile : DeckUtils.getDeckFiles()) {
|
|
if (target.equals(deckFile.getName())) {
|
|
return deckFile;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|