1 // SgfUtil.java
2 
3 package net.sf.gogui.sgf;
4 
5 import java.util.regex.Matcher;
6 import java.util.regex.Pattern;
7 import net.sf.gogui.game.TimeSettings;
8 
9 public final class SgfUtil
10 {
11     /** Result of parseOvertime(). */
12     public static final class Overtime
13     {
14         public long m_byoyomi;
15 
16         public int m_byoyomiMoves;
17     }
18 
19     /** Format byoyomi information for OT property.
20         The format is "N moves / S min" or "N moves / S sec"
21         This format is also recognized by parseOvertime.
22         Returns null, if timeSettings does not define byoyomi */
getOvertime(TimeSettings timeSettings)23     public static String getOvertime(TimeSettings timeSettings)
24     {
25         if (! timeSettings.getUseByoyomi())
26             return null;
27         StringBuilder result = new StringBuilder();
28         int byoyomiMoves = timeSettings.getByoyomiMoves();
29         long byoyomi = timeSettings.getByoyomi();
30         result.append(byoyomiMoves);
31         result.append(" moves / ");
32         if (byoyomi % 60000 == 0)
33         {
34             result.append(byoyomi / 60000L);
35             result.append(" min");
36         }
37         else
38         {
39             result.append(byoyomi / 1000L);
40             result.append(" sec");
41         }
42         return result.toString();
43     }
44 
parseOvertime(String value)45     public static Overtime parseOvertime(String value)
46     {
47         value = value.trim();
48         Overtime result = null;
49 
50         /* Used by SgfWriter */
51         result =
52             parseOvertime(value, "(\\d+)\\s*moves\\s*/\\s*(\\d+)\\s*sec",
53                           true, 1000L);
54         if (result != null)
55             return result;
56 
57         /* Used by Smart Go */
58         result =
59             parseOvertime(value, "(\\d+)\\s*moves\\s*/\\s*(\\d+)\\s*min",
60                           true, 60000L);
61         if (result != null)
62             return result;
63 
64         /* Used by Quarry, CGoban 2 */
65         result =
66             parseOvertime(value, "(\\d+)/(\\d+)\\s*canadian", true, 1000L);
67         if (result != null)
68             return result;
69 
70         return result;
71     }
72 
73     /** Parse value of TM property.
74         According to FF4, TM needs to be a real value, but older SGF versions
75         allow a string with unspecified content. We try to parse a few known
76         formats.
77         @return The (pre-byoyomi-)time in milliseconds or -1, if the
78         format was not recognized */
parseTime(String value)79     public static long parseTime(String value)
80     {
81         value = value.trim();
82         try
83         {
84             return (long)(Double.parseDouble(value) * 1000);
85         }
86         catch (NumberFormatException e1)
87         {
88         }
89         try
90         {
91             Pattern pattern;
92             Matcher matcher;
93 
94             // Pattern as written by CGoban 1.9.12
95             pattern = Pattern.compile("(\\d{1,2}):(\\d{2})");
96             matcher = pattern.matcher(value);
97             if (matcher.matches())
98             {
99                 assert matcher.groupCount() == 2;
100                 return (Integer.parseInt(matcher.group(1)) * 60000L
101                         + Integer.parseInt(matcher.group(2)) * 1000L);
102             }
103 
104             pattern = Pattern.compile("(\\d+):(\\d{2}):(\\d{2})");
105             matcher = pattern.matcher(value);
106             if (matcher.matches())
107             {
108                 assert matcher.groupCount() == 3;
109                 return (Integer.parseInt(matcher.group(1)) * 3600000L
110                         + Integer.parseInt(matcher.group(2)) * 60000L
111                         + Integer.parseInt(matcher.group(3)) * 1000L);
112             }
113 
114             pattern =
115                 Pattern.compile("(\\d+)\\s*(?:h|hr|hrs|hours|hours)(?:\\s+each)?+");
116             matcher = pattern.matcher(value);
117             if (matcher.matches())
118             {
119                 assert matcher.groupCount() == 1;
120                 return Integer.parseInt(matcher.group(1)) * 3600000L;
121             }
122 
123             pattern =
124                 Pattern.compile("(\\d+)\\s*(?:m|min)");
125             matcher = pattern.matcher(value);
126             if (matcher.matches())
127             {
128                 assert matcher.groupCount() == 1;
129                 return Integer.parseInt(matcher.group(1)) * 60000L;
130             }
131         }
132         catch (NumberFormatException e2)
133         {
134             assert false; // patterns should match only valid integers
135         }
136         return -1;
137     }
138 
139     /** Make constructor unavailable; class is for namespace only. */
SgfUtil()140     private SgfUtil()
141     {
142     }
143 
parseOvertime(String value, String regex, boolean byoyomiMovesFirst, long timeUnitFactor)144     private static Overtime parseOvertime(String value, String regex,
145                                           boolean byoyomiMovesFirst,
146                                           long timeUnitFactor)
147     {
148         Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
149         Matcher matcher = pattern.matcher(value);
150         if (matcher.matches())
151         {
152             assert matcher.groupCount() == 2;
153             try
154             {
155                 String group1;
156                 String group2;
157                 if (byoyomiMovesFirst)
158                 {
159                     group1 = matcher.group(1);
160                     group2 = matcher.group(2);
161                 }
162                 else
163                 {
164                     group1 = matcher.group(2);
165                     group2 = matcher.group(1);
166                 }
167                 Overtime overtime = new Overtime();
168                 overtime.m_byoyomiMoves = Integer.parseInt(group1);
169                 overtime.m_byoyomi =
170                     (long)(Double.parseDouble(group2) * timeUnitFactor);
171                 return overtime;
172             }
173             catch (NumberFormatException e)
174             {
175                 // should not happen if patterns match only integer
176                 assert false;
177                 return null;
178             }
179         }
180         else
181             return null;
182     }
183 }
184