// Analyze.java package net.sf.gogui.tools.twogtp; import java.io.File; import java.io.PrintStream; import java.text.NumberFormat; import java.util.ArrayList; import net.sf.gogui.util.ErrorMessage; import net.sf.gogui.util.FileUtil; import net.sf.gogui.util.Histogram; import net.sf.gogui.util.HtmlUtil; import net.sf.gogui.util.Statistics; import net.sf.gogui.util.StringUtil; import net.sf.gogui.util.Table; /** Analyze the game results and produce a HTML formatted report. */ public class Analyze { public Analyze(String filename, boolean force) throws Exception { File file = new File(filename); readTable(file); File htmlFile = new File(FileUtil.replaceExtension(file, "dat", "html")); File dataFile = new File(FileUtil.replaceExtension(file, "dat", "summary.dat")); if (! force) { if (htmlFile.exists()) throw new ErrorMessage("File " + htmlFile + " exists"); if (dataFile.exists()) throw new ErrorMessage("File " + dataFile + " exists"); } calcStatistics(); writeHtml(htmlFile); writeData(dataFile); } private static final class ResultStatistics { public Statistics m_unknownResult = new Statistics(); public Statistics m_unknownScore = new Statistics(); public Statistics m_win = new Statistics(); public Histogram m_histo = new Histogram(-1000, 1000, 10); } private int m_duplicates; private int m_errors; private int m_games; private int m_gamesUsed; private static final String COLOR_HEADER = "#91aee8"; private static final String COLOR_INFO = "#e0e0e0"; private final ArrayList<Entry> m_entries = new ArrayList<Entry>(128); private final Statistics m_length = new Statistics(); private final ResultStatistics m_statisticsBlack = new ResultStatistics(); private final ResultStatistics m_statisticsReferee = new ResultStatistics(); private final ResultStatistics m_statisticsWhite = new ResultStatistics(); private final Statistics m_cpuBlack = new Statistics(); private final Statistics m_cpuWhite = new Statistics(); private final Statistics m_timeBlack = new Statistics(); private final Statistics m_timeWhite = new Statistics(); private Table m_table; private void calcStatistics() { for (Entry e : m_entries) { ++m_games; if (e.m_error) { ++m_errors; continue; } if (! e.m_duplicate.equals("") && ! e.m_duplicate.equals("-")) { ++m_duplicates; continue; } ++m_gamesUsed; parseResult(e.m_resultBlack, m_statisticsBlack); parseResult(e.m_resultWhite, m_statisticsWhite); parseResult(e.m_resultReferee, m_statisticsReferee); m_timeBlack.add(e.m_timeBlack); m_timeWhite.add(e.m_timeWhite); m_cpuBlack.add(e.m_cpuBlack); m_cpuWhite.add(e.m_cpuWhite); m_length.add(e.m_length); } } private void parseResult(String result, ResultStatistics statistics) { boolean hasResult = false; boolean hasScore = false; boolean win = false; double score = 0f; String s = result.trim(); try { if (! s.equals("?")) { if (s.startsWith("B+")) { hasResult = true; win = true; String scoreString = s.substring(2); if (! scoreString.equals("") && ! scoreString.equals("R")) { score = Double.parseDouble(scoreString); hasScore = true; } } else if (s.startsWith("W+")) { hasResult = true; win = false; String scoreString = s.substring(2); if (! scoreString.equals("") && ! scoreString.equals("R")) { score = -Double.parseDouble(scoreString); hasScore = true; } } else if (! s.equals("")) System.err.println("Ignored invalid result: " + result); } } catch (NumberFormatException e) { System.err.println("Ignored invalid score: " + result); } if (hasScore && (score < statistics.m_histo.getHistoMin() || score > statistics.m_histo.getHistoMax())) { System.err.println("Ignored invalid score: " + result); hasScore = false; } statistics.m_unknownResult.add(hasResult ? 0 : 1); if (hasResult) statistics.m_win.add(win ? 1 : 0); statistics.m_unknownScore.add(hasScore ? 0 : 1); if (hasScore) statistics.m_histo.add(score); } private void readTable(File file) throws Exception { m_table = new Table(); m_table.read(file); try { for (int i = 0; i < m_table.getNumberRows(); ++i) { int gameIndex = m_table.getInt("GAME", i); String resultBlack = m_table.get("RES_B", i); String resultWhite = m_table.get("RES_W", i); String resultReferee = m_table.get("RES_R", i); boolean alternated = (m_table.getInt("ALT", i) != 0); String duplicate = m_table.get("DUP", i); int length = m_table.getInt("LEN", i); double timeBlack = 0; double timeWhite = 0; try { timeBlack = m_table.getDouble("TIME_B", i); timeWhite = m_table.getDouble("TIME_W", i); } catch (Table.InvalidLocation e) { // twogtp versions before 1.1pre2 did not save TIME_B, // TIME_W, we still support analyzing such old tables for // a while } double cpuBlack = m_table.getDouble("CPU_B", i); double cpuWhite = m_table.getDouble("CPU_W", i); boolean error = (m_table.getInt("ERR", i) != 0); String errorMessage = m_table.get("ERR_MSG", i); m_entries.add(new Entry(gameIndex, resultBlack, resultWhite, resultReferee, alternated, duplicate, length, timeBlack, timeWhite, cpuBlack, cpuWhite, error, errorMessage)); } } catch (NumberFormatException e) { throw new ErrorMessage("Wrong file format"); } } private void writeHtml(File file) throws Exception { String gamePrefix = "game"; if (file.getName().endsWith(".html")) { String name = file.getName(); gamePrefix = name.substring(0, name.length() - 5); } PrintStream out = new PrintStream(file); NumberFormat format = StringUtil.getNumberFormat(1); String black; if (m_table.hasProperty("BlackLabel")) black = m_table.getProperty("BlackLabel"); else if (m_table.hasProperty("Black")) // Older versions of TwoGtp do not have BlackLabel property black = m_table.getProperty("Black"); else black = "Black"; String white; if (m_table.hasProperty("WhiteLabel")) white = m_table.getProperty("WhiteLabel"); else if (m_table.hasProperty("White")) // Older versions of TwoGtp do not have WhiteLabel property white = m_table.getProperty("White"); else white = "Black"; boolean useXml = (! m_table.getProperty("Xml", "0").equals("0")); out.print("<html>\n" + "<head>\n" + "<title>" + black + " - " + white + "</title>\n" + HtmlUtil.getMeta("TwoGtp") + "<style type=\"text/css\">\n" + "<!--\n" + "body { margin:0; }\n" + "-->\n" + "</style>\n" + "</head>\n" + "<body bgcolor=\"white\" text=\"black\" link=\"#0000ee\"" + " vlink=\"#551a8b\">\n" + "<table border=\"0\" width=\"100%\" bgcolor=\"" + COLOR_HEADER + "\">\n" + "<tr><td>\n" + "<h1>" + black + " - " + white + "</h1>\n" + "</td></tr>\n" + "</table>\n" + "<table width=\"100%\" bgcolor=\"" + COLOR_INFO + "\">\n"); String referee = m_table.getProperty("Referee", ""); if (referee.equals("-") || referee.equals("")) referee = null; writePropertyHtmlRow(out, "Black", "Black"); writePropertyHtmlRow(out, "White", "White"); writePropertyHtmlRow(out, "Size", "Size"); writePropertyHtmlRow(out, "Komi", "Komi"); if (m_table.hasProperty("Openings")) writePropertyHtmlRow(out, "Openings", "Openings"); writePropertyHtmlRow(out, "Date", "Date"); writePropertyHtmlRow(out, "Host", "Host"); writePropertyHtmlRow(out, "Referee", "Referee"); writePropertyHtmlRow(out, "BlackVersion", "Black version"); writePropertyHtmlRow(out, "BlackCommand", "Black command"); writePropertyHtmlRow(out, "WhiteVersion", "White version"); writePropertyHtmlRow(out, "WhiteCommand", "White command"); if (referee != null) { writePropertyHtmlRow(out, "RefereeVersion", "Referee version"); writePropertyHtmlRow(out, "RefereeCommand", "Referee command"); } writeHtmlRow(out, "Games", m_games); writeHtmlRow(out, "Errors", m_errors); writeHtmlRow(out, "Duplicates", m_duplicates); writeHtmlRow(out, "Games used", m_gamesUsed); writeHtmlRow(out, "Game length", m_length, format); writeHtmlRow(out, "Time Black", m_timeBlack, format); writeHtmlRow(out, "Time White", m_timeWhite, format); writeHtmlRow(out, "CPU Time Black", m_cpuBlack, format); writeHtmlRow(out, "CPU Time White", m_cpuWhite, format); out.print("</table>\n" + "<hr>\n"); if (referee != null) { writeHtmlResults(out, referee, m_statisticsReferee); out.println("<hr>"); } writeHtmlResults(out, black, m_statisticsBlack); out.println("<hr>"); writeHtmlResults(out, white, m_statisticsWhite); out.println("<hr>"); out.print("<table border=\"0\" width=\"100%\" cellpadding=\"0\"" + " cellspacing=\"1\">\n" + "<thead>\n" + "<tr bgcolor=\"" + COLOR_HEADER + "\">\n" + "<th>Game</th>\n"); if (referee != null) out.print("<th>Result " + referee + "</th>\n"); out.print("<th>Result " + black + "</th>\n" + "<th>Result " + white + "</th>\n"); out.print("<th>Colors Exchanged</th>\n" + "<th>Duplicate</th>\n" + "<th>Length</th>\n" + "<th>Time " + black + "</th>\n" + "<th>Time " + white + "</th>\n" + "<th>CPU Time " + black + "</th>\n" + "<th>CPU Time " + white + "</th>\n" + "<th>Error</th>\n" + "<th>Error Message</th>\n" + "</tr>\n" + "</thead>\n"); String gameSuffix = (useXml ? ".xml" : ".sgf"); for (Entry e : m_entries) { String name = gamePrefix + "-" + e.m_gameIndex + gameSuffix; out.print("<tr align=\"center\" bgcolor=\"" + COLOR_INFO + "\"><td><a href=\"" + name + "\">" + name + "</a></td>\n"); if (referee != null) out.print("<td>" + e.m_resultReferee + "</td>"); out.print("<td>" + e.m_resultBlack + "</td>" + "<td>" + e.m_resultWhite + "</td>"); out.print("<td>" + (e.m_alternated ? "1" : "0") + "</td>" + "<td>" + e.m_duplicate + "</td>" + "<td>" + e.m_length + "</td>" + "<td>" + e.m_timeBlack + "</td>" + "<td>" + e.m_timeWhite + "</td>" + "<td>" + e.m_cpuBlack + "</td>" + "<td>" + e.m_cpuWhite + "</td>" + "<td>" + (e.m_error ? "1" : "") + "</td>" + "<td>" + e.m_errorMessage + "</td>" + "</tr>\n"); } out.print("</table>\n" + HtmlUtil.getFooter("TwoGtp") + "</body>\n" + "</html>\n"); out.close(); } private void writeHtmlResults(PrintStream out, String name, ResultStatistics statistics) throws Exception { NumberFormat format = StringUtil.getNumberFormat(1); out.print("<div style=\"margin:1em\">\n" + "<h2>Result " + name + "</h2>\n" + "<p>\n" + "<table border=\"0\">\n"); if (statistics.m_histo.getCount() > 0) writeHtmlRow(out, "Black score", statistics.m_histo, format); if (statistics.m_win.getCount() > 0) writeHtmlRowPercentData(out, "Black wins", statistics.m_win, format); out.print("<tr><th align=\"left\">Unknown result" + ":</th><td align=\"left\">" + format.format(statistics.m_unknownResult.getMean() * 100) + "%" + "</td></tr>\n" + "<tr><th align=\"left\">Unknown score" + ":</th><td align=\"left\">" + format.format(statistics.m_unknownScore.getMean() * 100) + "%" + "</td></tr>\n" + "</table>\n" + "</p>\n"); statistics.m_histo.printHtml(out); out.print("</div>\n"); } private void writePropertyHtmlRow(PrintStream out, String key, String keyLabel) throws Exception { String value = m_table.getProperty(key, ""); writeHtmlRow(out, keyLabel, value); } private void writeHtmlRow(PrintStream out, String label, String value) throws Exception { out.print("<tr><th align=\"left\" valign=\"top\" nowrap>" + label + ":</th><td align=\"left\">" + value + "</td></tr>\n"); } private void writeHtmlRow(PrintStream out, String label, int value) throws Exception { writeHtmlRow(out, label, Integer.toString(value)); } private void writeHtmlRow(PrintStream out, String label, Statistics statistics, NumberFormat format) throws Exception { String value; if (statistics.getCount() == 0) value = ""; else value = format.format(statistics.getMean()) + " (±" + format.format(statistics.getError()) + ") <small>min=" + format.format(statistics.getMin()) + " max=" + format.format(statistics.getMax()) + " deviation=" + format.format(statistics.getDeviation()) + "</small>"; writeHtmlRow(out, label, value); } private void writeHtmlRowPercentData(PrintStream out, String label, Statistics statistics, NumberFormat format) throws Exception { String value; if (statistics.getCount() == 0) value = ""; else value = format.format(statistics.getMean() * 100) + "% (±" + format.format(statistics.getError() * 100) + ")"; writeHtmlRow(out, label, value); } private void writeData(File file) throws Exception { PrintStream out = new PrintStream(file); NumberFormat format1 = StringUtil.getNumberFormat(1); NumberFormat format2 = StringUtil.getNumberFormat(3); Histogram histoBlack = m_statisticsBlack.m_histo; Histogram histoWhite = m_statisticsWhite.m_histo; Histogram histoReferee = m_statisticsReferee.m_histo; Statistics winBlack = m_statisticsBlack.m_win; Statistics winWhite = m_statisticsWhite.m_win; Statistics winReferee = m_statisticsReferee.m_win; Statistics unknownBlack = m_statisticsBlack.m_unknownScore; Statistics unknownWhite = m_statisticsWhite.m_unknownScore; Statistics unknownReferee = m_statisticsReferee.m_unknownScore; out.print("#GAMES\tERR\tDUP\tUSED\tRES_B\tERR_B\tWIN_B\tERRW_B\t" + "UNKN_B\tRES_W\tERR_W\tWIN_W\tERRW_W\tUNKN_W\t" + "RES_R\tERR_R\tWIN_R\tERRW_R\tUNKN_R\n" + m_games + "\t" + m_errors + "\t" + m_duplicates + "\t" + m_gamesUsed + "\t" + format1.format(histoBlack.getMean()) + "\t" + format1.format(histoBlack.getError()) + "\t" + format2.format(winBlack.getMean()) + "\t" + format2.format(winBlack.getError()) + "\t" + format2.format(unknownBlack.getMean()) + "\t" + format1.format(histoWhite.getMean()) + "\t" + format1.format(histoWhite.getError()) + "\t" + format2.format(winWhite.getMean()) + "\t" + format2.format(winWhite.getError()) + "\t" + format2.format(unknownWhite.getMean()) + "\t" + format1.format(histoReferee.getMean()) + "\t" + format1.format(histoReferee.getError()) + "\t" + format2.format(winReferee.getMean()) + "\t" + format2.format(winReferee.getError()) + "\t" + format2.format(unknownReferee.getMean()) + "\n"); out.close(); } } final class Entry { public int m_gameIndex; public String m_resultBlack; public String m_resultReferee; public String m_resultWhite; public boolean m_alternated; public String m_duplicate; public int m_length; public double m_timeBlack; public double m_timeWhite; public double m_cpuBlack; public double m_cpuWhite; public boolean m_error; public String m_errorMessage; public Entry(int gameIndex, String resultBlack, String resultWhite, String resultReferee, boolean alternated, String duplicate, int length, double timeBlack, double timeWhite, double cpuBlack, double cpuWhite, boolean error, String errorMessage) { m_gameIndex = gameIndex; m_resultBlack = resultBlack; m_resultWhite = resultWhite; m_resultReferee = resultReferee; m_alternated = alternated; m_duplicate = (duplicate.equals("-") ? "" : duplicate); m_length = length; m_timeBlack = timeBlack; m_timeWhite = timeWhite; m_cpuBlack = cpuBlack; m_cpuWhite = cpuWhite; m_error = error; m_errorMessage = errorMessage; if (m_errorMessage == null) m_errorMessage = ""; } }