Subversion Repositories Games.Chess Giants

Rev

Rev 108 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
33 pmbaty 1
#include "chess.h"
2
#include "data.h"
3
/* last modified 05/15/14 */
4
/*
5
 *******************************************************************************
6
 *                                                                             *
7
 *   NextEvasion() is used to select the next move from the current move list  *
8
 *   when the king is in check.  We use GenerateEvasions() (in movgen.c) to    *
9
 *   generate a list of moves that get us out of check.  The only unusual      *
10
 *   feature is that these moves are all legal and do not need to be vetted    *
11
 *   with the usual Check() function to test for legality.                     *
12
 *                                                                             *
13
 *******************************************************************************
14
 */
15
int NextEvasion(TREE * RESTRICT tree, int ply, int wtm) {
16
  int *movep, *sortv;
17
 
18
  switch (tree->next_status[ply].phase) {
19
/*
20
 ************************************************************
21
 *                                                          *
22
 *  First try the transposition table move (which might be  *
23
 *  the principal variation move as we first move down the  *
24
 *  tree).  If it is good enough to cause a cutoff, we      *
25
 *  avoided the overhead of generating legal moves.         *
26
 *                                                          *
27
 ************************************************************
28
 */
29
    case HASH_MOVE:
30
      if (tree->hash_move[ply]) {
31
        tree->next_status[ply].phase = GENERATE_ALL_MOVES;
32
        tree->curmv[ply] = tree->hash_move[ply];
33
        if (ValidMove(tree, ply, wtm, tree->curmv[ply]))
34
          return HASH_MOVE;
35
#if defined(DEBUG)
36
        else
37
          Print(128, "bad move from hash table, ply=%d\n", ply);
38
#endif
39
      }
40
/*
41
 ************************************************************
42
 *                                                          *
43
 *  Now generate all legal moves by using the special       *
44
 *  GenerateCheckEvasions() procedure.  Then sort the moves *
45
 *  based on the expected gain or loss.  this is deferred   *
46
 *  until now to see if the hash move is good enough to     *
47
 *  produce a cutoff and avoid this effort.                 *
48
 *                                                          *
49
 *  Once we confirm that the move does not lose any         *
50
 *  material, we sort these non-losing moves into MVV/LVA   *
51
 *  order which appears to be a slightly faster move        *
52
 *  ordering idea.  Unsafe evasion moves are sorted using   *
53
 *  the original Swap() score to keep them last in the move *
54
 *  list.  Note that this move list contains both captures  *
55
 *  and non-captures.  We try the safe captures first due   *
56
 *  to the way the sort score is computed.                  *
57
 *                                                          *
58
 ************************************************************
59
 */
60
    case GENERATE_ALL_MOVES:
61
      tree->last[ply] =
62
          GenerateCheckEvasions(tree, ply, wtm, tree->last[ply - 1]);
63
      tree->next_status[ply].phase = REMAINING_MOVES;
64
      for (movep = tree->last[ply - 1], sortv = tree->sort_value;
65
          movep < tree->last[ply]; movep++, sortv++)
66
        if (tree->hash_move[ply] && *movep == tree->hash_move[ply]) {
67
          *sortv = -999999;
68
          *movep = 0;
69
        } else {
70
          if (pcval[Piece(*movep)] <= pcval[Captured(*movep)])
71
            *sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
72
          else {
73
            *sortv = Swap(tree, *movep, wtm);
74
            if (*sortv >= 0)
75
              *sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
76
          }
77
        }
78
/*
79
 ************************************************************
80
 *                                                          *
81
 *  This is a simple insertion sort algorithm.  It seems be *
82
 *  be no faster than a normal bubble sort, but using this  *
83
 *  eliminated a lot of explaining about "why?". :)         *
84
 *                                                          *
85
 ************************************************************
86
 */
87
      if (tree->last[ply] > tree->last[ply - 1] + 1) {
88
        int temp1, temp2, *tmovep, *tsortv, *end;
89
 
90
        sortv = tree->sort_value + 1;
91
        end = tree->last[ply];
92
        for (movep = tree->last[ply - 1] + 1; movep < end; movep++, sortv++) {
93
          temp1 = *movep;
94
          temp2 = *sortv;
95
          tmovep = movep - 1;
96
          tsortv = sortv - 1;
97
          while (tmovep >= tree->last[ply - 1] && *tsortv < temp2) {
98
            *(tsortv + 1) = *tsortv;
99
            *(tmovep + 1) = *tmovep;
100
            tmovep--;
101
            tsortv--;
102
          }
103
          *(tmovep + 1) = temp1;
104
          *(tsortv + 1) = temp2;
105
        }
106
      }
107
      tree->next_status[ply].last = tree->last[ply - 1];
108
/*
109
 ************************************************************
110
 *                                                          *
111
 *  Now try the moves in sorted order.                      *
112
 *                                                          *
113
 ************************************************************
114
 */
115
    case REMAINING_MOVES:
116
      for (; tree->next_status[ply].last < tree->last[ply];
117
          tree->next_status[ply].last++)
118
        if (*tree->next_status[ply].last) {
119
          tree->curmv[ply] = *tree->next_status[ply].last++;
120
          return REMAINING_MOVES;
121
        }
122
      return NONE;
123
    default:
124
      printf("oops!  next_status.phase is bad! [evasion %d]\n",
125
          tree->next_status[ply].phase);
126
  }
127
  return NONE;
128
}
129
 
