Commit | Line | Data |
---|---|---|
7eeb782e AT |
1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ |
2 | * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * | |
3 | * http://www.gnu.org/software/gnugo/ for more information. * | |
4 | * * | |
5 | * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * | |
6 | * 2008 and 2009 by the Free Software Foundation. * | |
7 | * * | |
8 | * This program is free software; you can redistribute it and/or * | |
9 | * modify it under the terms of the GNU General Public License as * | |
10 | * published by the Free Software Foundation - version 3 or * | |
11 | * (at your option) any later version. * | |
12 | * * | |
13 | * This program is distributed in the hope that it will be useful, * | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
16 | * GNU General Public License in file COPYING for more details. * | |
17 | * * | |
18 | * You should have received a copy of the GNU General Public * | |
19 | * License along with this program; if not, write to the Free * | |
20 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * | |
21 | * Boston, MA 02111, USA. * | |
22 | \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
23 | ||
24 | /* ============================================================= *\ | |
25 | * Time handling * | |
26 | * for GNU Go * | |
27 | * __ __ * | |
28 | * < > < > * | |
29 | * +--++-------++--+ * | |
30 | * | .'11 12 1'. | * | |
31 | * | :10 \ 2: | * | |
32 | * | :9 @-> 3: | * | |
33 | * | :8 4; | * | |
34 | * | '..7 6 5..' | * | |
35 | * |_______________| * | |
36 | * * | |
37 | \* ============================================================= */ | |
38 | ||
39 | #include "clock.h" | |
40 | #include "gg_utils.h" | |
41 | #include "board.h" | |
42 | ||
43 | /* Level data */ | |
44 | static int level = DEFAULT_LEVEL; /* current level */ | |
45 | static int level_offset = 0; | |
46 | static int min_level = 0; | |
47 | static int max_level = gg_max(DEFAULT_LEVEL, 10); | |
48 | ||
49 | ||
50 | /*************************/ | |
51 | /* Datas and other stuff */ | |
52 | /*************************/ | |
53 | ||
54 | /* clock parameters */ | |
55 | static int main_time = -1; | |
56 | static int byoyomi_time = -1; | |
57 | static int byoyomi_stones = -1; /* <= 0 if no byo-yomi */ | |
58 | ||
59 | /* Keep track of the remaining time left. | |
60 | * If stones_left is zero, .._time_left is the remaining main time. | |
61 | * Otherwise, the remaining time for this byoyomi period. | |
62 | */ | |
63 | struct remaining_time_data { | |
64 | double time_left; | |
65 | double time_for_last_move; | |
66 | int stones; | |
67 | int movenum; | |
68 | int in_byoyomi; | |
69 | }; | |
70 | ||
71 | struct timer_data { | |
72 | struct remaining_time_data official; | |
73 | struct remaining_time_data estimated; | |
74 | int time_out; | |
75 | }; | |
76 | ||
77 | static struct timer_data black_time_data; | |
78 | static struct timer_data white_time_data; | |
79 | ||
80 | ||
81 | /* Echo a time value in STANDARD format */ | |
82 | static void | |
83 | timeval_print(FILE *outfile, double tv) | |
84 | { | |
85 | int min; | |
86 | double sec; | |
87 | ||
88 | min = (int) tv / 60; | |
89 | sec = tv - min*60; | |
90 | ||
91 | fprintf(outfile, "%3dmin %.2fsec ", min, sec); | |
92 | } | |
93 | ||
94 | ||
95 | /* Print the clock status for one side. */ | |
96 | void | |
97 | clock_print(int color) | |
98 | { | |
99 | struct timer_data *const td | |
100 | = (color == BLACK) ? &black_time_data : &white_time_data; | |
101 | ||
102 | fprintf(stderr, "clock: "); | |
103 | fprintf(stderr, "%s ", color_to_string(color)); | |
104 | ||
105 | if (td->time_out) | |
106 | fprintf(stderr, "TIME OUT! "); | |
107 | else { | |
108 | if (td->estimated.in_byoyomi) { | |
109 | fprintf(stderr, "byoyomi"); | |
110 | timeval_print(stderr, td->estimated.time_left); | |
111 | fprintf(stderr, "for %d stones.", td->estimated.stones); | |
112 | } | |
113 | else | |
114 | timeval_print(stderr, td->estimated.time_left); | |
115 | ||
116 | } | |
117 | fprintf(stderr, "\n"); | |
118 | } | |
119 | ||
120 | ||
121 | /******************************/ | |
122 | /* Initialization functions */ | |
123 | /******************************/ | |
124 | ||
125 | /* | |
126 | * Initialize the time settings for this game. | |
127 | * -1 means "do not modify this value". | |
128 | * | |
129 | * byo_time > 0 and byo_stones == 0 means no time settings. | |
130 | */ | |
131 | void | |
132 | clock_settings(int time, int byo_time, int byo_stones) | |
133 | { | |
134 | if (time >= 0) | |
135 | main_time = time; | |
136 | if (byo_time >= 0) | |
137 | byoyomi_time = byo_time; | |
138 | if (byo_stones >= 0) | |
139 | byoyomi_stones = byo_stones; | |
140 | init_timers(); | |
141 | } | |
142 | ||
143 | /* Get time settings. Returns 1 if any time settings have been made, | |
144 | * 0 otherwise. | |
145 | */ | |
146 | int | |
147 | have_time_settings(void) | |
148 | { | |
149 | /* According to the semantics of the GTP command 'time_settings', the | |
150 | * following signifies no time limits. | |
151 | */ | |
152 | if (byoyomi_time > 0 && byoyomi_stones == 0) | |
153 | return 0; | |
154 | else | |
155 | return (main_time >= 0 || byoyomi_time >= 0); | |
156 | } | |
157 | ||
158 | ||
159 | /* Initialize all timers. */ | |
160 | void | |
161 | init_timers() | |
162 | { | |
163 | white_time_data.official.time_left = main_time; | |
164 | white_time_data.official.time_for_last_move = -1.0; | |
165 | white_time_data.official.stones = 0; | |
166 | white_time_data.official.movenum = 0; | |
167 | white_time_data.official.in_byoyomi = 0; | |
168 | white_time_data.estimated = white_time_data.official; | |
169 | white_time_data.time_out = 0; | |
170 | black_time_data = white_time_data; | |
171 | ||
172 | level_offset = 0; | |
173 | } | |
174 | ||
175 | ||
176 | /*****************************/ | |
177 | /* Clock access functions. */ | |
178 | /*****************************/ | |
179 | ||
180 | ||
181 | void | |
182 | update_time_left(int color, int time_left, int stones) | |
183 | { | |
184 | struct timer_data *const td | |
185 | = ((color == BLACK) ? &black_time_data : &white_time_data); | |
186 | int time_used = td->official.time_left - time_left; | |
187 | ||
188 | if (time_left > 0) | |
189 | td->time_out = 0; | |
190 | else | |
191 | td->time_out = 1; | |
192 | ||
193 | /* Did our estimate for time usage go wrong? */ | |
194 | if (time_used > 0 | |
195 | && gg_abs(time_used - td->estimated.time_for_last_move) >= 1.0) | |
196 | td->estimated.time_for_last_move = time_used; | |
197 | td->estimated.stones = stones; | |
198 | td->estimated.movenum = movenum; | |
199 | /* Did our clock go wrong? */ | |
200 | if (gg_abs(td->estimated.time_left - time_left) >= 1.0) | |
201 | td->estimated.time_left = time_left; | |
202 | if (stones > 0) | |
203 | td->estimated.in_byoyomi = 1; | |
204 | else | |
205 | td->estimated.in_byoyomi = 0; | |
206 | ||
207 | td->official.stones = stones; | |
208 | td->official.movenum = movenum; | |
209 | td->official.time_for_last_move = td->official.time_for_last_move - time_left; | |
210 | td->official.time_left = time_left; | |
211 | td->official.in_byoyomi = td->estimated.in_byoyomi; | |
212 | } | |
213 | ||
214 | /* | |
215 | * Update the estimated timer after a move has been made. | |
216 | */ | |
217 | void | |
218 | clock_push_button(int color) | |
219 | { | |
220 | static double last_time = -1.0; | |
221 | static int last_movenum = -1; | |
222 | struct timer_data *const td | |
223 | = (color == BLACK) ? &black_time_data : &white_time_data; | |
224 | double now = gg_gettimeofday(); | |
225 | ||
226 | if (!have_time_settings()) | |
227 | return; | |
228 | ||
229 | if (last_movenum >= 0 | |
230 | && movenum == last_movenum + 1 | |
231 | && movenum > td->estimated.movenum) { | |
232 | double time_used = now - last_time; | |
233 | td->estimated.time_left -= time_used; | |
234 | td->estimated.movenum = movenum; | |
235 | td->estimated.time_for_last_move = time_used; | |
236 | if (td->estimated.time_left < 0) { | |
237 | if (td->estimated.in_byoyomi || byoyomi_stones == 0) { | |
238 | DEBUG(DEBUG_TIME, "%s ran out of time.\n", color_to_string(color)); | |
239 | if (debug & DEBUG_TIME) | |
240 | clock_print(color); | |
241 | td->time_out = 1; | |
242 | } | |
243 | else { | |
244 | /* Entering byoyomi. */ | |
245 | gg_assert(!(td->estimated.in_byoyomi)); | |
246 | td->estimated.in_byoyomi = 1; | |
247 | td->estimated.stones = byoyomi_stones - 1; | |
248 | td->estimated.time_left += byoyomi_time; | |
249 | if (td->estimated.time_left < 0) | |
250 | td->time_out = 1; | |
251 | } | |
252 | } | |
253 | else if (td->estimated.stones > 0) { | |
254 | gg_assert(td->estimated.in_byoyomi); | |
255 | td->estimated.stones = td->estimated.stones - 1; | |
256 | if (td->estimated.stones == 0) { | |
257 | td->estimated.time_left = byoyomi_time; | |
258 | td->estimated.stones = byoyomi_stones; | |
259 | } | |
260 | } | |
261 | } | |
262 | ||
263 | last_movenum = movenum; | |
264 | last_time = now; | |
265 | ||
266 | /* Update main timer. */ | |
267 | if (debug & DEBUG_TIME) | |
268 | clock_print(color); | |
269 | } | |
270 | ||
271 | ||
272 | /**********************/ | |
273 | /* Autolevel system */ | |
274 | /**********************/ | |
275 | ||
276 | ||
277 | /* Analyze the two most recent time reports and determine the time | |
278 | * spent on the last moves, the (effective) number of stones left and | |
279 | * the (effective) remaining time. | |
280 | */ | |
281 | static int | |
282 | analyze_time_data(int color, double *time_for_last_move, double *time_left, | |
283 | int *stones_left) | |
284 | { | |
285 | struct remaining_time_data *const timer | |
286 | = (color == BLACK) ? &black_time_data.estimated | |
287 | : &white_time_data.estimated; | |
288 | ||
289 | /* Do we have any time limits. */ | |
290 | if (!have_time_settings()) | |
291 | return 0; | |
292 | ||
293 | /* If we don't have consistent time information yet, just return. */ | |
294 | if (timer->time_for_last_move < 0.0) | |
295 | return 0; | |
296 | ||
297 | *time_for_last_move = timer->time_for_last_move; | |
298 | ||
299 | if (timer->stones == 0) { | |
300 | /* Main time running. */ | |
301 | *time_left = timer->time_left + byoyomi_time; | |
302 | if (byoyomi_time > 0) | |
303 | *stones_left = byoyomi_stones; | |
304 | else { | |
305 | /* Absolute time. Here we aim to be able to play at least X more | |
306 | * moves or a total of Y moves. We choose Y as a third of the | |
307 | * number of vertices and X as 40% of Y. For 19x19 this means | |
308 | * that we aim to play at least a total of 120 moves | |
309 | * (corresponding to a 240 move game) or another 24 moves. | |
310 | * | |
311 | * FIXME: Maybe we should use the game_status of | |
312 | * influence_evaluate_position() here to guess how many moves | |
313 | * are remaining. | |
314 | */ | |
315 | int nominal_moves = board_size * board_size / 3; | |
316 | *stones_left = gg_max(nominal_moves - movenum / 2, | |
317 | 2 * nominal_moves / 5); | |
318 | } | |
319 | } | |
320 | else { | |
321 | *time_left = timer->time_left; | |
322 | *stones_left = timer->stones; | |
323 | } | |
324 | return 1; | |
325 | } | |
326 | ||
327 | ||
328 | /* Adjust the level offset given information of current playing speed | |
329 | * and remaining time and stones. | |
330 | */ | |
331 | void | |
332 | adjust_level_offset(int color) | |
333 | { | |
334 | double time_for_last_move; | |
335 | double time_left; | |
336 | int stones_left; | |
337 | ||
338 | if (!analyze_time_data(color, &time_for_last_move, &time_left, &stones_left)) | |
339 | return; | |
340 | ||
341 | ||
342 | /* These rules are both crude and ad hoc. | |
343 | * | |
344 | * FIXME: Use rules with at least some theoretical basis. | |
345 | */ | |
346 | if (time_left < time_for_last_move * (stones_left + 3)) | |
347 | level_offset--; | |
348 | if (time_left < time_for_last_move * stones_left) | |
349 | level_offset--; | |
350 | if (3 * time_left < 2 * time_for_last_move * stones_left) | |
351 | level_offset--; | |
352 | if (2 * time_left < time_for_last_move * stones_left) | |
353 | level_offset--; | |
354 | if (3 * time_left < time_for_last_move * stones_left) | |
355 | level_offset--; | |
356 | ||
357 | if (time_for_last_move == 0) | |
358 | time_for_last_move = 1; | |
359 | if (time_left > time_for_last_move * (stones_left + 6)) | |
360 | level_offset++; | |
361 | if (time_left > 2 * time_for_last_move * (stones_left + 6)) | |
362 | level_offset++; | |
363 | ||
364 | if (level + level_offset < min_level) | |
365 | level_offset = min_level - level; | |
366 | ||
367 | if (level + level_offset > max_level) | |
368 | level_offset = max_level - level; | |
369 | ||
370 | DEBUG(DEBUG_TIME, "New level %d (%d %C %f %f %d)\n", level + level_offset, | |
371 | movenum / 2, color, time_for_last_move, time_left, stones_left); | |
372 | } | |
373 | ||
374 | ||
375 | /********************************/ | |
376 | /* Interface to level settings. */ | |
377 | /********************************/ | |
378 | ||
379 | int | |
380 | get_level() | |
381 | { | |
382 | return level + level_offset; | |
383 | } | |
384 | ||
385 | void | |
386 | set_level(int new_level) | |
387 | { | |
388 | level = new_level; | |
389 | level_offset = 0; | |
390 | if (level > max_level) | |
391 | max_level = level; | |
392 | if (level < min_level) | |
393 | min_level = level; | |
394 | } | |
395 | ||
396 | void | |
397 | set_max_level(int new_max) | |
398 | { | |
399 | max_level = new_max; | |
400 | } | |
401 | ||
402 | void | |
403 | set_min_level(int new_min) | |
404 | { | |
405 | min_level = new_min; | |
406 | } | |
407 | ||
408 | ||
409 | /* | |
410 | * Local Variables: | |
411 | * tab-width: 8 | |
412 | * c-basic-offset: 2 | |
413 | * End: | |
414 | */ |