From 1c0ebbae4ce4dec2d66f4390d35b6d02703356cb Mon Sep 17 00:00:00 2001 From: Sol Date: Sun, 17 Apr 2016 16:32:20 +0000 Subject: [PATCH] - Adding Tournaments to Simulater --- .../src/main/java/forge/deck/DeckGroup.java | 2 +- .../src/main/java/forge/game/Match.java | 7 + .../main/java/forge/view/SimulateMatch.java | 128 ++++++++++++++++-- .../java/forge/quest/QuestDraftUtils.java | 40 +++++- .../tournament/system/AbstractTournament.java | 34 ++++- .../tournament/system/TournamentBracket.java | 53 +++++--- .../tournament/system/TournamentPlayer.java | 4 + .../system/TournamentRoundRobin.java | 95 +++++++++++-- .../tournament/system/TournamentSwiss.java | 10 +- 9 files changed, 328 insertions(+), 45 deletions(-) diff --git a/forge-core/src/main/java/forge/deck/DeckGroup.java b/forge-core/src/main/java/forge/deck/DeckGroup.java index 9a3bc9c7dcd..001bf35bb5e 100644 --- a/forge-core/src/main/java/forge/deck/DeckGroup.java +++ b/forge-core/src/main/java/forge/deck/DeckGroup.java @@ -64,7 +64,7 @@ public class DeckGroup extends DeckBase { /** * Sets the human deck. * - * @param humanDeck the new human deck + * @param humanDeck0 the new human deck */ public final void setHumanDeck(final Deck humanDeck0) { humanDeck = humanDeck0; diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-game/src/main/java/forge/game/Match.java index f1ac043fd8e..c340257b765 100644 --- a/forge-game/src/main/java/forge/game/Match.java +++ b/forge-game/src/main/java/forge/game/Match.java @@ -159,6 +159,13 @@ public class Match { return getGamesWonBy(questPlayer) >= rules.getGamesToWinMatch(); } + public RegisteredPlayer getWinner() { + if (this.isMatchOver()) { + return gamesPlayedRo.get(gamesPlayedRo.size()-1).getWinningPlayer().getRegisteredPlayer(); + } + return null; + } + public List getPlayers() { return players; } diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java index 1886192f224..99d42de14b4 100644 --- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java +++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java @@ -3,7 +3,10 @@ package forge.view; import java.io.File; import java.util.*; +import forge.LobbyPlayer; +import forge.deck.DeckGroup; import forge.properties.ForgeConstants; +import forge.tournament.system.*; import forge.util.storage.IStorage; import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.time.StopWatch; @@ -76,6 +79,19 @@ public class SimulateMatch { type = GameType.valueOf(WordUtils.capitalize(params.get("f").get(0))); } + GameRules rules = new GameRules(type); + rules.setAppliedVariants(EnumSet.of(type)); + + if (matchSize != 0) { + rules.setGamesPerMatch(matchSize); + } + + if (params.containsKey("t")) { + simulateTournament(params, rules, outputGamelog); + System.out.flush(); + return; + } + List pp = new ArrayList(); StringBuilder sb = new StringBuilder(); @@ -103,16 +119,11 @@ public class SimulateMatch { pp.add(rp); i++; } + sb.append(" - ").append(Lang.nounWithNumeral(nGames, "game")).append(" of ").append(type); System.out.println(sb.toString()); - GameRules rules = new GameRules(type); - rules.setAppliedVariants(EnumSet.of(type)); - - if (matchSize != 0) { - rules.setGamesPerMatch(matchSize); - } Match mc = new Match(rules, pp, "Test"); if (matchSize != 0) { @@ -132,22 +143,18 @@ public class SimulateMatch { } private static void argumentHelp() { - System.out.println("Syntax: forge.exe sim -d ... -n [N] -m [M] -f [F] -q"); + System.out.println("Syntax: forge.exe sim -d ... -n [N] -m [M] -t [T] -p [P] -f [F] -q"); System.out.println("\tsim - stands for simulation mode"); System.out.println("\tdeck1 (or deck2,...,X) - constructed deck name or filename (has to be quoted when contains multiple words)"); System.out.println("\tdeck is treated as file if it ends with a dot followed by three numbers or letters"); System.out.println("\tN - number of games, defaults to 1 (Ignores match setting)"); System.out.println("\tM - Play full match of X games, typically 1,3,5 games. (Optional, overrides N)"); + System.out.println("\tT - Type of tournament to run with all provided decks (Bracket, RoundRobin, Swiss)"); + System.out.println("\tP - Amount of players per match (used only with Tournaments, defaults to 2)"); System.out.println("\tF - format of games, defaults to constructed"); System.out.println("\tq - Quiet flag. Output just the game result, not the entire game log."); } - /** - * TODO: Write javadoc for this method. - * @param mc - * @param iGame - * @param outputGamelog - */ private static void simulateSingleMatch(Match mc, int iGame, boolean outputGamelog) { StopWatch sw = new StopWatch(); sw.start(); @@ -171,6 +178,101 @@ public class SimulateMatch { System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName())); } + private static void simulateTournament(Map> params, GameRules rules, boolean outputGamelog) { + String tournament = params.get("t").get(0); + AbstractTournament tourney = null; + int matchPlayers = params.containsKey("p") ? Integer.parseInt(params.get("p").get(0)) : 2; + + DeckGroup deckGroup = new DeckGroup("SimulatedTournament"); + List players = new ArrayList<>(); + int numPlayers = 0; + for(String deck : params.get("d")) { + Deck d = deckFromCommandLineParameter(deck, rules.getGameType()); + if (d == null) { + System.out.println(String.format("Could not load deck - %s, match cannot start", deck)); + return; + } + + deckGroup.addAiDeck(d); + players.add(new TournamentPlayer(GamePlayerUtil.createAiPlayer(d.getName(), 0), numPlayers)); + numPlayers++; + } + + if ("bracket".equalsIgnoreCase(tournament)) { + tourney = new TournamentBracket(players, matchPlayers); + } else if ("roundrobin".equalsIgnoreCase(tournament)) { + tourney = new TournamentRoundRobin(players, matchPlayers); + } else if ("swiss".equalsIgnoreCase(tournament)) { + //tourney = new TournamentSwiss() + } + if (tourney == null) { + System.out.println("Failed to initialize tournament, bailing out"); + return; + } + + tourney.initializeTournament(); + + String lastWinner = ""; + int curRound = 0; + while(!tourney.isTournamentOver()) { + if (tourney.getActiveRound() != curRound) { + if (curRound != 0) { + System.out.println(String.format("End Round - %d", curRound)); + } + curRound = tourney.getActiveRound(); + System.out.println(""); + System.out.println(String.format("Round %d Pairings:", curRound)); + + for(TournamentPairing pairing : tourney.getActivePairings()) { + StringBuilder sb = new StringBuilder(); + for(TournamentPlayer tp : pairing.getPairedPlayers()) { + sb.append(tp.getPlayer().getName()).append(" "); + } + System.out.println(sb.toString()); + } + System.out.println(""); + } + + TournamentPairing pairing = tourney.getNextPairing(); + List regPlayers = AbstractTournament.registerTournamentPlayers(pairing, deckGroup); + + StringBuilder sb = new StringBuilder(); + sb.append("Round ").append(tourney.getActiveRound()).append(" -"); + for(TournamentPlayer tp : pairing.getPairedPlayers()) { + sb.append(" ").append(tp.getPlayer().getName()); + } + if (pairing.isBye()) { + sb.append(" - BYE"); + } + + System.out.println(sb.toString()); + + if (!pairing.isBye()) { + Match mc = new Match(rules, regPlayers, "TourneyMatch"); + + int iGame = 0; + while (!mc.isMatchOver()) { + // play games until the match ends + simulateSingleMatch(mc, iGame, outputGamelog); + iGame++; + } + LobbyPlayer winner = mc.getWinner().getPlayer(); + for (TournamentPlayer tp : pairing.getPairedPlayers()) { + if (winner.equals(tp.getPlayer())) { + pairing.setWinner(tp); + lastWinner = winner.getName(); + System.out.println(String.format("Match Winner - %s!", lastWinner)); + System.out.println(""); + break; + } + } + } + + tourney.reportMatchCompletion(pairing); + } + tourney.outputTournamentResults(); + } + public static Match simulateOffthreadGame(List decks, GameType format, int games) { return null; } diff --git a/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java b/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java index 6ead9f8b6ab..f8d823adcf9 100644 --- a/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java +++ b/forge-gui/src/main/java/forge/quest/QuestDraftUtils.java @@ -5,8 +5,10 @@ import forge.GuiBase; import forge.deck.Deck; import forge.deck.DeckGroup; import forge.deck.DeckSection; +import forge.game.Game; import forge.game.GameRules; import forge.game.GameType; +import forge.game.Match; import forge.game.player.RegisteredPlayer; import forge.interfaces.IGuiGame; import forge.match.HostedMatch; @@ -24,6 +26,7 @@ import java.util.List; public class QuestDraftUtils { private static final List matchups = new ArrayList<>(); public static boolean TOURNAMENT_TOGGLE = false; + public static boolean AI_BACKGROUND = false; public static boolean matchInProgress = false; private static boolean waitForUserInput = false; @@ -304,9 +307,9 @@ public class QuestDraftUtils { final DeckGroup decks = FModel.getQuest().getAssets().getDraftDeckStorage().get(QuestEventDraft.DECK_NAME); boolean waitForUserInput = pairing.hasPlayer(GamePlayerUtil.getGuiPlayer()); - GameRules rules = createQuestDraftRuleset(); + final GameRules rules = createQuestDraftRuleset(); - List registered = registerTournamentPlayers(pairing, draft, decks); + final List registered = TournamentBracket.registerTournamentPlayers(pairing, decks); RegisteredPlayer registeredHuman = null; if (waitForUserInput) { @@ -320,8 +323,37 @@ public class QuestDraftUtils { } 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()); + if (AI_BACKGROUND) { + System.out.println("Spawning a thread to simulate the match"); + // Show Dialog popup + + /* + FThreads.invokeInBackgroundThread(new Runnable() { + @Override + public void run() { + Match mc = new Match(rules, registered, "Simulated Match"); + Game gm = mc.createGame(); + mc.startGame(gm); + } + });*/ + + Match mc = new Match(rules, registered, "Simulated Match"); + String winner = null; + while (!mc.isMatchOver()) { + Game gm = mc.createGame(); + mc.startGame(gm); + // Update dialog with winner + } + + RegisteredPlayer regPlayer = mc.getWinner(); + //draft.setWinner(regPlayer.getPlayer().getName()); + //FModel.getQuest().save(); + gui.finishGame(); + } else { + final HostedMatch newMatch = GuiBase.getInterface().hostMatch(); + newMatch.startMatch(rules, null, registered, registeredHuman, GuiBase.getInterface().getNewGuiGame()); + } + } } diff --git a/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java b/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java index 168071bfe12..a6939351086 100644 --- a/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java +++ b/forge-gui/src/main/java/forge/tournament/system/AbstractTournament.java @@ -1,14 +1,18 @@ package forge.tournament.system; +import com.google.common.collect.Lists; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import forge.LobbyPlayer; +import forge.deck.DeckGroup; +import forge.game.player.RegisteredPlayer; import forge.player.GamePlayerUtil; import forge.util.MyRandom; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; @SuppressWarnings("serial") @@ -52,6 +56,8 @@ public abstract class AbstractTournament implements Serializable { return activePairings.get(0); } + public int getActiveRound() { return activeRound; } + public boolean isContinualPairing() { return continualPairing; } public void setContinualPairing(boolean continualPairing) { this.continualPairing = continualPairing; } @@ -74,7 +80,7 @@ public abstract class AbstractTournament implements Serializable { } abstract public void generateActivePairings(); - abstract public void reportMatchCompletion(TournamentPairing pairing); + abstract public boolean reportMatchCompletion(TournamentPairing pairing); abstract public boolean completeRound(); public void finishMatch(TournamentPairing pairing) { @@ -88,6 +94,32 @@ public abstract class AbstractTournament implements Serializable { return (initialized && activeRound == totalRounds && activePairings.isEmpty()); } + public void outputTournamentResults() { + Collections.sort(allPlayers, new Comparator() { + @Override + public int compare(TournamentPlayer o1, TournamentPlayer o2) { + return o2.getWins()*3 + o2.getTies() - o1.getWins()*3 + o1.getTies(); + } + }); + System.out.println("Name\t\tWins\tLosses\tTies"); + for(TournamentPlayer tp : allPlayers) { + System.out.println(String.format("%s\t\t%d\t%d\t%d", tp.getPlayer().getName(), tp.getWins(), + tp.getLosses(), tp.getTies())); + } + } + + public static List registerTournamentPlayers(TournamentPairing pairing, 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; + } + public void addTournamentPlayer(LobbyPlayer pl) { TournamentPlayer player = new TournamentPlayer(pl); allPlayers.add(player); diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java b/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java index 29600284826..412403e8953 100644 --- a/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentBracket.java @@ -12,6 +12,11 @@ public class TournamentBracket extends AbstractTournament { // Don't initialize the tournament if no players are available } + public TournamentBracket(List allPlayers, int pairingAmount) { + super((int)(Math.ceil(Math.log(allPlayers.size())/Math.log(2))), allPlayers); + this.playersInPairing = pairingAmount; + } + public TournamentBracket(int ttlRnds, List allPlayers) { super(ttlRnds, allPlayers); initializeTournament(); @@ -26,42 +31,58 @@ public class TournamentBracket extends AbstractTournament { @Override public void generateActivePairings() { activeRound++; + + int numByes = 0; + if (activeRound == 1) { + // Determine how many first round byes there should be. + int fullBracketSize = (int)(Math.pow(2, Math.ceil(Math.log(this.remainingPlayers.size())/Math.log(2)))); + numByes = fullBracketSize - this.remainingPlayers.size(); + } + + // The first X remaining players will receive the required first round Byes + // Since this is a bracket, this should "even" the bracket out. + // Preferably our brackets will always have 2^X amount of players List pair = new ArrayList<>(); int count = 0; for (TournamentPlayer tp : this.remainingPlayers) { pair.add(tp); count++; - if (count == this.playersInPairing) { + if (count == this.playersInPairing || numByes > 0) { count = 0; - activePairings.add(new TournamentPairing(activeRound, pair)); + TournamentPairing pairing = new TournamentPairing(activeRound, pair); + if (numByes > 0) { + numByes--; + pairing.setBye(true); + } + + activePairings.add(pairing); 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) { + public boolean reportMatchCompletion(TournamentPairing pairing) { + // Returns whether there are more matches left in this round finishMatch(pairing); - for (TournamentPlayer tp : pairing.getPairedPlayers()) { - if (!tp.equals(pairing.getWinner())) { - remainingPlayers.remove(tp); - tp.setActive(false); + if (!pairing.isBye()) { + for (TournamentPlayer tp : pairing.getPairedPlayers()) { + if (!tp.equals(pairing.getWinner())) { + tp.addLoss(); + remainingPlayers.remove(tp); + tp.setActive(false); + } else { + tp.addWin(); + } } } if (activePairings.isEmpty()) { completeRound(); + return false; } + return true; } @Override diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java b/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java index 11245dce5b2..6897bed52f9 100644 --- a/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentPlayer.java @@ -29,6 +29,10 @@ public class TournamentPlayer { this.player = player; } + public void addLoss() { losses++; } + public void addWin() { wins++; } + public void addTie() { ties++; } + public int getWins() { return wins; } diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java b/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java index 17691084bce..1353386d59f 100644 --- a/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentRoundRobin.java @@ -1,34 +1,113 @@ package forge.tournament.system; +import com.google.common.collect.Lists; +import forge.player.GamePlayerUtil; + +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; @SuppressWarnings("serial") 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, int pairingAmount) { + super(ttlRnds); + this.playersInPairing = pairingAmount; + // Don't initialize the tournament if no players are available + } + public TournamentRoundRobin(int ttlRnds, List allPlayers) { super(ttlRnds, allPlayers); initializeTournament(); } - @Override - public void generateActivePairings() { - + public TournamentRoundRobin(List allPlayers, int pairingAmount) { + super(allPlayers.size() % 2 == 0 ? allPlayers.size() - 1 : allPlayers.size(), allPlayers); + this.playersInPairing = pairingAmount; } @Override - public void reportMatchCompletion(TournamentPairing pairing) { + public void generateActivePairings() { + int numPlayers = this.remainingPlayers.size(); + List pair = new ArrayList<>(); + int count = 0; + List roundPairings = Lists.newArrayList(this.remainingPlayers); + if (numPlayers % 2 == 1) { + roundPairings.add(new TournamentPlayer(GamePlayerUtil.createAiPlayer("BYE", 0))); + numPlayers++; + } + + TournamentPlayer pivot = roundPairings.get(0); + roundPairings.remove(0); + + for(int i = 0; i < activeRound; i++) { + // Rotate X amount of players, where X is the current round-1 + TournamentPlayer rotate = roundPairings.get(0); + roundPairings.remove(0); + roundPairings.add(rotate); + } + roundPairings.add(0, pivot); + + activeRound++; + + for(int i = 0; i < numPlayers/2; i++) { + boolean bye = false; + if (roundPairings.get(i).getPlayer().getName().equals("BYE")) { + bye = true; + } else { + pair.add(roundPairings.get(i)); + } + + if (roundPairings.get(numPlayers-i-1).getPlayer().getName().equals("BYE")) { + bye = true; + } else { + pair.add(roundPairings.get(numPlayers-i-1)); + } + TournamentPairing pairing = new TournamentPairing(activeRound, pair); + pairing.setBye(bye); + activePairings.add(pairing); + pair = new ArrayList<>(); + } + } + + @Override + public boolean reportMatchCompletion(TournamentPairing pairing) { + // Returns whether there are more matches left in this round + finishMatch(pairing); + + if (!pairing.isBye()) { + for (TournamentPlayer tp : pairing.getPairedPlayers()) { + if (!tp.equals(pairing.getWinner())) { + tp.addLoss(); + } else { + tp.addWin(); + } + } + } + + if (activePairings.isEmpty()) { + completeRound(); + return false; + } + return true; } @Override public boolean completeRound() { - return false; + if (activeRound < totalRounds) { + if (continualPairing) { + generateActivePairings(); + } + return true; + } else { + endTournament(); + return false; + } } @Override public void endTournament() { - + this.activePairings.clear(); } - - } diff --git a/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java b/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java index c02ab93df64..2738e1613a7 100644 --- a/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java +++ b/forge-gui/src/main/java/forge/tournament/system/TournamentSwiss.java @@ -4,6 +4,12 @@ import java.util.List; @SuppressWarnings("serial") public class TournamentSwiss extends AbstractTournament { + //http://www.wizards.com/DCI/downloads/Swiss_Pairings.pdf + public TournamentSwiss(int ttlRnds, int pairingAmount) { + super(ttlRnds); + this.playersInPairing = pairingAmount; + // Don't initialize the tournament if no players are available + } public TournamentSwiss(int ttlRnds, List allPlayers) { super(ttlRnds, allPlayers); @@ -16,8 +22,8 @@ public class TournamentSwiss extends AbstractTournament { } @Override - public void reportMatchCompletion(TournamentPairing pairing) { - + public boolean reportMatchCompletion(TournamentPairing pairing) { + return false; } @Override