130
/* last modified 05/15/14 */
131
/*
132
 *******************************************************************************
133
 *                                                                             *
134
 *   NextMove() is used to select the next move from the current move list.    *
135
 *                                                                             *
136
 *   The "excluded move" code below simply collects any moves that were        *
137
 *   searched without being generated (hash move and up to 4 killers).  We     *
138
 *   save them in the NEXT structure and make sure to exclude them when        *
139
 *   searching after a move generation to avoid the duplicated effort.         *
140
 *                                                                             *
141
 *******************************************************************************
142
 */
143
int NextMove(TREE * RESTRICT tree, int ply, int wtm) {
144
  int *movep, *sortv;
145
 
146
  switch (tree->next_status[ply].phase) {
147
/*
148
 ************************************************************
149
 *                                                          *
150
 *  First, try the transposition table move (which will be  *
151
 *  the principal variation move as we first move down the  *
152
 *  tree).                                                  *
153
 *                                                          *
154
 ************************************************************
155
 */
156
    case HASH_MOVE:
157
      tree->next_status[ply].num_excluded = 0;
158
      tree->next_status[ply].phase = GENERATE_CAPTURE_MOVES;
159
      if (tree->hash_move[ply]) {
160
        tree->curmv[ply] = tree->hash_move[ply];
161
        tree->next_status[ply].excluded_moves[tree->next_status[ply].
162
            num_excluded++]
163
            = tree->curmv[ply];
164
        if (ValidMove(tree, ply, wtm, tree->curmv[ply]))
165
          return HASH_MOVE;
166
#if defined(DEBUG)
167
        else
168
          Print(128, "bad move from hash table, ply=%d\n", ply);
169
#endif
170
      }
171
/*
172
 ************************************************************
173
 *                                                          *
174
 *  Generate captures and sort them based on the simple     *
175
 *  MVV/LVA ordering where we try to capture the most       *
176
 *  valuable victim piece possible, using the least         *
177
 *  valuable attacking piece possible.  Later we will test  *
178
 *  to see if the capture appears to lose material and we   *
179
 *  will defer searching it until later.                    *
180
 *                                                          *
181
 ************************************************************
182
 */
183
    case GENERATE_CAPTURE_MOVES:
184
      tree->next_status[ply].phase = CAPTURE_MOVES;
185
      tree->last[ply] = GenerateCaptures(tree, ply, wtm, tree->last[ply - 1]);
186
      tree->next_status[ply].remaining = 0;
187
      for (movep = tree->last[ply - 1], sortv = tree->sort_value;
188
          movep < tree->last[ply]; movep++, sortv++)
189
        if (*movep == tree->hash_move[ply]) {
190
          *sortv = -999999;
191
          *movep = 0;
192
          tree->next_status[ply].num_excluded = 0;
193
        } else {
194
          *sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
195
          tree->next_status[ply].remaining++;
196
        }
197
/*
198
 ************************************************************
199
 *                                                          *
200
 *  This is a simple insertion sort algorithm.  It seems to *
201
 *  be no faster than a normal bubble sort, but using this  *
202
 *  eliminated a lot of explaining about "why?". :)         *
203
 *                                                          *
204
 ************************************************************
205
 */
206
      if (tree->last[ply] > tree->last[ply - 1] + 1) {
207
        int temp1, temp2, *tmovep, *tsortv, *end;
208
 
209
        sortv = tree->sort_value + 1;
210
        end = tree->last[ply];
211
        for (movep = tree->last[ply - 1] + 1; movep < end; movep++, sortv++) {
212
          temp1 = *movep;
213
          temp2 = *sortv;
214
          tmovep = movep - 1;
215
          tsortv = sortv - 1;
216
          while (tmovep >= tree->last[ply - 1] && *tsortv < temp2) {
217
            *(tsortv + 1) = *tsortv;
218
            *(tmovep + 1) = *tmovep;
219
            tmovep--;
220
            tsortv--;
221
          }
222
          *(tmovep + 1) = temp1;
223
          *(tsortv + 1) = temp2;
224
        }
225
      }
226
      tree->next_status[ply].last = tree->last[ply - 1];
227
/*
228
 ************************************************************
229
 *                                                          *
230
 *  Try the captures moves, which are in order based on     *
231
 *  MVV/LVA ordering.  If a larger-valued piece captures a  *
232
 *  lesser-valued piece, and Swap() says it loses material, *
233
 *  this capture will be deferred until later.              *
234
 *                                                          *
235
 ************************************************************
236
 */
237
    case CAPTURE_MOVES:
238
      while (tree->next_status[ply].remaining) {
239
        tree->curmv[ply] = *(tree->next_status[ply].last++);
240
        if (!--tree->next_status[ply].remaining)
241
          tree->next_status[ply].phase = KILLER_MOVE_1;
242
        if (pcval[Piece(tree->curmv[ply])] > pcval[Captured(tree->curmv[ply])]
243
            && Swap(tree, tree->curmv[ply], wtm) < 0)
244
          continue;
245
        *(tree->next_status[ply].last - 1) = 0;
246
        return CAPTURE_MOVES;
247
      }
248
/*
249
 ************************************************************
250
 *                                                          *
251
 *  Now, try the killer moves.  This phase tries the two    *
252
 *  killers for the current ply without generating moves,   *
253
 *  which saves time if a cutoff occurs.  After those two   *
254
 *  killers are searched, we try the killers from two plies *
255
 *  back since they have greater depth and might produce a  *
256
 *  cutoff if the current two do not.                       *
257
 *                                                          *
258
 ************************************************************
259
 */
260
    case KILLER_MOVE_1:
261
      if (!Exclude(tree, ply, tree->killers[ply].move1) &&
262
          ValidMove(tree, ply, wtm, tree->killers[ply].move1)) {
263
        tree->curmv[ply] = tree->killers[ply].move1;
264
        tree->next_status[ply].excluded_moves[tree->next_status[ply].
265
            num_excluded++]
266
            = tree->curmv[ply];
267
        tree->next_status[ply].phase = KILLER_MOVE_2;
268
        return KILLER_MOVE_1;
269
      }
270
    case KILLER_MOVE_2:
271
      if (!Exclude(tree, ply, tree->killers[ply].move2) &&
272
          ValidMove(tree, ply, wtm, tree->killers[ply].move2)) {
273
        tree->curmv[ply] = tree->killers[ply].move2;
274
        tree->next_status[ply].excluded_moves[tree->next_status[ply].
275
            num_excluded++]
276
            = tree->curmv[ply];
277
        if (ply < 3) {
278
          tree->next_status[ply].phase = GENERATE_ALL_MOVES;
279
        } else
280
          tree->next_status[ply].phase = KILLER_MOVE_3;
281
        return KILLER_MOVE_2;
282
      }
283
    case KILLER_MOVE_3:
284
      if (!Exclude(tree, ply, tree->killers[ply - 2].move1) &&
285
          ValidMove(tree, ply, wtm, tree->killers[ply - 2].move1)) {
286
        tree->curmv[ply] = tree->killers[ply - 2].move1;
287
        tree->next_status[ply].excluded_moves[tree->next_status[ply].
288
            num_excluded++]
289
            = tree->curmv[ply];
290
        tree->next_status[ply].phase = KILLER_MOVE_4;
291
        return KILLER_MOVE_3;
292
      }
293
    case KILLER_MOVE_4:
294
      if (!Exclude(tree, ply, tree->killers[ply - 2].move2) &&
295
          ValidMove(tree, ply, wtm, tree->killers[ply - 2].move2)) {
296
        tree->curmv[ply] = tree->killers[ply - 2].move2;
297
        tree->next_status[ply].excluded_moves[tree->next_status[ply].
298
            num_excluded++]
299
            = tree->curmv[ply];
300
        tree->next_status[ply].phase = GENERATE_ALL_MOVES;
301
        return KILLER_MOVE_4;
302
      }
303
/*
304
 ************************************************************
305
 *                                                          *
306
 *  Now, generate all non-capturing moves, which get added  *
307
 *  to the move list behind any captures we did not search. *
308
 *                                                          *
309
 ************************************************************
310
 */
311
    case GENERATE_ALL_MOVES:
312
      tree->last[ply] = GenerateNoncaptures(tree, ply, wtm, tree->last[ply]);
313
      tree->next_status[ply].phase = REMAINING_MOVES;
314
      tree->next_status[ply].last = tree->last[ply - 1];
315
/*
316
 ************************************************************
317
 *                                                          *
318
 *  Then we try the rest of the set of moves, but we use    *
319
 *  Exclude() function to skip any moves we have already    *
320
 *  searched (hash or killers).                             *
321
 *                                                          *
322
 ************************************************************
323
 */
324
    case REMAINING_MOVES:
325
      for (; tree->next_status[ply].last < tree->last[ply];
326
          tree->next_status[ply].last++)
327
        if (*tree->next_status[ply].last) {
328
          if (!Exclude(tree, ply, *tree->next_status[ply].last)) {
329
            tree->curmv[ply] = *tree->next_status[ply].last++;
330
            return REMAINING_MOVES;
331
          }
332
        }
333
      return NONE;
334
    default:
335
      Print(4095, "oops!  next_status.phase is bad! [normal %d]\n",
336
          tree->next_status[ply].phase);
337
  }
338
  return NONE;
339
}
340
 
