package ww10.gui; import game.stats.BankrollObserver; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.swing.SwingUtilities; import util.Utils; import com.biotools.meerkat.Action; import com.biotools.meerkat.Card; import com.biotools.meerkat.GameInfo; import com.biotools.meerkat.GameObserver; /** * This BankrollObserver opens a window while the game is running, showing the current * progress. A bankroll-chart is updated every 10 seconds.<br> * <br> * The window closes itself on the end of the game.<br> * * Register this class on a GameRunner and call {@link #createGraph()} in the end * to open a window with the final result */ public class DataModel implements BankrollObserver, GameObserver { private final SwingGUI gui = new SwingGUI(this); private String[] playerNames; private volatile int currentGamesPlayed; private HashMap<String, String> chosenNames = new HashMap<String, String>(); private final LinkedList<GameStats> bankrollWindow = new LinkedList<GameStats>(); private final LinkedList<GameStats> plotTimes = new LinkedList<GameStats>(); private final LinkedList<Long> markerTimes = new LinkedList<Long>(); private int nbGamesInTotalProfit = 0; private final Map<String, Double> currentTotalProfit = new HashMap<String, Double>(); private final long plotWindow = 60 * 1000; private final long minBankrollWindow = plotWindow / 3; private long lastSubmitTime = 0; private final HashMap<String, int[]> currentActionFreqs = new HashMap<String, int[]>(); private GameInfo gameInfo; public synchronized String[] getPlayerNames() { return playerNames; } public synchronized int getCurrentGamesPlayed() { return currentGamesPlayed; } public synchronized String getChosenName(String fixedName) { String chosenName = chosenNames.get(fixedName); if (chosenName == null) return fixedName; else return chosenName; } public synchronized String getLongChosenName(String fixedName) { String chosenName = chosenNames.get(fixedName); if (chosenName == null) return fixedName; else return chosenName + " (" + fixedName + ")"; } public synchronized void onSubmit(final String fixedName, final String chosenName) { //TODO update GUI chosenNames.put(fixedName, chosenName); lastSubmitTime = System.currentTimeMillis(); final int fCurrentGamesPlayed = currentGamesPlayed; markerTimes.add(lastSubmitTime); SwingUtilities.invokeLater(new Runnable() { public void run() { gui.averageProfitPanel.addMarker(fCurrentGamesPlayed, fixedName + " submits " + chosenName); } }); } @Override public synchronized void gameStarted(int numSeatPermutations, int numGames, Set<String> playerNames) { this.playerNames = playerNames.toArray(new String[playerNames.size()]); Arrays.sort(this.playerNames); for (String player : playerNames) { currentTotalProfit.put(player, 0.0); currentActionFreqs.put(player, new int[5]); } SwingUtilities.invokeLater(new Runnable() { public void run() { gui.initialize(); } }); } @Override public synchronized void updateBankroll(int seatpermutation, final Map<String, Double> playerDelta) { currentGamesPlayed++; //update stats currentGameStats.time = System.currentTimeMillis(); currentGameStats.game = currentGamesPlayed; currentGameStats.bankrollDeltas = playerDelta; addGameStats(currentGameStats); bankrollWindow.add(currentGameStats); if (currentGamesPlayed % 1 == 0) { updatePlot(); } } private void updatePlot() { //remove old stats // update bankroll removeFromWindow(); // update plot // update data points final int fNbRemoved = removeDatapoints(); final int fCurrentGamesPlayed = currentGamesPlayed; final ConcurrentHashMap<String, Double> fCurrentAvgProfit = getAvgProfit(); //update status int currentGamesPlayed = getCurrentGamesPlayed(); double gamesPerSecond = getGamesPerSecond(); int window = getCurrentBankrollGameWindow(); final String status = "Completed: " + currentGamesPlayed + " games, " + "Speed: " + Utils.roundToCents(gamesPerSecond) + " games/s, " + "Window: " + window + " games"; //update markers final int fNbRemovedMarkers = removeMarkers(); //update action frequencies final ConcurrentHashMap<String, int[]> fActionFreqs = new ConcurrentHashMap<String, int[]>(); for (Entry<String, int[]> entry : currentActionFreqs.entrySet()) { fActionFreqs.put(entry.getKey(), entry.getValue().clone()); } SwingUtilities.invokeLater(new Runnable() { public void run() { gui.averageProfitPanel.removeFirstDataPoints(fNbRemoved); gui.averageProfitPanel.addDataPoints(fCurrentGamesPlayed, fCurrentAvgProfit); gui.changeStatus(status); gui.averageProfitPanel.removeFirstMarkers(fNbRemovedMarkers); gui.actionPanel.updateActionFrequencies(fActionFreqs); } }); plotTimes.add(currentGameStats); } private int removeFromWindow() { Iterator<GameStats> iter = bankrollWindow.iterator(); boolean done = false; int nbRemoved = 0; long currentTime = System.currentTimeMillis(); while (!done && iter.hasNext()) { GameStats first = iter.next(); if (first.time < Math.min(lastSubmitTime, currentTime - minBankrollWindow)) { nbRemoved++; undoGameStats(first); iter.remove(); } else { done = true; } } return nbRemoved; } private int removeDatapoints() { Iterator<GameStats> iter = plotTimes.iterator(); boolean done = false; int nbRemoved = 0; long currentTime = System.currentTimeMillis(); while (!done && iter.hasNext()) { GameStats first = iter.next(); if (first.time < currentTime - plotWindow) { nbRemoved++; iter.remove(); } else { done = true; } } return nbRemoved; } private int removeMarkers() { Iterator<Long> iter = markerTimes.iterator(); boolean done = false; int nbRemoved = 0; long currentTime = System.currentTimeMillis(); while (!done && iter.hasNext()) { long first = iter.next(); if (first < currentTime - plotWindow) { nbRemoved++; iter.remove(); } else { done = true; } } return nbRemoved; } private ConcurrentHashMap<String, Double> getAvgProfit() { ConcurrentHashMap<String, Double> avgProfit = new ConcurrentHashMap<String, Double>(); for (String player : playerNames) { double currentProfit = currentTotalProfit.get(player); avgProfit.put(player, currentProfit / nbGamesInTotalProfit); } return avgProfit; } private void addGameStats(final GameStats stats) { nbGamesInTotalProfit++; for (String player : playerNames) { //profit double currentProfit = currentTotalProfit.get(player); currentTotalProfit.put(player, currentProfit + stats.bankrollDeltas.get(player)); //actions int[] prevFreq = currentActionFreqs.get(player); int[] statFreq = stats.actionFreqs.get(player); for (int i = 0; i < statFreq.length; i++) { prevFreq[i] += statFreq[i]; } } } private void undoGameStats(final GameStats stats) { nbGamesInTotalProfit--; for (String player : playerNames) { //profit double currentProfit = currentTotalProfit.get(player); currentTotalProfit.put(player, currentProfit - stats.bankrollDeltas.get(player)); //actions int[] prevFreq = currentActionFreqs.get(player); int[] statFreq = stats.actionFreqs.get(player); for (int i = 0; i < statFreq.length; i++) { prevFreq[i] -= statFreq[i]; } } } public synchronized float getGamesPerSecond() { int nbGames = getCurrentBankrollGameWindow(); long time = getCurrentBankrollTimeWindow(); return (float) (nbGames / (time / 1000.0)); } public synchronized int getCurrentBankrollGameWindow() { return bankrollWindow.getLast().game - bankrollWindow.getFirst().game; } public synchronized long getCurrentBankrollTimeWindow() { return bankrollWindow.getLast().time - bankrollWindow.getFirst().time; } private GameStats currentGameStats; @Override public void actionEvent(int pos, Action act) { String player = gameInfo.getPlayerName(pos); int[] freq = currentGameStats.actionFreqs.get(player); switch (act.getType()) { case Action.SMALL_BLIND: break; case Action.BIG_BLIND: break; case Action.CALL: freq[3]++; break; case Action.RAISE: freq[4]++; break; case Action.BET: freq[1]++; break; case Action.CHECK: freq[0]++; break; case Action.FOLD: freq[2]++; break; case Action.MUCK: break; } } @Override public void dealHoleCardsEvent() { } @Override public void gameOverEvent() { } @Override public void gameStartEvent(GameInfo gameInfo) { this.gameInfo = gameInfo; currentGameStats = new GameStats(); for (String player : playerNames) { currentGameStats.actionFreqs.put(player, new int[5]); } } @Override public void gameStateChanged() { } @Override public void showdownEvent(int arg0, Card arg1, Card arg2) { } @Override public void stageEvent(int arg0) { } @Override public void winEvent(int arg0, double arg1, String arg2) { } } final class GameStats { long time; int game; Map<String, Double> bankrollDeltas; final Map<String, int[]> actionFreqs = new HashMap<String, int[]>(); public GameStats() { } }