};
template <NodeType NT>
- Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning);
+ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
template <NodeType NT>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = DEPTH_ZERO);
// high/low anymore.
while (true)
{
- bestValue = ::search<PV>(rootPos, ss, alpha, beta, rootDepth, false, false);
+ bestValue = ::search<PV>(rootPos, ss, alpha, beta, rootDepth, false);
// Bring the best move to the front. It is critical that sorting
// is done with a stable algorithm because all the values but the
// search<>() is the main search function for both PV and non-PV nodes
template <NodeType NT>
- Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning) {
+ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
// Use quiescence search when needed
if (depth < ONE_PLY)
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
inCheck = pos.checkers();
+ Color us = pos.side_to_move();
moveCount = captureCount = quietCount = ss->moveCount = 0;
bestValue = -VALUE_INFINITE;
maxValue = VALUE_INFINITE;
beta = std::min(mate_in(ss->ply+1), beta);
if (alpha >= beta)
return alpha;
+
+ // Check if there exists a move which draws by repetition, or an alternative
+ // earlier move to this position.
+ if ( pos.rule50_count() >= 3
+ && alpha < VALUE_DRAW
+ && pos.has_game_cycle(ss->ply))
+ {
+ alpha = VALUE_DRAW;
+ if (alpha >= beta)
+ return alpha;
+ }
}
assert(0 <= ss->ply && ss->ply < MAX_PLY);
else if (!pos.capture_or_promotion(ttMove))
{
int penalty = -stat_bonus(depth);
- thisThread->mainHistory[pos.side_to_move()][from_to(ttMove)] << penalty;
+ thisThread->mainHistory[us][from_to(ttMove)] << penalty;
update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
}
}
}
}
- // Step 6. Evaluate the position statically
+ // Step 6. Static evaluation of the position
if (inCheck)
{
ss->staticEval = eval = VALUE_NONE;
improving = false;
- goto moves_loop;
+ goto moves_loop; // Skip early pruning when in check
}
else if (ttHit)
{
ss->staticEval, TT.generation());
}
- improving = ss->staticEval >= (ss-2)->staticEval
- ||(ss-2)->staticEval == VALUE_NONE;
-
- if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move()))
- goto moves_loop;
-
- // Step 7. Razoring (skipped when in check, ~2 Elo)
+ // Step 7. Razoring (~2 Elo)
if ( !PvNode
&& depth < 3 * ONE_PLY
&& eval <= alpha - RazorMargin[depth / ONE_PLY])
return v;
}
- // Step 8. Futility pruning: child node (skipped when in check, ~30 Elo)
+ improving = ss->staticEval >= (ss-2)->staticEval
+ || (ss-2)->staticEval == VALUE_NONE;
+
+ // Step 8. Futility pruning: child node (~30 Elo)
if ( !rootNode
&& depth < 7 * ONE_PLY
&& eval - futility_margin(depth, improving) >= beta
// Step 9. Null move search with verification search (~40 Elo)
if ( !PvNode
+ && (ss-1)->currentMove != MOVE_NULL
+ && (ss-1)->statScore < 22500
&& eval >= beta
&& ss->staticEval >= beta - 36 * depth / ONE_PLY + 225
- && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->nmp_odd))
+ && !excludedMove
+ && pos.non_pawn_material(us)
+ && (ss->ply > thisThread->nmp_min_ply || us != thisThread->nmp_color))
{
assert(eval - beta >= 0);
pos.do_null_move(st);
- Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode, true);
+ Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode);
pos.undo_null_move();
if (nullValue >= VALUE_MATE_IN_MAX_PLY)
nullValue = beta;
- if (abs(beta) < VALUE_KNOWN_WIN && (depth < 12 * ONE_PLY || thisThread->nmp_ply))
+ if (thisThread->nmp_min_ply || (abs(beta) < VALUE_KNOWN_WIN && depth < 12 * ONE_PLY))
return nullValue;
- // Do verification search at high depths. Disable null move pruning
- // for side to move for the first part of the remaining search tree.
- thisThread->nmp_ply = ss->ply + 3 * (depth-R) / 4;
- thisThread->nmp_odd = ss->ply % 2;
+ assert(!thisThread->nmp_min_ply); // Recursive verification is not allowed
- Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false, true);
+ // Do verification search at high depths, with null move pruning disabled
+ // for us, until ply exceeds nmp_min_ply.
+ thisThread->nmp_min_ply = ss->ply + 3 * (depth-R) / 4 - 1;
+ thisThread->nmp_color = us;
- thisThread->nmp_odd = thisThread->nmp_ply = 0;
+ Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
+
+ thisThread->nmp_min_ply = 0;
if (v >= beta)
return nullValue;
}
}
- // Step 10. ProbCut (skipped when in check, ~10 Elo)
+ // Step 10. ProbCut (~10 Elo)
// If we have a good enough capture and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move.
if ( !PvNode
&& depth >= 5 * ONE_PLY
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
{
- assert(is_ok((ss-1)->currentMove));
-
Value rbeta = std::min(beta + 216 - 48 * improving, VALUE_INFINITE);
MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory);
int probCutCount = 0;
// If the qsearch held perform the regular search
if (value >= rbeta)
- value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode, false);
+ value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode);
pos.undo_move(move);
}
}
- // Step 11. Internal iterative deepening (skipped when in check, ~2 Elo)
+ // Step 11. Internal iterative deepening (~2 Elo)
if ( depth >= 8 * ONE_PLY
&& !ttMove)
{
Depth d = 3 * depth / 4 - 2 * ONE_PLY;
- search<NT>(pos, ss, alpha, beta, d, cutNode, true);
+ search<NT>(pos, ss, alpha, beta, d, cutNode);
tte = TT.probe(posKey, ttHit);
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
{
Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE);
ss->excludedMove = move;
- value = search<NonPV>(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode, true);
+ value = search<NonPV>(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode);
ss->excludedMove = MOVE_NONE;
if (value < rBeta)
// Step 14. Pruning at shallow depth (~170 Elo)
if ( !rootNode
- && pos.non_pawn_material(pos.side_to_move())
+ && pos.non_pawn_material(us)
&& bestValue > VALUE_MATED_IN_MAX_PLY)
{
if ( !captureOrPromotion
&& !pos.see_ge(make_move(to_sq(move), from_sq(move))))
r -= 2 * ONE_PLY;
- ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)]
+ ss->statScore = thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
Depth d = std::max(newDepth - r, ONE_PLY);
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true, false);
+ value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
doFullDepthSearch = (value > alpha && d != newDepth);
}
// Step 17. Full depth search when LMR is skipped or fails high
if (doFullDepthSearch)
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode, false);
+ value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
// For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the
(ss+1)->pv = pv;
(ss+1)->pv[0] = MOVE_NONE;
- value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false, false);
+ value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
}
// Step 18. Undo move
else
{
assert(value >= beta); // Fail high
- ss->statScore = std::max(ss->statScore, 0);
+ ss->statScore = 0;
break;
}
}
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY));
}
// Bonus for prior countermove that caused the fail low
- else if ( depth >= 3 * ONE_PLY
+ else if ( (depth >= 3 * ONE_PLY || PvNode)
&& !pos.captured_piece()
&& is_ok((ss-1)->currentMove))
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));