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()) + " (&plusmn;"
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) + "% (&plusmn;"
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