341
/* last modified 05/15/14 */
342
/*
343
 *******************************************************************************
344
 *                                                                             *
345
 *   NextRootMove() is used to select the next move from the root move list.   *
346
 *                                                                             *
347
 *******************************************************************************
348
 */
349
int NextRootMove(TREE * RESTRICT tree, TREE * RESTRICT mytree, int wtm) {
350
  int which, i;
351
  uint64_t total_nodes;
352
 
353
/*
354
 ************************************************************
355
 *                                                          *
356
 *  First, we check to see if we only have one legal move.  *
357
 *  If so, and we are not pondering, we stop after a short  *
358
 *  search, saving time, but making sure we have something  *
359
 *  to ponder.                                              *
360
 *                                                          *
361
 ************************************************************
362
 */
363
  if (!annotate_mode && !pondering && !booking && n_root_moves == 1 &&
364
      iteration_depth > 4) {
365
    abort_search = 1;
366
    return NONE;
367
  }
368
/*
369
 ************************************************************
370
 *                                                          *
371
 *  For the moves at the root of the tree, the list has     *
372
 *  already been generated and sorted.                      *
373
 *                                                          *
374
 *  We simply have to find the first move that has a zero   *
375
 *  "already searched" flag and choose that one.  We do set *
376
 *  the "already searched" flag for this move before we     *
377
 *  return so that it won't be searched again in another    *
378
 *  thread.                                                 *
379
 *                                                          *
380
 ************************************************************
381
 */
382
  for (which = 0; which < n_root_moves; which++)
383
    if (!(root_moves[which].status & 8)) {
384
      if (search_move) {
385
        if (root_moves[which].move != search_move) {
386
          root_moves[which].status |= 8;
387
          continue;
388
        }
389
      }
390
      tree->curmv[1] = root_moves[which].move;
391
      root_moves[which].status |= 8;
392
/*
393
 ************************************************************
394
 *                                                          *
395
 *  We have found a move to search.  If appropriate, we     *
396
 *  display this move, along with the time and information  *
397
 *  such as which move this is in the list and how many     *
398
 *  are left to search before this iteration is done, and   *
399
 *  a "status" character that shows the state of the        *
400
 *  current search ("?" means we are pondering, waiting on  *
401
 *  a move to be entered, "*" means we are searching and    *
402
 *  our clock is running).  We also display the NPS for     *
403
 *  the search, simply for information about how fast the   *
404
 *  machine is running.                                     *
405
 *                                                          *
406
 ************************************************************
407
 */
408
      if (tree->nodes_searched > noise_level && display_options & 32) {
409
        Lock(lock_io);
410
        sprintf_s(mytree->remaining_moves_text, sizeof (mytree->remaining_moves_text), "%d/%d", which + 1, // Pierre-Marie Baty -- use safe version
411
            n_root_moves);
412
        end_time = ReadClock();
413
        if (pondering)
414
          printf("         %2i   %s%7s?  ", iteration_depth,
415
              Display2Times(end_time - start_time),
416
              mytree->remaining_moves_text);
417
        else
418
          printf("         %2i   %s%7s*  ", iteration_depth,
419
              Display2Times(end_time - start_time),
420
              mytree->remaining_moves_text);
421
        if (display_options & 32 && display_options & 64)
422
          printf("%d. ", move_number);
423
        if (display_options & 32 && display_options & 64 && Flip(wtm))
424
          printf("... ");
425
        strcpy_s(mytree->root_move_text, sizeof (mytree->root_move_text), OutputMove(tree, tree->curmv[1], 1, // Pierre-Marie Baty -- use safe version
426
                wtm));
427
        total_nodes = block[0]->nodes_searched;
428
        for (i = 1; i < MAX_BLOCKS; i++)
429
          if (block[i] && block[i]->used)
430
            total_nodes += block[i]->nodes_searched;
431
        nodes_per_second = (unsigned int) (total_nodes * 100 / Max(end_time - start_time, 1)); // Pierre-Marie Baty -- added type cast
432
        i = strlen(mytree->root_move_text);
433
        i = (i < 8) ? i : 8;
434
        strncat_s(mytree->root_move_text, sizeof (mytree->root_move_text), "          ", 8 - i); // Pierre-Marie Baty -- use safe version
435
        printf("%s", mytree->root_move_text);
436
        printf("(%snps)             \r", DisplayKMB(nodes_per_second));
437
        fflush(stdout);
438
        Unlock(lock_io);
439
      }
440
/*
441
 ************************************************************
442
 *                                                          *
443
 *  Bit of a tricky exit.  If the move is not flagged as    *
444
 *  "OK to search in parallel or reduce" then we return     *
445
 *  "HASH_MOVE" which will prevent Search() from reducing   *
446
 *  the move (LMR).  Otherwise we return the more common    *
447
 *  "REMAINING_MOVES" value which allows LMR to be used on  *
448
 *  those root moves.                                       *
449
 *                                                          *
450
 ************************************************************
451
 */
452
      if ((root_moves[which].status & 4) == 0)
453
        return HASH_MOVE;
454
      else
455
        return REMAINING_MOVES;
456
    }
457
  return NONE;
458
}
459
 
