Updated README: Equal sign not required with `--mode` flag.
[sgk-go] / engine / clock.c
CommitLineData
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 */
44static int level = DEFAULT_LEVEL; /* current level */
45static int level_offset = 0;
46static int min_level = 0;
47static int max_level = gg_max(DEFAULT_LEVEL, 10);
48
49
50/*************************/
51/* Datas and other stuff */
52/*************************/
53
54/* clock parameters */
55static int main_time = -1;
56static int byoyomi_time = -1;
57static 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 */
63struct 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
71struct timer_data {
72 struct remaining_time_data official;
73 struct remaining_time_data estimated;
74 int time_out;
75};
76
77static struct timer_data black_time_data;
78static struct timer_data white_time_data;
79
80
81/* Echo a time value in STANDARD format */
82static void
83timeval_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. */
96void
97clock_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 */
131void
132clock_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 */
146int
147have_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. */
160void
161init_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
181void
182update_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 */
217void
218clock_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 */
281static int
282analyze_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 */
331void
332adjust_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
379int
380get_level()
381{
382 return level + level_offset;
383}
384
385void
386set_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
396void
397set_max_level(int new_max)
398{
399 max_level = new_max;
400}
401
402void
403set_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 */