1 #include <math.h>
2 #include "chess.h"
3 #include "data.h"
4 /* last modified 02/23/14 */
5 /*
6  *******************************************************************************
7  *                                                                             *
8  *   TimeAdjust() is called to adjust timing variables after each program move *
9  *   is made.  It simply increments the number of moves made, decrements the   *
10  *   amount of time used, and then makes any necessary adjustments based on    *
11  *   the time controls.                                                        *
12  *                                                                             *
13  *******************************************************************************
14  */
TimeAdjust(int side,int time_used)15 void TimeAdjust(int side, int time_used) {
16 /*
17  ************************************************************
18  *                                                          *
19  *  Decrement the number of moves remaining to the next     *
20  *  time control.  Then subtract the time the program took  *
21  *  to choose its move from the time remaining.             *
22  *                                                          *
23  ************************************************************
24  */
25   tc_moves_remaining[side]--;
26   tc_time_remaining[side] -=
27       (tc_time_remaining[side] >
28       time_used) ? time_used : tc_time_remaining[side];
29   if (!tc_moves_remaining[side]) {
30     if (tc_sudden_death == 2)
31       tc_sudden_death = 1;
32     tc_moves_remaining[side] += tc_secondary_moves;
33     tc_time_remaining[side] += tc_secondary_time;
34     Print(4095, "time control reached (%s)\n", (side) ? "white" : "black");
35   }
36   if (tc_increment)
37     tc_time_remaining[side] += tc_increment;
38 }
39 
40 /* last modified 10/01/14 */
41 /*
42  *******************************************************************************
43  *                                                                             *
44  *   TimeCheck() is used to determine when the search should stop.  It uses    *
45  *   several conditions to make this determination:  (1) The search time has   *
46  *   exceeded the time per move limit;  (2) The value at the root of the tree  *
47  *   has not dropped too low.                                                  *
48  *                                                                             *
49  *   We use one additional trick here to avoid stopping the search just before *
50  *   we change to a better move.  We simply do our best to complete the        *
51  *   iteration which means we have searched every move to this same depth.     *
52  *                                                                             *
53  *   This is implemented by having Search() call TimeCheck() passing it a      *
54  *   value of one (1) for the parameter busy.  TimeCheck() will only end the   *
55  *   search if we have exceeded the max time limit.  Otherwise, we continue.   *
56  *   Iterate() calls TimeCheck() passing it a value of "0" for busy, which     *
57  *   simply says "now, if we have used the target time limit (which can be     *
58  *   modified by the "difficulty value), we will stop and not try another      *
59  *   iteration."                                                               *
60  *                                                                             *
61  *   The "difficulty" value is used to implement the concept of an "easy move" *
62  *   or a "hard move".  With an easy move, we want to spend less time since    *
63  *   the easy move is obvious.  The opposite idea is a hard move, where we     *
64  *   actually want to spend more time to be sure we don't make a mistake by`   *
65  *   moving too quickly.                                                       *
66  *                                                                             *
67  *   The basic methodology revolves around how many times we change our mind   *
68  *   on the best move at the root of the tree.                                 *
69  *                                                                             *
70  *   The "difficulty" variable is initially set to 100, which represents a     *
71  *   percentage of the actual target time we should spend on this move.  If    *
72  *   we end an iteration without having changed our mind at all, difficulty    *
73  *   is reduced by multiplying by .9, with a lower bound of 60%.               *
74  *                                                                             *
75  *   If we change our mind during an iteration, there are two cases.  (1) If   *
76  *   difficulty is < 100%, we set it back to 100% +20% for each time we        *
77  *   changed the best move.  (2) if difficulty is already at 100% or higher,   *
78  *   we multiply difficulty by .80, then add 20% for each root move change.    *
79  *   For example, suppose we are at difficulty=60%, and we suddenly change our *
80  *   mind twice this iteration (3 different best moves).                       *
81  *                                                                             *
82  *   difficulty = 100 + 3*20 = 160% of the actual target time will be used.    *
83  *                                                                             *
84  *   Suppose we change back and forth between two best moves multiple times,   *
85  *   with difficulty currently at 100%.  The first time:                       *
86  *                                                                             *
87  *   difficulty = .80 * 100 + 2*20 = 120%                                      *
88  *                                                                             *
89  *   The next iteration:                                                       *
90  *                                                                             *
91  *   difficulty = .80 * 120 + 2 * 20 = 96% _ 40% = 136%                        *
92  *                                                                             *
93  *   The next iteration:                                                       *
94  *                                                                             *
95  *   difficulty = .80 * 136% + 40% = 149%                                      *
96  *                                                                             *
97  *   If we stop changing our mind, then difficulty starts on a downward trend. *
98  *   The basic idea is that if we are locked in on a move, we can make it a    *
99  *   bit quicker, but if we are changing back and forth, we are going to spend *
100  *   more time to try to choose the best move.                                 *
101  *                                                                             *
102  *******************************************************************************
103  */
TimeCheck(TREE * RESTRICT tree,int busy)104 int TimeCheck(TREE * RESTRICT tree, int busy) {
105   int time_used;
106   int i, ndone;
107 
108 /*
109  ************************************************************
110  *                                                          *
111  *  Check to see if we need to "burp" the time to let the   *
112  *  operator know the search is progressing and how much    *
113  *  time has been used so far.                              *
114  *                                                          *
115  ************************************************************
116  */
117   time_used = (ReadClock() - start_time);
118   if (time_used >= noise_level && display_options & 16 && time_used > burp) {
119     Lock(lock_io);
120     if (pondering)
121       printf("         %2i   %s%7s?  ", iteration, Display2Times(time_used),
122           tree->remaining_moves_text);
123     else
124       printf("         %2i   %s%7s*  ", iteration, Display2Times(time_used),
125           tree->remaining_moves_text);
126     if (display_options & 16)
127       printf("%d. ", move_number);
128     if ((display_options & 16) && Flip(root_wtm))
129       printf("... ");
130     printf("%s(%snps)             \r", tree->root_move_text,
131         DisplayKMB(nodes_per_second, 0));
132     burp = (time_used / 1500) * 1500 + 1500;
133     fflush(stdout);
134     Unlock(lock_io);
135   }
136 /*
137  ************************************************************
138  *                                                          *
139  *  First, check to see if there is only one root move.  If *
140  *  so, and we are not pondering, searching a book move or  *
141  *  or annotating a game, we can return and make this move  *
142  *  instantly.  We do need to finish iteration 1 so that we *
143  *  actually back up a move to play.                        *
144  *                                                          *
145  ************************************************************
146  */
147   if (n_root_moves == 1 && !booking && !annotate_mode && !pondering &&
148       iteration > 1 && (time_used > time_limit || time_used > 100))
149     return 1;
150   if (iteration <= 2)
151     return 0;
152 /*
153  ************************************************************
154  *                                                          *
155  *  If we are pondering or in analyze mode, we do not       *
156  *  terminate on time since there is no time limit placed   *
157  *  on these searches.  If we have reached the absolute     *
158  *  time limit, we stop the search instantly.               *
159  *                                                          *
160  ************************************************************
161  */
162   if (pondering || analyze_mode)
163     return 0;
164   if (time_used > absolute_time_limit)
165     return 1;
166 /*
167  ************************************************************
168  *                                                          *
169  *  If the operator has specified a specific time limit, we *
170  *  stop when we hit that regardless of any other tests     *
171  *  used during normal timeing.                             *
172  *                                                          *
173  ************************************************************
174  */
175   if (search_time_limit) {
176     if (time_used < time_limit)
177       return 0;
178     else
179       return 1;
180   }
181 /*
182  ************************************************************
183  *                                                          *
184  *  If we are under the time limit already set, we do not   *
185  *  terminate the search.  Once we reach that limit, we     *
186  *  abort the search if we are fixing to start another      *
187  *  iteration, otherwise we keep searching to try to        *
188  *  complete the current iteration.                         *
189  *                                                          *
190  ************************************************************
191  */
192   if (time_used < (difficulty * time_limit) / 100)
193     return 0;
194   if (!busy)
195     return 1;
196 /*
197  ************************************************************
198  *                                                          *
199  *  We have reached the target time limit.  If we are in    *
200  *  the middle of an iteration, we keep going unless we are *
201  *  stuck on the first move, where there is no benefit to   *
202  *  continuing and this will just burn clock time away.     *
203  *                                                          *
204  *  This is a bit tricky, because if we are on the first    *
205  *  move AND we have failed low, we want to continue the    *
206  *  search to find something better, if we have not failed  *
207  *  low, we will abort the search in the test that follows  *
208  *  this one.                                               *
209  *                                                          *
210  ************************************************************
211  */
212   ndone = 0;
213   for (i = 0; i < n_root_moves; i++)
214     if (root_moves[i].status & 8)
215       ndone++;
216   if (ndone == 1 && !(root_moves[0].status & 1))
217     return 1;
218 /*
219  ************************************************************
220  *                                                          *
221  *  We are in the middle of an iteration, we have used the  *
222  *  allocated time limit, but we have more moves left to    *
223  *  search.  We forge on until we complete the iteration    *
224  *  which will terminate the search, or until we reach the  *
225  *  "absolute_time_limit" where we terminate the search no  *
226  *  matter what is going on.                                *
227  *                                                          *
228  ************************************************************
229  */
230   if (time_used + 300 > tc_time_remaining[root_wtm])
231     return 1;
232   return 0;
233 }
234 
235 /* last modified 09/30/14 */
236 /*
237  *******************************************************************************
238  *                                                                             *
239  *   TimeSet() is called to set the two variables "time_limit" and             *
240  *   "absolute_time_limit" which controls the amount of time taken by the      *
241  *   iterated search.  It simply takes the timing controls as set by the user  *
242  *   and uses these values to calculate how much time should be spent on the   *
243  *   next search.                                                              *
244  *                                                                             *
245  *******************************************************************************
246  */
TimeSet(int search_type)247 void TimeSet(int search_type) {
248   int mult = 0, extra = 0;
249   int surplus, average;
250   int simple_average;
251 
252   surplus = 0;
253   average = 0;
254 /*
255  ************************************************************
256  *                                                          *
257  *  Check to see if we are in a sudden-death type of time   *
258  *  control.  If so, we have a fixed amount of time         *
259  *  remaining.  Set the search time accordingly and exit.   *
260  *                                                          *
261  *  The basic algorithm is to divide the remaining time     *
262  *  left on the clock by a constant (that is larger for     *
263  *  ponder=off games since we don't get to ponder and save  *
264  *  time as the game progresses) and add the increment.     *
265  *                                                          *
266  *  Set our MAX search time to the smaller of 5 * the time  *
267  *  limit or 1/2 of the time left on the clock.             *
268  *                                                          *
269  ************************************************************
270  */
271   if (tc_sudden_death == 1) {
272     time_limit =
273         (tc_time_remaining[root_wtm] -
274         tc_safety_margin) / (ponder ? 20 : 26) + tc_increment;
275     absolute_time_limit =
276         Min(time_limit * 5, tc_time_remaining[root_wtm] / 2);
277   }
278 /*
279  ************************************************************
280  *                                                          *
281  *  We are not in a sudden_death situation.  We now have    *
282  *  two choices:  If the program has saved enough time to   *
283  *  meet the surplus requirement, then we simply divide     *
284  *  the time left evenly among the moves left.  If we       *
285  *  haven't yet saved up a cushion so that "hard-moves"     *
286  *  can be searched more thoroughly, we simply take the     *
287  *  number of moves divided into the total time as the      *
288  *  target.                                                 *
289  *                                                          *
290  ************************************************************
291  */
292   else {
293     if (move_number <= tc_moves)
294       simple_average = (tc_time - tc_safety_margin) / tc_moves;
295     else
296       simple_average =
297           (tc_secondary_time - tc_safety_margin) / tc_secondary_moves;
298     surplus =
299         Max(tc_time_remaining[root_wtm] - tc_safety_margin -
300         simple_average * tc_moves_remaining[root_wtm], 0);
301     average =
302         (tc_time_remaining[root_wtm] - tc_safety_margin +
303         tc_moves_remaining[root_wtm] * tc_increment)
304         / tc_moves_remaining[root_wtm];
305     if (surplus < tc_safety_margin)
306       time_limit = (average < simple_average) ? average : simple_average;
307     else
308       time_limit =
309           (average < 2.0 * simple_average) ? average : 2.0 * simple_average;
310   }
311   if (tc_increment > 200 && moves_out_of_book < 2)
312     time_limit *= 1.2;
313   if (time_limit <= 0)
314     time_limit = 5;
315   absolute_time_limit =
316       time_limit + surplus / 2 + (tc_time_remaining[root_wtm] -
317       tc_safety_margin) / 4;
318   if (absolute_time_limit > 5 * time_limit)
319     absolute_time_limit = 5 * time_limit;
320   if (absolute_time_limit > tc_time_remaining[root_wtm] / 2)
321     absolute_time_limit = tc_time_remaining[root_wtm] / 2;
322 /*
323  ************************************************************
324  *                                                          *
325  *  The "usage" option can be used to force the time limit  *
326  *  higher or lower than normal.  The new "timebook"        *
327  *  command can also modify the target time making the      *
328  *  program use more time early in the game as it exits the *
329  *  book, knowing it will save time later on by ponder hits *
330  *  and instant moves.                                      *
331  *                                                          *
332  ************************************************************
333  */
334   if (usage_level)
335     time_limit *= 1.0 + usage_level / 100.0;
336   if (first_nonbook_factor && moves_out_of_book < first_nonbook_span) {
337     mult =
338         (first_nonbook_span - moves_out_of_book + 1) * first_nonbook_factor;
339     extra = time_limit * mult / first_nonbook_span / 100;
340     time_limit += extra;
341   }
342 /*
343  ************************************************************
344  *                                                          *
345  *  If the operator has set an absolute search time limit   *
346  *  already, then we simply copy this value and return.     *
347  *                                                          *
348  ************************************************************
349  */
350   if (search_time_limit) {
351     time_limit = search_time_limit;
352     absolute_time_limit = time_limit;
353   }
354   if (search_type == puzzle || search_type == booking) {
355     time_limit /= 10;
356     absolute_time_limit = time_limit * 3;
357   }
358   if (!tc_sudden_death && !search_time_limit &&
359       time_limit > 3 * tc_time / tc_moves)
360     time_limit = 3 * tc_time / tc_moves;
361   time_limit = Min(time_limit, absolute_time_limit);
362   if (search_type != puzzle) {
363     if (!tc_sudden_death)
364       Print(32, "        time surplus %s  ", DisplayTime(surplus));
365     else
366       Print(32, "        ");
367     Print(32, "time limit %s", DisplayTimeKibitz(time_limit));
368     Print(32, " (%s)\n", DisplayTimeKibitz(absolute_time_limit));
369   }
370   if (time_limit <= 1) {
371     time_limit = 1;
372     usage_level = 0;
373   }
374 }
375