Rev 108 | Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
33 | pmbaty | 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 | */ |
||
15 | void TimeAdjust(int time_used, int side) { |
||
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 02/23/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 | */ |
||
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 (tree->nodes_searched > noise_level && display_options & 32 && |
||
119 | time_used > burp) { |
||
120 | Lock(lock_io); |
||
121 | if (pondering) |
||
122 | printf(" %2i %s%7s? ", iteration_depth, |
||
123 | Display2Times(time_used), tree->remaining_moves_text); |
||
124 | else |
||
125 | printf(" %2i %s%7s* ", iteration_depth, |
||
126 | Display2Times(time_used), tree->remaining_moves_text); |
||
127 | if (display_options & 32 && display_options & 64) |
||
128 | printf("%d. ", move_number); |
||
129 | if ((display_options & 32) && (display_options & 64) && Flip(root_wtm)) |
||
130 | printf("... "); |
||
131 | printf("%s(%snps) \r", tree->root_move_text, |
||
132 | DisplayKMB(nodes_per_second)); |
||
133 | burp = (time_used / 1500) * 1500 + 1500; |
||
134 | fflush(stdout); |
||
135 | Unlock(lock_io); |
||
136 | } |
||
137 | /* |
||
138 | ************************************************************ |
||
139 | * * |
||
140 | * First, check to see if there is only one root move. If * |
||
141 | * so, and we are not pondering, searching a book move or * |
||
142 | * or annotating a game, we can return and make this move * |
||
143 | * instantly. We do need to finish iteration 1 so that we * |
||
144 | * actually back up a move to play. * |
||
145 | * * |
||
146 | ************************************************************ |
||
147 | */ |
||
148 | if (n_root_moves == 1 && !booking && !annotate_mode && !pondering && |
||
149 | iteration_depth > 1) |
||
150 | return 1; |
||
151 | if (iteration_depth <= 2) |
||
152 | return 0; |
||
153 | /* |
||
154 | ************************************************************ |
||
155 | * * |
||
156 | * If we are pondering or in analyze mode, we do not * |
||
157 | * terminate on time since there is no time limit placed * |
||
158 | * on these searches. If we have reached the absolute * |
||
159 | * time limit, we stop the search instantly. * |
||
160 | * * |
||
161 | ************************************************************ |
||
162 | */ |
||
163 | if (pondering || analyze_mode) |
||
164 | return 0; |
||
165 | if (time_used > absolute_time_limit) |
||
166 | return 1; |
||
167 | /* |
||
168 | ************************************************************ |
||
169 | * * |
||
170 | * If the operator has specified a specific time limit, we * |
||
171 | * stop when we hit that regardless of any other tests * |
||
172 | * used during normal timeing. * |
||
173 | * * |
||
174 | ************************************************************ |
||
175 | */ |
||
176 | if (search_time_limit) { |
||
177 | if (time_used < time_limit) |
||
178 | return 0; |
||
179 | else |
||
180 | return 1; |
||
181 | } |
||
182 | /* |
||
183 | ************************************************************ |
||
184 | * * |
||
185 | * If we are under the time limit already set, we do not * |
||
186 | * terminate the search. Once we reach that limit, we * |
||
187 | * abort the search if we are fixing to start another * |
||
188 | * iteration, otherwise we keep searching to try to * |
||
189 | * complete the current iteration. * |
||
190 | * * |
||
191 | ************************************************************ |
||
192 | */ |
||
193 | if (time_used < (difficulty * time_limit) / 100) |
||
194 | return 0; |
||
195 | if (!busy) |
||
196 | return 1; |
||
197 | /* |
||
198 | ************************************************************ |
||
199 | * * |
||
200 | * We have reached the target time limit. If we are in * |
||
201 | * the middle of an iteration, we keep going unless we are * |
||
202 | * stuck on the first move, where there is no benefit to * |
||
203 | * continuing and this will just burn clock time away. * |
||
204 | * * |
||
205 | * This is a bit tricky, because if we are on the first * |
||
206 | * move AND we have failed low, we want to continue the * |
||
207 | * search to find something better, if we have not failed * |
||
208 | * low, we will abort the search in the test that follows * |
||
209 | * this one. * |
||
210 | * * |
||
211 | ************************************************************ |
||
212 | */ |
||
213 | ndone = 0; |
||
214 | for (i = 0; i < n_root_moves; i++) |
||
215 | if (root_moves[i].status & 8) |
||
216 | ndone++; |
||
217 | if (ndone == 1 && !(root_moves[0].status & 1)) |
||
218 | return 1; |
||
219 | /* |
||
220 | ************************************************************ |
||
221 | * * |
||
222 | * We are in the middle of an iteration, we have used the * |
||
223 | * allocated time limit, but we have more moves left to * |
||
224 | * search. We forge on until we complete the iteration * |
||
225 | * which will terminate the search, or until we reach the * |
||
226 | * "absolute_time_limit" where we terminate the search no * |
||
227 | * matter what is going on. * |
||
228 | * * |
||
229 | ************************************************************ |
||
230 | */ |
||
231 | if (time_used + 300 > tc_time_remaining[root_wtm]) |
||
232 | return 1; |
||
233 | return 0; |
||
234 | } |
||
235 | |||
236 | /* last modified 02/23/14 */ |
||
237 | /* |
||
238 | ******************************************************************************* |
||
239 | * * |
||
240 | * TimeSet() is called to set the two variables "time_limit" and * |
||
241 | * "absolute_time_limit" which controls the amount of time taken by the * |
||
242 | * iterated search. It simply takes the timing controls as set by the user * |
||
243 | * and uses these values to calculate how much time should be spent on the * |
||
244 | * next search. * |
||
245 | * * |
||
246 | ******************************************************************************* |
||
247 | */ |
||
248 | void TimeSet(int search_type) { |
||
249 | int mult = 0, extra = 0; |
||
250 | int surplus, average; |
||
251 | int simple_average; |
||
252 | |||
253 | surplus = 0; |
||
254 | average = 0; |
||
255 | /* |
||
256 | ************************************************************ |
||
257 | * * |
||
258 | * Check to see if we are in a sudden-death type of time * |
||
259 | * control. If so, we have a fixed amount of time * |
||
260 | * remaining. Set the search time accordingly and exit. * |
||
261 | * * |
||
262 | * If we have less than 5 seconds on the clock prior to * |
||
263 | * the increment, then limit our search to the increment. * |
||
264 | * * |
||
265 | * If we have less than 2.5 seconds on the clock prior to * |
||
266 | * the increment, then limit our search to half the * |
||
267 | * increment in an attempt to add some time to our buffer. * |
||
268 | * * |
||
269 | * Set our MAX search time to half the remaining time. * |
||
270 | * * |
||
271 | * If our search time will drop the clock below 1 second, * |
||
272 | * then limit our MAX search time to the normal search * |
||
273 | * time. This is done to stop any extensions from * |
||
274 | * dropping us too low. * |
||
275 | * * |
||
276 | ************************************************************ |
||
277 | */ |
||
278 | if (tc_sudden_death == 1) { |
||
279 | if (tc_increment) { |
||
280 | time_limit = |
||
281 | (tc_time_remaining[root_wtm] - |
||
282 | tc_operator_time * tc_moves_remaining[root_wtm]) / |
||
283 | (ponder ? 20 : 26) + tc_increment; |
||
284 | if (tc_time_remaining[root_wtm] < 500 + tc_increment) { |
||
285 | time_limit = tc_increment; |
||
286 | if (tc_time_remaining[root_wtm] < 250 + tc_increment) |
||
287 | time_limit /= 2; |
||
288 | } |
||
289 | absolute_time_limit = tc_time_remaining[root_wtm] / 2 + tc_increment; |
||
290 | if (absolute_time_limit < time_limit || |
||
291 | tc_time_remaining[root_wtm] - time_limit < 100) |
||
292 | absolute_time_limit = time_limit; |
||
293 | if (tc_time_remaining[root_wtm] - time_limit < 50) { |
||
294 | time_limit = tc_time_remaining[root_wtm] - 50; |
||
295 | if (time_limit < 5) |
||
296 | time_limit = 5; |
||
297 | } |
||
298 | if (tc_time_remaining[root_wtm] - absolute_time_limit < 25) { |
||
299 | absolute_time_limit = tc_time_remaining[root_wtm] - 25; |
||
300 | if (absolute_time_limit < 5) |
||
301 | absolute_time_limit = 5; |
||
302 | } |
||
303 | |||
304 | } else { |
||
305 | time_limit = tc_time_remaining[root_wtm] / (ponder ? 20 : 26); |
||
306 | absolute_time_limit = |
||
307 | Min(time_limit * 5, tc_time_remaining[root_wtm] / 2); |
||
308 | } |
||
309 | } |
||
310 | /* |
||
311 | ************************************************************ |
||
312 | * * |
||
313 | * We are not in a sudden_death situation. We now have * |
||
314 | * two choices: If the program has saved enough time to * |
||
315 | * meet the surplus requirement, then we simply divide * |
||
316 | * the time left evenly among the moves left. If we * |
||
317 | * haven't yet saved up a cushion so that "fail-lows" * |
||
318 | * have extra time to find a solution, we simply take the * |
||
319 | * number of moves divided into the total time less the * |
||
320 | * necessary operator time as the target. * |
||
321 | * * |
||
322 | ************************************************************ |
||
323 | */ |
||
324 | else { |
||
325 | if (move_number <= tc_moves) |
||
326 | simple_average = |
||
327 | (tc_time - |
||
328 | (tc_operator_time * tc_moves_remaining[root_wtm])) / tc_moves; |
||
329 | else |
||
330 | simple_average = |
||
331 | (tc_secondary_time - |
||
332 | (tc_operator_time * tc_moves_remaining[root_wtm])) / |
||
333 | tc_secondary_moves; |
||
334 | surplus = |
||
335 | Max(tc_time_remaining[root_wtm] - |
||
336 | (tc_operator_time * tc_moves_remaining[root_wtm]) - |
||
337 | simple_average * tc_moves_remaining[root_wtm], 0); |
||
338 | average = |
||
339 | (tc_time_remaining[root_wtm] - |
||
340 | (tc_operator_time * tc_moves_remaining[root_wtm]) + |
||
341 | tc_moves_remaining[root_wtm] * tc_increment) |
||
342 | / tc_moves_remaining[root_wtm]; |
||
343 | if (surplus < tc_safety_margin) |
||
344 | time_limit = (average < simple_average) ? average : simple_average; |
||
345 | else |
||
346 | time_limit = |
||
347 | (average < 2/*.0*/ * simple_average) ? average : 2/*.0*/ * simple_average; // Pierre-Marie Baty -- this is integer math |
||
348 | } |
||
349 | if (surplus < 0) |
||
350 | surplus = 0; |
||
351 | if (tc_increment > 200 && moves_out_of_book < 2) |
||
352 | /*time_limit *= 1.2;*/ time_limit = (time_limit * 12) / 10; // Pierre-Marie Baty -- this is integer math |
||
353 | if (time_limit <= 0) |
||
354 | time_limit = 5; |
||
355 | absolute_time_limit = |
||
356 | time_limit + surplus / 2 + ((tc_time_remaining[root_wtm] - |
||
357 | tc_operator_time * tc_moves_remaining[root_wtm]) / 4); |
||
358 | if (absolute_time_limit > 6 * time_limit) |
||
359 | absolute_time_limit = 6 * time_limit; |
||
360 | if (absolute_time_limit > tc_time_remaining[root_wtm] / 2) |
||
361 | absolute_time_limit = tc_time_remaining[root_wtm] / 2; |
||
362 | /* |
||
363 | ************************************************************ |
||
364 | * * |
||
365 | * The "usage" option can be used to force the time limit * |
||
366 | * higher or lower than normal. The new "timebook" * |
||
367 | * command can also modify the target time making the * |
||
368 | * program use more time early in the game as it exits the * |
||
369 | * book, knowing it will save time later on by ponder hits * |
||
370 | * and instant moves. * |
||
371 | * * |
||
372 | ************************************************************ |
||
373 | */ |
||
374 | if (usage_level) |
||
375 | /*time_limit *= 1.0 + usage_level / 100.0;*/ time_limit += time_limit * usage_level / 100; // Pierre-Marie Baty -- this is integer math |
||
376 | if (first_nonbook_factor && moves_out_of_book < first_nonbook_span) { |
||
377 | mult = |
||
378 | (first_nonbook_span - moves_out_of_book + 1) * first_nonbook_factor; |
||
379 | extra = time_limit * mult / first_nonbook_span / 100; |
||
380 | time_limit += extra; |
||
381 | } |
||
382 | /* |
||
383 | ************************************************************ |
||
384 | * * |
||
385 | * If the operator has set an absolute search time limit * |
||
386 | * already, then we simply copy this value and return. * |
||
387 | * * |
||
388 | ************************************************************ |
||
389 | */ |
||
390 | if (search_time_limit) { |
||
391 | time_limit = search_time_limit; |
||
392 | absolute_time_limit = time_limit; |
||
393 | } |
||
394 | if (search_type == puzzle || search_type == booking) { |
||
395 | time_limit /= 10; |
||
396 | absolute_time_limit = time_limit * 3; |
||
397 | } |
||
398 | if (!tc_sudden_death && !search_time_limit && |
||
399 | time_limit > 3 * tc_time / tc_moves) |
||
400 | time_limit = 3 * tc_time / tc_moves; |
||
401 | time_limit = Min(time_limit, absolute_time_limit); |
||
402 | if (search_type != puzzle) { |
||
403 | if (!tc_sudden_death) |
||
404 | Print(128, " time surplus %s ", DisplayTime(surplus)); |
||
405 | else |
||
406 | Print(128, " "); |
||
407 | Print(128, "time limit %s", DisplayTimeKibitz(time_limit)); |
||
408 | Print(128, " (+%s)", DisplayTimeKibitz(extra)); |
||
409 | Print(128, " (%s)", DisplayTimeKibitz(absolute_time_limit)); |
||
410 | if (fabs(usage_level) > 0.0001) { |
||
411 | Print(128, "/"); |
||
412 | Print(128, "(%d)", usage_level); |
||
413 | } |
||
414 | Print(128, "\n"); |
||
415 | } |
||
416 | if (time_limit <= 1) { |
||
417 | time_limit = 1; |
||
418 | usage_level = 0; |
||
419 | } |
||
420 | } |