diff --git a/.gitattributes b/.gitattributes index 8aeebe77ab0..56b3f539870 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19076,6 +19076,16 @@ forge-gui/src/main/java/forge/sound/MusicPlaylist.java -text forge-gui/src/main/java/forge/sound/NoSoundClip.java -text forge-gui/src/main/java/forge/sound/SoundEffectType.java -text forge-gui/src/main/java/forge/sound/SoundSystem.java -text +forge-gui/src/main/java/forge/tournament/TournamentData.java -text +forge-gui/src/main/java/forge/tournament/TournamentIO.java -text +forge-gui/src/main/java/forge/tournament/TournamentUtil.java -text +forge-gui/src/main/java/forge/tournament/TournamentWinLoseController.java -text +forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java -text +forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java -text +forge-gui/src/main/java/forge/tournament/system/TournamentPairing.java -text +forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java -text +forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java -text +forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java -text forge-gui/src/main/java/forge/util/Callback.java -text forge-gui/src/main/java/forge/util/Evaluator.java -text forge-gui/src/main/java/forge/util/GuiDisplayUtil.java -text diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestDraft.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestDraft.java index f5901e577ef..415e16a546c 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestDraft.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestDraft.java @@ -1,5 +1,6 @@ package forge.screens.home.quest; +import com.beust.jcommander.internal.Lists; import com.google.common.collect.ImmutableList; import forge.GuiBase; import forge.Singletons; @@ -36,6 +37,9 @@ import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; import forge.toolbox.JXButtonPanel; +import forge.tournament.system.TournamentBracket; +import forge.tournament.system.TournamentPairing; +import forge.tournament.system.TournamentPlayer; import javax.swing.*; import java.awt.event.*; @@ -438,13 +442,17 @@ public enum CSubmenuQuestDraft implements ICDoc { } private void updateTournamentActive() { - - final VSubmenuQuestDraft view = VSubmenuQuestDraft.SINGLETON_INSTANCE; - if (FModel.getQuest().getAchievements().getCurrentDraft() == null) { return; } + if (QuestDraftUtils.TOURNAMENT_TOGGLE) { + updateTournamentActiveForBracket(); + return; + } + + final VSubmenuQuestDraft view = VSubmenuQuestDraft.SINGLETON_INSTANCE; + for (int i = 0; i < 15; i++) { String playerID = FModel.getQuest().getAchievements().getCurrentDraft().getStandings()[i]; @@ -493,6 +501,82 @@ public enum CSubmenuQuestDraft implements ICDoc { } + private void updateTournamentBoxLabel(String playerID, int iconID, int box, boolean first) { + final VSubmenuQuestDraft view = VSubmenuQuestDraft.SINGLETON_INSTANCE; + + SkinImage icon = FSkin.getAvatars().get(iconID); + + if (icon == null) { + icon = FSkin.getAvatars().get(0); + } + + if (first) { + view.getLblsMatchups()[box].setPlayerOne(playerID, icon); + } else { + view.getLblsMatchups()[box].setPlayerTwo(playerID, icon); + } + } + + private void updateTournamentActiveForBracket() { + final VSubmenuQuestDraft view = VSubmenuQuestDraft.SINGLETON_INSTANCE; + + QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft(); + TournamentBracket bracket = draft.getBracket(); + + if (bracket == null) { + return; + } + + // Combine finished pairings with active round pairings + List allPairings = Lists.newArrayList(); + allPairings.addAll(bracket.getCompletedPairings()); + allPairings.addAll(bracket.getActivePairings()); + + int count = 0; + int playerCount = 0; + int lastWinner = 0; + for(TournamentPairing tp : allPairings) { + boolean first = true; + String playerID = "Undetermined"; + int iconID = 0; + for(TournamentPlayer player : tp.getPairedPlayers()) { + if (player.getIndex() == -1) { + playerID = FModel.getPreferences().getPref(FPref.PLAYER_NAME); + if (FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",").length > 0) { + iconID = Integer.parseInt(FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",")[0]); + } + } + else{ + playerID = player.getPlayer().getName(); + iconID = player.getIndex(); + } + updateTournamentBoxLabel(playerID, iconID, count, first); + + if (tp.getWinner() != null && tp.getWinner().equals(player)) { + // Temporarily fill in winner box + lastWinner = playerCount; + updateTournamentBoxLabel(player.getPlayer().getName(), player.getIndex(), playerCount/4 + 4, count%2 == 0); + } + first = false; + playerCount++; + } + count++; + } + if (!bracket.isTournamentOver()) { + for (int i = lastWinner/2+9 ; i < 15; i++) { + String playerID = "Undetermined"; + int iconID = GuiBase.getInterface().getAvatarCount() - 1; + updateTournamentBoxLabel(playerID, iconID, i / 2, i%2 == 0); + } + } + + if (draft.playerHasMatchesLeft()) { + view.getBtnLeaveTournament().setText("Leave Tournament"); + } else { + view.getBtnLeaveTournament().setText("Collect Prizes"); + } + } + public void setCompletedDraft(final DeckGroup finishedDraft) { QuestDraftUtils.completeDraft(finishedDraft); @@ -587,7 +671,6 @@ public enum CSubmenuQuestDraft implements ICDoc { gui = GuiBase.getInterface().getNewGuiGame(); QuestDraftUtils.startNextMatch(gui); - } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/QuestDraftWinLose.java b/forge-gui-desktop/src/main/java/forge/screens/match/QuestDraftWinLose.java index f1ea8165725..04036b38864 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/QuestDraftWinLose.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/QuestDraftWinLose.java @@ -70,6 +70,7 @@ public class QuestDraftWinLose extends ControlWinLose { final String winner = lastGame.getWinningPlayerName(); quest.getAchievements().getCurrentDraft().setWinner(winner); quest.save(); + VSubmenuQuestDraft.SINGLETON_INSTANCE.populate(); } if (!gameHadHumanPlayer) { diff --git a/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java b/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java index 6275b8abe10..6ead9f8b6ab 100644 --- a/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java +++ b/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java @@ -1,5 +1,6 @@ package forge.quest; +import com.google.common.collect.Lists; import forge.GuiBase; import forge.deck.Deck; import forge.deck.DeckGroup; @@ -12,6 +13,9 @@ import forge.match.HostedMatch; import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.properties.ForgePreferences.FPref; +import forge.tournament.system.TournamentBracket; +import forge.tournament.system.TournamentPairing; +import forge.tournament.system.TournamentPlayer; import forge.util.storage.IStorage; import java.util.ArrayList; @@ -19,6 +23,7 @@ import java.util.List; public class QuestDraftUtils { private static final List matchups = new ArrayList<>(); + public static boolean TOURNAMENT_TOGGLE = false; public static boolean matchInProgress = false; private static boolean waitForUserInput = false; @@ -42,11 +47,11 @@ public class QuestDraftUtils { } public static String getDeckLegality() { - final String message = GameType.QuestDraft.getDeckFormat().getDeckConformanceProblem(FModel.getQuest().getAssets().getDraftDeckStorage().get(QuestEventDraft.DECK_NAME).getHumanDeck()); - if (message != null && FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) { - return message; + if (!FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) { + return null; } - return null; + + return GameType.QuestDraft.getDeckFormat().getDeckConformanceProblem(FModel.getQuest().getAssets().getDraftDeckStorage().get(QuestEventDraft.DECK_NAME).getHumanDeck()); } private static int getPreviousMatchup(final int position) { @@ -81,11 +86,35 @@ public class QuestDraftUtils { return -1; } + public static void startNextMatchInTournament(final IGuiGame gui) { + final QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft(); + TournamentBracket bracket = draft.getBracket(); + + TournamentPairing pairing = bracket.getNextPairing(); + if (pairing == null) { + if (bracket.isTournamentOver()) { + // Somehow tournament didn't end on its own? + } else { + // Generate next round and regrab the next pairing + bracket.generateActivePairings(); + pairing = bracket.getNextPairing(); + } + } + + updateFromTournament(gui); + } + public static void startNextMatch(final IGuiGame gui) { + if (TOURNAMENT_TOGGLE) { + startNextMatchInTournament(gui); + return; + } + if (!matchups.isEmpty()) { return; } + // If matchups isn't empty I don't get here. Right? matchups.clear(); final QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft(); @@ -130,29 +159,19 @@ public class QuestDraftUtils { //If no previous matches need doing, start the next round as normal if (!foundMatchups) { + // Fall through to avoid repetition switch (currentSet) { case 7: addMatchup(0, 1, draft); - addMatchup(2, 3, draft); - addMatchup(4, 5, draft); - addMatchup(6, 7, draft); - break; case 8: addMatchup(2, 3, draft); - addMatchup(4, 5, draft); - addMatchup(6, 7, draft); - break; case 9: addMatchup(4, 5, draft); - addMatchup(6, 7, draft); - break; case 10: addMatchup(6, 7, draft); break; case 11: addMatchup(8, 9, draft); - addMatchup(10, 11, draft); - break; case 12: addMatchup(10, 11, draft); break; @@ -205,7 +224,22 @@ public class QuestDraftUtils { matchups.add(matchup); } + private static GameRules createQuestDraftRuleset() { + final GameRules rules = new GameRules(GameType.QuestDraft); + rules.setPlayForAnte(false); + rules.setMatchAnteRarity(false); + rules.setGamesPerMatch(3); + rules.setManaBurn(FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN)); + rules.setCanCloneUseTargetsImage(FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE)); + return rules; + } + public static void update(final IGuiGame gui) { + if (TOURNAMENT_TOGGLE) { + updateFromTournament(gui); + return; + } + if (matchups.isEmpty()) { return; } @@ -226,17 +260,71 @@ public class QuestDraftUtils { waitForUserInput = false; } - final GameRules rules = new GameRules(GameType.QuestDraft); - rules.setPlayForAnte(false); - rules.setMatchAnteRarity(false); - rules.setGamesPerMatch(3); - rules.setManaBurn(FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN)); - rules.setCanCloneUseTargetsImage(FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE)); + GameRules rules = createQuestDraftRuleset(); final HostedMatch newMatch = GuiBase.getInterface().hostMatch(); newMatch.startMatch(rules, null, nextMatch.matchStarter, nextMatch.humanPlayer, GuiBase.getInterface().getNewGuiGame()); } + private static List registerTournamentPlayers(TournamentPairing pairing, QuestEventDraft draft, DeckGroup decks) { + List registered = Lists.newArrayList(); + for (TournamentPlayer pl : pairing.getPairedPlayers()) { + if (pl.getIndex() == -1) { + registered.add(new RegisteredPlayer(decks.getHumanDeck()).setPlayer(pl.getPlayer())); + } else { + registered.add(new RegisteredPlayer(decks.getAiDecks().get(pl.getIndex())).setPlayer(pl.getPlayer())); + } + } + return registered; + } + + private static void updateFromTournament(final IGuiGame gui) { + // If Human involved launch into a UI, if not show a "Simulating" screen. And simulate the game off-thread + if (waitForUserInput || matchInProgress || gui == null) { + return; + } + + gui.enableOverlay(); + final QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft(); + if (draft == null) { + return; + } + + TournamentBracket bracket = draft.getBracket(); + TournamentPairing pairing = bracket.getNextPairing(); + + if (pairing == null) { + // bracket.isTournamentOver() + + gui.disableOverlay(); + return; + } + + matchInProgress = true; + final DeckGroup decks = FModel.getQuest().getAssets().getDraftDeckStorage().get(QuestEventDraft.DECK_NAME); + + boolean waitForUserInput = pairing.hasPlayer(GamePlayerUtil.getGuiPlayer()); + GameRules rules = createQuestDraftRuleset(); + + List registered = registerTournamentPlayers(pairing, draft, decks); + RegisteredPlayer registeredHuman = null; + + if (waitForUserInput) { + final HostedMatch newMatch = GuiBase.getInterface().hostMatch(); + for(RegisteredPlayer rp : registered) { + if (rp.getPlayer().equals(GamePlayerUtil.getGuiPlayer())) { + registeredHuman = rp; + } + } + newMatch.startMatch(rules, null, registered, registeredHuman, GuiBase.getInterface().getNewGuiGame()); + } else { + // TODO Show a "Simulating Dialog" and simulate off-thread. Temporary replication of code for now + gui.disableOverlay(); + final HostedMatch newMatch = GuiBase.getInterface().hostMatch(); + newMatch.startMatch(rules, null, registered, registeredHuman, GuiBase.getInterface().getNewGuiGame()); + } + } + public static void continueMatches(final IGuiGame gui) { waitForUserInput = false; update(gui); @@ -246,6 +334,11 @@ public class QuestDraftUtils { matchInProgress = false; waitForUserInput = false; matchups.clear(); + if (TOURNAMENT_TOGGLE) { + final QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft(); + TournamentBracket bracket = draft.getBracket(); + bracket.endTournament(); + } } private static final class DraftMatchup { diff --git a/forge-gui/src/main/java/forge/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/quest/QuestEventDraft.java index 2db69465894..8ba885a0194 100644 --- a/forge-gui/src/main/java/forge/quest/QuestEventDraft.java +++ b/forge-gui/src/main/java/forge/quest/QuestEventDraft.java @@ -33,6 +33,9 @@ import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.quest.data.QuestPreferences.QPref; import forge.quest.io.ReadPriceList; +import forge.tournament.system.TournamentBracket; +import forge.tournament.system.TournamentPairing; +import forge.tournament.system.TournamentPlayer; import forge.util.NameGenerator; import forge.util.storage.IStorage; @@ -79,6 +82,8 @@ public class QuestEventDraft { public static final String UNDETERMINED = "quest_draft_undetermined_place"; public static final String HUMAN = "quest_draft_human_place"; public static final String DECK_NAME = "Tournament Deck"; + public static final int TOTAL_ROUNDS = 3; + public static final int PLAYERS_IN_PAIRING = 2; private static transient final ReadPriceList PRICE_LIST_READER = new ReadPriceList(); private static transient final Map MAP_PRICES = PRICE_LIST_READER.getPriceList(); @@ -90,6 +95,8 @@ public class QuestEventDraft { private int entryFee = 0; private String[] standings = new String[15]; + private TournamentBracket bracket; + private String[] aiNames = new String[7]; private int[] aiIcons = new int[7]; @@ -169,6 +176,10 @@ public class QuestEventDraft { age--; } + public TournamentBracket getBracket() { return bracket; } + + public void setBracket(TournamentBracket brckt) { bracket = brckt; } + public void saveToRegularDraft() { final String tournamentName = FModel.getQuest().getName() + " Tournament Deck " + new SimpleDateFormat("EEE d MMM yyyy HH-mm-ss").format(new Date()); final DeckGroup original = FModel.getQuest().getDraftDecks().get(QuestEventDraft.DECK_NAME); @@ -208,6 +219,18 @@ public class QuestEventDraft { public void setWinner(final String playerName) { + if (QuestDraftUtils.TOURNAMENT_TOGGLE) { + TournamentPairing pairing = bracket.getNextPairing(); + for(TournamentPlayer player : pairing.getPairedPlayers()) { + if (player.getPlayer().getName().equals(playerName)) { + pairing.setWinner(player); + bracket.reportMatchCompletion(pairing); + break; + } + } + // Since this updates the brackets that we're still pseudo-using let it fall through + } + boolean isHumanPlayer = true; for (final String name : aiNames) { @@ -245,37 +268,7 @@ public class QuestEventDraft { } - switch (playerIndex) { - case 0: - case 1: - standings[8] = standings[playerIndex]; - break; - case 2: - case 3: - standings[9] = standings[playerIndex]; - break; - case 4: - case 5: - standings[10] = standings[playerIndex]; - break; - case 6: - case 7: - standings[11] = standings[playerIndex]; - break; - case 8: - case 9: - standings[12] = standings[playerIndex]; - break; - case 10: - case 11: - standings[13] = standings[playerIndex]; - break; - case 12: - case 13: - standings[14] = standings[playerIndex]; - break; - } - + standings[playerIndex/2+8] = standings[playerIndex]; } /** @@ -529,6 +522,10 @@ public class QuestEventDraft { public boolean playerHasMatchesLeft() { + if (QuestDraftUtils.TOURNAMENT_TOGGLE) { + return !bracket.isTournamentOver() && bracket.isPlayerRemaining(-1); + } + int playerIndex = -1; for (int i = standings.length - 1; i >= 0; i--) { if (standings[i].equals(HUMAN)) { @@ -581,6 +578,13 @@ public class QuestEventDraft { } public int getPlayerPlacement() { + // "1st Place" and "2nd Place" are accurate + // "3rd Place" is the two remaining players who won in the first round + // "4th Place" is anyone who lost in the first round + + if (QuestDraftUtils.TOURNAMENT_TOGGLE) { + return 5 - bracket.getFurthestRound(-1); + } int playerIndex = -1; for (int i = standings.length - 1; i >= 0; i--) { @@ -896,6 +900,7 @@ public class QuestEventDraft { Collections.shuffle(players); + // Initialize tournament for (int i = 0; i < players.size(); i++) { event.standings[i] = players.get(i); } @@ -930,6 +935,8 @@ public class QuestEventDraft { } + event.bracket = createBracketFromStandings(event.standings, event.aiNames, event.aiIcons); + return event; } @@ -1040,4 +1047,49 @@ public class QuestEventDraft { } + public static TournamentBracket createBracketFromStandings(String[] standings, String[] aiNames, int[] aiIcons) { + TournamentBracket bracket = new TournamentBracket(TOTAL_ROUNDS, PLAYERS_IN_PAIRING); + bracket.setContinualPairing(false); + + int roundParticipants = (int)(Math.pow(PLAYERS_IN_PAIRING, TOTAL_ROUNDS)); + int i; + + // Initialize Players + for(i = 0; i < roundParticipants; i++) { + if (standings[i].equals(HUMAN)) { + bracket.addTournamentPlayer(GamePlayerUtil.getGuiPlayer()); + } else { + int idx = Integer.valueOf(standings[i]) - 1; + bracket.addTournamentPlayer(GamePlayerUtil.createAiPlayer(aiNames[idx], aiIcons[idx]), idx); + } + } + + bracket.setInitialized(true); + bracket.generateActivePairings(); + while(i < 14) { + TournamentPairing pairing = bracket.getNextPairing(); + if (pairing == null) { + // No pairings available. Generate next round and continue + bracket.generateActivePairings(); + pairing = bracket.getNextPairing(); + // Pairing really shouldn't be null at this point + } + if (standings[i].equals(UNDETERMINED)) { + // Bracket now up to date! + break; + } else { + int idx = standings[i].equals(HUMAN) ? -1 : Integer.valueOf(standings[i]) - 1; + pairing.setWinnerByIndex(idx); + bracket.reportMatchCompletion(pairing); + } + i += 1; + } + + if (i == 14) { + // Tournament has ended! Do I have to do anything? + System.out.println("Tournament done..."); + } + + return bracket; + } } diff --git a/forge-gui/src/main/java/forge/quest/data/QuestAchievements.java b/forge-gui/src/main/java/forge/quest/data/QuestAchievements.java index 572a3c49880..c03b1626b3d 100644 --- a/forge-gui/src/main/java/forge/quest/data/QuestAchievements.java +++ b/forge-gui/src/main/java/forge/quest/data/QuestAchievements.java @@ -284,7 +284,14 @@ public class QuestAchievements { FModel.getQuest().getDraftDecks().delete(QuestEventDraft.DECK_NAME); return null; } - return drafts.get(currentDraft); + + try { + return drafts.get(currentDraft); + } + catch(ArrayIndexOutOfBoundsException e) { + return null; + } + } public int getCurrentDraftIndex() { diff --git a/forge-gui/src/main/java/forge/quest/io/QuestDataIO.java b/forge-gui/src/main/java/forge/quest/io/QuestDataIO.java index 1353e9ebb34..7937d1fd30e 100644 --- a/forge-gui/src/main/java/forge/quest/io/QuestDataIO.java +++ b/forge-gui/src/main/java/forge/quest/io/QuestDataIO.java @@ -198,6 +198,10 @@ public class QuestDataIO { QuestDataIO.setFinalField(QuestData.class, "matchLength", newData, 3); } + if (saveVersion < 11) { + // Migrate DraftTournaments to use new Tournament class + } + final QuestAssets qS = newData.getAssets(); final QuestAchievements qA = newData.getAchievements(); @@ -499,7 +503,6 @@ public class QuestDataIO { QuestEventDraftContainer drafts = (QuestEventDraftContainer) source; for (QuestEventDraft draft : drafts) { - writer.startNode("draft"); writer.startNode("title"); @@ -534,6 +537,11 @@ public class QuestDataIO { } writer.endNode(); + // TODO Save bracket instead of standings + //writer.startNode("bracket"); + //draft.getBracket().exportToXML(writer); + //writer.endNode(); + writer.startNode("aiNames"); i = 0; for (String name : draft.getAINames()) { @@ -575,13 +583,13 @@ public class QuestDataIO { while (reader.hasMoreChildren()) { reader.moveDown(); - + // TODO Add Tournament String draftName = null; String boosterConfiguration = null; int entryFee = 1500; int age = 15; String block = null; - String[] standings = new String[15]; + String[] standings = null; String[] aiNames = new String[7]; int[] aiIcons = new int[7]; boolean started = false; @@ -604,6 +612,8 @@ public class QuestDataIO { block = reader.getValue(); break; case "standings": + // TODO Leaving for older quest datas, but will convert immediately to bracket + standings = new String[15]; int i = 0; while (reader.hasMoreChildren()) { reader.moveDown(); @@ -611,6 +621,9 @@ public class QuestDataIO { reader.moveUp(); } break; + case "bracket": + // TODO For newer quest datas, that store brackets + break; case "aiNames": int ii = 0; while (reader.hasMoreChildren()) { @@ -643,7 +656,11 @@ public class QuestDataIO { draft.setBoosterConfiguration(boosterConfiguration); draft.setEntryFee(entryFee); draft.setBlock(block); + // TODO Stop setting standings once Bracket is loading from IO draft.setStandings(standings); + if (standings != null) { + draft.setBracket(QuestEventDraft.createBracketFromStandings(standings, aiNames, aiIcons)); + } draft.setAINames(aiNames); draft.setAIIcons(aiIcons); draft.setStarted(started); diff --git a/forge-gui/src/main/java/forge/tournament/TournamentData.java b/forge-gui/src/main/java/forge/tournament/TournamentData.java new file mode 100644 index 00000000000..4d7e600a78d --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/TournamentData.java @@ -0,0 +1,148 @@ +package forge.tournament; + +import com.thoughtworks.xstream.annotations.XStreamOmitField; +import forge.GuiBase; +import forge.deck.Deck; +import forge.game.GameType; +import forge.game.player.RegisteredPlayer; +import forge.match.HostedMatch; +import forge.properties.ForgeConstants; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class TournamentData { + @XStreamOmitField + private String name; // set based on the the filename on load + + private transient HostedMatch hostedMatch = null; + + private int completed; + private String timestamp; + private List eventRecords = new ArrayList(); + private List eventNames = new ArrayList(); + private Deck userDeck; + private List decks; + + public TournamentData() { + } + + public void setName(String name0) { + name = name0; + } + + public void rename(final String newName) { + File newpath = new File(ForgeConstants.GAUNTLET_DIR.userPrefLoc, newName + ".dat"); + File oldpath = new File(ForgeConstants.GAUNTLET_DIR.userPrefLoc, name + ".dat"); + oldpath.renameTo(newpath); + + name = newName; + TournamentIO.saveTournament(this); + } + + public String getName() { + return name; + } + + public String getDisplayName() { + if (name.startsWith(TournamentIO.PREFIX_LOCKED)) { //trim locked prefix if needed + return name.substring(TournamentIO.PREFIX_LOCKED.length()); + } + return name; + } + + public void stamp() { + final DateFormat dateFormat = new SimpleDateFormat("MM-dd-yy, H:m"); + timestamp = dateFormat.format(new Date()).toString(); + } + + /** Resets a Tournament data to an unplayed state, then stamps and saves. */ + public void reset() { + completed = 0; + stamp(); + eventRecords.clear(); + + for (int i = 0; i < decks.size(); i++) { + eventRecords.add(""); + } + + TournamentIO.saveTournament(this); + } + + public String getTimestamp() { + return timestamp; + } + + public void setCompleted(final int i0) { + completed = i0; + } + + public int getCompleted() { + return completed; + } + + public void setUserDeck(final Deck d0) { + userDeck = d0; + } + + public Deck getUserDeck() { + return userDeck; + } + + public List getDeckNames() { + final List names = new ArrayList(); + for (final Deck d : decks) { names.add(d.getName()); } + return names; + } + + public void setEventRecords(final List records0) { + eventRecords = records0; + } + + public List getEventRecords() { + return eventRecords; + } + + public void setEventNames(final List names0) { + eventNames = names0; + } + + public List getEventNames() { + return eventNames; + } + + public void setDecks(final List decks0) { + decks = decks0; + } + + public List getDecks() { + return decks; + } + + public void startRound(final List players, final RegisteredPlayer human) { + hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.startMatch(GameType.Tournament, null, players, human, GuiBase.getInterface().getNewGuiGame()); + } + + public void nextRound(final List players, final RegisteredPlayer human) { + if (hostedMatch == null) { + throw new IllegalStateException("Cannot advance round when no match has been hosted."); + } + + hostedMatch.endCurrentGame(); + startRound(players, human); + } + + @Override + public String toString() { + String str = getDisplayName(); + if (decks != null) { + str += " (" + decks.size() + " opponents)"; + } + return str; + } +} \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/tournament/TournamentIO.java b/forge-gui/src/main/java/forge/tournament/TournamentIO.java new file mode 100644 index 00000000000..bc0242e4238 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/TournamentIO.java @@ -0,0 +1,190 @@ +package forge.tournament; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import forge.deck.CardPool; +import forge.item.PaperCard; +import forge.model.FModel; +import forge.properties.ForgeConstants; +import forge.util.IgnoringXStream; +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public class TournamentIO { + /** Prompt in text field for new (unsaved) built gauntlets. */ + public static final String TXF_PROMPT = "[New Tournament]"; + /** suffix for all gauntlet data files */ + public static final String SUFFIX_DATA = ".dat"; + /** Prefix for quick tournament save files. */ + public static final String PREFIX_QUICK = "Quick_"; + /** Prefix for custom tournament save files. */ + public static final String PREFIX_CUSTOM = "Custom_"; + /** Regex for locked tournament save files. */ + public static final String PREFIX_LOCKED = "LOCKED_"; + + protected static XStream getSerializer(final boolean isIgnoring) { + final XStream xStream = isIgnoring ? new IgnoringXStream() : new XStream(); + xStream.registerConverter(new DeckSectionToXml()); + xStream.autodetectAnnotations(true); + return xStream; + } + + public static File getTournamentFile(final String name) { + return new File(ForgeConstants.TOURNAMENT_DIR.userPrefLoc, name + SUFFIX_DATA); + } + + public static File getTournamentFile(final TournamentData gd) { + return getTournamentFile(gd.getName()); + } + + public static File[] getTournamentFilesUnlocked(final String prefix) { + final FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(final File dir, final String name) { + return ((prefix == null || name.startsWith(prefix)) && name.endsWith(SUFFIX_DATA)); + } + }; + + final File folder = new File(ForgeConstants.TOURNAMENT_DIR.userPrefLoc); + return folder.listFiles(filter); + } + + public static File[] getTournamentFilesLocked() { + final FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(final File dir, final String name) { + return (name.startsWith(PREFIX_LOCKED) && name.endsWith(SUFFIX_DATA)); + } + }; + + final File folder = new File(ForgeConstants.TOURNAMENT_DIR.defaultLoc); + return folder.listFiles(filter); + } + + public static TournamentData loadTournament(final File xmlSaveFile) { + GZIPInputStream zin = null; + boolean isCorrupt = false; + try { + zin = new GZIPInputStream(new FileInputStream(xmlSaveFile)); + final InputStreamReader reader = new InputStreamReader(zin); + + final TournamentData data = (TournamentData)TournamentIO.getSerializer(true).fromXML(reader); + + final String filename = xmlSaveFile.getName(); + data.setName(filename.substring(0, filename.length() - SUFFIX_DATA.length())); + return data; + } + catch (final IOException e) { + e.printStackTrace(); + } + catch (final Exception e) { //if there's a non-IO exception, delete the corrupt file + e.printStackTrace(); + isCorrupt = true; + } + finally { + if (zin != null) { + try { + zin.close(); + } catch (final IOException e) { + System.out.println("error closing tournament data reader: " + e); + } + } + } + if (isCorrupt) { + try { + xmlSaveFile.delete(); + } catch (final Exception e) { + System.out.println("error delete corrupt tournament file: " + e); + } + } + return null; + } + + public static void saveTournament(final TournamentData gd0) { + try { + final XStream xStream = TournamentIO.getSerializer(false); + TournamentIO.savePacked(xStream, gd0); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + private static void savePacked(final XStream xStream0, final TournamentData gd0) throws IOException { + final BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(getTournamentFile(gd0))); + final GZIPOutputStream zout = new GZIPOutputStream(bout); + xStream0.toXML(gd0, zout); + zout.flush(); + zout.close(); + } + + private static class DeckSectionToXml implements Converter { + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(final Class clasz) { + return clasz.equals(CardPool.class); + } + + @Override + public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { + for (final Map.Entry e : (CardPool) source) { + writeCardPrinted(e.getKey(), e.getValue(), writer); + } + } + + @Override + public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { + final CardPool result = new CardPool(); + while (reader.hasMoreChildren()) { + reader.moveDown(); + final String sCnt = reader.getAttribute("n"); + final int cnt = StringUtils.isNumeric(sCnt) ? Integer.parseInt(sCnt) : 1; + final String nodename = reader.getNodeName(); + + if ("string".equals(nodename)) { + result.add(FModel.getMagicDb().getCommonCards().getCard(reader.getValue())); + } else if ("card".equals(nodename)) { // new format + result.add(readCardPrinted(reader), cnt); + } + reader.moveUp(); + } + + return result; + } + + private static void writeCardPrinted(final PaperCard cref, final Integer count, final HierarchicalStreamWriter writer) { + writer.startNode("card"); + writer.addAttribute("c", cref.getName()); + writer.addAttribute("s", cref.getEdition()); + if (cref.isFoil()) { + writer.addAttribute("foil", "1"); + } + writer.addAttribute("i", Integer.toString(cref.getArtIndex())); + writer.addAttribute("n", count.toString()); + writer.endNode(); + } + + private static PaperCard readCardPrinted(final HierarchicalStreamReader reader) { + final String name = reader.getAttribute("c"); + final String set = reader.getAttribute("s"); + final String sIndex = reader.getAttribute("i"); + final short index = StringUtils.isNumeric(sIndex) ? Short.parseShort(sIndex) : 0; + final boolean foil = "1".equals(reader.getAttribute("foil")); + PaperCard card = FModel.getMagicDb().getCommonCards().getCard(name, set, index); + if (null == card) { + card = FModel.getMagicDb().getCommonCards().getCard(name, set, -1); + } + if (null == card) { + throw new RuntimeException("Unsupported card found in quest save: " + name + " from edition " + set); + } + return foil ? FModel.getMagicDb().getCommonCards().getFoiled(card) : card; + } + } +} diff --git a/forge-gui/src/main/java/forge/tournament/TournamentUtil.java b/forge-gui/src/main/java/forge/tournament/TournamentUtil.java new file mode 100644 index 00000000000..a3eaa4be2d5 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/TournamentUtil.java @@ -0,0 +1,76 @@ +package forge.tournament; + +import forge.deck.Deck; +import forge.deck.DeckType; +import forge.deck.DeckgenUtil; +import forge.model.FModel; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class TournamentUtil { + public static TournamentData createQuickTournament(final Deck userDeck, final int numOpponents, final List allowedDeckTypes) { + TournamentData tournament = new TournamentData(); + setDefaultTournamentName(tournament, TournamentIO.PREFIX_QUICK); + FModel.setTournamentData(tournament); + + // Generate tournament decks + Deck deck; + final List eventNames = new ArrayList(); + final List decks = new ArrayList(); + + for (int i = 0; i < numOpponents; i++) { + int randType = (int)Math.floor(Math.random() * allowedDeckTypes.size()); + switch (allowedDeckTypes.get(randType)) { + case COLOR_DECK: + deck = DeckgenUtil.getRandomColorDeck(true); + eventNames.add("Random colors deck"); + break; + case CUSTOM_DECK: + deck = DeckgenUtil.getRandomCustomDeck(); + eventNames.add(deck.getName()); + break; + case PRECONSTRUCTED_DECK: + deck = DeckgenUtil.getRandomPreconDeck(); + eventNames.add(deck.getName()); + break; + case QUEST_OPPONENT_DECK: + deck = DeckgenUtil.getRandomQuestDeck(); + eventNames.add(deck.getName()); + break; + case THEME_DECK: + deck = DeckgenUtil.getRandomThemeDeck(); + eventNames.add(deck.getName()); + break; + default: + continue; + } + decks.add(deck); + } + + tournament.setDecks(decks); + tournament.setEventNames(eventNames); + tournament.setUserDeck(userDeck); + + // Reset all variable fields to 0, stamps and saves automatically. + tournament.reset(); + return tournament; + } + + public static void setDefaultTournamentName(TournamentData tournament, String prefix) { + final File[] arrFiles = TournamentIO.getTournamentFilesUnlocked(prefix); + final Set setNames = new HashSet(); + for (File f : arrFiles) { + setNames.add(f.getName()); + } + + int num = 1; + while (setNames.contains(prefix + num + TournamentIO.SUFFIX_DATA)) { + num++; + } + tournament.setName(prefix + num); + } +} diff --git a/forge-gui/src/main/java/forge/tournament/TournamentWinLoseController.java b/forge-gui/src/main/java/forge/tournament/TournamentWinLoseController.java new file mode 100644 index 00000000000..238c1e75fb4 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/TournamentWinLoseController.java @@ -0,0 +1,132 @@ +package forge.tournament; + + +import com.google.common.collect.Lists; +import forge.LobbyPlayer; +import forge.assets.FSkinProp; +import forge.deck.Deck; +import forge.game.GameView; +import forge.game.player.RegisteredPlayer; +import forge.interfaces.IButton; +import forge.interfaces.IWinLoseView; +import forge.model.FModel; +import forge.player.GamePlayerUtil; + +import java.util.List; + +abstract public class TournamentWinLoseController { + private final IWinLoseView view; + private final GameView lastGame; + + public TournamentWinLoseController(IWinLoseView view0, final GameView game0) { + view = view0; + lastGame = game0; + } + + public void showOutcome() { + final TournamentData gd = FModel.getTournamentData(); + final List lstEventNames = gd.getEventNames(); + final List lstDecks = gd.getDecks(); + final List lstEventRecords = gd.getEventRecords(); + final int len = lstDecks.size(); + final int num = gd.getCompleted(); + FSkinProp icon = null; + String message1 = null; + String message2 = null; + + // No restarts. + view.getBtnRestart().setVisible(false); + + // Generic event record. + lstEventRecords.set(gd.getCompleted(), "Ongoing"); + + // Match won't be saved until it is over. This opens up a cheat + // or failsafe mechanism (depending on your perspective) in which + // the player can restart Forge to replay a match. + // Pretty sure this can't be fixed until in-game states can be + // saved. Doublestrike 07-10-12 + LobbyPlayer questPlayer = GamePlayerUtil.getGuiPlayer(); + + // In all cases, update stats. + lstEventRecords.set(gd.getCompleted(), lastGame.getGamesWonBy(questPlayer) + " - " + + (lastGame.getNumPlayedGamesInMatch() - lastGame.getGamesWonBy(questPlayer) + 1)); + + boolean isMatchOver = lastGame.isMatchOver(); + if (isMatchOver) { + gd.setCompleted(gd.getCompleted() + 1); + + // Win match case + if (lastGame.isMatchWonBy(questPlayer)) { + // Tournament complete: Remove save file + if (gd.getCompleted() == lstDecks.size()) { + icon = FSkinProp.ICO_QUEST_COIN; + message1 = "CONGRATULATIONS!"; + message2 = "You made it through the tournament!"; + + view.getBtnContinue().setVisible(false); + view.getBtnQuit().setText("OK"); + + // Remove save file if it's a quickie, or just reset it. + if (gd.getName().startsWith(TournamentIO.PREFIX_QUICK)) { + TournamentIO.getTournamentFile(gd).delete(); + } + else { + gd.reset(); + } + } + // Or, save and move to next game + else { + gd.stamp(); + TournamentIO.saveTournament(gd); + + view.getBtnContinue().setText("Next Round (" + (gd.getCompleted() + 1) + + "/" + len + ")"); + view.getBtnContinue().setVisible(true); + view.getBtnContinue().setEnabled(true); + view.getBtnQuit().setText("Save and Quit"); + } + } + // Lose match case; stop tournament. + else { + icon = FSkinProp.ICO_QUEST_HEART; + message1 = "DEFEATED!"; + message2 = "You have failed to pass the tournament."; + + view.getBtnContinue().setVisible(false); + + // Remove save file if it's a quickie, or just reset it. + if (gd.getName().startsWith(TournamentIO.PREFIX_QUICK)) { + TournamentIO.getTournamentFile(gd).delete(); + } + else { + gd.reset(); + } + } + } + + gd.setEventRecords(lstEventRecords); + + showOutcome(isMatchOver, message1, message2, icon, lstEventNames, lstEventRecords, len, num); + } + + public final boolean actionOnContinue() { + if (lastGame.isMatchOver()) { + // To change the AI deck, we have to create a new match. + final TournamentData gd = FModel.getTournamentData(); + final RegisteredPlayer human = new RegisteredPlayer(gd.getUserDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); + final Deck aiDeck = gd.getDecks().get(gd.getCompleted()); + final List players = Lists.newArrayList(); + players.add(human); + players.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); + + view.hide(); + saveOptions(); + gd.nextRound(players, human); + return true; + } + return false; + } + + protected abstract void showOutcome(boolean isMatchOver, String message1, String message2, FSkinProp icon, List lstEventNames, List lstEventRecords, int len, int num); + protected abstract void saveOptions(); +} diff --git a/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java b/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java new file mode 100644 index 00000000000..943623467c1 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java @@ -0,0 +1,161 @@ +package forge.tournament.system; + +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import forge.LobbyPlayer; +import forge.player.GamePlayerUtil; +import forge.util.MyRandom; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractTournament implements Serializable { + protected int activeRound; + protected int totalRounds; + protected int playersInPairing = 2; + protected boolean initialized = false; + protected boolean continualPairing = true; + protected final List allPlayers = new ArrayList(); + protected transient final List remainingPlayers = new ArrayList(); + protected final List completedPairings = new ArrayList<>(); + protected final List activePairings = new ArrayList<>(); + + public List getCompletedPairings() { return completedPairings; } + public List getActivePairings() { return activePairings; } + + public AbstractTournament(int ttlRnds) { + activeRound = 0; + totalRounds = ttlRnds; + } + + public AbstractTournament(int ttlRnds, List plrs) { + activeRound = 0; + totalRounds = ttlRnds; + allPlayers.addAll(plrs); + remainingPlayers.addAll(plrs); + } + + public void initializeTournament() { + // "Randomly" seed players to start tournament + Collections.shuffle(remainingPlayers, MyRandom.getRandom()); + generateActivePairings(); + initialized = true; + } + + public TournamentPairing getNextPairing() { + if (activePairings.isEmpty()) { + return null; + } + return activePairings.get(0); + } + + public boolean isContinualPairing() { return continualPairing; } + + public void setContinualPairing(boolean continualPairing) { this.continualPairing = continualPairing; } + + public boolean isInitialized() { return initialized; } + + public void setInitialized(boolean initialized) { this.initialized = initialized; } + + + public boolean isPlayerRemaining(TournamentPlayer player) { + return remainingPlayers.contains(player); + } + public boolean isPlayerRemaining(int index) { + for(TournamentPlayer player : remainingPlayers) { + if (player.getIndex() == index) { + return true; + } + } + return false; + } + + abstract public void generateActivePairings(); + abstract public void reportMatchCompletion(TournamentPairing pairing); + abstract public boolean completeRound(); + + public void finishMatch(TournamentPairing pairing) { + activePairings.remove(pairing); + completedPairings.add(pairing); + } + + abstract public void endTournament(); + + public boolean isTournamentOver() { + return (initialized && activeRound == totalRounds && activePairings.isEmpty()); + } + + public void addTournamentPlayer(LobbyPlayer pl) { + TournamentPlayer player = new TournamentPlayer(pl); + allPlayers.add(player); + remainingPlayers.add(player); + } + + public void addTournamentPlayer(LobbyPlayer pl, int idx) { + TournamentPlayer player = new TournamentPlayer(pl, idx); + allPlayers.add(player); + remainingPlayers.add(player); + } + + public void createTournamentPlayersForDraft(String[] names, int[] icons) { + int size = names.length; + for(int i = 0; i < size; i++) { + TournamentPlayer player = new TournamentPlayer(GamePlayerUtil.createAiPlayer(names[i], icons[i]), i); + allPlayers.add(player); + remainingPlayers.add(player); + } + } + + // Probably should be a interface here, not a standalone function + public void exportToXML(HierarchicalStreamWriter writer) { + /* + protected int activeRound; + protected int totalRounds; + protected int playersInPairing = 2; + protected boolean initialized = false; + protected final List allPlayers = new ArrayList(); + protected final List completedPairings = new ArrayList<>(); + protected final List activePairings = new ArrayList<>(); + */ + writer.startNode("activeRound"); + writer.setValue(Integer.toString(activeRound)); + writer.endNode(); + + writer.startNode("totalRounds"); + writer.setValue(Integer.toString(totalRounds)); + writer.endNode(); + + writer.startNode("playersInPairing"); + writer.setValue(Integer.toString(playersInPairing)); + writer.endNode(); + + writer.startNode("initialized"); + writer.setValue(Boolean.toString(initialized)); + writer.endNode(); + + writer.startNode("allPlayers"); + for (TournamentPlayer player : allPlayers) { + writer.startNode("tournamentPlayer"); + writer.setValue(player.toString()); + writer.endNode(); + } + writer.endNode(); + + writer.startNode("completedPairings"); + for (TournamentPairing pairing : completedPairings) { + writer.startNode("tournamentPairing"); + pairing.exportToXML(writer); + writer.endNode(); + } + writer.endNode(); + + writer.startNode("completedPairings"); + for (TournamentPairing pairing : activePairings) { + writer.startNode("tournamentPairing"); + pairing.exportToXML(writer); + writer.endNode(); + } + writer.endNode(); + } +} diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java b/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java new file mode 100644 index 00000000000..cad32838b40 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java @@ -0,0 +1,104 @@ +package forge.tournament.system; + +import java.util.ArrayList; +import java.util.List; + +public class TournamentBracket extends AbstractTournament { + // Bracket implies single elimination. For non-single elimination, use Swiss or RoundRobin + public TournamentBracket(int ttlRnds, int pairingAmount) { + super(ttlRnds); + this.playersInPairing = pairingAmount; + // Don't initialize the tournament if no players are available + } + + public TournamentBracket(int ttlRnds, List allPlayers) { + super(ttlRnds, allPlayers); + initializeTournament(); + } + + public TournamentBracket(int ttlRnds, List allPlayers, int pairingAmount) { + super(ttlRnds, allPlayers); + this.playersInPairing = pairingAmount; + initializeTournament(); + } + + @Override + public void generateActivePairings() { + activeRound++; + List pair = new ArrayList<>(); + int count = 0; + for (TournamentPlayer tp : this.remainingPlayers) { + pair.add(tp); + count++; + if (count == this.playersInPairing) { + count = 0; + activePairings.add(new TournamentPairing(activeRound, pair)); + pair = new ArrayList<>(); + } + } + + if (count >= 1) { + // Leftover players. Really shouldn't happen in a Bracket. + TournamentPairing pairing = new TournamentPairing(activeRound, pair); + if (count == 1) { + pairing.setBye(true); + } + activePairings.add(pairing); + } + } + + @Override + public void reportMatchCompletion(TournamentPairing pairing) { + finishMatch(pairing); + + for (TournamentPlayer tp : pairing.getPairedPlayers()) { + if (!tp.equals(pairing.getWinner())) { + remainingPlayers.remove(tp); + tp.setActive(false); + } + } + + if (activePairings.isEmpty()) { + completeRound(); + } + } + + @Override + public boolean completeRound() { + if (activeRound < totalRounds) { + if (continualPairing) { + generateActivePairings(); + } + return true; + } else { + endTournament(); + return false; + } + } + + @Override + public void endTournament() { + this.activePairings.clear(); + } + + public int getFurthestRound(int index) { + // If won in round 3, furthest round is 4 + // If lost in any round, that's the furthest round + for (int i = completedPairings.size(); --i >= 0;) { + TournamentPairing pairing = completedPairings.get(i); + for(TournamentPlayer player : pairing.getPairedPlayers()) { + if (player.getIndex() == index) { + int roundAdjustment = pairing.getWinner().equals(player) ? 1 : 0; + return pairing.getRound() + roundAdjustment; + } + } + } + // Really shouldn't get here + return 0; + } + + public static TournamentBracket importFromXML() { + TournamentBracket bracket = new TournamentBracket(3, 2); + return bracket; + } +} diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentPairing.java b/forge-gui/src/main/java/forge/tournament/system/TournamentPairing.java new file mode 100644 index 00000000000..81c7df395e6 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentPairing.java @@ -0,0 +1,60 @@ +package forge.tournament.system; + +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import forge.LobbyPlayer; +import forge.game.GameOutcome; + +import java.util.ArrayList; +import java.util.List; + +public class TournamentPairing { + private int round; + private boolean bye = false; + private final List pairedPlayers = new ArrayList(); + private final List outcomes = new ArrayList(); + private TournamentPlayer winner; + + public TournamentPairing(int rnd, List plyrs) { + pairedPlayers.addAll(plyrs); + round = rnd; + winner = null; + } + + public int getRound() { return round; } + + public void setRound(int round) { this.round = round; } + + public boolean isBye() { return bye; } + + public void setBye(boolean bye) { this.bye = bye; } + + public List getPairedPlayers() { return pairedPlayers; } + + public List getOutcomes() { return outcomes; } + + public TournamentPlayer getWinner() { return winner; } + + public void setWinner(TournamentPlayer winner) { this.winner = winner; } + + public void setWinnerByIndex(int index) { + for(TournamentPlayer pl : pairedPlayers) { + if (pl.getIndex() == index) { + this.winner = pl; + return; + } + } + } + + public boolean hasPlayer(LobbyPlayer player) { + for(TournamentPlayer pl : this.pairedPlayers) { + if (pl.getPlayer().equals(player)) { + return true; + } + } + return false; + } + + public void exportToXML(HierarchicalStreamWriter writer) { + + } +} diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java b/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java new file mode 100644 index 00000000000..11245dce5b2 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java @@ -0,0 +1,71 @@ +package forge.tournament.system; + +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import forge.LobbyPlayer; + +public class TournamentPlayer { + private LobbyPlayer player; + private int wins = 0, + losses = 0, + ties = 0; + private boolean active = true; + + private int index = -1; + + public TournamentPlayer(LobbyPlayer plyr) { + player = plyr; + } + + public TournamentPlayer(LobbyPlayer plyr, int idx) { + player = plyr; + index = idx; + } + + public LobbyPlayer getPlayer() { + return player; + } + + public void setPlayer(LobbyPlayer player) { + this.player = player; + } + + public int getWins() { + return wins; + } + + public void setWins(int wins) { + this.wins = wins; + } + + public int getLosses() { + return losses; + } + + public void setLosses(int losses) { + this.losses = losses; + } + + public int getTies() { + return ties; + } + + public void setTies(int ties) { + this.ties = ties; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public int getIndex() { return index; } + + public void setIndex(int index) { this.index = index; } + + public void exportToXML(HierarchicalStreamWriter writer) { + + } +} diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java b/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java new file mode 100644 index 00000000000..b85d76d4221 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java @@ -0,0 +1,33 @@ +package forge.tournament.system; + +import java.util.List; + +public class TournamentRoundRobin extends AbstractTournament { + // Round Robin tournaments where you play everyone in your group/pod. Declare winner or break to top X + public TournamentRoundRobin(int ttlRnds, List allPlayers) { + super(ttlRnds, allPlayers); + initializeTournament(); + } + + @Override + public void generateActivePairings() { + + } + + @Override + public void reportMatchCompletion(TournamentPairing pairing) { + + } + + @Override + public boolean completeRound() { + return false; + } + + @Override + public void endTournament() { + + } + + +} diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java b/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java new file mode 100644 index 00000000000..063ea506e05 --- /dev/null +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java @@ -0,0 +1,31 @@ +package forge.tournament.system; + +import java.util.List; + +public class TournamentSwiss extends AbstractTournament { + + public TournamentSwiss(int ttlRnds, List allPlayers) { + super(ttlRnds, allPlayers); + initializeTournament(); + } + + @Override + public void generateActivePairings() { + + } + + @Override + public void reportMatchCompletion(TournamentPairing pairing) { + + } + + @Override + public boolean completeRound() { + return false; + } + + @Override + public void endTournament() { + + } +}