Do not use all memory during pondering.
[pachi/derm.git] / timeinfo.c
blob9b09b968c53f2d28812ed7a139a58d9e2d194123
1 #include <assert.h>
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <math.h>
6 #include <time.h>
8 #define DEBUG
10 #include "debug.h"
11 #include "timeinfo.h"
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 bool
17 time_parse(struct time_info *ti, char *s)
19 switch (s[0]) {
20 case '_': ti->period = TT_TOTAL; s++; break;
21 default: ti->period = TT_MOVE; break;
23 switch (s[0]) {
24 case '=':
25 ti->dim = TD_GAMES;
26 ti->len.games = atoi(++s);
27 break;
28 default:
29 if (!isdigit(s[0]))
30 return false;
31 ti->dim = TD_WALLTIME;
32 ti->len.t.recommended_time = atof(s);
33 ti->len.t.max_time = ti->len.t.recommended_time;
34 ti->len.t.net_lag = MAX_NET_LAG;
35 ti->len.t.timer_start = 0;
36 ti->len.t.byoyomi_time = 0.0;
37 ti->len.t.byoyomi_periods = 0;
38 break;
40 return true;
43 /* Update time settings according to gtp time_settings or kgs-time_settings command. */
44 void
45 time_settings(struct time_info *ti, int main_time, int byoyomi_time, int byoyomi_stones, int byoyomi_periods)
47 if (byoyomi_time > 0 && byoyomi_stones == 0) {
48 ti->period = TT_NULL; // no time limit, rely on engine default
49 } else {
50 ti->period = TT_TOTAL;
51 ti->dim = TD_WALLTIME;
52 ti->len.t.max_time = (double) main_time; // byoyomi will be added at next genmove
53 ti->len.t.recommended_time = ti->len.t.max_time;
54 ti->len.t.timer_start = 0;
55 ti->len.t.net_lag = MAX_NET_LAG;
56 ti->len.t.byoyomi_time = (double) byoyomi_time;
57 if (byoyomi_stones > 0)
58 ti->len.t.byoyomi_time /= byoyomi_stones;
59 ti->len.t.byoyomi_periods = byoyomi_periods;
63 /* Update time information according to gtp time_left command.
64 * kgs doesn't give time_left for the first move, so make sure
65 * that just time_settings + time_select_best still work. */
66 void
67 time_left(struct time_info *ti, int time_left, int stones_left)
69 assert(ti->period != TT_NULL);
70 ti->dim = TD_WALLTIME;
71 ti->len.t.max_time = (double)time_left;
73 if (ti->len.t.byoyomi_periods > 0 && stones_left > 0) {
74 ti->len.t.byoyomi_periods = stones_left; // field misused by kgs
75 stones_left = 1;
77 /* For non-canadian byoyomi, we use all periods as main time. */
78 if (stones_left == 0 || ti->len.t.byoyomi_periods > 1) {
79 /* Main time */
80 ti->period = TT_TOTAL;
81 ti->len.t.recommended_time = ti->len.t.max_time;
82 /* byoyomi_time, net_lag & timer_start unchanged. */
83 } else {
84 ti->period = TT_MOVE;
85 ti->len.t.byoyomi_time = ((double)time_left)/stones_left;
86 ti->len.t.recommended_time = ti->len.t.byoyomi_time;
87 /* net_lag & timer_start unchanged. */
91 /* Set correct time information before making a move, and
92 * always make it time per move for the engine. */
93 void
94 time_prepare_move(struct time_info *ti, struct board *board)
96 int moves_left;
98 if (ti->period == TT_TOTAL) {
99 moves_left = board_estimated_moves_left(board);
100 assert(moves_left > 0);
101 if (ti->dim == TD_GAMES) {
102 ti->period = TT_MOVE;
103 ti->len.games /= moves_left;
106 if (ti->period == TT_NULL || ti->dim != TD_WALLTIME)
107 return;
109 double now = time_now();
110 double lag;
111 if (!ti->len.t.timer_start) {
112 ti->len.t.timer_start = now; // we're playing the first game move
113 lag = 0;
114 } else {
115 lag = now - ti->len.t.timer_start;
116 // TODO: keep statistics to get good estimate of lag not just current move
117 ti->len.t.max_time -= lag; // can become < 0, taken into account below
118 ti->len.t.recommended_time -= lag;
119 if (DEBUGL(2) && lag > MAX_NET_LAG)
120 fprintf(stderr, "lag %0.2f > max_net_lag %0.2f\n", lag, MAX_NET_LAG);
122 if (ti->period == TT_TOTAL) {
123 /* For non-canadian byoyomi, we use all periods as main time, just making sure
124 * to avoid running out of the last one. */
125 if (ti->len.t.byoyomi_periods > 1) {
126 ti->len.t.max_time += (ti->len.t.byoyomi_periods - 1) * ti->len.t.byoyomi_time;
127 // Will add 1 more byoyomi_time just below
129 if (ti->len.t.byoyomi_time > 0) {
130 ti->len.t.max_time += ti->len.t.byoyomi_time;
131 ti->len.t.recommended_time = ti->len.t.max_time;
133 /* Maximize the number of moves played uniformly in main time, while
134 * not playing faster in main time than in byoyomi. At this point,
135 * the main time remaining is ti->len.t.max_time and already includes
136 * the first (canadian) or all byoyomi periods.
137 * main_speed = max_time / main_moves >= byoyomi_time
138 * => main_moves <= max_time / byoyomi_time */
139 double actual_byoyomi = ti->len.t.byoyomi_time - MAX_NET_LAG;
140 if (actual_byoyomi > 0) {
141 int main_moves = (int)(ti->len.t.max_time / actual_byoyomi);
142 if (moves_left > main_moves)
143 moves_left = main_moves; // will do the rest in byoyomi
144 if (moves_left <= 0) // possible if too much lag
145 moves_left = 1;
148 ti->period = TT_MOVE;
149 ti->len.t.recommended_time /= moves_left; // may be < 0 if too much lag
151 // To simplify the engine code, do not leave negative times:
152 if (ti->len.t.recommended_time < 0)
153 ti->len.t.recommended_time = 0;
154 if (ti->len.t.max_time < 0)
155 ti->len.t.max_time = 0;
156 assert(ti->len.t.recommended_time <= ti->len.t.max_time + 0.001);
158 /* Use a larger safety margin if we risk losing on time on this move: */
159 double safe_margin = RESERVED_BYOYOMI_PERCENT * ti->len.t.byoyomi_time/100;
160 if (safe_margin > MAX_NET_LAG && ti->len.t.recommended_time >= ti->len.t.max_time - MAX_NET_LAG) {
161 ti->len.t.net_lag = safe_margin;
162 } else {
163 ti->len.t.net_lag = MAX_NET_LAG;
166 if (DEBUGL(1))
167 fprintf(stderr, "recommended_time %0.2f, max_time %0.2f, byoyomi %0.2f, lag %0.2f max %0.2f\n",
168 ti->len.t.recommended_time, ti->len.t.max_time, ti->len.t.byoyomi_time, lag,
169 ti->len.t.net_lag);
172 /* Start our timer. kgs does this (correctly) on "play" not "genmove"
173 * unless we are making the first move of the game. */
174 void
175 time_start_timer(struct time_info *ti)
177 if (ti->period != TT_NULL && ti->dim == TD_WALLTIME)
178 ti->len.t.timer_start = time_now();
181 /* Returns true if we are in byoyomi (or should play as if in byo yomi
182 * because remaining time per move in main time is less than byoyomi time
183 * per move). */
184 bool
185 time_in_byoyomi(struct time_info *ti) {
186 return ti->period == TT_MOVE && ti->dim == TD_WALLTIME && ti->len.t.byoyomi_time > 0
187 && ti->len.t.recommended_time <= ti->len.t.byoyomi_time + 0.001;
190 /* Returns the current time. */
191 double
192 time_now(void)
194 struct timespec now;
195 clock_gettime(CLOCK_REALTIME, &now);
196 return now.tv_sec + now.tv_nsec/1000000000.0;
199 /* Sleep for a given interval (in seconds). Return immediately if interval < 0. */
200 void
201 time_sleep(double interval)
203 struct timespec ts;
204 double sec;
205 ts.tv_nsec = (int)(modf(interval, &sec)*1000000000.0);
206 ts.tv_sec = (int)sec;
207 nanosleep(&ts, NULL); /* ignore error if interval was < 0 */