package ww10; import game.PublicGameInfo; import game.stats.BankrollObserver; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import bots.prologbot.PrologBot; import com.biotools.meerkat.Action; import com.biotools.meerkat.Card; import com.biotools.meerkat.GameInfo; import com.biotools.meerkat.GameObserver; public class DataModel implements BankrollObserver, GameObserver { private final ReentrantLock pauseLock = new ReentrantLock(); private List<String> playerNames; private volatile int currentGamesPlayed; //Bidirectional map private HashMap<String, String> chosenNames = new HashMap<String, String>(); private HashMap<String, String> fixedNames = new HashMap<String, String>(); private final LinkedList<GameStats> bankrollWindow = new LinkedList<GameStats>(); //private final LinkedList<GameStats> plotTimes = new LinkedList<GameStats>(); private final LinkedList<MarkerStats> markerTimes = new LinkedList<MarkerStats>(); private int nbGamesInTotalProfit = 0; private final Map<String, Double> currentTotalProfit = new HashMap<String, Double>(); private final Map<String, Long> currentRuntime = new HashMap<String, Long>(); private final long plotWindow = 180 * 1000; private final long minBankrollWindow = 60*1000; //60s private final int maxBankRollSize = 10000; private long lastSubmitTime = System.currentTimeMillis(); private final HashMap<String, int[]> currentActionFreqs = new HashMap<String, int[]>(); private final HashMap<String, HashMap<String, Integer>> currentRulesUsed = new HashMap<String, HashMap<String, Integer>>(); private final HashMap<String, Long> lastSubmits = new HashMap<String, Long>(); private GameInfo gameInfo; private boolean paused = false; private boolean terminated = false; public DataModel() { } public synchronized List<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 String getFixedName(String chosenName){ return fixedNames.get(chosenName); } public synchronized int getNbFolds(String player){ return currentActionFreqs.get(player)[0]; } public synchronized int getNbCalls(String player){ return currentActionFreqs.get(player)[1]; } public synchronized int getNbRaises(String player){ return currentActionFreqs.get(player)[2]; } public synchronized long getRunTime(String player) { return currentRuntime.get(player); } public synchronized HashMap<String, Integer> getRulesUsed(String player){ return currentRulesUsed.get(player); } public synchronized double getAvgProfit(String player){ return getAvgProfit().get(player); } public synchronized int getLastSubmit(String player){ return (int) (System.currentTimeMillis() - lastSubmits.get(player)); } public synchronized int getLastSubmit() { return (int) (System.currentTimeMillis() - lastSubmitTime); } public synchronized void refreshLastSubmit() { lastSubmitTime = System.currentTimeMillis(); } private int[] markerLevelUse = new int[20]; public synchronized void onSubmit(final String fixedName, final String chosenName) { String oldName = chosenNames.get(fixedName); if(oldName != null && !oldName.equals(chosenName)){ fixedNames.remove(oldName); } chosenNames.put(fixedName, chosenName); fixedNames.put(chosenName, fixedName); lastSubmitTime = System.currentTimeMillis(); lastSubmits.put(fixedName, lastSubmitTime); MarkerStats stats = new MarkerStats(lastSubmitTime, getAvailableMarkerLevel()); markerTimes.add(stats); } private int getAvailableMarkerLevel() { int minValue = markerLevelUse[0]; int minIndex = 0; for (int i = 1; i < markerLevelUse.length; i++) { if (markerLevelUse[i] < minValue) { minValue = markerLevelUse[i]; minIndex = i; } } markerLevelUse[minIndex]++; return minIndex; } @Override public synchronized void gameStarted(int numSeatPermutations, int numGames, Set<String> playerNames) { this.playerNames = Arrays.asList(playerNames.toArray(new String[playerNames.size()])); Collections.sort(this.playerNames, new Comparator<String>() { @Override public int compare(String o1, String o2) { try { int id1 = Integer.parseInt(o1.replaceAll("#", "")); int id2 = Integer.parseInt(o2.replaceAll("#", "")); return id1 - id2; } catch (NumberFormatException e) { return o1.compareTo(o2); } } }); lastSubmitTime = System.currentTimeMillis(); for (String player : playerNames) { currentTotalProfit.put(player, 0.0); currentActionFreqs.put(player, new int[3]); HashMap<String, Integer> rules = new HashMap<String, Integer>(); rules.put("Geen", 0); currentRulesUsed.put(player, rules); currentRuntime.put(player, 0L); lastSubmits.put(player, System.currentTimeMillis()); } int framerate = 10; int delay = 1000 / framerate; int interval = 1000 / framerate; final Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { public void run() { //TODO if table is deleted, terminate timer if(terminated) { timer.cancel(); } pauseLock.lock(); pauseLock.unlock(); // System.out.println("UPDATE PLOT"); if(!paused) { updatePlot(); } } }, delay, interval); } public synchronized void gamePaused(){ paused = true; } public synchronized void gameResumed(){ paused = false; } public synchronized void gameTerminated(){ terminated = true; } /** * First try pause lock, then synchronize on this. */ @Override public void updateBankroll(int seatpermutation, final Map<String, Double> playerDelta) { pauseLock.lock(); pauseLock.unlock(); synchronized (this) { currentGamesPlayed++; //update stats currentGameStats.time = System.currentTimeMillis(); currentGameStats.game = currentGamesPlayed; currentGameStats.bankrollDeltas = playerDelta; addGameStats(currentGameStats); bankrollWindow.add(currentGameStats); // if (currentGamesPlayed % 1 == 0) { // updatePlot(); // } } } public synchronized void updatePlot() { //remove old stats // update bankroll removeFromWindow(); // update plot // TODO: Are these two useful? // removeDatapoints(); removeMarkers(); //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) || bankrollWindow.size() > maxBankRollSize) { 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<MarkerStats> iter = markerTimes.iterator(); boolean done = false; int nbRemoved = 0; long currentTime = System.currentTimeMillis(); while (!done && iter.hasNext()) { MarkerStats first = iter.next(); if (first.time < currentTime - plotWindow) { nbRemoved++; markerLevelUse[first.level]--; iter.remove(); } else { done = true; } } return nbRemoved; } private ConcurrentHashMap<String, Double> getAvgProfit() { if (nbGamesInTotalProfit == 0) nbGamesInTotalProfit++; 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]; } //rules HashMap<String, Integer> prevRules = currentRulesUsed.get(player); HashMap<String, Integer> statsRules = stats.ruleCounts.get(player); for (Entry<String, Integer> e : statsRules.entrySet()) { Integer count = prevRules.get(e.getKey()); if (count == null) { count = 0; } prevRules.put(e.getKey(), count + e.getValue()); } // runtime long prevRuntime = currentRuntime.get(player); long gameRuntime = stats.runtimes.get(player); currentRuntime.put(player, prevRuntime + gameRuntime); } } 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]; } //rules HashMap<String, Integer> prevRules = currentRulesUsed.get(player); HashMap<String, Integer> statsRules = stats.ruleCounts.get(player); for (Entry<String, Integer> e : statsRules.entrySet()) { Integer count = prevRules.get(e.getKey()); if (count == null) { count = 0; } prevRules.put(e.getKey(), count - e.getValue()); } // runtime long prevRuntime = currentRuntime.get(player); long gameRuntime = stats.runtimes.get(player); currentRuntime.put(player, prevRuntime - gameRuntime); } } public synchronized float getGamesPerSecond() { int nbGames = getCurrentBankrollGameWindow(); long time = getCurrentBankrollTimeWindow(); if (time == 0) return 0; return (float) (nbGames / (time / 1000.0)); } public synchronized int getCurrentBankrollGameWindow() { if (bankrollWindow.isEmpty()) return 0; return bankrollWindow.getLast().game - bankrollWindow.getFirst().game; } public synchronized long getCurrentBankrollTimeWindow() { if (bankrollWindow.isEmpty()) return 0; 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[1]++; updateRuleEvalStats(player); break; case Action.RAISE: freq[2]++; updateRuleEvalStats(player); break; case Action.BET: freq[2]++; updateRuleEvalStats(player); break; case Action.CHECK: freq[1]++; updateRuleEvalStats(player); break; case Action.FOLD: freq[0]++; updateRuleEvalStats(player); break; case Action.MUCK: break; } } private void updateRuleEvalStats(String player) { PublicGameInfo publicGameInfo = (PublicGameInfo) gameInfo; PrologBot bot = (PrologBot) (publicGameInfo.getPlayer(player).getBot()); // rules String lur = bot.getLastUsedRule(); if (lur == null) { throw new IllegalStateException(); } // System.out.println(player + " used rule " + lur); HashMap<String, Integer> ruleCounts = currentGameStats.ruleCounts.get(player); Integer count = ruleCounts.get(lur); if (count == null) { count = 0; } ruleCounts.put(lur, count + 1); // runtime long actionRuntime = bot.getLastRuntime(); Long prevRuntime = currentGameStats.runtimes.get(player); currentGameStats.runtimes.put(player, prevRuntime + actionRuntime); } @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[3]); HashMap<String, Integer> rules = new HashMap<String, Integer>(); rules.put("Geen", 0); currentGameStats.ruleCounts.put(player, rules); currentGameStats.runtimes.put(player, 0L); } } @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) { } public void togglePaused() { if (pauseLock.isHeldByCurrentThread()) { pauseLock.unlock(); } else { pauseLock.lock(); } } } final class GameStats { long time; int game; Map<String, Double> bankrollDeltas; final HashMap<String, int[]> actionFreqs = new HashMap<String, int[]>(); final HashMap<String, HashMap<String, Integer>> ruleCounts = new HashMap<String, HashMap<String, Integer>>(); final HashMap<String, Long> runtimes = new HashMap<String, Long>(); public GameStats() { } } final class MarkerStats { final long time; final int level; public MarkerStats(long time, int level) { this.time = time; this.level = level; } }