12 #include "tactics/util.h"
15 /* Max net lag in seconds. TODO: estimate dynamically. */
16 #define MAX_NET_LAG 2.0
17 /* Minimal thinking time; in case reserved time gets smaller than MAX_NET_LAG,
18 * this makes sure we play minimally sensible moves even in massive time
19 * pressure; we still keep MAX_NET_LAG-MIN_THINK_WITH_LAG safety margin.
20 * Note that this affects only lag adjustmnet - if reserved time *before*
21 * lag adjustment gets too small, we still respect it and don't apply
22 * MIN_THINK_WITH_LAG. */
23 #define MIN_THINK_WITH_LAG (MAX_NET_LAG / 2)
24 /* Reserve 15% of byoyomi time as safety margin if risk of losing on time */
25 #define RESERVED_BYOYOMI_PERCENT 15
27 /* For safety, use at most 2 times the desired time on a single move
28 * in sudden death and 1.1 times in byoyomi. */
29 #define MAX_SUDDEN_DEATH_RATIO 2.0
30 #define MAX_BYOYOMI_TIME_RATIO 1.1
33 time_parse(struct time_info
*ti
, char *s
)
36 case '_': ti
->period
= TT_TOTAL
; s
++; break;
37 default: ti
->period
= TT_MOVE
; break;
42 ti
->len
.games
= atoi(++s
);
47 ti
->dim
= TD_WALLTIME
;
48 ti
->len
.t
.timer_start
= 0;
49 if (ti
->period
== TT_TOTAL
) {
50 ti
->len
.t
.main_time
= atof(s
);
51 ti
->len
.t
.byoyomi_time
= 0.0;
52 ti
->len
.t
.byoyomi_time_max
= 0.0;
53 ti
->len
.t
.byoyomi_periods
= 0;
54 ti
->len
.t
.byoyomi_stones
= 0;
55 ti
->len
.t
.byoyomi_stones_max
= 0;
56 } else { assert(ti
->period
== TT_MOVE
);
57 ti
->len
.t
.main_time
= 0.0;
58 ti
->len
.t
.byoyomi_time
= atof(s
);
59 ti
->len
.t
.byoyomi_time_max
= ti
->len
.t
.byoyomi_time
;
60 ti
->len
.t
.byoyomi_periods
= 1;
61 ti
->len
.t
.byoyomi_stones
= 1;
62 ti
->len
.t
.byoyomi_stones_max
= 1;
69 /* Update time settings according to gtp time_settings or kgs-time_settings command. */
71 time_settings(struct time_info
*ti
, int main_time
, int byoyomi_time
, int byoyomi_stones
, int byoyomi_periods
)
74 ti
->period
= TT_NULL
; // no time limit, rely on engine default
76 ti
->period
= main_time
> 0 ? TT_TOTAL
: TT_MOVE
;
77 ti
->dim
= TD_WALLTIME
;
78 ti
->len
.t
.timer_start
= 0;
79 ti
->len
.t
.main_time
= (double) main_time
;
80 ti
->len
.t
.byoyomi_time
= (double) byoyomi_time
;
81 ti
->len
.t
.byoyomi_periods
= byoyomi_periods
;
82 ti
->len
.t
.byoyomi_stones
= byoyomi_stones
;
83 ti
->len
.t
.canadian
= byoyomi_stones
> 0;
84 if (byoyomi_time
> 0) {
85 /* Normally, only one of byoyomi_periods and
86 * byoyomi_stones arguments will be > 0. However,
87 * our data structure uses generalized byoyomi
88 * specification that will assume "1 byoyomi period
89 * of N stones" for Canadian byoyomi and "N byoyomi
90 * periods of 1 stone" for Japanese byoyomi. */
91 if (ti
->len
.t
.byoyomi_periods
< 1)
92 ti
->len
.t
.byoyomi_periods
= 1;
93 if (ti
->len
.t
.byoyomi_stones
< 1)
94 ti
->len
.t
.byoyomi_stones
= 1;
96 assert(!ti
->len
.t
.byoyomi_periods
&& !ti
->len
.t
.byoyomi_stones
);
98 ti
->len
.t
.byoyomi_time_max
= ti
->len
.t
.byoyomi_time
;
99 ti
->len
.t
.byoyomi_stones_max
= ti
->len
.t
.byoyomi_stones
;
103 /* Update time information according to gtp time_left command.
104 * kgs doesn't give time_left for the first move, so make sure
105 * that just time_settings + time_stop_conditions still work. */
107 time_left(struct time_info
*ti
, int time_left
, int stones_left
)
109 assert(ti
->period
!= TT_NULL
);
110 ti
->dim
= TD_WALLTIME
;
112 if (!time_left
&& !stones_left
) {
113 /* Some GTP peers send time_left 0 0 at the end of main time. */
114 ti
->period
= TT_MOVE
;
115 ti
->len
.t
.main_time
= 0;
116 ti
->len
.t
.byoyomi_time
= ti
->len
.t
.byoyomi_time_max
;
117 ti
->len
.t
.byoyomi_stones
= ti
->len
.t
.byoyomi_stones_max
;
119 } else if (!stones_left
) {
121 ti
->period
= TT_TOTAL
;
122 ti
->len
.t
.main_time
= time_left
;
123 ti
->len
.t
.byoyomi_time
= ti
->len
.t
.byoyomi_time_max
;
124 ti
->len
.t
.byoyomi_stones
= ti
->len
.t
.byoyomi_stones_max
;
128 ti
->period
= TT_MOVE
;
129 ti
->len
.t
.main_time
= 0;
130 ti
->len
.t
.byoyomi_time
= time_left
;
131 if (ti
->len
.t
.canadian
) {
132 ti
->len
.t
.byoyomi_stones
= stones_left
;
134 // field misused by kgs
135 ti
->len
.t
.byoyomi_periods
= stones_left
;
140 /* Start our timer. kgs does this (correctly) on "play" not "genmove"
141 * unless we are making the first move of the game. */
143 time_start_timer(struct time_info
*ti
)
145 if (ti
->period
!= TT_NULL
&& ti
->dim
== TD_WALLTIME
)
146 ti
->len
.t
.timer_start
= time_now();
150 time_sub(struct time_info
*ti
, double interval
, bool new_move
)
152 assert(ti
->dim
== TD_WALLTIME
&& ti
->period
!= TT_NULL
);
154 if (ti
->period
== TT_TOTAL
) {
155 ti
->len
.t
.main_time
-= interval
;
156 if (ti
->len
.t
.main_time
>= 0)
158 if (ti
->len
.t
.byoyomi_time
<= 0) {
159 /* No byoyomi to save us. */
160 fprintf(stderr
, "*** LOST ON TIME internally! (%0.2f, spent %0.2fs on last move)\n",
161 ti
->len
.t
.main_time
, interval
);
162 /* What can we do? Pretend this didn't happen. */
163 ti
->len
.t
.main_time
= 1.0f
;
166 /* Fall-through to byoyomi. */
167 ti
->period
= TT_MOVE
;
168 interval
= -ti
->len
.t
.main_time
;
169 ti
->len
.t
.main_time
= 0;
172 ti
->len
.t
.byoyomi_time
-= interval
;
173 if (ti
->len
.t
.byoyomi_time
< 0) {
175 if (--ti
->len
.t
.byoyomi_periods
< 1) {
176 fprintf(stderr
, "*** LOST ON TIME internally! (%0.2f, spent %0.2fs on last move)\n",
177 ti
->len
.t
.byoyomi_time
, interval
);
178 /* Well, what can we do? Pretend this didn't happen. */
179 ti
->len
.t
.byoyomi_periods
= 1;
181 ti
->len
.t
.byoyomi_time
= ti
->len
.t
.byoyomi_time_max
;
182 ti
->len
.t
.byoyomi_stones
= ti
->len
.t
.byoyomi_stones_max
;
185 if (new_move
&& --ti
->len
.t
.byoyomi_stones
< 1) {
186 /* Finished a period. */
187 ti
->len
.t
.byoyomi_time
= ti
->len
.t
.byoyomi_time_max
;
188 ti
->len
.t
.byoyomi_stones
= ti
->len
.t
.byoyomi_stones_max
;
192 /* Returns the current time. */
196 #if _POSIX_TIMERS > 0
198 clock_gettime(CLOCK_REALTIME
, &now
);
199 return now
.tv_sec
+ now
.tv_nsec
/1000000000.0;
202 gettimeofday(&now
, NULL
);
203 return now
.tv_sec
+ now
.tv_usec
/1000000.0;
207 /* Sleep for a given interval (in seconds). Return immediately if interval < 0. */
209 time_sleep(double interval
)
212 unsigned int t
= interval
* 1000.0;
217 ts
.tv_nsec
= (int)(modf(interval
, &sec
)*1000000000.0);
218 ts
.tv_sec
= (int)sec
;
219 nanosleep(&ts
, NULL
); /* ignore error if interval was < 0 */
224 /* Returns true if we are in byoyomi (or should play as if in byo yomi
225 * because remaining time per move in main time is less than byoyomi time
228 time_in_byoyomi(struct time_info
*ti
) {
229 assert(ti
->dim
== TD_WALLTIME
);
230 if (!ti
->len
.t
.byoyomi_time
)
231 return false; // there is no byoyomi!
232 assert(ti
->len
.t
.byoyomi_stones
> 0);
233 if (!ti
->len
.t
.main_time
)
234 return true; // we _are_ in byoyomi
235 if (ti
->len
.t
.main_time
<= ti
->len
.t
.byoyomi_time
/ ti
->len
.t
.byoyomi_stones
+ 0.001)
236 return true; // our basic time left is less than byoyomi time per move
240 /* Set worst.time to all available remaining time (main time plus usable
241 * byoyomi), to be spread over returned number of moves (expected game
242 * length minus moves to be played in final byoyomi - if we would not be
243 * able to spend more time on them in main time anyway). */
245 time_stop_set_remaining(struct time_info
*ti
, struct board
*b
, double net_lag
, struct time_stop
*stop
)
247 int moves_left
= board_estimated_moves_left(b
);
248 stop
->worst
.time
= ti
->len
.t
.main_time
;
250 if (!ti
->len
.t
.byoyomi_time
)
253 /* Time for one move in byoyomi. */
254 assert(ti
->len
.t
.byoyomi_stones
> 0);
255 double move_time
= ti
->len
.t
.byoyomi_time
/ ti
->len
.t
.byoyomi_stones
;
257 /* (i) Plan to extend our thinking time to make use of byoyom. */
259 /* For Japanese byoyomi with N>1 periods, we use N-1 periods
260 * as main time, keeping the last one as insurance against
261 * unexpected net lag. */
262 if (ti
->len
.t
.byoyomi_periods
> 2) {
263 stop
->worst
.time
+= (ti
->len
.t
.byoyomi_periods
- 2) * move_time
;
264 // Will add 1 more byoyomi_time just below
267 /* In case of Canadian byoyomi, include time that can be spent
268 * on its first move. */
269 stop
->worst
.time
+= move_time
;
271 /* (ii) Do not play faster in main time than we would in byoyomi. */
273 /* Maximize the number of moves played uniformly in main time,
274 * while not playing faster in main time than in byoyomi.
275 * At this point, the main time remaining is stop->worst.time and
276 * already includes the first (canadian) or N-1 byoyomi periods. */
277 double real_move_time
= move_time
- net_lag
;
278 if (real_move_time
> 0) {
279 int main_moves
= stop
->worst
.time
/ real_move_time
;
280 if (moves_left
> main_moves
) {
281 /* We plan to do too many moves in main time,
282 * do the rest in byoyomi. */
283 moves_left
= main_moves
;
285 if (moves_left
<= 0) // possible if too much lag
292 /* Adjust the recommended per-move time based on the current game phase.
293 * We expect stop->worst to be total time available, stop->desired the current
294 * per-move time allocation, and set stop->desired to adjusted per-move time. */
296 time_stop_phase_adjust(struct board
*b
, int fuseki_end
, int yose_start
, struct time_stop
*stop
)
298 int bsize
= (board_size(b
)-2)*(board_size(b
)-2);
299 fuseki_end
= fuseki_end
* bsize
/ 100; // move nb at fuseki end
300 yose_start
= yose_start
* bsize
/ 100; // move nb at yose start
301 assert(fuseki_end
< yose_start
);
303 /* No adjustments in yose. */
304 if (b
->moves
>= yose_start
)
306 int moves_to_yose
= (yose_start
- b
->moves
) / 2;
307 // ^- /2 because we only consider the moves we have to play ourselves
308 int left_at_yose_start
= board_estimated_moves_left(b
) - moves_to_yose
;
309 if (left_at_yose_start
< MIN_MOVES_LEFT
)
310 left_at_yose_start
= MIN_MOVES_LEFT
;
312 /* This particular value of middlegame_time will continuously converge
313 * to effective "yose_time" value as we approach yose_start. */
314 double middlegame_time
= stop
->worst
.time
/ left_at_yose_start
;
315 if (middlegame_time
< stop
->desired
.time
)
318 if (b
->moves
< fuseki_end
) {
319 assert(fuseki_end
> 0);
320 /* At the game start, use stop->desired.time (rather
321 * conservative estimate), then gradually prolong it. */
322 double beta
= b
->moves
/ fuseki_end
;
323 stop
->desired
.time
= middlegame_time
* beta
+ stop
->desired
.time
* (1 - beta
);
325 } else { assert(b
->moves
< yose_start
);
326 /* Middlegame, start with relatively large value, then
327 * converge to the uniform-timeslice yose value. */
328 stop
->desired
.time
= middlegame_time
;
333 lag_adjust(double *time
, double net_lag
)
335 double nolag_time
= *time
;
337 if (*time
< MIN_THINK_WITH_LAG
&& nolag_time
> MIN_THINK_WITH_LAG
)
338 *time
= MIN_THINK_WITH_LAG
;
341 /* Pre-process time_info for search control and sets the desired stopping conditions. */
343 time_stop_conditions(struct time_info
*ti
, struct board
*b
, int fuseki_end
, int yose_start
,
344 floating_t max_maintime_ratio
, struct time_stop
*stop
)
346 /* We must have _some_ limits by now, be it random default values! */
347 assert(ti
->period
!= TT_NULL
);
349 /* Special-case limit by number of simulations. */
350 if (ti
->dim
== TD_GAMES
) {
351 if (ti
->period
== TT_TOTAL
) {
352 ti
->period
= TT_MOVE
;
353 ti
->len
.games
/= board_estimated_moves_left(b
);
356 stop
->desired
.playouts
= ti
->len
.games
;
357 /* We force worst == desired, so note that we will NOT loop
358 * until best == winner. */
359 stop
->worst
.playouts
= ti
->len
.games
;
363 assert(ti
->dim
== TD_WALLTIME
);
366 /* Minimum net lag (seconds) to be reserved in the time for move. */
367 double net_lag
= MAX_NET_LAG
;
368 net_lag
+= time_now() - ti
->len
.t
.timer_start
;
369 // TODO: keep statistics to get good estimate of lag not just current move
372 if (ti
->period
== TT_TOTAL
&& time_in_byoyomi(ti
)) {
373 /* Technically, we are still in main time, but we can
374 * effectively switch to byoyomi scheduling since we
375 * have less time available than one byoyomi move takes. */
376 ti
->period
= TT_MOVE
;
380 if (ti
->period
== TT_MOVE
) {
381 /* We are in byoyomi, or almost! */
383 /* The period can still include some tiny remnant of main
384 * time if we are just switching to byoyomi. */
385 double period_len
= ti
->len
.t
.byoyomi_time
+ ti
->len
.t
.main_time
;
387 stop
->worst
.time
= period_len
;
388 assert(ti
->len
.t
.byoyomi_stones
> 0);
389 stop
->desired
.time
= period_len
/ ti
->len
.t
.byoyomi_stones
;
391 /* Use a larger safety margin if we risk losing on time on
392 * this move; it makes no sense to have 30s byoyomi and wait
393 * until 28s to play our move). */
394 if (stop
->desired
.time
>= period_len
- net_lag
) {
395 double safe_margin
= RESERVED_BYOYOMI_PERCENT
* stop
->desired
.time
/ 100;
396 if (safe_margin
> net_lag
)
397 net_lag
= safe_margin
;
400 /* Make recommended_old == average(recommended_new, max) */
401 double worst_time
= stop
->desired
.time
* MAX_BYOYOMI_TIME_RATIO
;
402 if (worst_time
< stop
->worst
.time
)
403 stop
->worst
.time
= worst_time
;
404 stop
->desired
.time
*= (2 - MAX_BYOYOMI_TIME_RATIO
);
406 } else { assert(ti
->period
== TT_TOTAL
);
407 /* We are in main time. */
409 assert(ti
->len
.t
.main_time
> 0);
410 /* Set worst.time to all available remaining time, to be spread
411 * over returned number of moves. */
412 int moves_left
= time_stop_set_remaining(ti
, b
, net_lag
, stop
);
414 /* Allocate even slice of the remaining time for next move. */
415 stop
->desired
.time
= stop
->worst
.time
/ moves_left
;
416 assert(stop
->desired
.time
> 0 && stop
->worst
.time
> 0);
417 assert(stop
->desired
.time
<= stop
->worst
.time
+ 0.001);
419 /* Furthermore, tweak the slice based on the game phase. */
420 time_stop_phase_adjust(b
, fuseki_end
, yose_start
, stop
);
422 /* Put final upper bound on maximal time spent on the move.
423 * Keep enough time for sudden death (or near SD) games. */
424 double worst_time
= stop
->desired
.time
;
425 if (ti
->len
.t
.byoyomi_time_max
> ti
->len
.t
.byoyomi_stones_max
) {
426 worst_time
*= max_maintime_ratio
;
428 worst_time
*= MAX_SUDDEN_DEATH_RATIO
;
430 if (worst_time
< stop
->worst
.time
)
431 stop
->worst
.time
= worst_time
;
432 if (stop
->desired
.time
> stop
->worst
.time
)
433 stop
->desired
.time
= stop
->worst
.time
;
437 fprintf(stderr
, "desired %0.2f, worst %0.2f, clock [%d] %0.2f + %0.2f/%d*%d, lag %0.2f\n",
438 stop
->desired
.time
, stop
->worst
.time
,
439 ti
->dim
, ti
->len
.t
.main_time
,
440 ti
->len
.t
.byoyomi_time
, ti
->len
.t
.byoyomi_stones
,
441 ti
->len
.t
.byoyomi_periods
, net_lag
);
443 /* Account for lag. */
444 lag_adjust(&stop
->desired
.time
, net_lag
);
445 lag_adjust(&stop
->worst
.time
, net_lag
);