#include "chess.h"
#include "data.h"
/* last modified 05/15/14 */
/*
*******************************************************************************
* *
* NextEvasion() is used to select the next move from the current move list *
* when the king is in check. We use GenerateEvasions() (in movgen.c) to *
* generate a list of moves that get us out of check. The only unusual *
* feature is that these moves are all legal and do not need to be vetted *
* with the usual Check() function to test for legality. *
* *
*******************************************************************************
*/
int NextEvasion(TREE * RESTRICT tree, int ply, int wtm) {
int *movep, *sortv;
switch (tree->next_status[ply].phase) {
/*
************************************************************
* *
* First try the transposition table move (which might be *
* the principal variation move as we first move down the *
* tree). If it is good enough to cause a cutoff, we *
* avoided the overhead of generating legal moves. *
* *
************************************************************
*/
case HASH_MOVE:
if (tree->hash_move[ply]) {
tree->next_status[ply].phase = GENERATE_ALL_MOVES;
tree->curmv[ply] = tree->hash_move[ply];
if (ValidMove(tree, ply, wtm, tree->curmv[ply]))
return HASH_MOVE;
#if defined(DEBUG)
else
Print(128, "bad move from hash table, ply=%d\n", ply);
#endif
}
/*
************************************************************
* *
* Now generate all legal moves by using the special *
* GenerateCheckEvasions() procedure. Then sort the moves *
* based on the expected gain or loss. this is deferred *
* until now to see if the hash move is good enough to *
* produce a cutoff and avoid this effort. *
* *
* Once we confirm that the move does not lose any *
* material, we sort these non-losing moves into MVV/LVA *
* order which appears to be a slightly faster move *
* ordering idea. Unsafe evasion moves are sorted using *
* the original Swap() score to keep them last in the move *
* list. Note that this move list contains both captures *
* and non-captures. We try the safe captures first due *
* to the way the sort score is computed. *
* *
************************************************************
*/
case GENERATE_ALL_MOVES:
tree->last[ply] =
GenerateCheckEvasions(tree, ply, wtm, tree->last[ply - 1]);
tree->next_status[ply].phase = REMAINING_MOVES;
for (movep = tree->last[ply - 1], sortv = tree->sort_value;
movep < tree->last[ply]; movep++, sortv++)
if (tree->hash_move[ply] && *movep == tree->hash_move[ply]) {
*sortv = -999999;
*movep = 0;
} else {
if (pcval[Piece(*movep)] <= pcval[Captured(*movep)])
*sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
else {
*sortv = Swap(tree, *movep, wtm);
if (*sortv >= 0)
*sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
}
}
/*
************************************************************
* *
* This is a simple insertion sort algorithm. It seems be *
* be no faster than a normal bubble sort, but using this *
* eliminated a lot of explaining about "why?". :) *
* *
************************************************************
*/
if (tree->last[ply] > tree->last[ply - 1] + 1) {
int temp1, temp2, *tmovep, *tsortv, *end;
sortv = tree->sort_value + 1;
end = tree->last[ply];
for (movep = tree->last[ply - 1] + 1; movep < end; movep++, sortv++) {
temp1 = *movep;
temp2 = *sortv;
tmovep = movep - 1;
tsortv = sortv - 1;
while (tmovep >= tree->last[ply - 1] && *tsortv < temp2) {
*(tsortv + 1) = *tsortv;
*(tmovep + 1) = *tmovep;
tmovep--;
tsortv--;
}
*(tmovep + 1) = temp1;
*(tsortv + 1) = temp2;
}
}
tree->next_status[ply].last = tree->last[ply - 1];
/*
************************************************************
* *
* Now try the moves in sorted order. *
* *
************************************************************
*/
case REMAINING_MOVES:
for (; tree->next_status[ply].last < tree->last[ply];
tree->next_status[ply].last++)
if (*tree->next_status[ply].last) {
tree->curmv[ply] = *tree->next_status[ply].last++;
return REMAINING_MOVES;
}
return NONE;
default:
printf("oops! next_status.phase is bad! [evasion %d]\n",
tree->next_status[ply].phase);
}
return NONE;
}
/* last modified 05/15/14 */
/*
*******************************************************************************
* *
* NextMove() is used to select the next move from the current move list. *
* *
* The "excluded move" code below simply collects any moves that were *
* searched without being generated (hash move and up to 4 killers). We *
* save them in the NEXT structure and make sure to exclude them when *
* searching after a move generation to avoid the duplicated effort. *
* *
*******************************************************************************
*/
int NextMove(TREE * RESTRICT tree, int ply, int wtm) {
int *movep, *sortv;
switch (tree->next_status[ply].phase) {
/*
************************************************************
* *
* First, try the transposition table move (which will be *
* the principal variation move as we first move down the *
* tree). *
* *
************************************************************
*/
case HASH_MOVE:
tree->next_status[ply].num_excluded = 0;
tree->next_status[ply].phase = GENERATE_CAPTURE_MOVES;
if (tree->hash_move[ply]) {
tree->curmv[ply] = tree->hash_move[ply];
tree->next_status[ply].excluded_moves[tree->next_status[ply].
num_excluded++]
= tree->curmv[ply];
if (ValidMove(tree, ply, wtm, tree->curmv[ply]))
return HASH_MOVE;
#if defined(DEBUG)
else
Print(128, "bad move from hash table, ply=%d\n", ply);
#endif
}
/*
************************************************************
* *
* Generate captures and sort them based on the simple *
* MVV/LVA ordering where we try to capture the most *
* valuable victim piece possible, using the least *
* valuable attacking piece possible. Later we will test *
* to see if the capture appears to lose material and we *
* will defer searching it until later. *
* *
************************************************************
*/
case GENERATE_CAPTURE_MOVES:
tree->next_status[ply].phase = CAPTURE_MOVES;
tree->last[ply] = GenerateCaptures(tree, ply, wtm, tree->last[ply - 1]);
tree->next_status[ply].remaining = 0;
for (movep = tree->last[ply - 1], sortv = tree->sort_value;
movep < tree->last[ply]; movep++, sortv++)
if (*movep == tree->hash_move[ply]) {
*sortv = -999999;
*movep = 0;
tree->next_status[ply].num_excluded = 0;
} else {
*sortv = 128 * pcval[Captured(*movep)] - pcval[Piece(*movep)];
tree->next_status[ply].remaining++;
}
/*
************************************************************
* *
* This is a simple insertion sort algorithm. It seems to *
* be no faster than a normal bubble sort, but using this *
* eliminated a lot of explaining about "why?". :) *
* *
************************************************************
*/
if (tree->last[ply] > tree->last[ply - 1] + 1) {
int temp1, temp2, *tmovep, *tsortv, *end;
sortv = tree->sort_value + 1;
end = tree->last[ply];
for (movep = tree->last[ply - 1] + 1; movep < end; movep++, sortv++) {
temp1 = *movep;
temp2 = *sortv;
tmovep = movep - 1;
tsortv = sortv - 1;
while (tmovep >= tree->last[ply - 1] && *tsortv < temp2) {
*(tsortv + 1) = *tsortv;
*(tmovep + 1) = *tmovep;
tmovep--;
tsortv--;
}
*(tmovep + 1) = temp1;
*(tsortv + 1) = temp2;
}
}
tree->next_status[ply].last = tree->last[ply - 1];
/*
************************************************************
* *
* Try the captures moves, which are in order based on *
* MVV/LVA ordering. If a larger-valued piece captures a *
* lesser-valued piece, and Swap() says it loses material, *
* this capture will be deferred until later. *
* *
************************************************************
*/
case CAPTURE_MOVES:
while (tree->next_status[ply].remaining) {
tree->curmv[ply] = *(tree->next_status[ply].last++);
if (!--tree->next_status[ply].remaining)
tree->next_status[ply].phase = KILLER_MOVE_1;
if (pcval[Piece(tree->curmv[ply])] > pcval[Captured(tree->curmv[ply])]
&& Swap(tree, tree->curmv[ply], wtm) < 0)
continue;
*(tree->next_status[ply].last - 1) = 0;
return CAPTURE_MOVES;
}
/*
************************************************************
* *
* Now, try the killer moves. This phase tries the two *
* killers for the current ply without generating moves, *
* which saves time if a cutoff occurs. After those two *
* killers are searched, we try the killers from two plies *
* back since they have greater depth and might produce a *
* cutoff if the current two do not. *
* *
************************************************************
*/
case KILLER_MOVE_1:
if (!Exclude(tree, ply, tree->killers[ply].move1) &&
ValidMove(tree, ply, wtm, tree->killers[ply].move1)) {
tree->curmv[ply] = tree->killers[ply].move1;
tree->next_status[ply].excluded_moves[tree->next_status[ply].
num_excluded++]
= tree->curmv[ply];
tree->next_status[ply].phase = KILLER_MOVE_2;
return KILLER_MOVE_1;
}
case KILLER_MOVE_2:
if (!Exclude(tree, ply, tree->killers[ply].move2) &&
ValidMove(tree, ply, wtm, tree->killers[ply].move2)) {
tree->curmv[ply] = tree->killers[ply].move2;
tree->next_status[ply].excluded_moves[tree->next_status[ply].
num_excluded++]
= tree->curmv[ply];
if (ply < 3) {
tree->next_status[ply].phase = GENERATE_ALL_MOVES;
} else
tree->next_status[ply].phase = KILLER_MOVE_3;
return KILLER_MOVE_2;
}
case KILLER_MOVE_3:
if (!Exclude(tree, ply, tree->killers[ply - 2].move1) &&
ValidMove(tree, ply, wtm, tree->killers[ply - 2].move1)) {
tree->curmv[ply] = tree->killers[ply - 2].move1;
tree->next_status[ply].excluded_moves[tree->next_status[ply].
num_excluded++]
= tree->curmv[ply];
tree->next_status[ply].phase = KILLER_MOVE_4;
return KILLER_MOVE_3;
}
case KILLER_MOVE_4:
if (!Exclude(tree, ply, tree->killers[ply - 2].move2) &&
ValidMove(tree, ply, wtm, tree->killers[ply - 2].move2)) {
tree->curmv[ply] = tree->killers[ply - 2].move2;
tree->next_status[ply].excluded_moves[tree->next_status[ply].
num_excluded++]
= tree->curmv[ply];
tree->next_status[ply].phase = GENERATE_ALL_MOVES;
return KILLER_MOVE_4;
}
/*
************************************************************
* *
* Now, generate all non-capturing moves, which get added *
* to the move list behind any captures we did not search. *
* *
************************************************************
*/
case GENERATE_ALL_MOVES:
tree->last[ply] = GenerateNoncaptures(tree, ply, wtm, tree->last[ply]);
tree->next_status[ply].phase = REMAINING_MOVES;
tree->next_status[ply].last = tree->last[ply - 1];
/*
************************************************************
* *
* Then we try the rest of the set of moves, but we use *
* Exclude() function to skip any moves we have already *
* searched (hash or killers). *
* *
************************************************************
*/
case REMAINING_MOVES:
for (; tree->next_status[ply].last < tree->last[ply];
tree->next_status[ply].last++)
if (*tree->next_status[ply].last) {
if (!Exclude(tree, ply, *tree->next_status[ply].last)) {
tree->curmv[ply] = *tree->next_status[ply].last++;
return REMAINING_MOVES;
}
}
return NONE;
default:
Print(4095, "oops! next_status.phase is bad! [normal %d]\n",
tree->next_status[ply].phase);
}
return NONE;
}
/* last modified 05/15/14 */
/*
*******************************************************************************
* *
* NextRootMove() is used to select the next move from the root move list. *
* *
*******************************************************************************
*/
int NextRootMove(TREE * RESTRICT tree, TREE * RESTRICT mytree, int wtm) {
int which, i;
uint64_t total_nodes;
/*
************************************************************
* *
* First, we check to see if we only have one legal move. *
* If so, and we are not pondering, we stop after a short *
* search, saving time, but making sure we have something *
* to ponder. *
* *
************************************************************
*/
if (!annotate_mode && !pondering && !booking && n_root_moves == 1 &&
iteration_depth > 4) {
abort_search = 1;
return NONE;
}
/*
************************************************************
* *
* For the moves at the root of the tree, the list has *
* already been generated and sorted. *
* *
* We simply have to find the first move that has a zero *
* "already searched" flag and choose that one. We do set *
* the "already searched" flag for this move before we *
* return so that it won't be searched again in another *
* thread. *
* *
************************************************************
*/
for (which = 0; which < n_root_moves; which++)
if (!(root_moves[which].status & 8)) {
if (search_move) {
if (root_moves[which].move != search_move) {
root_moves[which].status |= 8;
continue;
}
}
tree->curmv[1] = root_moves[which].move;
root_moves[which].status |= 8;
/*
************************************************************
* *
* We have found a move to search. If appropriate, we *
* display this move, along with the time and information *
* such as which move this is in the list and how many *
* are left to search before this iteration is done, and *
* a "status" character that shows the state of the *
* current search ("?" means we are pondering, waiting on *
* a move to be entered, "*" means we are searching and *
* our clock is running). We also display the NPS for *
* the search, simply for information about how fast the *
* machine is running. *
* *
************************************************************
*/
if (tree->nodes_searched > noise_level && display_options & 32) {
Lock(lock_io);
sprintf_s(mytree->remaining_moves_text, sizeof (mytree->remaining_moves_text), "%d/%d", which + 1, // Pierre-Marie Baty -- use safe version
n_root_moves);
end_time = ReadClock();
if (pondering)
printf(" %2i %s%7s? ", iteration_depth
,
Display2Times(end_time - start_time),
mytree->remaining_moves_text);
else
printf(" %2i %s%7s* ", iteration_depth
,
Display2Times(end_time - start_time),
mytree->remaining_moves_text);
if (display_options & 32 && display_options & 64)
if (display_options & 32 && display_options & 64 && Flip(wtm))
strcpy_s(mytree->root_move_text, sizeof (mytree->root_move_text), OutputMove(tree, tree->curmv[1], 1, // Pierre-Marie Baty -- use safe version
wtm));
total_nodes = block[0]->nodes_searched;
for (i = 1; i < MAX_BLOCKS; i++)
if (block[i] && block[i]->used)
total_nodes += block[i]->nodes_searched;
nodes_per_second = (unsigned int) (total_nodes * 100 / Max(end_time - start_time, 1)); // Pierre-Marie Baty -- added type cast
i
= strlen(mytree
->root_move_text
);
i = (i < 8) ? i : 8;
strncat_s(mytree->root_move_text, sizeof (mytree->root_move_text), " ", 8 - i); // Pierre-Marie Baty -- use safe version
printf("%s", mytree
->root_move_text
);
printf("(%snps) \r", DisplayKMB
(nodes_per_second
));
Unlock(lock_io);
}
/*
************************************************************
* *
* Bit of a tricky exit. If the move is not flagged as *
* "OK to search in parallel or reduce" then we return *
* "HASH_MOVE" which will prevent Search() from reducing *
* the move (LMR). Otherwise we return the more common *
* "REMAINING_MOVES" value which allows LMR to be used on *
* those root moves. *
* *
************************************************************
*/
if ((root_moves[which].status & 4) == 0)
return HASH_MOVE;
else
return REMAINING_MOVES;
}
return NONE;
}
/* last modified 05/15/14 */
/*
*******************************************************************************
* *
* NextRootMoveParallel() is used to determine if the next root move can be *
* searched in parallel. If it appears to Iterate() that one of the moves *
* following the first move might become the best move, the 'no parallel' *
* flag is set to speed up finding the new best move. This flag is set if *
* this root move has an "age" value > 0 which indicates this move was the *
* "best move" within the previous 3 search depths. We want to search such *
* moves as quickly as possible, prior to starting a parallel search at the *
* root, in case this move once again becomes the best move and provides a *
* better alpha bound. *
* *
*******************************************************************************
*/
int NextRootMoveParallel(void) {
int which;
/*
************************************************************
* *
* Here we simply check the root_move status flag that is *
* set in Iterate() after each iteration is completed. A *
* value of "1" indicates this move has to be searched by *
* all processors, splitting must wait until after all *
* such moves have been searched individually. *
* *
************************************************************
*/
for (which = 0; which < n_root_moves; which++)
if (!(root_moves[which].status & 8))
break;
if (which < n_root_moves && root_moves[which].status & 4)
return 1;
return 0;
}
/* last modified 05/15/14 */
/*
*******************************************************************************
* *
* Exclude() searches the list of moves searched prior to generating a move *
* list to exclude those that were searched via a hash table best move or *
* through the killer moves for the current ply and two plies back. *
* *
* The variable next_status[].num_excluded is the total number of non- *
* generated moves we searched. next_status[].remaining is initially set to *
* num_excluded, but each time an excluded move is found, the counter is *
* decremented. Once all excluded moves have been found, we avoid running *
* through the list of excluded moves on each call and simply return. *
* *
*******************************************************************************
*/
int Exclude(TREE * RESTRICT tree, int ply, int move) {
int i;
if (tree->next_status[ply].num_excluded)
for (i = 0; i < tree->next_status[ply].num_excluded; i++)
if (move == tree->next_status[ply].excluded_moves[i]) {
tree->next_status[ply].remaining--;
return 1;
}
return 0;
}