13 #define MAX_NET_LAG 2.0 /* Max net lag in seconds. TODO: estimate dynamically. */
14 #define RESERVED_BYOYOMI_PERCENT 15 /* Reserve 15% of byoyomi time as safety margin if risk of losing on time */
16 /* For safety, use at most 3 times the desired time on a single move
17 * in main time, and 1.1 times in byoyomi. */
18 #define MAX_MAIN_TIME_EXTENSION 3.0
19 #define MAX_BYOYOMI_TIME_EXTENSION 1.1
22 time_parse(struct time_info
*ti
, char *s
)
25 case '_': ti
->period
= TT_TOTAL
; s
++; break;
26 default: ti
->period
= TT_MOVE
; break;
31 ti
->len
.games
= atoi(++s
);
36 ti
->dim
= TD_WALLTIME
;
37 ti
->len
.t
.recommended_time
= atof(s
);
38 ti
->len
.t
.max_time
= ti
->len
.t
.recommended_time
;
39 ti
->len
.t
.net_lag
= MAX_NET_LAG
;
40 ti
->len
.t
.timer_start
= 0;
41 ti
->len
.t
.byoyomi_time
= 0.0;
42 ti
->len
.t
.byoyomi_periods
= 0;
48 /* Update time settings according to gtp time_settings or kgs-time_settings command. */
50 time_settings(struct time_info
*ti
, int main_time
, int byoyomi_time
, int byoyomi_stones
, int byoyomi_periods
)
52 if (byoyomi_time
> 0 && byoyomi_stones
== 0) {
53 ti
->period
= TT_NULL
; // no time limit, rely on engine default
55 ti
->period
= TT_TOTAL
;
56 ti
->dim
= TD_WALLTIME
;
57 ti
->len
.t
.max_time
= (double) main_time
; // byoyomi will be added at next genmove
58 ti
->len
.t
.recommended_time
= ti
->len
.t
.max_time
;
59 ti
->len
.t
.timer_start
= 0;
60 ti
->len
.t
.net_lag
= MAX_NET_LAG
;
61 ti
->len
.t
.byoyomi_time
= (double) byoyomi_time
;
62 if (byoyomi_stones
> 0)
63 ti
->len
.t
.byoyomi_time
/= byoyomi_stones
;
64 ti
->len
.t
.byoyomi_periods
= byoyomi_periods
;
68 /* Update time information according to gtp time_left command.
69 * kgs doesn't give time_left for the first move, so make sure
70 * that just time_settings + time_select_best still work. */
72 time_left(struct time_info
*ti
, int time_left
, int stones_left
)
74 assert(ti
->period
!= TT_NULL
);
75 ti
->dim
= TD_WALLTIME
;
76 ti
->len
.t
.max_time
= (double)time_left
;
78 if (ti
->len
.t
.byoyomi_periods
> 0 && stones_left
> 0) {
79 ti
->len
.t
.byoyomi_periods
= stones_left
; // field misused by kgs
82 if (stones_left
== 0) {
84 ti
->period
= TT_TOTAL
;
85 ti
->len
.t
.recommended_time
= ti
->len
.t
.max_time
;
86 /* byoyomi_time, net_lag & timer_start unchanged. */
89 ti
->len
.t
.byoyomi_time
= ((double)time_left
)/stones_left
;
90 ti
->len
.t
.recommended_time
= ti
->len
.t
.byoyomi_time
;
91 /* net_lag & timer_start unchanged. */
95 /* Start our timer. kgs does this (correctly) on "play" not "genmove"
96 * unless we are making the first move of the game. */
98 time_start_timer(struct time_info
*ti
)
100 if (ti
->period
!= TT_NULL
&& ti
->dim
== TD_WALLTIME
)
101 ti
->len
.t
.timer_start
= time_now();
104 /* Returns true if we are in byoyomi (or should play as if in byo yomi
105 * because remaining time per move in main time is less than byoyomi time
108 time_in_byoyomi(struct time_info
*ti
) {
109 return ti
->period
== TT_MOVE
&& ti
->dim
== TD_WALLTIME
&& ti
->len
.t
.byoyomi_time
> 0
110 && ti
->len
.t
.recommended_time
<= ti
->len
.t
.byoyomi_time
+ 0.001;
113 /* Returns the current time. */
118 clock_gettime(CLOCK_REALTIME
, &now
);
119 return now
.tv_sec
+ now
.tv_nsec
/1000000000.0;
122 /* Sleep for a given interval (in seconds). Return immediately if interval < 0. */
124 time_sleep(double interval
)
128 ts
.tv_nsec
= (int)(modf(interval
, &sec
)*1000000000.0);
129 ts
.tv_sec
= (int)sec
;
130 nanosleep(&ts
, NULL
); /* ignore error if interval was < 0 */
134 /* Pre-process time_info for search control and sets the desired stopping conditions. */
136 time_stop_conditions(struct time_info
*ti
, struct board
*b
, int fuseki_end
, int yose_start
, struct time_stop
*stop
)
138 /*** Transform @ti to TT_MOVE and set up recommended/max time and
139 * net lag information. */
143 if (ti
->period
== TT_TOTAL
) {
144 moves_left
= board_estimated_moves_left(b
);
145 assert(moves_left
> 0);
146 if (ti
->dim
== TD_GAMES
) {
147 ti
->period
= TT_MOVE
;
148 ti
->len
.games
/= moves_left
;
151 if (ti
->period
== TT_NULL
|| ti
->dim
!= TD_WALLTIME
)
154 double now
= time_now();
156 if (!ti
->len
.t
.timer_start
) {
157 ti
->len
.t
.timer_start
= now
; // we're playing the first game move
160 lag
= now
- ti
->len
.t
.timer_start
;
161 // TODO: keep statistics to get good estimate of lag not just current move
162 ti
->len
.t
.max_time
-= lag
; // can become < 0, taken into account below
163 ti
->len
.t
.recommended_time
-= lag
;
164 if (DEBUGL(1) && lag
> MAX_NET_LAG
)
165 fprintf(stderr
, "lag %0.2f > max_net_lag %0.2f\n", lag
, MAX_NET_LAG
);
167 if (ti
->period
== TT_TOTAL
) {
168 if (ti
->len
.t
.byoyomi_time
> 0) {
169 /* For non-canadian byoyomi with N>1 periods, we use N-1 periods as main time,
170 * keeping the last one as insurance against unexpected net lag. */
171 if (ti
->len
.t
.byoyomi_periods
> 2) {
172 ti
->len
.t
.max_time
+= (ti
->len
.t
.byoyomi_periods
- 2) * ti
->len
.t
.byoyomi_time
;
173 // Will add 1 more byoyomi_time just below
175 ti
->len
.t
.max_time
+= ti
->len
.t
.byoyomi_time
;
176 ti
->len
.t
.recommended_time
= ti
->len
.t
.max_time
;
178 /* Maximize the number of moves played uniformly in main time, while
179 * not playing faster in main time than in byoyomi. At this point,
180 * the main time remaining is ti->len.t.max_time and already includes
181 * the first (canadian) or N-1 byoyomi periods.
182 * main_speed = max_time / main_moves >= byoyomi_time
183 * => main_moves <= max_time / byoyomi_time */
184 double actual_byoyomi
= ti
->len
.t
.byoyomi_time
- MAX_NET_LAG
;
185 if (actual_byoyomi
> 0) {
186 int main_moves
= (int)(ti
->len
.t
.max_time
/ actual_byoyomi
);
187 if (moves_left
> main_moves
)
188 moves_left
= main_moves
; // will do the rest in byoyomi
189 if (moves_left
<= 0) // possible if too much lag
193 ti
->period
= TT_MOVE
;
194 ti
->len
.t
.recommended_time
/= moves_left
;
196 // To simplify the engine code, do not leave negative times:
197 if (ti
->len
.t
.recommended_time
< 0)
198 ti
->len
.t
.recommended_time
= 0;
199 if (ti
->len
.t
.max_time
< 0)
200 ti
->len
.t
.max_time
= 0;
201 assert(ti
->len
.t
.recommended_time
<= ti
->len
.t
.max_time
+ 0.001);
203 /* Use a larger safety margin if we risk losing on time on this move: */
204 double safe_margin
= RESERVED_BYOYOMI_PERCENT
* ti
->len
.t
.byoyomi_time
/100;
205 if (safe_margin
> MAX_NET_LAG
&& ti
->len
.t
.recommended_time
>= ti
->len
.t
.max_time
- MAX_NET_LAG
) {
206 ti
->len
.t
.net_lag
= safe_margin
;
208 ti
->len
.t
.net_lag
= MAX_NET_LAG
;
212 fprintf(stderr
, "recommended_time %0.2f, max_time %0.2f, byoyomi %0.2f, lag %0.2f max %0.2f\n",
213 ti
->len
.t
.recommended_time
, ti
->len
.t
.max_time
, ti
->len
.t
.byoyomi_time
, lag
,
217 /*** Setup desired/worst time limits based on recommended/max time. */
220 assert(ti
->period
== TT_MOVE
);
222 if (ti
->dim
== TD_GAMES
) {
223 stop
->desired
.playouts
= ti
->len
.games
;
224 /* We force worst == desired, so note that we will not loop
225 * until best == winner. */
226 stop
->worst
.playouts
= ti
->len
.games
;
230 double desired_time
= ti
->len
.t
.recommended_time
;
232 if (time_in_byoyomi(ti
)) {
233 // make recommended == average(desired, worst)
234 worst_time
= desired_time
* MAX_BYOYOMI_TIME_EXTENSION
;
235 desired_time
*= (2 - MAX_BYOYOMI_TIME_EXTENSION
);
238 int bsize
= (board_size(b
)-2)*(board_size(b
)-2);
239 fuseki_end
= fuseki_end
* bsize
/ 100; // move nb at fuseki end
240 yose_start
= yose_start
* bsize
/ 100; // move nb at yose start
241 assert(fuseki_end
< yose_start
);
243 /* Before yose, spend some extra. */
244 if (b
->moves
< yose_start
) {
245 int moves_to_yose
= (yose_start
- b
->moves
) / 2;
246 // ^- /2 because we only consider the moves we have to play ourselves
247 int left_at_yose_start
= board_estimated_moves_left(b
) - moves_to_yose
;
248 if (left_at_yose_start
< MIN_MOVES_LEFT
)
249 left_at_yose_start
= MIN_MOVES_LEFT
;
250 double longest_time
= ti
->len
.t
.max_time
/ left_at_yose_start
;
251 if (longest_time
< desired_time
) {
252 // Should rarely happen, but keep desired_time anyway
253 } else if (b
->moves
< fuseki_end
) {
254 assert(fuseki_end
> 0);
255 desired_time
+= ((longest_time
- desired_time
) * b
->moves
) / fuseki_end
;
256 } else { assert(b
->moves
< yose_start
);
257 desired_time
= longest_time
;
260 worst_time
= desired_time
* MAX_MAIN_TIME_EXTENSION
;
262 if (worst_time
> ti
->len
.t
.max_time
)
263 worst_time
= ti
->len
.t
.max_time
;
264 if (desired_time
> worst_time
)
265 desired_time
= worst_time
;
267 stop
->desired
.time
= ti
->len
.t
.timer_start
+ desired_time
- ti
->len
.t
.net_lag
;
268 stop
->worst
.time
= ti
->len
.t
.timer_start
+ worst_time
- ti
->len
.t
.net_lag
;
269 // Both stop points may be in the past if too much lag.
272 fprintf(stderr
, "desired time %.02f, worst %.02f\n", desired_time
, worst_time
);