460
/* last modified 05/15/14 */
461
/*
462
 *******************************************************************************
463
 *                                                                             *
464
 *   NextRootMoveParallel() is used to determine if the next root move can be  *
465
 *   searched in parallel.  If it appears to Iterate() that one of the moves   *
466
 *   following the first move might become the best move, the 'no parallel'    *
467
 *   flag is set to speed up finding the new best move.  This flag is set if   *
468
 *   this root move has an "age" value > 0 which indicates this move was the   *
469
 *   "best move" within the previous 3 search depths.  We want to search such  *
470
 *   moves as quickly as possible, prior to starting a parallel search at the  *
471
 *   root, in case this move once again becomes the best move and provides a   *
472
 *   better alpha bound.                                                       *
473
 *                                                                             *
474
 *******************************************************************************
475
 */
476
int NextRootMoveParallel(void) {
477
  int which;
478
 
479
/*
480
 ************************************************************
481
 *                                                          *
482
 *  Here we simply check the root_move status flag that is  *
483
 *  set in Iterate() after each iteration is completed.  A  *
484
 *  value of "1" indicates this move has to be searched by  *
485
 *  all processors, splitting must wait until after all     *
486
 *  such moves have been searched individually.             *
487
 *                                                          *
488
 ************************************************************
489
 */
490
  for (which = 0; which < n_root_moves; which++)
491
    if (!(root_moves[which].status & 8))
492
      break;
493
  if (which < n_root_moves && root_moves[which].status & 4)
494
    return 1;
495
  return 0;
496
}
497
 
498
/* last modified 05/15/14 */
499
/*
500
 *******************************************************************************
501
 *                                                                             *
502
 *   Exclude() searches the list of moves searched prior to generating a move  *
503
 *   list to exclude those that were searched via a hash table best move or    *
504
 *   through the killer moves for the current ply and two plies back.          *
505
 *                                                                             *
506
 *   The variable next_status[].num_excluded is the total number of non-       *
507
 *   generated moves we searched.  next_status[].remaining is initially set to *
508
 *   num_excluded, but each time an excluded move is found, the counter is     *
509
 *   decremented.  Once all excluded moves have been found, we avoid running   *
510
 *   through the list of excluded moves on each call and simply return.        *
511
 *                                                                             *
512
 *******************************************************************************
513
 */
514
int Exclude(TREE * RESTRICT tree, int ply, int move) {
515
  int i;
516
 
517
  if (tree->next_status[ply].num_excluded)
518
    for (i = 0; i < tree->next_status[ply].num_excluded; i++)
519
      if (move == tree->next_status[ply].excluded_moves[i]) {
520
        tree->next_status[ply].remaining--;
521
        return 1;
522
      }
523
  return 0;
524
}