#include "chess.h"
#include "data.h"
/* last modified 05/08/14 */
/*
*******************************************************************************
* *
* Search() is the recursive routine used to implement the alpha/beta *
* negamax search (similar to minimax but simpler to code.) Search() is *
* called whenever there is "depth" remaining so that all moves are subject *
* to searching. Search() recursively calls itself so long as there is at *
* least one ply of depth left, otherwise it calls Quiesce() instead. *
* *
*******************************************************************************
*/
int Search(TREE * RESTRICT tree, int alpha, int beta, int side, int depth,
int ply, int do_null) {
ROOT_MOVE temp_rm;
uint64_t start_nodes = tree->nodes_searched;
int first_tried = 0, moves_searched = 0, repeat = 0;
int original_alpha = alpha, value = 0, t_beta = beta;
int extend, reduce, max_reduce, i;
int pv_node = alpha != beta - 1;
/*
************************************************************
* *
* Step 1. Check to see if we have searched enough nodes *
* that it is time to peek at how much time has been used, *
* or if is time to check for operator keyboard input. *
* This is usually enough nodes to force a time/input *
* check about once per second, except when the target *
* time per move is very small, in which case we try to *
* check the time more frequently. *
* *
* Note that we check input or time-out in thread 0. This *
* makes the code simpler and eliminates some problematic *
* race conditions. *
* *
************************************************************
*/
#if defined(NODES)
if (--temp_search_nodes <= 0) {
abort_search = 1;
return 0;
}
#endif
if (tree->thread_id == 0) {
if (--next_time_check <= 0) {
next_time_check = nodes_between_time_checks;
if (TimeCheck(tree, 1)) {
abort_search = 1;
return 0;
}
if (CheckInput()) {
Interrupt(ply);
if (abort_search)
return 0;
}
}
}
if (ply >= MAXPLY - 1)
return beta;
/*
************************************************************
* *
* Step 2. Check for draw by repetition, which includes *
* 50 move draws also. This is the quickest way to get *
* out of further searching, with minimal effort. This *
* and the next two steps are skipped for moves at the *
* root (ply = 1). *
* *
************************************************************
*/
if (ply > 1) {
if ((repeat = Repeat(tree, ply))) {
value = DrawScore(side);
if (value < beta)
SavePV(tree, ply, 0);
#if defined(TRACE)
if (ply <= trace_level)
printf("draw by repetition detected, ply=%d.\n", ply
);
#endif
return value;
}
/*
************************************************************
* *
* Step 3. Check the transposition/refutation (hash) *
* table to see if we have searched this position *
* previously and still have the results available. We *
* might get a real score, or a bound, or perhaps only a *
* good move to try first. The possible results are: *
* *
* 1. HashProbe() returns "HASH_HIT". This terminates *
* the search instantly and we simply return the value *
* found in the hash table. This value is simply the *
* value we found when we did a real search in this *
* position previously, and HashProbe() verifies that the *
* value is useful based on draft and current bounds. *
* *
* 2. HashProbe() returns "AVOID_NULL_MOVE" which means *
* the hashed score/bound was no good, but it indicated *
* that trying a null-move in this position would be a *
* waste of time since it will likely fail low, not high. *
* *
* 3. HashProbe() returns "HASH_MISS" when forces us to *
* do a normal search to resolve this node. *
* *
************************************************************
*/
switch (HashProbe(tree, ply, depth, side, alpha, beta, &value)) {
case HASH_HIT:
return value;
case AVOID_NULL_MOVE:
do_null = 0;
case HASH_MISS:
break;
}
/*
************************************************************
* *
* Step 4. Now it's time to try a probe into the endgame *
* tablebase files. This is done if we notice that there *
* are 6 or fewer pieces left on the board. EGTB_use *
* tells us how many pieces to probe on. Note that this *
* can be zero when trying to swindle the opponent, so *
* that no probes are done since we know it is a draw. *
* This is another way to get out of the search quickly, *
* but not as quickly as the previous steps since this can *
* result in an I/O operation. *
* *
* Note that in "swindle mode" this can be turned off by *
* Iterate() setting "EGTB_use = 0" so that we won't probe *
* the EGTBs since we are searching only the root moves *
* that lead to a draw and we want to play the move that *
* makes the draw more difficult to reach by the opponent *
* to give him a chance to make a mistake. *
* *
* Another special case is that we slightly fudge the *
* score for draws. In a normal circumstance, draw=0.00 *
* since it is "equal". However, here we add 0.01 if *
* white has more material, or subtract 0.01 if black has *
* more material, since in a drawn KRP vs KR we would *
* prefer to have the KRP side since the opponent can make *
* a mistake and convert the draw to a loss. *
* *
************************************************************
*/
#if !defined(NOEGTB)
if (depth > 6 && TotalAllPieces <= EGTB_use &&
Castle(ply, white) + Castle(ply, black) == 0 &&
(CaptureOrPromote(tree->curmv[ply - 1]) || ply < 3)) {
int egtb_value;
tree->egtb_probes++;
if (EGTBProbe(tree, ply, side, &egtb_value)) {
tree->egtb_probes_successful++;
alpha = egtb_value;
if (MateScore(alpha))
alpha += (alpha > 0) ? -ply + 1 : ply;
else if (alpha == 0) {
alpha = DrawScore(side);
if (Material > 0)
alpha += (side) ? 1 : -1;
else if (Material < 0)
alpha -= (side) ? 1 : -1;
}
if (alpha < beta)
SavePV(tree, ply, 2);
return alpha;
}
}
#endif
/*
************************************************************
* *
* Step 5. We now know there is no quick way to get out *
* of here, which leaves one more possibility, although it *
* does require s search, but to a reduced depth. We *
* try a null move to see if we can get a quick cutoff *
* with only a little work. This operates as follows. *
* Instead of making a legal move, the side on move passes *
* and does nothing. The resulting position is searched *
* to a shallower depth than normal (usually 3 plies less *
* but settable by the user.) This will result in a cutoff *
* if our position is very good, but it produces the *
* cutoff much quicker since the search is far shallower *
* than a normal search that would also be likely to fail *
* high. *
* *
* This is skipped for any of the following reasons: *
* *
* 1. The side on move is in check. The null move *
* results in an illegal position. *
* 2. No more than one null move can appear in succession *
* as all this does is burn 6 plies of depth. *
* 3. The side on move has only pawns left, which makes *
* zugzwang positions more likely. *
* 4. The transposition table probe found an entry that *
* indicates that a null-move search will not fail *
* high, so we avoid the wasted effort. *
* *
************************************************************
*/
tree->inchk[ply + 1] = 0;
tree->last[ply] = tree->last[ply - 1];
if (do_null && !pv_node && depth > 1 && !tree->inchk[ply]
&& TotalPieces(side, occupied)) {
uint64_t save_hash_key;
tree->curmv[ply] = 0;
tree->phase[ply] = NULL_MOVE;
#if defined(TRACE)
if (ply <= trace_level)
Trace(tree, ply, depth, side, beta - 1, beta, "Search1", NULL_MOVE);
#endif
tree->status[ply + 1] = tree->status[ply];
Reversible(ply + 1) = 0;
save_hash_key = HashKey;
if (EnPassant(ply + 1)) {
HashEP(EnPassant(ply + 1));
EnPassant(ply + 1) = 0;
}
if (depth - null_depth - 1 > 0)
value =
-Search(tree, -beta, -beta + 1, Flip(side),
depth - null_depth - 1, ply + 1, NO_NULL);
else
value = -Quiesce(tree, -beta, -beta + 1, Flip(side), ply + 1, 1);
HashKey = save_hash_key;
if (abort_search || tree->stop)
return 0;
if (value >= beta) {
HashStore(tree, ply, depth, side, LOWER, value, tree->hash_move[ply]);
return value;
}
}
/*
************************************************************
* *
* Step 6. This step is rarely executed. It is used when *
* there is no best move from the hash table, and this is *
* a PV node, since we need a good move to search first. *
* While killers moves are good, they are not quite good *
* enough. the simplest solution is to try a shallow *
* search (depth-2) to get a move. note that when we call *
* Search() with depth-2, it, too, will not have a hash *
* move, and will therefore recursively continue this *
* process, hence the name "internal iterative deepening." *
* *
************************************************************
*/
tree->next_status[ply].phase = HASH_MOVE;
if (!tree->hash_move[ply] && depth >= 6 && do_null && ply > 1) {
if (pv_node) {
do {
tree->curmv[ply] = 0;
if (depth - 2 > 0)
value = Search(tree, alpha, beta, side, depth - 2, ply, DO_NULL);
else
value = Quiesce(tree, alpha, beta, side, ply, 1);
if (abort_search || tree->stop)
break;
if (value > alpha) {
if (value < beta) {
if ((int) tree->pv[ply - 1].pathl > ply)
tree->hash_move[ply] = tree->pv[ply - 1].path[ply];
} else
tree->hash_move[ply] = tree->curmv[ply];
tree->last[ply] = tree->last[ply - 1];
tree->next_status[ply].phase = HASH_MOVE;
}
} while (0);
}
}
}
/*
************************************************************
* *
* Step 7. Now iterate through the move list and search *
* the resulting positions. Note that Search() culls any *
* move that is not legal by using Check(). The special *
* case is that we must find one legal move to search to *
* confirm that it's not a mate or draw. *
* *
* We have three possible procedures we call here, one is *
* specific to ply=1 (NextRootMove()), the second is a *
* specific routine to escape check (NextEvasion()) and *
* the last is the normal NextMove() procedure that does *
* the usual move ordering stuff. *
* *
* Special case: if we have searched one move at root, *
* and the returned score == alpha, we want to get out of *
* here and return to Iterate() where the search bounds *
* will be adjusted. Otherwise we would search all root *
* moves and possibly fail low after expending a sig- *
* nificant amount of time. *
* *
************************************************************
*/
tree->next_status[ply].phase = HASH_MOVE;
while (1) {
if (ply > 1)
tree->phase[ply] =
(tree->inchk[ply]) ? NextEvasion(tree, ply, side) : NextMove(tree,
ply, side);
else if (moves_searched == 1 && alpha == original_alpha)
break;
else
tree->phase[ply] = NextRootMove(tree, tree, side);
if (!tree->phase[ply])
break;
#if defined(TRACE)
if (ply <= trace_level)
Trace(tree, ply, depth, side, alpha, beta, "Search2", tree->phase[ply]);
#endif
MakeMove(tree, ply, tree->curmv[ply], side);
tree->nodes_searched++;
if (tree->inchk[ply] || !Check(side))
do {
if (++moves_searched == 1)
first_tried = tree->curmv[ply];
/*
************************************************************
* *
* Step 7a. If the move to be made checks the opponent, *
* then we need to remember that he's in check and also *
* extend the depth by one ply for him to get out. Note *
* that if the move gives check, it is not a candidate for *
* either depth reduction or forward-pruning. *
* *
* We do not extend unsafe checking moves (as indicated by *
* Swap(), a SEE algorithm, since these are usually a *
* waste of time and simply blow up the tree search space. *
* *
************************************************************
*/
extend = 0;
reduce = 0;
if (Check(Flip(side))) {
tree->inchk[ply + 1] = 1;
if (SwapO(tree, tree->curmv[ply], side) <= 0) {
extend = check_depth;
tree->extensions_done++;
}
} else
tree->inchk[ply + 1] = 0;
/*
************************************************************
* *
* Step 7b. Now for the forward-pruning stuff. The idea *
* here is based on the old FUTILITY idea, where if the *
* current material + a fudge factor is lower than alpha, *
* then there is little promise in searching this move to *
* make up for the material deficit. This is not done if *
* we chose to extend this move in the previous step. *
* *
* This is a useful idea in today's 20+ ply searches, as *
* when we near the tips, if we are too far behind in *
* material, there is little time left to recover it and *
* moves that don't bring us closer to a reasonable *
* material balance can safely be skipped. It is much *
* more dangerous in shallow searches. *
* *
* We have an array of pruning margin values that are *
* indexed by depth (remaining plies left until we drop *
* into the quiescence search) and which increase with *
* depth since more depth means a greater chance of *
* bringing the score back up to alpha or beyond. If the *
* current material + the bonus is less than alpha, we *
* simply avoid searching this move at all, and skip to *
* the next move without expending any more effort. Note *
* that this is classic forward-pruning and can certainly *
* introduce errors into the search. However, cluster *
* testing has shown that this improves play in real *
* games. The current implementation only prunes in the *
* last 5 plies before quiescence, although this can be *
* tuned with the "eval" command changing the "pruning *
* depth" value to something other than 6 (test is for *
* depth < pruning depth, current value is 6 which prunes *
* in last 5 plies only). Testing shows no benefit in *
* larger values than 6, although this might change in *
* future versions as other things are modified. *
* *
* Exception: *
* *
* We do not prune if we are safely pushing a passed *
* pawn to the 6th rank, where it becomes very dangerous *
* since it can promote in two more moves. *
* *
************************************************************
*/
if (tree->phase[ply] == REMAINING_MOVES && !tree->inchk[ply]
&& !extend && moves_searched > 1) {
if (depth < pruning_depth &&
MaterialSTM(side) + pruning_margin[depth] <= alpha) {
if (Piece(tree->curmv[ply]) != pawn ||
!Passed(To(tree->curmv[ply]), side)
|| rankflip[side][Rank(To(tree->curmv[ply]))] < RANK6) {
tree->moves_fpruned++;
continue;
}
}
/*
************************************************************
* *
* Step 7c. Now it's time to try to reduce the search *
* depth if the move appears to be "poor". To reduce the *
* search, the following requirements must be met: *
* *
* (1) We must be in the REMAINING_MOVES part of the move *
* ordering, so that we have nearly given up on *
* failing high on any move. *
* *
* (2) We must not be too close to the horizon (this is *
* the LMR_remaining_depth value). *
* *
* (3) The current move must not be a checking move and *
* the side to move can not be in check. *
* *
************************************************************
*/
if (Piece(tree->curmv[ply]) != pawn ||
!Passed(To(tree->curmv[ply]), side)
|| rankflip[side][Rank(To(tree->curmv[ply]))] < RANK6) {
reduce = LMR_min_reduction;
if (moves_searched > 3)
reduce = LMR_max_reduction;
max_reduce = Max(depth - 1 - LMR_remaining_depth, 0);
if (reduce > max_reduce)
reduce = max_reduce;
if (reduce)
tree->reductions_done++;
}
}
/*
************************************************************
* *
* Step 7d. We have determined whether the depth is to *
* be changed by an extension or a reduction. If we get *
* to this point, then the move is not being pruned. So *
* off we go to a recursive search/quiescence call to work *
* our way toward a terminal node. *
* *
* There is one special-case to handle. If the depth was *
* reduced, and Search() returns a value >= beta then *
* accepting that is risky (we reduced the move as we *
* thought it was bad and expected it to fail low) so we *
* repeat the search using the original (non-reduced) *
* depth to see if the fail-high happens again. *
* *
************************************************************
*/
if (depth + extend - reduce - 1 > 0) {
value =
-Search(tree, -t_beta, -alpha, Flip(side),
depth + extend - reduce - 1, ply + 1, DO_NULL);
if (value > alpha && reduce)
value =
-Search(tree, -t_beta, -alpha, Flip(side), depth - 1, ply + 1,
DO_NULL);
} else
value = -Quiesce(tree, -t_beta, -alpha, Flip(side), ply + 1, 1);
if (abort_search || tree->stop)
break;
/*
************************************************************
* *
* Step 7e. This is the PVS re-search code. If we reach *
* this point and value > alpha and value < beta, then *
* this can not be a null-window search. We have to re- *
* search the position with the original beta value (not *
* alpha+1 as is the usual case in PVS) to see if it still *
* fails high before we treat this as a real fail-high and *
* back up the value to the previous ply. *
* *
* Special case: ply == 1. *
* *
* In this case, we need to clean up and then move the *
* best move to the top of the root move list, and return *
* back to Iterate() to let it produce the usual informa- *
* tive output and re-start the search with a new beta *
* value. We also reset the failhi_delta back to 16, *
* since an earlier fail-high or fail low in this *
* iteration could have left it at a large value. *
* *
* Last step is to build a usable PV in case this move *
* fails low on the re-search, because we do want to play *
* this move no matter what happens. *
* *
************************************************************
*/
if (value > alpha && value < beta && moves_searched > 1) {
if (ply == 1) {
alpha = value;
UnmakeMove(tree, ply, tree->curmv[ply], side);
root_beta = alpha;
failhi_delta = 16;
for (i = 0; i < n_root_moves; i++)
if (tree->curmv[1] == root_moves[i].move)
break;
if (i < n_root_moves) {
temp_rm = root_moves[i];
for (; i > 0; i--)
root_moves[i] = root_moves[i - 1];
root_moves[0] = temp_rm;
}
root_moves[0].bm_age = 4;
tree->pv[1].path[1] = tree->curmv[1];
tree->pv[1].pathl = 2;
tree->pv[1].pathh = 0;
tree->pv[1].pathd = iteration_depth;
tree->pv[0] = tree->pv[1];
return alpha;
}
if (depth + extend - 1 > 0)
value =
-Search(tree, -beta, -alpha, Flip(side), depth + extend - 1,
ply + 1, DO_NULL);
else
value = -Quiesce(tree, -beta, -alpha, Flip(side), ply + 1, 1);
if (abort_search || tree->stop)
break;
}
/*
************************************************************
* *
* Step 7f. We have completed the search/re-search and we *
* we have the final score. Now we need to check for a *
* fail-high which terminates this search instantly since *
* no further searching is required. On a fail high, we *
* need to update the killer moves, and hash table before *
* we return. *
* *
* If ply == 1, we call Output() which will dump the new *
* PV. But but we need to back up the PV to ply=0 so that *
* it will be available to tell main() which move to make. *
* *
************************************************************
*/
if (value > alpha) {
alpha = value;
if (ply == 1) {
tree->pv[1].pathv = value;
Output(tree, beta);
tree->pv[0] = tree->pv[1];
}
if (value >= beta) {
Killer(tree, ply, tree->curmv[ply]);
UnmakeMove(tree, ply, tree->curmv[ply], side);
HashStore(tree, ply, depth, side, LOWER, value, tree->curmv[ply]);
tree->fail_highs++;
if (moves_searched == 1)
tree->fail_high_first_move++;
return value;
}
}
t_beta = alpha + 1;
} while (0);
UnmakeMove(tree, ply, tree->curmv[ply], side);
if (abort_search || tree->stop)
return 0;
/*
************************************************************
* *
* Step 7g. If are doing an SMP search, and we have idle *
* processors, now is the time to get them involved. We *
* have now satisfied the "young brothers wait" condition *
* since we have searched one move. All that is left is *
* to check the split constraints to see if we are an *
* acceptable split point. *
* *
* (1) We can't split within N plies of the frontier *
* nodes to avoid excessive split overhead. *
* *
* (2) We can't split until at least M nodes have been *
* searched since this thread was last split, to *
* avoid splitting too often, mainly in endgames. *
* *
* (3) We have to have searched one legal move to avoid *
* splitting at a node where we have no legal moves *
* (the first move tried might have been illegal as *
* in when we encounter a stalemate). *
* *
* (4) If we are at ply=1, we can't split unless the *
* smp_split_at_root flag is set to 1, AND the next *
* move in the ply=1 move list is not flagged as *
* "do not search in parallel" which happens when *
* this move was a best move in the last couple of *
* searches and we want all processors on it at once *
* to get a score back quicker. *
* *
* (5) if the variable smp_split is > 0, we have idle *
* threads that can help, however if smp_split < 0, *
* we are already doing a split on another thread *
* so there is no point in waiting to try one here. *
* *
* SearchParallel() primarily contains steps 7 through 7f *
* which is the main search loop. We do the final clean- *
* up below when either we finish the search normally or *
* a parallel search completes and returns to this point. *
* *
* Special case: we do not split if we are at ply=1 and *
* alpha == original_alpha. That means the first move *
* failed low, and we are going to exit search and return *
* to Iterate() to report this. *
* *
* One potential problem is that multiple threads can get *
* to this point at the same time, and they all stack up *
* waiting to grab the lock_smp lock that protects the *
* split operation. I now have a new lock, lock_split, *
* to try to limit this wasted time. A thread now has to *
* acquire that lock, and then it tests the smp_split *
* to see if a split STILL needs to be done. If not, we *
* release the lock and move on, rather than waiting on *
* main lock_smp lock. *
* *
* If we acquire the lock_split lock AND we notice that *
* smp_split is still set to 1, we quickly set smp_split *
* to zero (-1) so that other threads will bug out rather *
* than trying to split and end up in a queue behind us, *
* waiting while we split and they try to split and fail. *
* We release lock_split to eliminate the log-jam of *
* threads waiting to split and get them back into their *
* normal searches, and jump right into Thread(). *
* *
* The smp_split = -1 has a complex meaning, but simply *
* stated it means "split currently in progress". Here, *
* that means "do not attempt a split since we are already *
* in the middle of one." It also tells individual *
* threads to not change smp_split to 1 when they become *
* idle, because with a split in progress, it is likely we *
* will pick them up automatically. *
* *
************************************************************
*/
#if (CPUS > 1)
if (smp_split > 0 && depth >= smp_min_split_depth && moves_searched &&
tree->nodes_searched - start_nodes > smp_split_nodes && (ply > 1 ||
(smp_split_at_root && NextRootMoveParallel() &&
alpha != original_alpha)))
do {
Lock(lock_split);
if (smp_split <= 0) {
Unlock(lock_split);
break;
}
smp_split = -1;
Unlock(lock_split);
tree->alpha = alpha;
tree->beta = beta;
tree->value = alpha;
tree->side = side;
tree->ply = ply;
tree->depth = depth;
tree->moves_searched = moves_searched;
if (Thread(tree)) {
if (abort_search || tree->stop)
return 0;
if (tree->thread_id == 0 && CheckInput())
Interrupt(ply);
value = tree->value;
if (value > alpha) {
if (value >= beta) {
HashStore(tree, ply, depth, side, LOWER, value, tree->cutmove);
return value;
}
alpha = value;
break;
}
}
} while (0);
#endif
}
/*
************************************************************
* *
* Step 8. All moves have been searched. If none were *
* legal, return either MATE or DRAW depending on whether *
* the side to move is in check or not. *
* *
************************************************************
*/
if (abort_search || tree->stop)
return 0;
if (moves_searched == 0) {
value = (Check(side)) ? -(MATE - ply) : DrawScore(side);
if (value >= alpha && value < beta) {
SavePV(tree, ply, 0);
#if defined(TRACE)
if (ply <= trace_level)
printf("Search() no moves! ply=%d\n", ply
);
#endif
}
return value;
} else {
int bestmove, type;
bestmove =
(alpha == original_alpha) ? first_tried : tree->pv[ply].path[ply];
type = (alpha == original_alpha) ? UPPER : EXACT;
if (repeat == 2 && alpha != -(MATE - ply - 1)) {
value = DrawScore(side);
if (value < beta)
SavePV(tree, ply, 0);
#if defined(TRACE)
if (ply <= trace_level)
printf("draw by 50 move rule detected, ply=%d.\n", ply
);
#endif
return value;
} else if (alpha != original_alpha) {
tree->pv[ply - 1] = tree->pv[ply];
tree->pv[ply - 1].path[ply - 1] = tree->curmv[ply - 1];
Killer(tree, ply, tree->pv[ply].path[ply]);
}
HashStore(tree, ply, depth, side, type, alpha, bestmove);
return alpha;
}
}
/* last modified 05/08/14 */
/*
*******************************************************************************
* *
* SearchParallel() is the recursive routine used to implement alpha/beta *
* negamax search using parallel threads. When this code is called, the *
* first move has already been searched, so all that is left is to search *
* the remainder of the moves and then return. Note that the hash table and *
* such can't be modified here since this only represents a part of the *
* search at this ply. All of that is deferred until we return and reach *
* the original instance of Search() where we have the complete results from *
* all the threads that are helping here. *
* *
*******************************************************************************
*/
int SearchParallel(TREE * RESTRICT tree, int alpha, int beta, int value,
int side, int depth, int ply) {
ROOT_MOVE temp_rm;
int extend, reduce, max_reduce, i;
/*
************************************************************
* *
* Step 7. Now we continue to iterate through the move *
* list and search the resulting positions. Note that *
* SearchParallel() culls any move that is not legal by *
* using Check(). The special case mentioned in Search() *
* is not an issue here as we don't do a parallel split *
* until we have searched one legal move. *
* *
* We have three possible procedures we call here, one is *
* specific to ply=1 (NextRootMove()), the second is a *
* specific routine to escape check (NextEvasion()) and *
* the last is the normal NextMove() procedure that does *
* the usual move ordering stuff. *
* *
************************************************************
*/
while (1) {
Lock(tree->parent->lock);
if (ply > 1)
tree->phase[ply] =
(tree->inchk[ply]) ? NextEvasion((TREE *) tree->parent, ply,
side) : NextMove((TREE *)
tree->parent, ply, side);
else
tree->phase[ply] = NextRootMove(tree->parent, tree, side);
tree->curmv[ply] = tree->parent->curmv[ply];
Unlock(tree->parent->lock);
if (!tree->phase[ply])
break;
#if defined(TRACE)
if (ply <= trace_level)
Trace(tree, ply, depth, side, alpha, beta, "SearchParallel",
tree->phase[ply]);
#endif
MakeMove(tree, ply, tree->curmv[ply], side);
tree->nodes_searched++;
if (tree->inchk[ply] || !Check(side))
do {
tree->parent->moves_searched++;
/*
************************************************************
* *
* Step 7a. If the move to be made checks the opponent, *
* then we need to remember that he's in check and also *
* extend the depth by one ply for him to get out. Note *
* that if the move gives check, it is not a candidate for *
* either depth reduction or forward-pruning. *
* *
* We do not extend unsafe checking moves (as indicated by *
* Swap(), a SEE algorithm, since these are usually a *
* waste of time and simply blow up the tree search space. *
* *
************************************************************
*/
extend = 0;
reduce = 0;
if (Check(Flip(side))) {
tree->inchk[ply + 1] = 1;
if (SwapO(tree, tree->curmv[ply], side) <= 0) {
extend = check_depth;
tree->extensions_done++;
}
} else
tree->inchk[ply + 1] = 0;
/*
************************************************************
* *
* Step 7b. Now for the forward-pruning stuff. The idea *
* here is based on the old FUTILITY idea, where if the *
* current material + a fudge factor is lower than alpha, *
* then there is little promise in searching this move to *
* make up for the material deficit. This is not done if *
* we chose to extend this move in the previous step. *
* *
* This is a useful idea in today's 20+ ply searches, as *
* when we near the tips, if we are too far behind in *
* material, there is little time left to recover it and *
* moves that don't bring us closer to a reasonable *
* material balance can safely be skipped. It is much *
* more dangerous in shallow searches. *
* *
* We have an array of pruning margin values that are *
* indexed by depth (remaining plies left until we drop *
* into the quiescence search) and which increase with *
* depth since more depth means a greater chance of *
* bringing the score back up to alpha or beyond. If the *
* current material + the bonus is less than alpha, we *
* simply avoid searching this move at all, and skip to *
* the next move without expending any more effort. Note *
* that this is classic forward-pruning and can certainly *
* introduce errors into the search. However, cluster *
* testing has shown that this improves play in real *
* games. The current implementation only prunes in the *
* last 5 plies before quiescence, although this can be *
* tuned with the "eval" command changing the "pruning *
* depth" value to something other than 6 (test is for *
* depth < pruning depth, current value is 6 which prunes *
* in last 5 plies only). Testing shows no benefit in *
* larger values than 6, although this might change in *
* future versions as other things are modified. *
* *
* Exception: *
* *
* We do not prune if we are safely pushing a passed *
* pawn to the 6th rank, where it becomes very dangerous *
* since it can promote in two more moves. *
* *
************************************************************
*/
if (tree->phase[ply] == REMAINING_MOVES && !tree->inchk[ply]
&& !extend && tree->parent->moves_searched > 1) {
if (depth < pruning_depth &&
MaterialSTM(side) + pruning_margin[depth] <= alpha) {
if (Piece(tree->curmv[ply]) != pawn ||
!Passed(To(tree->curmv[ply]), side)
|| rankflip[side][Rank(To(tree->curmv[ply]))] < RANK6) {
tree->moves_fpruned++;
continue;
}
}
/*
************************************************************
* *
* Step 7c. Now it's time to try to reduce the search *
* depth if the move appears to be "poor". To reduce the *
* search, the following requirements must be met: *
* *
* (1) We must be in the REMAINING_MOVES part of the move *
* ordering, so that we have nearly given up on *
* failing high on any move. *
* *
* (2) We must not be too close to the horizon (this is *
* the LMR_remaining_depth value). *
* *
* (3) The current move must not be a checking move and *
* the side to move can not be in check. *
* *
************************************************************
*/
if (Piece(tree->curmv[ply]) != pawn ||
!Passed(To(tree->curmv[ply]), side)
|| rankflip[side][Rank(To(tree->curmv[ply]))] < RANK6) {
reduce = LMR_min_reduction;
if (tree->parent->moves_searched > 3)
reduce = LMR_max_reduction;
max_reduce = Max(depth - 1 - LMR_remaining_depth, 0);
if (reduce > max_reduce)
reduce = max_reduce;
if (reduce)
tree->reductions_done++;
}
}
/*
************************************************************
* *
* Step 7d. We have determined whether the depth is to *
* be changed by an extension or a reduction. If we get *
* to this point, then the move is not being pruned. So *
* off we go to a recursive search/quiescence call to work *
* our way toward a terminal node. *
* *
* There is one special-cases to handle. If the depth was *
* reduced, and Search() returns a value >= beta then *
* accepting that is risky (we reduced the move as we *
* thought it was bad and expected it to fail low) so we *
* repeat the search using the original (non-reduced) *
* depth to see if the fail-high happens again. *
* *
************************************************************
*/
if (depth + extend - reduce - 1 > 0) {
value =
-Search(tree, -alpha - 1, -alpha, Flip(side),
depth + extend - reduce - 1, ply + 1, DO_NULL);
if (value > alpha && reduce)
value =
-Search(tree, -alpha - 1, -alpha, Flip(side), depth - 1,
ply + 1, DO_NULL);
} else
value = -Quiesce(tree, -alpha - 1, -alpha, Flip(side), ply + 1, 1);
if (abort_search || tree->stop)
break;
/*
************************************************************
* *
* Step 7e. This is the PVS re-search code. If we reach *
* this point and value > alpha and value < beta, then *
* this can not be a null-window search. We have to re- *
* search the position with the original beta value (not *
* alpha+1 as is the usual case in PVS) to see if it still *
* fails high before we treat this as a real fail-high and *
* back up the value to the previous ply. *
* *
* Special case: ply == 1. *
* *
* In this case, we need to clean up and then move the *
* best move to the top of the root move list, and then *
* return back to the normal Search(), which will then *
* return back to Iterate() to let it produce the usual *
* informative output and re-start the search with a new *
* beta value. We also reset the failhi_delta back to 16, *
* since an earlier fail-high or fail low in this *
* iteration could have left it at a large value. *
* *
* Last step is to build a usable PV in case this move *
* fails low on the re-search, because we do want to play *
* this move no matter what happens. *
* *
************************************************************
*/
if (value > alpha && value < beta) {
if (ply == 1) {
int proc;
alpha = value;
parallel_aborts++;
UnmakeMove(tree, ply, tree->curmv[ply], side);
Lock(lock_smp);
Lock(tree->parent->lock);
if (!tree->stop) {
for (proc = 0; proc < smp_max_threads; proc++)
if (tree->parent->siblings[proc] && proc != tree->thread_id)
ThreadStop(tree->parent->siblings[proc]);
root_beta = alpha;
failhi_delta = 16;
Lock(lock_root);
for (i = 0; i < n_root_moves; i++)
if (tree->curmv[1] == root_moves[i].move)
break;
if (i < n_root_moves) {
temp_rm = root_moves[i];
for (; i > 0; i--)
root_moves[i] = root_moves[i - 1];
root_moves[0] = temp_rm;
}
root_moves[0].bm_age = 4;
Unlock(lock_root);
tree->pv[1].path[1] = tree->curmv[1];
tree->pv[1].pathl = 2;
tree->pv[1].pathh = 0;
tree->pv[1].pathd = iteration_depth;
tree->pv[0] = tree->pv[1];
}
Unlock(tree->parent->lock);
Unlock(lock_smp);
return alpha;
}
if (depth + extend - 1 > 0)
value =
-Search(tree, -beta, -alpha, Flip(side), depth + extend - 1,
ply + 1, DO_NULL);
else
value = -Quiesce(tree, -beta, -alpha, Flip(side), ply + 1, 1);
if (abort_search || tree->stop)
break;
}
/*
************************************************************
* *
* Step 7f. We have completed the search/re-search and we *
* we have the final score. Now we need to check for a *
* fail-high which terminates this search instantly since *
* no further searching is required. On a fail high, we *
* need to update the killer moves, and hash table before *
* we return. *
* *
* Note that we can not produce a new PV here. At best, *
* we can produce a fail-high which will abort other *
* threads at this node (wasting time). *
* *
* Special case: If ply == 1, and we fail high on the *
* null-window search, we simply abort the search and then *
* return to the normal search, which will back us out to *
* Iterate() and inform the user and re-start the search. *
* *
* We then stop all threads (except the current thread *
* that is dealing with the fail high) since we are going *
* to back out quickly and then start a new search from *
* the root position. The split-block sibling ids lets us *
* know which threads should be stopped, and since we are *
* at the root (ply == 1) that essentially means "all *
* threads except for this one." *
* *
************************************************************
*/
if (value > alpha) {
alpha = value;
if (value >= beta) {
int proc;
parallel_aborts++;
UnmakeMove(tree, ply, tree->curmv[ply], side);
Lock(lock_smp);
Lock(tree->parent->lock);
if (!tree->stop)
for (proc = 0; proc < smp_max_threads; proc++)
if (tree->parent->siblings[proc] && proc != tree->thread_id)
ThreadStop(tree->parent->siblings[proc]);
Unlock(tree->parent->lock);
Unlock(lock_smp);
return alpha;
}
}
} while (0);
UnmakeMove(tree, ply, tree->curmv[ply], side);
if (abort_search || tree->stop)
break;
}
/*
************************************************************
* *
* Step 8. We are doing an SMP search, so there are no *
* "end-of-search" things to do. We have searched all the *
* remaining moves at this ply in parallel, and now return *
* and let the original search that started this sub-tree) *
* clean up, and do the tests for mate/stalemate, update *
* the hash table, etc. *
* *
* As we return, we end back up in Thread() where we *
* started, which then copies the best score/etc back to *
* the parent thread. *
* *
* We do need to flag the root move we tried to search, if *
* we were stopped early due to another root move failing *
* high. Otherwise this move appears to have been *
* searched already and will not be searched again until *
* the next iteration. *
* *
************************************************************
*/
if (tree->stop && ply == 1) {
int which;
Lock(lock_root);
for (which = 0; which < n_root_moves; which++)
if (root_moves[which].move == tree->curmv[ply]) {
root_moves[which].status &= 0xf7;
break;
}
Unlock(lock_root);
}
return alpha;
}