1 // Analyze.java 2 3 package net.sf.gogui.tools.twogtp; 4 5 import java.io.File; 6 import java.io.PrintStream; 7 import java.text.NumberFormat; 8 import java.util.ArrayList; 9 import net.sf.gogui.util.ErrorMessage; 10 import net.sf.gogui.util.FileUtil; 11 import net.sf.gogui.util.Histogram; 12 import net.sf.gogui.util.HtmlUtil; 13 import net.sf.gogui.util.Statistics; 14 import net.sf.gogui.util.StringUtil; 15 import net.sf.gogui.util.Table; 16 17 /** Analyze the game results and produce a HTML formatted report. */ 18 public class Analyze 19 { Analyze(String filename, boolean force)20 public Analyze(String filename, boolean force) throws Exception 21 { 22 File file = new File(filename); 23 readTable(file); 24 File htmlFile = 25 new File(FileUtil.replaceExtension(file, "dat", "html")); 26 File dataFile = 27 new File(FileUtil.replaceExtension(file, "dat", "summary.dat")); 28 if (! force) 29 { 30 if (htmlFile.exists()) 31 throw new ErrorMessage("File " + htmlFile + " exists"); 32 if (dataFile.exists()) 33 throw new ErrorMessage("File " + dataFile + " exists"); 34 } 35 calcStatistics(); 36 writeHtml(htmlFile); 37 writeData(dataFile); 38 } 39 40 private static final class ResultStatistics 41 { 42 public Statistics m_unknownResult = new Statistics(); 43 44 public Statistics m_unknownScore = new Statistics(); 45 46 public Statistics m_win = new Statistics(); 47 48 public Histogram m_histo = new Histogram(-1000, 1000, 10); 49 } 50 51 private int m_duplicates; 52 53 private int m_errors; 54 55 private int m_games; 56 57 private int m_gamesUsed; 58 59 private static final String COLOR_HEADER = "#91aee8"; 60 61 private static final String COLOR_INFO = "#e0e0e0"; 62 63 private final ArrayList<Entry> m_entries = new ArrayList<Entry>(128); 64 65 private final Statistics m_length = new Statistics(); 66 67 private final ResultStatistics m_statisticsBlack = new ResultStatistics(); 68 69 private final ResultStatistics m_statisticsReferee 70 = new ResultStatistics(); 71 72 private final ResultStatistics m_statisticsWhite = new ResultStatistics(); 73 74 private final Statistics m_cpuBlack = new Statistics(); 75 76 private final Statistics m_cpuWhite = new Statistics(); 77 78 private final Statistics m_timeBlack = new Statistics(); 79 80 private final Statistics m_timeWhite = new Statistics(); 81 82 private Table m_table; 83 calcStatistics()84 private void calcStatistics() 85 { 86 for (Entry e : m_entries) 87 { 88 ++m_games; 89 if (e.m_error) 90 { 91 ++m_errors; 92 continue; 93 } 94 if (! e.m_duplicate.equals("") && ! e.m_duplicate.equals("-")) 95 { 96 ++m_duplicates; 97 continue; 98 } 99 ++m_gamesUsed; 100 parseResult(e.m_resultBlack, m_statisticsBlack); 101 parseResult(e.m_resultWhite, m_statisticsWhite); 102 parseResult(e.m_resultReferee, m_statisticsReferee); 103 m_timeBlack.add(e.m_timeBlack); 104 m_timeWhite.add(e.m_timeWhite); 105 m_cpuBlack.add(e.m_cpuBlack); 106 m_cpuWhite.add(e.m_cpuWhite); 107 m_length.add(e.m_length); 108 } 109 } 110 parseResult(String result, ResultStatistics statistics)111 private void parseResult(String result, ResultStatistics statistics) 112 { 113 boolean hasResult = false; 114 boolean hasScore = false; 115 boolean win = false; 116 double score = 0f; 117 String s = result.trim(); 118 try 119 { 120 if (! s.equals("?")) 121 { 122 if (s.startsWith("B+")) 123 { 124 hasResult = true; 125 win = true; 126 String scoreString = s.substring(2); 127 if (! scoreString.equals("") && ! scoreString.equals("R")) 128 { 129 score = Double.parseDouble(scoreString); 130 hasScore = true; 131 } 132 } 133 else if (s.startsWith("W+")) 134 { 135 hasResult = true; 136 win = false; 137 String scoreString = s.substring(2); 138 if (! scoreString.equals("") && ! scoreString.equals("R")) 139 { 140 score = -Double.parseDouble(scoreString); 141 hasScore = true; 142 } 143 } 144 else if (! s.equals("")) 145 System.err.println("Ignored invalid result: " + result); 146 } 147 } 148 catch (NumberFormatException e) 149 { 150 System.err.println("Ignored invalid score: " + result); 151 } 152 if (hasScore && 153 (score < statistics.m_histo.getHistoMin() 154 || score > statistics.m_histo.getHistoMax())) 155 { 156 System.err.println("Ignored invalid score: " + result); 157 hasScore = false; 158 } 159 statistics.m_unknownResult.add(hasResult ? 0 : 1); 160 if (hasResult) 161 statistics.m_win.add(win ? 1 : 0); 162 statistics.m_unknownScore.add(hasScore ? 0 : 1); 163 if (hasScore) 164 statistics.m_histo.add(score); 165 } 166 readTable(File file)167 private void readTable(File file) throws Exception 168 { 169 m_table = new Table(); 170 m_table.read(file); 171 try 172 { 173 for (int i = 0; i < m_table.getNumberRows(); ++i) 174 { 175 int gameIndex = m_table.getInt("GAME", i); 176 String resultBlack = m_table.get("RES_B", i); 177 String resultWhite = m_table.get("RES_W", i); 178 String resultReferee = m_table.get("RES_R", i); 179 boolean alternated = (m_table.getInt("ALT", i) != 0); 180 String duplicate = m_table.get("DUP", i); 181 int length = m_table.getInt("LEN", i); 182 double timeBlack = 0; 183 double timeWhite = 0; 184 try 185 { 186 timeBlack = m_table.getDouble("TIME_B", i); 187 timeWhite = m_table.getDouble("TIME_W", i); 188 } 189 catch (Table.InvalidLocation e) 190 { 191 // twogtp versions before 1.1pre2 did not save TIME_B, 192 // TIME_W, we still support analyzing such old tables for 193 // a while 194 } 195 double cpuBlack = m_table.getDouble("CPU_B", i); 196 double cpuWhite = m_table.getDouble("CPU_W", i); 197 boolean error = (m_table.getInt("ERR", i) != 0); 198 String errorMessage = m_table.get("ERR_MSG", i); 199 m_entries.add(new Entry(gameIndex, resultBlack, resultWhite, 200 resultReferee, alternated, duplicate, 201 length, timeBlack, timeWhite, 202 cpuBlack, cpuWhite, error, 203 errorMessage)); 204 } 205 } 206 catch (NumberFormatException e) 207 { 208 throw new ErrorMessage("Wrong file format"); 209 } 210 } 211 writeHtml(File file)212 private void writeHtml(File file) throws Exception 213 { 214 String gamePrefix = "game"; 215 if (file.getName().endsWith(".html")) 216 { 217 String name = file.getName(); 218 gamePrefix = name.substring(0, name.length() - 5); 219 } 220 PrintStream out = new PrintStream(file); 221 NumberFormat format = StringUtil.getNumberFormat(1); 222 String black; 223 if (m_table.hasProperty("BlackLabel")) 224 black = m_table.getProperty("BlackLabel"); 225 else if (m_table.hasProperty("Black")) 226 // Older versions of TwoGtp do not have BlackLabel property 227 black = m_table.getProperty("Black"); 228 else 229 black = "Black"; 230 String white; 231 if (m_table.hasProperty("WhiteLabel")) 232 white = m_table.getProperty("WhiteLabel"); 233 else if (m_table.hasProperty("White")) 234 // Older versions of TwoGtp do not have WhiteLabel property 235 white = m_table.getProperty("White"); 236 else 237 white = "Black"; 238 boolean useXml = (! m_table.getProperty("Xml", "0").equals("0")); 239 out.print("<html>\n" + 240 "<head>\n" + 241 "<title>" + black + " - " + white + "</title>\n" + 242 HtmlUtil.getMeta("TwoGtp") + 243 "<style type=\"text/css\">\n" + 244 "<!--\n" + 245 "body { margin:0; }\n" + 246 "-->\n" + 247 "</style>\n" + 248 "</head>\n" + 249 "<body bgcolor=\"white\" text=\"black\" link=\"#0000ee\"" + 250 " vlink=\"#551a8b\">\n" + 251 "<table border=\"0\" width=\"100%\" bgcolor=\"" 252 + COLOR_HEADER + "\">\n" + 253 "<tr><td>\n" + 254 "<h1>" + black + " - " + white 255 + "</h1>\n" + 256 "</td></tr>\n" + 257 "</table>\n" + 258 "<table width=\"100%\" bgcolor=\"" + COLOR_INFO 259 + "\">\n"); 260 String referee = m_table.getProperty("Referee", ""); 261 if (referee.equals("-") || referee.equals("")) 262 referee = null; 263 writePropertyHtmlRow(out, "Black", "Black"); 264 writePropertyHtmlRow(out, "White", "White"); 265 writePropertyHtmlRow(out, "Size", "Size"); 266 writePropertyHtmlRow(out, "Komi", "Komi"); 267 if (m_table.hasProperty("Openings")) 268 writePropertyHtmlRow(out, "Openings", "Openings"); 269 writePropertyHtmlRow(out, "Date", "Date"); 270 writePropertyHtmlRow(out, "Host", "Host"); 271 writePropertyHtmlRow(out, "Referee", "Referee"); 272 writePropertyHtmlRow(out, "BlackVersion", "Black version"); 273 writePropertyHtmlRow(out, "BlackCommand", "Black command"); 274 writePropertyHtmlRow(out, "WhiteVersion", "White version"); 275 writePropertyHtmlRow(out, "WhiteCommand", "White command"); 276 if (referee != null) 277 { 278 writePropertyHtmlRow(out, "RefereeVersion", "Referee version"); 279 writePropertyHtmlRow(out, "RefereeCommand", "Referee command"); 280 } 281 writeHtmlRow(out, "Games", m_games); 282 writeHtmlRow(out, "Errors", m_errors); 283 writeHtmlRow(out, "Duplicates", m_duplicates); 284 writeHtmlRow(out, "Games used", m_gamesUsed); 285 writeHtmlRow(out, "Game length", m_length, format); 286 writeHtmlRow(out, "Time Black", m_timeBlack, format); 287 writeHtmlRow(out, "Time White", m_timeWhite, format); 288 writeHtmlRow(out, "CPU Time Black", m_cpuBlack, format); 289 writeHtmlRow(out, "CPU Time White", m_cpuWhite, format); 290 out.print("</table>\n" + 291 "<hr>\n"); 292 if (referee != null) 293 { 294 writeHtmlResults(out, referee, m_statisticsReferee); 295 out.println("<hr>"); 296 } 297 writeHtmlResults(out, black, m_statisticsBlack); 298 out.println("<hr>"); 299 writeHtmlResults(out, white, m_statisticsWhite); 300 out.println("<hr>"); 301 out.print("<table border=\"0\" width=\"100%\" cellpadding=\"0\"" 302 + " cellspacing=\"1\">\n" + 303 "<thead>\n" + 304 "<tr bgcolor=\"" + COLOR_HEADER + "\">\n" + 305 "<th>Game</th>\n"); 306 if (referee != null) 307 out.print("<th>Result " + referee + "</th>\n"); 308 out.print("<th>Result " + black + "</th>\n" + 309 "<th>Result " + white + "</th>\n"); 310 out.print("<th>Colors Exchanged</th>\n" + 311 "<th>Duplicate</th>\n" + 312 "<th>Length</th>\n" + 313 "<th>Time " + black + "</th>\n" + 314 "<th>Time " + white + "</th>\n" + 315 "<th>CPU Time " + black + "</th>\n" + 316 "<th>CPU Time " + white + "</th>\n" + 317 "<th>Error</th>\n" + 318 "<th>Error Message</th>\n" + 319 "</tr>\n" + 320 "</thead>\n"); 321 String gameSuffix = (useXml ? ".xml" : ".sgf"); 322 for (Entry e : m_entries) 323 { 324 String name = gamePrefix + "-" + e.m_gameIndex + gameSuffix; 325 out.print("<tr align=\"center\" bgcolor=\"" + COLOR_INFO 326 + "\"><td><a href=\"" + name + "\">" + name 327 + "</a></td>\n"); 328 if (referee != null) 329 out.print("<td>" + e.m_resultReferee + "</td>"); 330 out.print("<td>" + e.m_resultBlack + "</td>" + 331 "<td>" + e.m_resultWhite + "</td>"); 332 out.print("<td>" + (e.m_alternated ? "1" : "0") + "</td>" + 333 "<td>" + e.m_duplicate + "</td>" + 334 "<td>" + e.m_length + "</td>" + 335 "<td>" + e.m_timeBlack + "</td>" + 336 "<td>" + e.m_timeWhite + "</td>" + 337 "<td>" + e.m_cpuBlack + "</td>" + 338 "<td>" + e.m_cpuWhite + "</td>" + 339 "<td>" + (e.m_error ? "1" : "") + "</td>" + 340 "<td>" + e.m_errorMessage + "</td>" + 341 "</tr>\n"); 342 } 343 out.print("</table>\n" + 344 HtmlUtil.getFooter("TwoGtp") + 345 "</body>\n" + 346 "</html>\n"); 347 out.close(); 348 } 349 writeHtmlResults(PrintStream out, String name, ResultStatistics statistics)350 private void writeHtmlResults(PrintStream out, String name, 351 ResultStatistics statistics) 352 throws Exception 353 { 354 NumberFormat format = StringUtil.getNumberFormat(1); 355 out.print("<div style=\"margin:1em\">\n" + 356 "<h2>Result " + name + "</h2>\n" + 357 "<p>\n" + 358 "<table border=\"0\">\n"); 359 if (statistics.m_histo.getCount() > 0) 360 writeHtmlRow(out, "Black score", statistics.m_histo, format); 361 if (statistics.m_win.getCount() > 0) 362 writeHtmlRowPercentData(out, "Black wins", statistics.m_win, 363 format); 364 out.print("<tr><th align=\"left\">Unknown result" 365 + ":</th><td align=\"left\">" 366 + format.format(statistics.m_unknownResult.getMean() * 100) 367 + "%" + "</td></tr>\n" + 368 "<tr><th align=\"left\">Unknown score" 369 + ":</th><td align=\"left\">" 370 + format.format(statistics.m_unknownScore.getMean() * 100) 371 + "%" + "</td></tr>\n" + 372 "</table>\n" + 373 "</p>\n"); 374 statistics.m_histo.printHtml(out); 375 out.print("</div>\n"); 376 } 377 writePropertyHtmlRow(PrintStream out, String key, String keyLabel)378 private void writePropertyHtmlRow(PrintStream out, String key, 379 String keyLabel) 380 throws Exception 381 { 382 String value = m_table.getProperty(key, ""); 383 writeHtmlRow(out, keyLabel, value); 384 } 385 writeHtmlRow(PrintStream out, String label, String value)386 private void writeHtmlRow(PrintStream out, String label, 387 String value) throws Exception 388 { 389 out.print("<tr><th align=\"left\" valign=\"top\" nowrap>" + label 390 + ":</th><td align=\"left\">" + value + "</td></tr>\n"); 391 } 392 writeHtmlRow(PrintStream out, String label, int value)393 private void writeHtmlRow(PrintStream out, String label, 394 int value) throws Exception 395 { 396 writeHtmlRow(out, label, Integer.toString(value)); 397 } 398 writeHtmlRow(PrintStream out, String label, Statistics statistics, NumberFormat format)399 private void writeHtmlRow(PrintStream out, String label, 400 Statistics statistics, 401 NumberFormat format) throws Exception 402 { 403 String value; 404 if (statistics.getCount() == 0) 405 value = ""; 406 else 407 value = 408 format.format(statistics.getMean()) + " (±" 409 + format.format(statistics.getError()) 410 + ") <small>min=" + format.format(statistics.getMin()) 411 + " max=" + format.format(statistics.getMax()) 412 + " deviation=" + format.format(statistics.getDeviation()) 413 + "</small>"; 414 writeHtmlRow(out, label, value); 415 } 416 writeHtmlRowPercentData(PrintStream out, String label, Statistics statistics, NumberFormat format)417 private void writeHtmlRowPercentData(PrintStream out, String label, 418 Statistics statistics, 419 NumberFormat format) throws Exception 420 { 421 String value; 422 if (statistics.getCount() == 0) 423 value = ""; 424 else 425 value = 426 format.format(statistics.getMean() * 100) + "% (±" 427 + format.format(statistics.getError() * 100) + ")"; 428 writeHtmlRow(out, label, value); 429 } 430 writeData(File file)431 private void writeData(File file) throws Exception 432 { 433 PrintStream out = new PrintStream(file); 434 NumberFormat format1 = StringUtil.getNumberFormat(1); 435 NumberFormat format2 = StringUtil.getNumberFormat(3); 436 Histogram histoBlack = m_statisticsBlack.m_histo; 437 Histogram histoWhite = m_statisticsWhite.m_histo; 438 Histogram histoReferee = m_statisticsReferee.m_histo; 439 Statistics winBlack = m_statisticsBlack.m_win; 440 Statistics winWhite = m_statisticsWhite.m_win; 441 Statistics winReferee = m_statisticsReferee.m_win; 442 Statistics unknownBlack = m_statisticsBlack.m_unknownScore; 443 Statistics unknownWhite = m_statisticsWhite.m_unknownScore; 444 Statistics unknownReferee = m_statisticsReferee.m_unknownScore; 445 out.print("#GAMES\tERR\tDUP\tUSED\tRES_B\tERR_B\tWIN_B\tERRW_B\t" 446 + "UNKN_B\tRES_W\tERR_W\tWIN_W\tERRW_W\tUNKN_W\t" 447 + "RES_R\tERR_R\tWIN_R\tERRW_R\tUNKN_R\n" + 448 m_games + "\t" + m_errors + "\t" + m_duplicates + "\t" 449 + m_gamesUsed 450 + "\t" + format1.format(histoBlack.getMean()) 451 + "\t" + format1.format(histoBlack.getError()) 452 + "\t" + format2.format(winBlack.getMean()) 453 + "\t" + format2.format(winBlack.getError()) 454 + "\t" + format2.format(unknownBlack.getMean()) 455 + "\t" + format1.format(histoWhite.getMean()) 456 + "\t" + format1.format(histoWhite.getError()) 457 + "\t" + format2.format(winWhite.getMean()) 458 + "\t" + format2.format(winWhite.getError()) 459 + "\t" + format2.format(unknownWhite.getMean()) 460 + "\t" + format1.format(histoReferee.getMean()) 461 + "\t" + format1.format(histoReferee.getError()) 462 + "\t" + format2.format(winReferee.getMean()) 463 + "\t" + format2.format(winReferee.getError()) 464 + "\t" + format2.format(unknownReferee.getMean()) 465 + "\n"); 466 out.close(); 467 } 468 } 469 470 final class Entry 471 { 472 public int m_gameIndex; 473 474 public String m_resultBlack; 475 476 public String m_resultReferee; 477 478 public String m_resultWhite; 479 480 public boolean m_alternated; 481 482 public String m_duplicate; 483 484 public int m_length; 485 486 public double m_timeBlack; 487 488 public double m_timeWhite; 489 490 public double m_cpuBlack; 491 492 public double m_cpuWhite; 493 494 public boolean m_error; 495 496 public String m_errorMessage; 497 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)498 public Entry(int gameIndex, String resultBlack, String resultWhite, 499 String resultReferee, boolean alternated, String duplicate, 500 int length, double timeBlack, double timeWhite, 501 double cpuBlack, double cpuWhite, boolean error, 502 String errorMessage) 503 { 504 m_gameIndex = gameIndex; 505 m_resultBlack = resultBlack; 506 m_resultWhite = resultWhite; 507 m_resultReferee = resultReferee; 508 m_alternated = alternated; 509 m_duplicate = (duplicate.equals("-") ? "" : duplicate); 510 m_length = length; 511 m_timeBlack = timeBlack; 512 m_timeWhite = timeWhite; 513 m_cpuBlack = cpuBlack; 514 m_cpuWhite = cpuWhite; 515 m_error = error; 516 m_errorMessage = errorMessage; 517 if (m_errorMessage == null) 518 m_errorMessage = ""; 519 } 520 } 521