Line data Source code
1 : #pragma once
2 :
3 : #include "definition.hpp"
4 : #include "distributed.h"
5 : #include "dynamicConfig.hpp"
6 : #include "egt.hpp"
7 : #include "evalConfig.hpp"
8 : #include "evalTools.hpp"
9 : #include "moveApply.hpp"
10 : #include "moveGen.hpp"
11 : #include "movePseudoLegal.hpp"
12 : #include "moveSort.hpp"
13 : #include "positionTools.hpp"
14 : #include "searchConfig.hpp"
15 : #include "timeMan.hpp"
16 : #include "tools.hpp"
17 : #include "transposition.hpp"
18 :
19 : #define PERIODICCHECK uint64_t(1024)
20 :
21 : // be carefull isBadCap shall only be used on moves already detected as capture !
22 400989 : [[nodiscard]] inline bool isBadCap(const Move m) { return badCapScore(m) < DynamicConfig::badCapLimit; }
23 :
24 : FORCE_FINLINE void Searcher::updateStatBetaCut(const Position & p, const Move m, const DepthType height){
25 : const MType t = Move2Type(m);
26 : assert(isValidMoveType(t));
27 :
28 : if (isCapture(t) && !isPromotion(t)) {
29 : if(isBadCap(m)) stats.incr(Stats::sid_beta_bc);
30 : else stats.incr(Stats::sid_beta_gc);
31 : }
32 : else if (isPromotion(t)){
33 : stats.incr(Stats::sid_beta_p);
34 : }
35 : else if (t == T_std || isCastling(m)) {
36 : if (sameMove(m, killerT.killers[height][0])) stats.incr(Stats::sid_beta_k1);
37 : else if (sameMove(m, killerT.killers[height][1])) stats.incr(Stats::sid_beta_k2);
38 : else if (height > 1 && sameMove(m, killerT.killers[height - 2][0])) stats.incr(Stats::sid_beta_k3);
39 : else if (height > 1 && sameMove(m, killerT.killers[height - 2][1])) stats.incr(Stats::sid_beta_k4);
40 : else if (isValidMove(p.lastMove) && sameMove(counterT.counter[Move2From(p.lastMove)][correctedMove2ToKingDest(p.lastMove)], m)) stats.incr(Stats::sid_beta_c);
41 : else stats.incr(Stats::sid_beta_q);
42 : }
43 : else{
44 : assert(false);
45 : }
46 : }
47 :
48 : FORCE_FINLINE std::tuple<DepthType, DepthType, DepthType>
49 : Searcher::depthPolicy( [[maybe_unused]] const Position & p,
50 : [[maybe_unused]] DepthType depth,
51 : [[maybe_unused]] DepthType height,
52 : [[maybe_unused]] Move m,
53 : [[maybe_unused]] const PVSData & pvsData,
54 : [[maybe_unused]] const EvalData & evalData,
55 : [[maybe_unused]] ScoreType evalScore,
56 : [[maybe_unused]] DepthType extensions,
57 : [[maybe_unused]] bool isReductible) const{
58 :
59 : // Extension toggles.
60 : constexpr bool enableCheckQuietExtension = false;
61 : constexpr bool enableInCheckExtension = false;
62 : constexpr bool enableBMExtension = false;
63 : constexpr bool enableMateThreatExtension = false;
64 : constexpr bool enableQueenThreatExtension = false;
65 : constexpr bool enableCastlingExtension = false;
66 : constexpr bool enableRecaptureExtension = false;
67 : constexpr bool enableGoodHistoryExtension = false;
68 : constexpr bool enableCMHExtension = false;
69 :
70 : int extension = 0;
71 :
72 : const auto tryExtension = [&](const bool condition, const Stats::StatId statId){
73 : if (extension == 0 && condition) {
74 : stats.incr(statId);
75 : ++extension;
76 : }
77 : };
78 :
79 : if (!pvsData.validTTmove){
80 : if constexpr (enableCheckQuietExtension) {
81 : // correspond to first move (not being the TT one) or quiet with very good score (like killers)
82 : const bool weakEarlyMove = pvsData.earlyMove || (pvsData.isQuiet && Move2Score(m) >= HISTORY_MAX);
83 : tryExtension(weakEarlyMove && pvsData.isCheck, Stats::sid_checkExtension2);
84 : }
85 : if constexpr (enableInCheckExtension) {
86 : tryExtension(pvsData.earlyMove && pvsData.isInCheck, Stats::sid_checkExtension);
87 : }
88 : if constexpr (enableBMExtension) {
89 : tryExtension(pvsData.BMextension, Stats::sid_BMExtension);
90 : }
91 : if constexpr (enableMateThreatExtension) {
92 : tryExtension(pvsData.earlyMove && pvsData.mateThreat, Stats::sid_mateThreatExtension);
93 : }
94 : if constexpr (enableQueenThreatExtension) {
95 : tryExtension(pvsData.earlyMove &&
96 : p.pieces_const<P_wq>(p.c) &&
97 : pvsData.isQuiet && PieceTools::getPieceType(p, Move2From(m)) == P_wq &&
98 : isAttacked(p, BBTools::SquareFromBitBoard(p.pieces_const<P_wq>(p.c))) &&
99 : SEE_GE(p, m, -80),
100 : Stats::sid_queenThreatExtension);
101 : }
102 : if constexpr (enableCastlingExtension) {
103 : tryExtension(isCastling(m), Stats::sid_castlingExtension);
104 : }
105 : if constexpr (enableRecaptureExtension) {
106 : tryExtension(isValidMove(p.lastMove) &&
107 : Move2Type(p.lastMove) == T_capture &&
108 : !isBadCap(m) &&
109 : correctedMove2ToKingDest(m) == correctedMove2ToKingDest(p.lastMove),
110 : Stats::sid_recaptureExtension);
111 : }
112 : if constexpr (enableGoodHistoryExtension) {
113 : tryExtension(pvsData.isQuiet &&
114 : Move2Score(m) <= HISTORY_MAX &&
115 : Move2Score(m) > SearchConfig::historyExtensionThreshold,
116 : Stats::sid_goodHistoryExtension);
117 : }
118 : if constexpr (enableCMHExtension) {
119 : tryExtension(pvsData.isQuiet &&
120 : isCMHGood(p, Move2From(m), correctedMove2ToKingDest(m), pvsData.cmhPtr, 3 * HISTORY_MAX / 4),
121 : Stats::sid_CMHExtension);
122 : }
123 : }
124 :
125 : // Reduction toggles
126 : constexpr bool enableThreadBaseReduction = false;
127 : constexpr bool enableAggressiveRandomReduction = false;
128 : constexpr bool enableMobilityBasedReduction = false;
129 : constexpr bool enableReduceLessWhenInCheck = false;
130 : constexpr bool enableReduceLessWhenGivingCheck = false;
131 : constexpr bool enableReduceLessAtPVNode = false;
132 : constexpr bool enableReduceLessAfterAlphaUpdateAll = false;
133 : constexpr bool enableReduceLessOnSingularTT = false;
134 : constexpr bool enableReduceLessOnTheirTurn = false;
135 : constexpr bool enableReduceLessOnEmergencyDefence = false;
136 : constexpr bool enableReduceLessOnAdvancedPawnPush = false;
137 :
138 : constexpr bool enableCaptureBadCapExtraReduction = true;
139 : constexpr bool enableCaptureReduceLessIfTTMoveIsCap = false;
140 : constexpr bool enableCaptureReduceLessOnFormerPV = false;
141 : constexpr bool enableCaptureReduceLessOnAdvancedPawn = false;
142 :
143 : constexpr bool enableStdBaseLMRReduction = true;
144 : constexpr bool enableStdNotImprovingReduction = true;
145 : constexpr bool enableStdTTMoveIsCaptureReduction = true;
146 : constexpr bool enableStdFailHighReduction = true;
147 : constexpr bool enableStdHistoryReduction = true;
148 : constexpr bool enableStdFormerPVPrudence = true;
149 : constexpr bool enableStdKnownEndgamePrudence = true;
150 :
151 : constexpr bool enableCapBaseLMRReduction = true;
152 : constexpr bool enableCapHistoryReduction = true;
153 : constexpr bool enableCapPVNodePrudence = true;
154 : constexpr bool enableCapImprovingPrudence = true;
155 :
156 : int reduction = 0;
157 :
158 3318687 : if (isReductible) {
159 2978679 : const int depthIdx = std::min(static_cast<int>(depth), MAX_DEPTH - 1);
160 :
161 2978679 : if (Move2Type(m) == T_std){
162 : stats.incr(Stats::sid_lmr);
163 :
164 : const int threadReductionIdx = 0;
165 2581778 : const int moveIdx = std::min(pvsData.validMoveCount + threadReductionIdx, MAX_MOVE - 1);
166 :
167 : if constexpr (enableStdBaseLMRReduction) {
168 2581778 : reduction += SearchConfig::lmrReduction[depthIdx][moveIdx];
169 : }
170 : if constexpr (enableStdNotImprovingReduction) {
171 2581778 : reduction += static_cast<int>(!pvsData.improving);
172 : }
173 : if constexpr (enableStdTTMoveIsCaptureReduction) {
174 2581778 : reduction += static_cast<int>(pvsData.ttMoveIsCapture);
175 : }
176 :
177 : if constexpr (enableStdFailHighReduction) {
178 2581778 : const bool likelyFailHigh = pvsData.cutNode &&
179 1450704 : evalScore - SearchConfig::failHighReductionCoeff.threshold(
180 725352 : pvsData.marginDepth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving) > pvsData.beta;
181 2581778 : reduction += static_cast<int>(likelyFailHigh);
182 : }
183 :
184 : if constexpr (enableStdHistoryReduction) {
185 2581778 : const int hScore = HISTORY_DIV(2 * Move2Score(m));
186 2581778 : reduction -= std::min(3, hScore);
187 : }
188 :
189 : if constexpr (enableThreadBaseReduction) {
190 : reduction += static_cast<int>(id() % 2);
191 : }
192 :
193 : if constexpr (enableAggressiveRandomReduction) {
194 : constexpr int randomAggressiveReductionFactor = 0;
195 : const ScoreType randomShot = (stats.counters[Stats::sid_nodes] + stats.counters[Stats::sid_qnodes]) % 128;
196 : if (randomShot > 128 -
197 : randomAggressiveReductionFactor * pvsData.validQuietMoveCount) {
198 : stats.incr(Stats::sid_lmrAR);
199 : ++reduction;
200 : }
201 : }
202 :
203 : if constexpr (enableMobilityBasedReduction) {
204 : if (!pvsData.isInCheck) {
205 : const int16_t mobilityBalance = evalData.mobility[~p.c] - evalData.mobility[p.c];
206 : reduction += static_cast<int>(mobilityBalance / 16 < 0);
207 : }
208 : }
209 :
210 : if constexpr (enableReduceLessWhenInCheck) {
211 : reduction -= static_cast<int>(pvsData.isInCheck);
212 : }
213 : if constexpr (enableReduceLessWhenGivingCheck) {
214 : reduction -= static_cast<int>(pvsData.isCheck);
215 : }
216 :
217 : if constexpr (enableStdFormerPVPrudence) {
218 2581778 : reduction -= static_cast<int>(pvsData.formerPV || pvsData.ttPV);
219 : }
220 :
221 : if constexpr (enableReduceLessAtPVNode) {
222 : reduction -= static_cast<int>(pvsData.pvnode);
223 : }
224 : if constexpr (enableReduceLessAfterAlphaUpdateAll) {
225 : reduction -= static_cast<int>(!pvsData.cutNode && pvsData.alphaUpdated);
226 : }
227 : if constexpr (enableReduceLessOnSingularTT) {
228 : reduction -= static_cast<int>(pvsData.ttMoveSingularExt);
229 : }
230 : if constexpr (enableReduceLessOnTheirTurn) {
231 : reduction -= static_cast<int>(pvsData.theirTurn);
232 : }
233 : if constexpr (enableReduceLessOnEmergencyDefence) {
234 : reduction -= static_cast<int>(pvsData.isEmergencyDefence);
235 : }
236 :
237 : if constexpr (enableStdKnownEndgamePrudence) {
238 2581778 : reduction -= static_cast<int>(pvsData.isKnownEndGame);
239 : }
240 :
241 : if constexpr (enableReduceLessOnAdvancedPawnPush) {
242 : reduction -= static_cast<int>(2 * pvsData.isAdvancedPawnPush);
243 : }
244 : }
245 396901 : else if (Move2Type(m) == T_capture){
246 : stats.incr(Stats::sid_lmrcap);
247 :
248 : if constexpr (enableCapBaseLMRReduction) {
249 395554 : const int moveIdx = std::min(static_cast<int>(pvsData.validMoveCount), MAX_MOVE - 1);
250 395554 : reduction += SearchConfig::lmrReduction[depthIdx][moveIdx];
251 : }
252 :
253 : if constexpr (enableCapHistoryReduction) {
254 395554 : const Square to = Move2To(m); // ok this is a std capture (no ep)
255 791108 : const int hScore = HISTORY_DIV(SearchConfig::lmrCapHistoryFactor *
256 395554 : historyT.historyCap[PieceIdx(p.board_const(Move2From(m)))][to][Abs(p.board_const(to)) - 1]);
257 825832 : reduction -= std::max(-2, std::min(2, hScore));
258 : }
259 :
260 : if constexpr (enableCaptureBadCapExtraReduction) {
261 395554 : reduction += static_cast<int>(isBadCap(m) && reduction > 1);
262 : }
263 :
264 : if constexpr (enableCapPVNodePrudence) {
265 395554 : reduction -= static_cast<int>(pvsData.pvnode);
266 : }
267 :
268 : if constexpr (enableCaptureReduceLessIfTTMoveIsCap) {
269 : reduction -= static_cast<int>(pvsData.ttMoveIsCapture);
270 : }
271 : if constexpr (enableCaptureReduceLessOnFormerPV) {
272 : reduction -= static_cast<int>(pvsData.formerPV || pvsData.ttPV);
273 : }
274 :
275 : if constexpr (enableCapImprovingPrudence) {
276 395554 : reduction -= static_cast<int>(pvsData.improving);
277 : }
278 :
279 : if constexpr (enableCaptureReduceLessOnAdvancedPawn) {
280 : reduction -= static_cast<int>(2 * pvsData.isAdvancedPawnPush);
281 : }
282 : }
283 :
284 : // never extend more than reduce (to avoid search explosion)
285 : // except for first move when no TT hit where a +1 extension is possible
286 2978679 : if (extension - reduction > 0) reduction = extension;
287 : // clamp to depth = 0
288 2978679 : if (static_cast<int>(depth) - 1 + extension - reduction <= 0) reduction = static_cast<int>(depth) - 1 + extension;
289 : }
290 :
291 4123755 : const DepthType nextDepth = static_cast<DepthType>(static_cast<int>(depth) - 1 + extension - reduction);
292 :
293 3318687 : return {nextDepth, static_cast<DepthType>(extension), static_cast<DepthType>(reduction)};
294 : }
295 :
296 : #pragma GCC diagnostic push
297 : #pragma GCC diagnostic ignored "-Wconversion"
298 :
299 : FORCE_FINLINE void evalFeatures(const Position & p,
300 : array2d<BitBoard,2,6> & attFromPiece,
301 : colored<BitBoard> & att,
302 : colored<BitBoard> & att2,
303 : array2d<BitBoard,2,6> & checkers,
304 : colored<ScoreType> & kdanger,
305 : EvalScore & mobilityScore,
306 : colored<uint16_t> & mobility){
307 :
308 : const bool kingIsMandatory = DynamicConfig::isKingMandatory();
309 : const colored<BitBoard> pawns = {p.whitePawn(), p.blackPawn()};
310 2366863 : const colored<BitBoard> nonPawnMat = {p.allPieces[Co_White] & ~pawns[Co_White],
311 2366863 : p.allPieces[Co_Black] & ~pawns[Co_Black]};
312 2366863 : const colored<BitBoard> kingZone = {isValidSquare(p.king[Co_White]) ? BBTools::mask[p.king[Co_White]].kingZone : emptyBitBoard,
313 2366863 : isValidSquare(p.king[Co_Black]) ? BBTools::mask[p.king[Co_Black]].kingZone : emptyBitBoard};
314 2366863 : const BitBoard occupancy = p.occupancy();
315 :
316 7100589 : for (Color c = Co_White ; c <= Co_Black ; ++c){
317 33136082 : for (Piece pp = P_wp ; pp <= P_wk ; ++pp){
318 56804712 : BB::applyOn(p.pieces_const(c,pp), [&](const Square & k) {
319 : // aligned threats removing own piece (not pawn) in occupancy
320 44770503 : const BitBoard shadowTarget = BBTools::pfCoverage[pp - 1](k, occupancy ^ nonPawnMat[c], c);
321 44770503 : if (shadowTarget) {
322 44770503 : kdanger[~c] += BB::countBit(shadowTarget & kingZone[~c]) * EvalConfig::kingAttWeight[EvalConfig::katt_attack][pp - 1];
323 44770503 : const BitBoard target = BBTools::pfCoverage[pp - 1](k, occupancy, c); // real targets
324 44770503 : if (target) {
325 44770503 : attFromPiece[c][pp - 1] |= target;
326 44770503 : att2[c] |= att[c] & target;
327 44770503 : att[c] |= target;
328 44770503 : if (target & p.pieces_const<P_wk>(~c)) checkers[c][pp - 1] |= SquareToBitboard(k);
329 44770503 : kdanger[c] -= BB::countBit(target & kingZone[c]) * EvalConfig::kingAttWeight[EvalConfig::katt_defence][pp - 1];
330 : }
331 : }
332 44770503 : });
333 : }
334 : }
335 :
336 2366863 : if (kingIsMandatory){
337 2366863 : const File wkf = SQFILE(p.king[Co_White]);
338 2366863 : const File bkf = SQFILE(p.king[Co_Black]);
339 :
340 2366863 : const colored<BitBoard> semiOpenFiles = {BBTools::fillFile(pawns[Co_White]) & ~BBTools::fillFile(pawns[Co_Black]),
341 2366863 : BBTools::fillFile(pawns[Co_Black]) & ~BBTools::fillFile(pawns[Co_White])};
342 2366863 : const BitBoard openFiles = BBTools::openFiles(pawns[Co_White], pawns[Co_Black]);
343 :
344 2366863 : kdanger[Co_White] += EvalConfig::kingAttOpenfile * BB::countBit(BB::kingFlank[wkf] & openFiles) / 8;
345 2366863 : kdanger[Co_White] += EvalConfig::kingAttSemiOpenfileOpp * BB::countBit(BB::kingFlank[wkf] & semiOpenFiles[Co_White]) / 8;
346 2366863 : kdanger[Co_White] += EvalConfig::kingAttSemiOpenfileOur * BB::countBit(BB::kingFlank[wkf] & semiOpenFiles[Co_Black]) / 8;
347 2366863 : kdanger[Co_Black] += EvalConfig::kingAttOpenfile * BB::countBit(BB::kingFlank[bkf] & openFiles) / 8;
348 2366863 : kdanger[Co_Black] += EvalConfig::kingAttSemiOpenfileOpp * BB::countBit(BB::kingFlank[bkf] & semiOpenFiles[Co_Black]) / 8;
349 2366863 : kdanger[Co_Black] += EvalConfig::kingAttSemiOpenfileOur * BB::countBit(BB::kingFlank[bkf] & semiOpenFiles[Co_White]) / 8;
350 : }
351 :
352 2366863 : const colored<BitBoard> weakSquare = { att[Co_Black] & ~att2[Co_White] & (~att[Co_White] | attFromPiece[Co_White][P_wk - 1] | attFromPiece[Co_White][P_wq - 1]),
353 2366863 : att[Co_White] & ~att2[Co_Black] & (~att[Co_Black] | attFromPiece[Co_Black][P_wk - 1] | attFromPiece[Co_Black][P_wq - 1])};
354 :
355 2366863 : const colored<BitBoard> safeSquare = { ~att[Co_Black] | (weakSquare[Co_Black] & att2[Co_White]),
356 2366863 : ~att[Co_White] | (weakSquare[Co_White] & att2[Co_Black])};
357 :
358 : // reward safe checks
359 2366863 : kdanger[Co_White] += EvalConfig::kingAttSafeCheck[0] * BB::countBit(checkers[Co_Black][0] & att[Co_Black]);
360 2366863 : kdanger[Co_Black] += EvalConfig::kingAttSafeCheck[0] * BB::countBit(checkers[Co_White][0] & att[Co_White]);
361 11834315 : for (Piece pp = P_wn; pp < P_wk; ++pp) {
362 9467452 : kdanger[Co_White] += EvalConfig::kingAttSafeCheck[pp - 1] * BB::countBit(checkers[Co_Black][pp - 1] & safeSquare[Co_Black]);
363 9467452 : kdanger[Co_Black] += EvalConfig::kingAttSafeCheck[pp - 1] * BB::countBit(checkers[Co_White][pp - 1] & safeSquare[Co_White]);
364 : }
365 :
366 : // pieces mobility (second attack loop needed, knowing safeSquare ...)
367 : const colored<BitBoard> knights = {p.whiteKnight(), p.blackKnight()};
368 : const colored<BitBoard> bishops = {p.whiteBishop(), p.blackBishop()};
369 : const colored<BitBoard> rooks = {p.whiteRook(), p.blackRook()};
370 : const colored<BitBoard> queens = {p.whiteQueen(), p.blackQueen()};
371 : const colored<BitBoard> kings = {p.whiteKing(), p.blackKing()};
372 :
373 2366863 : evalMob<P_wn, Co_White>(p, knights[Co_White], mobilityScore, safeSquare[Co_White], occupancy, mobility[Co_White]);
374 2366863 : evalMob<P_wb, Co_White>(p, bishops[Co_White], mobilityScore, safeSquare[Co_White], occupancy, mobility[Co_White]);
375 2366863 : evalMob<P_wr, Co_White>(p, rooks [Co_White], mobilityScore, safeSquare[Co_White], occupancy, mobility[Co_White]);
376 2366863 : evalMobQ< Co_White>(p, queens [Co_White], mobilityScore, safeSquare[Co_White], occupancy, mobility[Co_White]);
377 2366863 : evalMobK< Co_White>(p, kings [Co_White], mobilityScore, ~att[Co_Black] , occupancy, mobility[Co_White]);
378 2366863 : evalMob<P_wn, Co_Black>(p, knights[Co_Black], mobilityScore, safeSquare[Co_Black], occupancy, mobility[Co_Black]);
379 2366863 : evalMob<P_wb, Co_Black>(p, bishops[Co_Black], mobilityScore, safeSquare[Co_Black], occupancy, mobility[Co_Black]);
380 2366863 : evalMob<P_wr, Co_Black>(p, rooks [Co_Black], mobilityScore, safeSquare[Co_Black], occupancy, mobility[Co_Black]);
381 2366863 : evalMobQ< Co_Black>(p, queens [Co_Black], mobilityScore, safeSquare[Co_Black], occupancy, mobility[Co_Black]);
382 2366863 : evalMobK< Co_Black>(p, kings [Co_Black], mobilityScore, ~att[Co_White] , occupancy, mobility[Co_Black]);
383 : }
384 :
385 : #pragma GCC diagnostic pop
386 :
387 : // this function shall be used on the initial position state before move is applied
388 : [[nodiscard]] FORCE_FINLINE bool isNoisy(const Position & /*p*/, const Move & m){
389 : return Move2Type(m) != T_std;
390 : /*
391 : if ( Move2Type(m) != T_std ) return true;
392 : const Square from = Move2From(m);
393 : const Square to = Move2To(m);
394 : const Piece pp = p.board_const(from);
395 : if (pp == P_wp){
396 : const BitBoard pAtt = p.c == Co_White ? BBTools::pawnAttacks<Co_White>(SquareToBitboard(to)) & p.allPieces[~p.c]:
397 : BBTools::pawnAttacks<Co_Black>(SquareToBitboard(to)) & p.allPieces[~p.c];
398 : if ( BB::countBit(pAtt) > 1 ) return true; ///@todo verify if the pawn is protected and/or not attacked ?
399 : }
400 : else if (pp == P_wn){
401 : const BitBoard nAtt = BBTools::coverage<P_wn>(to, p.occupancy(), p.c) & p.allPieces[~p.c];
402 : if ( BB::countBit(nAtt) > 1 ) return true; ///@todo verify if the knight is protected and/or not attacked ?
403 : }
404 : return false;
405 : */
406 : }
407 :
408 : FORCE_FINLINE void Searcher::timeCheck(){
409 : static uint64_t periodicCheck = 0ull; ///@todo this is slow because of guard variable
410 4947677 : if (periodicCheck == 0ull) {
411 4834 : periodicCheck = (TimeMan::maxNodes > 0) ? std::min(TimeMan::maxNodes, PERIODICCHECK) : PERIODICCHECK;
412 9668 : const Counter nodeCount = isStoppableCoSearcher ? stats.counters[Stats::sid_nodes] + stats.counters[Stats::sid_qnodes]
413 4834 : : ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
414 4834 : if (TimeMan::maxNodes > 0 && nodeCount > TimeMan::maxNodes) {
415 0 : stopFlag = true;
416 0 : Logging::LogIt(Logging::logInfo) << "stopFlag triggered (nodes limits) in thread " << id();
417 : }
418 4834 : if (getTimeDiff(startTime) > getCurrentMoveMs()) {
419 0 : stopFlag = true;
420 0 : Logging::LogIt(Logging::logInfo) << "stopFlag triggered in thread " << id();
421 : }
422 : Distributed::pollStat();
423 :
424 : if (!Distributed::isMainProcess()) {
425 : bool masterStopFlag;
426 : Distributed::get(&masterStopFlag, 1, Distributed::_winStopFromR0, 0);
427 : ThreadPool::instance().main().stopFlag = masterStopFlag;
428 : }
429 : }
430 4947677 : --periodicCheck;
431 4947677 : }
432 :
433 : // pvs inspired by Xiphos
434 : template<bool pvnode>
435 4947883 : ScoreType Searcher::pvs(ScoreType alpha,
436 : ScoreType beta,
437 : const Position& p,
438 : DepthType depth,
439 : DepthType height,
440 : PVList& pv,
441 : DepthType& seldepth,
442 : DepthType extensions,
443 : bool isInCheck_,
444 : bool cutNode_,
445 : const std::vector<MiniMove>* skipMoves) {
446 :
447 : // stopFlag management and time check. Only on main thread and not at each node (see PERIODICCHECK)
448 4947883 : if (isMainThread() || isStoppableCoSearcher) timeCheck();
449 4947883 : if (stopFlag) return STOPSCORE;
450 :
451 4947880 : debug_king_cap(p);
452 :
453 : // initiate the big pvsData structure with already given parameters
454 4947880 : PVSData pvsData;
455 4947880 : pvsData.cutNode = cutNode_;
456 4947880 : pvsData.isInCheck = isInCheck_;
457 4947880 : pvsData.alpha = alpha;
458 4947880 : pvsData.beta = beta;
459 4947880 : pvsData.pvnode = pvnode;
460 4947880 : pvsData.withoutSkipMove = skipMoves == nullptr;
461 4947880 : pvsData.previousMoveIsNullMove = p.lastMove == NULLMOVE;
462 :
463 : // some evaluation are used inside search
464 4947880 : EvalData evalData;
465 :
466 : // height is updated recursively in pvs and qsearch calls
467 : // but also given to Searcher itself in order to be available inside eval (which have "context").
468 : // @todo simplify this using only one solution !
469 4947880 : height_ = height;
470 4947880 : pvsData.theirTurn = height%2;
471 4947880 : pvsData.rootnode = height == 0;
472 :
473 : // we cannot search deeper than MAX_DEPTH, if so just return static evaluation in this case
474 4947880 : if (height >= MAX_DEPTH - 1 || depth >= MAX_DEPTH - 1) return eval(p, evalData, *this);
475 :
476 : // if not at root we check for draws (3rep, fifty and material)
477 4947880 : if (!pvsData.rootnode){
478 4946510 : if (const auto INRscore = interiorNodeRecognizer<pvnode>(p, height); INRscore.has_value()){
479 6580 : return INRscore.value();
480 : }
481 : }
482 :
483 : // on pvs leaf node, call a quiet search
484 4941300 : if (depth <= 0) {
485 : // don't enter qsearch when in check
486 : //if (pvsData.isInCheck) depth = 1;
487 : //else
488 1823696 : return qsearch(alpha, beta, p, height, seldepth, 0, true, pvnode, pvsData.isInCheck);
489 : }
490 :
491 : // update selective depth as soon as we "really enter" a node
492 3117604 : seldepth = std::max(seldepth,height);
493 :
494 : // update nodes count as soon as we "really enter" a node
495 3117604 : ++stats.counters[Stats::sid_nodes];
496 :
497 3117604 : if (!pvsData.rootnode){
498 : // mate distance pruning
499 3116234 : alpha = std::max(alpha, matedScore(height));
500 3116234 : beta = std::min(beta, matingScore(height + 1));
501 3116234 : if (alpha >= beta) return alpha;
502 : }
503 : else{
504 : // all threads clear rootScore, this is usefull for helper like in genfen or rescore.
505 : rootScores.clear();
506 : }
507 :
508 : // get the CMH pointer, can be needed to update tables
509 3117543 : getCMHPtr(p.halfmoves, pvsData.cmhPtr);
510 :
511 : // the position hash will be used quite often
512 3117543 : Hash pHash = computeHash(p);
513 : // let's consider skipmove(s) in position hashing
514 3117543 : if (skipMoves){
515 89393 : for (const auto & it : *skipMoves) {
516 0 : assert( isValidMove(it) );
517 44704 : pHash ^= Zobrist::hashMove(it);
518 : }
519 : }
520 :
521 : // Check for end-game knowledge
522 : Hash matHash = nullHash;
523 : MaterialHash::Terminaison terminaison = MaterialHash::Ter_Unknown;
524 3117543 : if (p.mat[Co_White][M_t] + p.mat[Co_Black][M_t] < 5) {
525 1437439 : matHash = MaterialHash::getMaterialHash(p.mat);
526 1437439 : if ( matHash != nullHash ){
527 : const MaterialHash::MaterialHashEntry& MEntry = MaterialHash::materialHashTable[matHash];
528 1437432 : terminaison = MEntry.t;
529 : }
530 : }
531 3117543 : pvsData.isKnownEndGame = terminaison != MaterialHash::Ter_Unknown;
532 :
533 : // probe TT
534 : TT::Entry e;
535 3117543 : const bool ttDepthOk = TT::getEntry(*this, p, pHash, depth, e);
536 3117543 : pvsData.bound = static_cast<TT::Bound>(e.b & ~TT::B_allFlags);
537 : // if depth of TT entry is enough, and not at root, then we consider to early return
538 3117543 : if (ttDepthOk && !pvsData.rootnode) {
539 524326 : if ((pvsData.bound == TT::B_alpha && e.s <= alpha)
540 462640 : || (pvsData.bound == TT::B_beta && e.s >= beta)
541 110994 : || (pvsData.bound == TT::B_exact) ) {
542 : if constexpr(!pvnode) {
543 : // increase history bonus of this move
544 416051 : if (e.m != INVALIDMINIMOVE){
545 : // quiet move history
546 416051 : if (Move2Type(e.m) == T_std){
547 172262 : updateTables(*this, p, depth, height, e.m, pvsData.bound, pvsData.cmhPtr);
548 : //if (pvsData.bound == TT::B_beta) historyT.update<1>(depth, e.m, p, pvsData.cmhPtr);
549 : //else if (pvsData.bound == TT::B_alpha) historyT.update<-1>(depth, e.m, p, pvsData.cmhPtr);
550 : }
551 : // capture history
552 243789 : else if ( isCapture(e.m) ){
553 243589 : if (pvsData.bound == TT::B_beta) historyT.updateCap<1>(depth, e.m, p);
554 15767 : else if (pvsData.bound == TT::B_alpha) historyT.updateCap<-1>(depth, e.m, p);
555 : }
556 : }
557 416051 : if (p.fifty < SearchConfig::ttMaxFiftyValideDepth && !pvsData.isKnownEndGame){
558 416042 : if (pvsData.bound == TT::B_alpha) stats.incr(Stats::sid_ttAlphaCut);
559 416042 : if (pvsData.bound == TT::B_exact) stats.incr(Stats::sid_ttExactCut);
560 416042 : if (pvsData.bound == TT::B_beta) stats.incr(Stats::sid_ttBetaCut);
561 416042 : return TT::adjustHashScore(e.s, height);
562 : }
563 : }
564 : ///@todo try returning also at pv node again (but this cuts pv ...)
565 : /*
566 : else { // in "good" condition, also return a score at pvnode
567 : if ( bound == TT::B_exact && e.d > 3*depth/2 && p.fifty < SearchConfig::ttMaxFiftyValideDepth){
568 : return TT::adjustHashScore(e.s, height);
569 : }
570 : }
571 : */
572 : }
573 : else{
574 : stats.incr(Stats::sid_ttWrongBound);
575 : }
576 : }
577 :
578 : // if entry hash is not null and entry move is valid,
579 : // then this is a valid TT move (we don't care about depth of the entry here)
580 : ///@todo try to store more things in TT bound ...
581 2701501 : pvsData.ttHit = e.h != nullHash;
582 2701501 : pvsData.validTTmove = pvsData.ttHit && e.m != INVALIDMINIMOVE;
583 2701501 : pvsData.ttPV = pvnode || (pvsData.validTTmove && (e.b & TT::B_ttPVFlag));
584 2701501 : pvsData.ttIsCheck = pvsData.validTTmove && (e.b & TT::B_isCheckFlag);
585 2701501 : pvsData.formerPV = pvsData.ttPV && !pvnode;
586 :
587 : // an idea from Ethereal :
588 : // near TT hits in terms of depth can trigger early returns
589 : // if cutoff is validated by a margin
590 :
591 : if constexpr(!pvnode){
592 2685411 : if ( !pvsData.rootnode
593 : //&& !pvsData.isInCheck
594 2685411 : && pvsData.ttHit
595 1355664 : && e.d > 0 // not from QS
596 518145 : && (pvsData.bound == TT::B_alpha)
597 178047 : && e.d >= depth - SearchConfig::ttAlphaCutDepth
598 87071 : && e.s + SearchConfig::ttAlphaCutMargin * (depth - SearchConfig::ttAlphaCutDepth) <= alpha){
599 : stats.incr(Stats::sid_ttAlphaLateCut);
600 8282 : return alpha;
601 : }
602 :
603 : // and it seems we can do the same with beta bound
604 2677129 : if ( !pvsData.rootnode
605 : //&& !pvsData.isInCheck
606 2677129 : && pvsData.ttHit
607 1347382 : && e.d > 0 // not from QS
608 509863 : && (pvsData.bound == TT::B_beta)
609 338529 : && e.d >= depth - SearchConfig::ttBetaCutDepth
610 147474 : && e.s - SearchConfig::ttBetaCutMargin * (depth - SearchConfig::ttBetaCutDepth) >= beta){
611 : stats.incr(Stats::sid_ttBetaLateCut);
612 42247 : return beta;
613 : }
614 : }
615 :
616 : #ifdef WITH_SYZYGY
617 : // probe TB
618 2650972 : ScoreType tbScore = 0;
619 16090 : if (!pvsData.rootnode
620 2649602 : && pvsData.withoutSkipMove
621 5264812 : && BB::countBit(p.allPieces[Co_White] | p.allPieces[Co_Black]) <= SyzygyTb::MAX_TB_MEN){
622 0 : if (SyzygyTb::probe_wdl(p, tbScore, true) > 0) {
623 0 : ++stats.counters[Stats::sid_tbHit1];
624 : // if this is a winning/losing EGT position, we add up static evaluation
625 : // this allow to go for mate better or to defend longer
626 0 : if (abs(tbScore) == SyzygyTb::TB_WIN_SCORE) {
627 0 : tbScore += eval(p, evalData, *this);
628 0 : tbScore = clampScore(tbScore);
629 : }
630 0 : else if (abs(tbScore) == SyzygyTb::TB_CURSED_SCORE) {
631 0 : Logging::LogIt(Logging::logInfo) << "cursed position " << GetFEN(p);
632 0 : tbScore = drawScore(p, height);
633 : }
634 : // store TB hits into TT (without associated move, but with max depth)
635 : //TT::setEntry(*this, pHash, INVALIDMOVE, TT::createHashScore(tbScore, height), TT::createHashScore(tbScore, height), TT::B_none, DepthType(MAX_DEPTH), isMainThread());
636 0 : return tbScore;
637 : }
638 : else {
639 : stats.incr(Stats::sid_tbFail);
640 : }
641 : }
642 : #endif
643 :
644 2650972 : assert(p.halfmoves - 1 < MAX_PLY && p.halfmoves - 1 >= 0);
645 :
646 : // now, let's get a static score for the position.
647 : ScoreType evalScore;
648 :
649 : // do not eval position when in check, we won't use it (as we won't forward prune)
650 2650972 : if (pvsData.isInCheck){
651 : evalScore = matedScore(height);
652 : }
653 : // skip eval: if nullmove just applied we can hack
654 : // but this won't work with asymetric HalfKA NNUE
655 2315845 : else if (!DynamicConfig::useNNUE && pvsData.previousMoveIsNullMove && height > 0){
656 0 : evalScore = 2 * EvalConfig::tempo - stack[p.halfmoves - 1].eval;
657 : #ifdef DEBUG_STATICEVAL
658 : checkEval(p,evalScore,*this,"null move trick (pvs)");
659 : #endif
660 : }
661 : else {
662 : // if we had a TT hit (with or without associated move),
663 : // we can use its eval instead of calling eval()
664 2315845 : if (pvsData.ttHit) {
665 : stats.incr(Stats::sid_ttschits);
666 1188398 : evalScore = e.e;
667 : // but we are missing game phase in this case
668 : // so let's look for a material match
669 1188398 : matHash = matHash != nullHash ? matHash : MaterialHash::getMaterialHash(p.mat);
670 629696 : if (matHash != nullHash) {
671 : stats.incr(Stats::sid_materialTableHitsSearch);
672 : const MaterialHash::MaterialHashEntry& MEntry = MaterialHash::materialHashTable[matHash];
673 1188286 : evalData.gp = MEntry.gamePhase();
674 : }
675 : else {
676 : stats.incr(Stats::sid_materialTableMissSearch);
677 : }
678 : #ifdef DEBUG_STATICEVAL
679 : checkEval(p,evalScore,*this,"from TT (pvs)");
680 : #endif
681 : }
682 : // if no TT hit call evaluation
683 : else {
684 : stats.incr(Stats::sid_ttscmiss);
685 1127447 : evalScore = eval(p, evalData, *this);
686 : #ifdef DEBUG_STATICEVAL
687 : checkEval(p,evalScore,*this,"from eval (pvs)");
688 : #endif
689 : }
690 : }
691 :
692 2650972 : if (evalData.gp == 0){
693 : // if no match yet, compute game phase now
694 335239 : ScoreType matScoreW = 0;
695 335239 : ScoreType matScoreB = 0;
696 335239 : evalData.gp = gamePhase(p.mat, matScoreW, matScoreB);
697 : }
698 :
699 : // insert only static eval in stack data, never hash score for consistancy!
700 2650972 : stack[p.halfmoves].eval = evalScore;
701 :
702 : // backup the static evaluation score as we will try to get a better evalScore approximation
703 : const ScoreType staticScore = evalScore;
704 :
705 : // if no TT hit yet, we insert an eval without a move here in case of forward pruning (depth is negative, bound is none) ...
706 : // Be carefull here, _data2 in Entry is always (INVALIDMOVE,B_none,-2) here, so that collisions are a lot more likely
707 2650972 : if (!pvsData.ttHit) TT::setEntry(*this, pHash, INVALIDMOVE, TT::createHashScore(evalScore, height), TT::createHashScore(staticScore, height), TT::B_none, -2, isMainThread());
708 :
709 : // if TT hit, we can use entry score as a best draft
710 : // but we set evalScoreIsHashScore to be aware of that !
711 2650972 : if (pvsData.ttHit && e.d > 0 && !pvsData.isInCheck && !pvsData.isKnownEndGame
712 409132 : && ((pvsData.bound == TT::B_alpha && e.s < evalScore) || (pvsData.bound == TT::B_beta && e.s > evalScore) || (pvsData.bound == TT::B_exact))){
713 191236 : evalScore = TT::adjustHashScore(e.s, height);
714 191236 : pvsData.evalScoreIsHashScore = true;
715 : stats.incr(Stats::sid_staticScoreIsFromSearch);
716 : }
717 :
718 : // take **initial** position situation (from game history) into account for pruning ?
719 : ///@todo retry this
720 2650972 : pvsData.isBoomingAttack = positionEvolution == MoveDifficultyUtil::PE_boomingAttack;
721 2650972 : pvsData.isBoomingDefend = positionEvolution == MoveDifficultyUtil::PE_boomingDefence;
722 2650972 : pvsData.isMoobingAttack = positionEvolution == MoveDifficultyUtil::PE_moobingAttack;
723 2650972 : pvsData.isMoobingDefend = positionEvolution == MoveDifficultyUtil::PE_moobingDefence;
724 :
725 : // take **initial** position situation (from IID variability) into account for pruning ?
726 : ///@todo retry this
727 2650972 : pvsData.isEmergencyDefence = false;//moveDifficulty == MoveDifficultyUtil::MD_moobDefenceIID;
728 2650972 : pvsData.isEmergencyAttack = false;//moveDifficulty == MoveDifficultyUtil::MD_moobAttackIID;
729 :
730 : // take **current** position danger level into account for purning
731 : // no HCE has been done, we need to work a little to get data from evaluation of the position
732 2650972 : if (!evalData.evalDone) {
733 : EvalScore mobilityScore = {0, 0};
734 2366863 : array2d<BitBoard,2,6> checkers = {{emptyBitBoard}};
735 2366863 : array2d<BitBoard,2,6> attFromPiece = {{emptyBitBoard}};
736 2366863 : colored<BitBoard> att = {emptyBitBoard, emptyBitBoard};
737 2366863 : colored<BitBoard> att2 = {emptyBitBoard, emptyBitBoard};
738 :
739 : evalFeatures(p, attFromPiece, att, att2, checkers, evalData.danger, mobilityScore, evalData.mobility);
740 :
741 2366863 : evalData.haveThreats[Co_White] = (att[Co_White] & p.allPieces[Co_Black]) != emptyBitBoard;
742 2366863 : evalData.haveThreats[Co_Black] = (att[Co_Black] & p.allPieces[Co_White]) != emptyBitBoard;
743 :
744 2366863 : evalData.goodThreats[Co_White] = ((attFromPiece[Co_White][P_wp - 1] & p.allPieces[Co_Black] & ~p.blackPawn()) |
745 2366863 : (attFromPiece[Co_White][P_wn - 1] & (p.blackQueen() | p.blackRook())) |
746 2366863 : (attFromPiece[Co_White][P_wb - 1] & (p.blackQueen() | p.blackRook())) |
747 2366863 : (attFromPiece[Co_White][P_wr - 1] & p.blackQueen())) != emptyBitBoard;
748 2366863 : evalData.goodThreats[Co_Black] = ((attFromPiece[Co_Black][P_wp - 1] & p.allPieces[Co_White] & ~p.whitePawn()) |
749 2366863 : (attFromPiece[Co_Black][P_wn - 1] & (p.whiteQueen() | p.whiteRook())) |
750 2366863 : (attFromPiece[Co_Black][P_wb - 1] & (p.whiteQueen() | p.whiteRook())) |
751 2366863 : (attFromPiece[Co_Black][P_wr - 1] & p.whiteQueen())) != emptyBitBoard;
752 : }
753 :
754 : // if the position is really intense, eventually for both side (thus very sharp), will try to prune/reduce a little less
755 2650972 : const int dangerFactor = (evalData.danger[Co_White] + evalData.danger[Co_Black]) / SearchConfig::dangerDivisor;
756 2650972 : const bool isDangerPrune = dangerFactor >= SearchConfig::dangerLimitPruning;
757 2650972 : const bool isDangerForwardPrune = dangerFactor >= SearchConfig::dangerLimitForwardPruning;
758 2650972 : const bool isDangerRed = dangerFactor >= SearchConfig::dangerLimitReduction;
759 : ///@todo this is not used for now
760 2650972 : if (isDangerPrune) stats.incr(Stats::sid_dangerPrune);
761 2650972 : if (isDangerForwardPrune) stats.incr(Stats::sid_dangerForwardPrune);
762 2650972 : if (isDangerRed) stats.incr(Stats::sid_dangerReduce);
763 :
764 : // if we are doing a big attack, we will look for tactics involving sacrifices
765 2650972 : const int dangerGoodAttack = evalData.danger[~p.c] / SearchConfig::dangerDivisor;
766 : // unless if we are ourself under attack
767 2650972 : const int dangerUnderAttack = evalData.danger[p.c] / SearchConfig::dangerDivisor;
768 :
769 : // when score is fluctuating a lot in the main ID search, let's prune a bit less
770 2650972 : const DepthType emergencyDepthCorrection = pvsData.isEmergencyDefence || pvsData.isEmergencyAttack;
771 : // take asymetry into account, prune less when it is not our turn
772 : const DepthType asymetryDepthCorrection = 0; //pvsData.theirTurn; ///@todo
773 : // when score is fluctuating a lot in the current game, let's prune a bit less
774 : const DepthType situationDepthCorrection = 0; /*pvsData.isBoomingAttack
775 : || pvsData.isMoobingAttack
776 : || pvsData.isBoomingDefend
777 : || pvsData.isMoobingDefend;*/
778 :
779 : ///@todo this is not used for now (==0)
780 : // take current situation and asymetry into account.
781 : const DepthType depthCorrection = emergencyDepthCorrection || asymetryDepthCorrection || situationDepthCorrection;
782 :
783 : // some heuristic will depend on not-updated initial alpha
784 2650972 : const ScoreType alphaInit = alpha;
785 :
786 : // moves loop data
787 2650972 : MoveList moves;
788 : bool moveGenerated = false;
789 : bool capMoveGenerated = false;
790 :
791 : // nmp things
792 2650972 : pvsData.mateThreat = false;
793 : MiniMove refutation = INVALIDMINIMOVE;
794 :
795 : // a depth that take TT depth into account
796 2650972 : pvsData.marginDepth = std::max(0, depth - (pvsData.evalScoreIsHashScore ? e.d : 0));
797 :
798 : // is the reported static eval better than a move before in the search tree ?
799 : // ! be carefull pvsData.improving implies not being in check !
800 2650972 : pvsData.improving = (!pvsData.isInCheck && height > 1 && stack[p.halfmoves].eval >= stack[p.halfmoves - 2].eval);
801 :
802 : // in end game situations, some prudence is required with pruning
803 2650972 : pvsData.isNotPawnEndGame = p.mat[p.c][M_t] > 0;
804 2650972 : pvsData.lessZugzwangRisk = pvsData.isNotPawnEndGame || evalData.mobility[p.c] > 4;
805 :
806 : // forward prunings (only at not pvnode)
807 : if constexpr(!pvnode){
808 : // removing the !isMateScore(beta) is not losing that much elo and allow for better check mate finding ...
809 2634882 : if (!DynamicConfig::mateFinder && !pvsData.rootnode && !pvsData.isInCheck /*&& !isMateScore(beta)*/) {
810 :
811 : // static null move
812 2301131 : if (SearchConfig::doStaticNullMove && !isMateScore(evalScore) && pvsData.lessZugzwangRisk && SearchConfig::staticNullMoveCoeff.isActive(depth, pvsData.evalScoreIsHashScore) ) {
813 2031456 : const ScoreType margin = SearchConfig::staticNullMoveCoeff.threshold(depth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving);
814 2031456 : if (evalScore > beta + margin) return stats.incr(Stats::sid_staticNullMove), evalScore - margin;
815 : }
816 :
817 : // (absence of) opponent threats pruning (idea origin from Koivisto)
818 1438295 : if ( SearchConfig::doThreatsPruning && !isMateScore(evalScore) && pvsData.lessZugzwangRisk && !evalData.goodThreats[~p.c] && SearchConfig::threatCoeff.isActive(depth, pvsData.evalScoreIsHashScore) ) {
819 545547 : const ScoreType margin = SearchConfig::threatCoeff.threshold(depth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving);
820 545547 : if (evalScore > beta + margin) return stats.incr(Stats::sid_threatsPruning), evalScore - margin;
821 : }
822 : /*
823 : // own threats pruning
824 : if ( SearchConfig::doThreatsPruning && !isMateScore(evalScore) && pvsData.lessZugzwangRisk && SearchConfig::ownThreatCoeff.isActive(depth, pvsData.evalScoreIsHashScore) &&
825 : !evalData.goodThreats[p.c] && evalScore < alpha - SearchConfig::ownThreatCoeff.threshold(depth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving) ){
826 : return stats.incr(Stats::sid_threatsPruning), alpha;
827 : }
828 : */
829 : // razoring
830 1353009 : if (const ScoreType rAlpha = alpha - SearchConfig::razoringCoeff.threshold(pvsData.marginDepth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving);
831 1353009 : SearchConfig::doRazoring && SearchConfig::razoringCoeff.isActive(depth, pvsData.evalScoreIsHashScore) && evalScore <= rAlpha) {
832 : stats.incr(Stats::sid_razoringTry);
833 174930 : if (!evalData.haveThreats[p.c]) return stats.incr(Stats::sid_razoringNoThreat), rAlpha;
834 147872 : const ScoreType qScore = qsearch(alpha, beta, p, height, seldepth, 0, true, pvnode, pvsData.isInCheck);
835 147872 : if (stopFlag) return STOPSCORE;
836 : //if (pvsData.evalScoreIsHashScore) return stats.incr(Stats::sid_razoringNoQ), qScore; // won't happen often as depth limit is small...
837 147872 : if (qScore <= alpha) return stats.incr(Stats::sid_razoring), qScore;
838 : }
839 :
840 : // null move
841 2394245 : if (SearchConfig::doNullMove && !subSearch &&
842 1192244 : depth >= SearchConfig::nullMoveMinDepth &&
843 1008618 : pvsData.lessZugzwangRisk &&
844 : //!pvsData.isKnownEndGame &&
845 997712 : pvsData.withoutSkipMove &&
846 970114 : (evalScore == beta || evalScore >= beta + SearchConfig::nullMoveMargin) &&
847 516832 : evalScore >= stack[p.halfmoves].eval &&
848 1715106 : stack[p.halfmoves].p.lastMove != NULLMOVE &&
849 501702 : (height >= nullMoveMinPly || nullMoveVerifColor != p.c)) {
850 491394 : PVList nullPV;
851 : stats.incr(Stats::sid_nullMoveTry);
852 982788 : const DepthType R = SearchConfig::nullMoveReductionInit +
853 982788 : depth / SearchConfig::nullMoveReductionDepthDivisor +
854 491394 : std::min((evalScore - beta) / SearchConfig::nullMoveDynamicDivisor, 7); // adaptative
855 491394 : const DepthType nullDepth = std::max(0, depth - R);
856 491394 : Position pN = p;
857 491394 : applyNull(*this, pN);
858 491394 : assert(pN.halfmoves < MAX_PLY && pN.halfmoves >= 0);
859 491394 : stack[pN.halfmoves].p = pN; ///@todo another expensive copy !!!!
860 491394 : stack[pN.halfmoves].h = pN.h;
861 491394 : ScoreType nullscore = -pvs<false>(-beta, -beta + 1, pN, nullDepth, height + 1, nullPV, seldepth, extensions, pvsData.isInCheck, !pvsData.cutNode);
862 491394 : if (stopFlag) return STOPSCORE;
863 : TT::Entry nullEThreat;
864 491394 : TT::getEntry(*this, pN, computeHash(pN), 0, nullEThreat);
865 491394 : if (nullEThreat.h != nullHash && nullEThreat.m != INVALIDMINIMOVE) refutation = nullEThreat.m;
866 491394 : if (isMatedScore(nullscore)) pvsData.mateThreat = true;
867 491394 : if (nullscore >= beta) { // verification search
868 221509 : if ( (!pvsData.lessZugzwangRisk || depth > SearchConfig::nullMoveVerifDepth) && nullMoveMinPly == 0){
869 : stats.incr(Stats::sid_nullMoveTry3);
870 106223 : nullMoveMinPly = height + 3*nullDepth/4;
871 106223 : nullMoveVerifColor = p.c;
872 106223 : nullscore = pvs<false>(beta - 1, beta, p, nullDepth, height+1, nullPV, seldepth, extensions, pvsData.isInCheck, false);
873 106223 : nullMoveMinPly = 0;
874 106223 : nullMoveVerifColor = Co_None;
875 106223 : if (stopFlag) return STOPSCORE;
876 106223 : if (nullscore >= beta ) return stats.incr(Stats::sid_nullMove2), nullscore;
877 : }
878 : else {
879 115286 : return stats.incr(Stats::sid_nullMove), (isMateScore(nullscore) ? beta : nullscore);
880 : }
881 : }
882 491394 : }
883 :
884 : // ProbCut
885 980587 : const ScoreType betaPC = beta + SearchConfig::probCutMargin + SearchConfig::probCutMarginSlope * pvsData.improving;
886 48519 : if (SearchConfig::doProbcut && depth >= SearchConfig::probCutMinDepth && !isMateScore(beta) &&
887 48519 : evalData.haveThreats[p.c]
888 : //&& !pvsData.isKnownEndGame
889 39126 : && (pvsData.cutNode || staticScore >= betaPC)
890 1006836 : && !( pvsData.validTTmove &&
891 22593 : e.d >= depth / SearchConfig::probCutSearchDepthFactor + 1 &&
892 19219 : e.s < betaPC)
893 : ) {
894 : stats.incr(Stats::sid_probcutTry);
895 : int probCutCount = 0;
896 : capMoveGenerated = true;
897 14457 : MoveGen::generate<MoveGen::GP_cap>(p, moves);
898 : //if (moves.empty()) stats.incr(Stats::sid_probcutNoCap); // should be 0 as there is threats ...
899 : #ifdef USE_PARTIAL_SORT
900 18113 : MoveSorter::score(*this, moves, p, evalData.gp, height, pvsData.cmhPtr, true, pvsData.isInCheck, pvsData.validTTmove ? &e : nullptr);
901 14457 : size_t offset = 0;
902 : const Move* it = nullptr;
903 : //if(!pvsData.cutNode) MoveSorter::sort(moves); // we expect one of the first best move to fail high on cutNode
904 53458 : while ((it = MoveSorter::pickNext(moves, offset/*, !pvsData.cutNode)*/)) && probCutCount < SearchConfig::probCutMaxMoves) {
905 : #else
906 : MoveSorter::scoreAndSort(*this, moves, p, evalData.gp, height, pvsData.cmhPtr, true, pvsData.isInCheck, pvsData.validTTmove ? &e : nullptr);
907 : for (auto it = moves.begin(); it != moves.end() && probCutCount < SearchConfig::probCutMaxMoves; ++it) {
908 : #endif
909 : stats.incr(Stats::sid_probcutMoves);
910 34149 : if ((pvsData.validTTmove && sameMove(e.m, *it)) || isBadCap(*it)) continue; // skip TT move if quiet or bad captures
911 11503 : Position p2 = p;
912 11503 : const MoveInfo moveInfo(p2,*it);
913 11503 : if (!applyMove(p2, moveInfo, true)) continue;
914 : #ifdef WITH_NNUE
915 11424 : NNUEEvaluator newEvaluator = p.evaluator();
916 : p2.associateEvaluator(newEvaluator);
917 11424 : applyMoveNNUEUpdate(p2, moveInfo);
918 : #endif
919 11424 : ++probCutCount;
920 11424 : ScoreType scorePC = -qsearch(-betaPC, -betaPC + 1, p2, height + 1, seldepth, 0, true, pvnode);
921 11424 : PVList pcPV;
922 11424 : if (stopFlag) return STOPSCORE;
923 11424 : const DepthType probCutSearchDepth = depth / SearchConfig::probCutSearchDepthFactor;
924 11424 : if (scorePC >= betaPC) {
925 : stats.incr(Stats::sid_probcutTry2);
926 6697 : scorePC = -pvs<false>(-betaPC, -betaPC + 1, p2, probCutSearchDepth, height + 1, pcPV, seldepth, extensions,
927 6697 : isPosInCheck(p2), !pvsData.cutNode);
928 : }
929 11424 : if (stopFlag) return STOPSCORE;
930 11424 : if (scorePC >= betaPC){
931 : /*
932 : const DepthType bonusDepth = probCutSearchDepth - 1 + (scorePC > (beta + SearchConfig::betaMarginDynamicHistory));
933 : historyT.updateCap<1>(bonusDepth, *it, p);
934 : for (auto it2 = moves.begin(); it2 != moves.end() && !sameMove(*it2, *it); ++it2) {
935 : if (isCapture(*it2))
936 : historyT.updateCap<-1>(bonusDepth, *it2, p);
937 : }
938 : */
939 : /*
940 : TT::setEntry(*this, pHash, *it, TT::createHashScore(scorePC, height), TT::createHashScore(evalScore, height),
941 : static_cast<TT::Bound>(TT::B_beta |
942 : (pvsData.ttPV ? TT::B_ttPVFlag : TT::B_none) |
943 : (pvsData.bestMoveIsCheck ? TT::B_isCheckFlag : TT::B_none) |
944 : (pvsData.isInCheck ? TT::B_isInCheckFlag : TT::B_none)),
945 : probCutSearchDepth-1,
946 : isMainThread()); // we applied one move
947 : */
948 6493 : return stats.incr(Stats::sid_probcut), scorePC;
949 : }
950 : }
951 : }
952 : }
953 : }
954 :
955 : // Ed Schroder style IIR
956 1323935 : if (SearchConfig::doIIR && depth >= SearchConfig::iirMinDepth && !pvsData.ttHit){
957 : stats.incr(Stats::sid_iid);
958 279780 : depth -= SearchConfig::iirReduction;
959 : }
960 :
961 : // Classic IID
962 : if (SearchConfig::doIID && !pvsData.validTTmove /*|| e.d < depth-4*/) {
963 : if ((pvnode && depth >= SearchConfig::iidMinDepth) || (pvsData.cutNode && depth >= SearchConfig::iidMinDepth2)) { ///@todo try with cutNode only ?
964 : stats.incr(Stats::sid_iid);
965 : PVList iidPV;
966 : DISCARD pvs<pvnode>(alpha, beta, p, depth / 2, height, iidPV, seldepth, extensions, pvsData.isInCheck, pvsData.cutNode, skipMoves);
967 : if (stopFlag) return STOPSCORE;
968 : TT::getEntry(*this, p, pHash, 0, e);
969 : pvsData.ttHit = e.h != nullHash;
970 : pvsData.validTTmove = pvsData.ttHit && e.m != INVALIDMINIMOVE;
971 : pvsData.bound = static_cast<TT::Bound>(e.b & ~TT::B_allFlags);
972 : pvsData.ttPV = pvnode || (pvsData.ttHit && (e.b & TT::B_ttPVFlag));
973 : pvsData.ttIsCheck = pvsData.validTTmove && (e.b & TT::B_isCheckFlag);
974 : pvsData.formerPV = pvsData.ttPV && !pvnode;
975 : if (pvsData.ttHit && e.d > 0 && !pvsData.isInCheck &&
976 : ((pvsData.bound == TT::B_alpha && e.s < evalScore) || (pvsData.bound == TT::B_beta && e.s > evalScore) || (pvsData.bound == TT::B_exact))) {
977 : evalScore = TT::adjustHashScore(e.s, height);
978 : pvsData.evalScoreIsHashScore = true;
979 : pvsData.marginDepth = std::max(0, depth - (pvsData.evalScoreIsHashScore ? e.d : 0)); // a depth that take TT depth into account
980 : }
981 : }
982 : }
983 :
984 : // reset killers
985 : if (height <= MAX_DEPTH - 2){
986 1323935 : killerT.killers[height + 1][0] = killerT.killers[height + 1][1] = INVALIDMOVE;
987 : }
988 :
989 : // backup refutation from null move heuristic
990 1323935 : stack[p.halfmoves].threat = refutation;
991 :
992 : // verify threat evolution for Botvinnik-Markoff extension
993 1307845 : const MiniMove formerRefutation = height > 1 ? stack[p.halfmoves - 2].threat : INVALIDMINIMOVE;
994 1323935 : pvsData.BMextension = DynamicConfig::useNNUE && height > 1 &&
995 1330698 : isValidMove(refutation) && isValidMove(formerRefutation) &&
996 19688 : (sameMove(refutation, formerRefutation) ||
997 45636 : (isCapture(refutation) && Move2To(refutation) == correctedMove2ToKingDest(formerRefutation)));
998 :
999 : // verify situation for various pruning heuristics
1000 1323935 : if (!pvsData.rootnode) {
1001 : // LMP
1002 1322565 : pvsData.lmp = SearchConfig::doLMP && depth <= SearchConfig::lmpMaxDepth;
1003 : // futility
1004 1322565 : const ScoreType futilityScore = alpha - SearchConfig::futilityPruningCoeff.threshold(pvsData.marginDepth, evalData.gp, pvsData.evalScoreIsHashScore, pvsData.improving);
1005 1322565 : pvsData.futility = SearchConfig::doFutility && SearchConfig::futilityPruningCoeff.isActive(depth, pvsData.evalScoreIsHashScore) && evalScore <= futilityScore;
1006 : // history pruning
1007 1322565 : pvsData.historyPruning = SearchConfig::doHistoryPruning && pvsData.isNotPawnEndGame && SearchConfig::historyPruningCoeff.isActive(depth, pvsData.improving);
1008 : // CMH pruning
1009 1322565 : pvsData.CMHPruning = SearchConfig::doCMHPruning && pvsData.isNotPawnEndGame && depth < SearchConfig::CMHMaxDepth[pvsData.improving];
1010 : // capture history pruning
1011 1322565 : pvsData.capHistoryPruning = SearchConfig::doCapHistoryPruning && pvsData.isNotPawnEndGame && SearchConfig::captureHistoryPruningCoeff.isActive(depth, pvsData.improving);
1012 : }
1013 :
1014 : #ifdef WITH_SYZYGY
1015 1323935 : if (pvsData.rootnode && pvsData.withoutSkipMove && (BB::countBit(p.allPieces[Co_White] | p.allPieces[Co_Black])) <= SyzygyTb::MAX_TB_MEN) {
1016 0 : tbScore = 0;
1017 0 : if (MoveList movesTB; SyzygyTb::probe_root(*this, p, tbScore, movesTB) < 0) { // only good moves if TB success
1018 : stats.incr(Stats::sid_tbFail);
1019 0 : if (capMoveGenerated) MoveGen::generate<MoveGen::GP_quiet>(p, moves, true);
1020 : else
1021 0 : if ( pvsData.isInCheck ) MoveGen::generate<MoveGen::GP_evasion>(p, moves, false);
1022 0 : else MoveGen::generate<MoveGen::GP_all>(p, moves, false);
1023 : }
1024 : else {
1025 : moves = movesTB;
1026 0 : ++stats.counters[Stats::sid_tbHit2];
1027 : }
1028 : moveGenerated = true;
1029 : }
1030 : #endif
1031 :
1032 : // bestScore / bestMove will be updated in move loop and returned at the end and inserted in TT
1033 : ScoreType bestScore = matedScore(height);
1034 16090 : Move bestMove = INVALIDMOVE;
1035 :
1036 : TT::Bound hashBound = TT::B_alpha;
1037 :
1038 : // try the tt move before move generation (if not skipped move)
1039 514839 : if (pvsData.validTTmove &&
1040 1330695 : (moves.empty() || !pvsData.rootnode) && // avoid trying TT move at root if coming from a TB probe hit that filled moves
1041 514839 : !isSkipMove(e.m, skipMoves)) {
1042 :
1043 : #ifdef DEBUG_APPLY
1044 : // to debug race condition in entry affectation !
1045 : if (!isPseudoLegal(p, e.m)) { Logging::LogIt(Logging::logFatal) << "invalide TT move !"; }
1046 : #endif
1047 :
1048 514839 : pvsData.ttMoveTried = true;
1049 514839 : pvsData.isTTMove = true;
1050 514839 : if (isCapture(e.m)) pvsData.ttMoveIsCapture = true;
1051 12877 : bestMove = e.m; // in order to preserve tt move for alpha bound entry
1052 :
1053 514839 : Position p2 = p;
1054 514839 : if (const MoveInfo moveInfo(p2,e.m); applyMove(p2, moveInfo, true)) {
1055 :
1056 : // We have a TT move.
1057 : // The TT move can trigger singular extension
1058 : // And it won't be reduced by any mean
1059 : // Remember that if no tt hit, depth has been reduced already (by IIR)
1060 : DepthType extension = 0;
1061 514839 : if (DynamicConfig::level > 80) {
1062 : // singular move extension
1063 514829 : if (pvsData.withoutSkipMove
1064 503491 : && depth >= SearchConfig::singularExtensionDepth
1065 103076 : && !pvsData.rootnode
1066 102907 : && !isMateScore(e.s)
1067 102294 : && (pvsData.bound == TT::B_exact || pvsData.bound == TT::B_beta)
1068 578173 : && e.d >= depth - SearchConfig::singularExtensionDepthMinus) {
1069 44714 : const ScoreType betaC = e.s - 2 * depth;
1070 44714 : PVList sePV;
1071 44714 : DepthType seSeldepth = 0;
1072 44714 : std::vector<MiniMove> skip{e.m};
1073 44714 : const ScoreType score = pvs<false>(betaC - 1, betaC, p, depth / 2, height, sePV, seSeldepth, extensions, pvsData.isInCheck, pvsData.cutNode, &skip);
1074 44714 : if (stopFlag) return STOPSCORE;
1075 44714 : if (score < betaC /*&& extensions <= 6*/) { // TT move is singular
1076 : stats.incr(Stats::sid_singularExtension);
1077 21397 : pvsData.ttMoveSingularExt=true;
1078 : ++extension;
1079 : // TT move is "very singular" and depth is small : kind of single reply extension
1080 21397 : if (/*!pvsData.pvnode &&*/ score < betaC - 4 * depth && extensions <= 6) {
1081 : stats.incr(Stats::sid_singularExtension2);
1082 : ++extension;
1083 : /*
1084 : if (score < betaC - 8 * depth && !pvsData.ttMoveIsCapture && extensions <= 6){
1085 : stats.incr(Stats::sid_singularExtension6);
1086 : ++extension;
1087 : }
1088 : */
1089 : }
1090 : }
1091 : // multi cut (at least the TT move and another move are above beta)
1092 23317 : else if (betaC >= beta) {
1093 10535 : return stats.incr(Stats::sid_singularExtension3), betaC; // fail-soft
1094 : }
1095 : // if TT move is above beta, we try a reduce search early to see if another move is above beta (from SF)
1096 12782 : else if (e.s >= beta) {
1097 : //const ScoreType score2 = pvs<false>(beta - 1, beta, p, depth - 4, height, sePV, seSeldepth, extensions, pvsData.isInCheck, pvsData.cutNode, &skip);
1098 : //if (score2 > beta) return stats.incr(Stats::sid_singularExtension4), beta; // fail-hard
1099 4874 : extension = -2 + pvsData.pvnode;
1100 : stats.incr(Stats::sid_singularExtension4);
1101 : }
1102 : /*
1103 : else if (pvsData.cutNode){
1104 : extension = -1;
1105 : stats.incr(Stats::sid_singularExtension7);
1106 : }
1107 : */
1108 7908 : else if (e.s <= alpha){
1109 : extension = -1;
1110 : stats.incr(Stats::sid_singularExtension5);
1111 : }
1112 44714 : }
1113 : }
1114 :
1115 : // prefetch as soon as possible
1116 504304 : TT::prefetch(computeHash(p2));
1117 :
1118 : #ifdef WITH_NNUE
1119 504304 : NNUEEvaluator newEvaluator = p.evaluator();
1120 : p2.associateEvaluator(newEvaluator);
1121 504304 : applyMoveNNUEUpdate(p2, moveInfo);
1122 : #endif
1123 :
1124 504304 : ++pvsData.validMoveCount;
1125 504304 : ++pvsData.validNonPrunedCount;
1126 :
1127 504304 : pvsData.earlyMove = true;
1128 1008608 : pvsData.isQuiet = Move2Type(e.m) == T_std && !isNoisy(p, e.m);
1129 504304 : if (pvsData.isQuiet) ++pvsData.validQuietMoveCount;
1130 :
1131 504304 : pvsData.isCheck = pvsData.ttIsCheck || isPosInCheck(p2);
1132 :
1133 504304 : assert(p2.halfmoves < MAX_PLY && p2.halfmoves >= 0);
1134 504304 : stack[p2.halfmoves].p = p2; ///@todo another expensive copy !!!!
1135 504304 : stack[p2.halfmoves].h = p2.h;
1136 :
1137 : #ifdef DEBUG_TT_CHECK
1138 : if (pvsData.ttIsCheck && !isPosInCheck(p2)) {
1139 : std::cout << "Error ttIsCheck" << std::endl;
1140 : std::cout << ToString(p2) << std::endl;
1141 : }
1142 : #endif
1143 :
1144 504304 : PVList childPV;
1145 : ScoreType ttScore;
1146 504304 : ttScore = -pvs<pvnode>(-beta, -alpha, p2, depth - 1 + extension, height + 1, childPV, seldepth, static_cast<DepthType>(extensions + extension), pvsData.isCheck, !pvsData.cutNode);
1147 :
1148 504304 : if (stopFlag) return STOPSCORE;
1149 :
1150 504304 : if (pvsData.rootnode) {
1151 1345 : rootScores.emplace_back(e.m, ttScore);
1152 : }
1153 :
1154 504304 : if (ttScore > bestScore) {
1155 504299 : if (pvsData.rootnode) previousBest = e.m;
1156 : bestScore = ttScore;
1157 504299 : bestMove = e.m;
1158 504299 : pvsData.bestMoveIsCheck = pvsData.isCheck;
1159 504299 : if (ttScore > alpha) {
1160 : hashBound = TT::B_exact;
1161 280578 : pvsData.alphaUpdated = true;
1162 : if constexpr(pvnode) updatePV(pv, bestMove, childPV);
1163 280578 : if (ttScore >= beta) {
1164 : stats.incr(Stats::sid_ttbeta);
1165 :
1166 : // increase history bonus of this move
1167 274965 : if (!pvsData.isInCheck){
1168 186305 : if (pvsData.isQuiet /*&& depth > 1*/) // quiet move history
1169 71863 : updateTables(*this, p, depth + (ttScore > (beta + SearchConfig::betaMarginDynamicHistory)), height, bestMove, TT::B_beta, pvsData.cmhPtr);
1170 114442 : else if (isCapture(bestMove)) // capture history
1171 114348 : historyT.updateCap<1>(depth + (ttScore > (beta + SearchConfig::betaMarginDynamicHistory)), bestMove, p);
1172 : }
1173 :
1174 274965 : TT::setEntry(*this, pHash, bestMove, TT::createHashScore(ttScore, height), TT::createHashScore(evalScore, height),
1175 274965 : static_cast<TT::Bound>(TT::B_beta |
1176 274965 : (pvsData.ttPV ? TT::B_ttPVFlag : TT::B_none) |
1177 549930 : (pvsData.bestMoveIsCheck ? TT::B_isCheckFlag : TT::B_none) |
1178 274965 : (pvsData.isInCheck ? TT::B_isInCheckFlag : TT::B_none)),
1179 : depth,
1180 274965 : isMainThread());
1181 : return ttScore;
1182 : }
1183 : stats.incr(Stats::sid_ttalpha);
1184 5613 : alpha = ttScore;
1185 : }
1186 : }
1187 5 : else if (pvsData.rootnode && !pvsData.isInCheck && ttScore < alpha - SearchConfig::failLowRootMargin /*&& !isMateScore(ttScore)*/) {
1188 0 : return alpha - SearchConfig::failLowRootMargin;
1189 : }
1190 504304 : }
1191 514839 : }
1192 :
1193 1038435 : ScoreType score = matedScore(height);
1194 : bool skipQuiet = false;
1195 : bool skipCap = false;
1196 :
1197 : // depending if probecut already generates capture or not, generate all moves or only missing quiets
1198 1038435 : if (!moveGenerated) {
1199 1025557 : if (capMoveGenerated) MoveGen::generate<MoveGen::GP_quiet>(p, moves, true);
1200 : else
1201 1036599 : if ( pvsData.isInCheck ) MoveGen::generate<MoveGen::GP_evasion>(p, moves, false);
1202 792594 : else MoveGen::generate<MoveGen::GP_all>(p, moves, false);
1203 : }
1204 1038435 : if (moves.empty()) return pvsData.isInCheck ? matedScore(height) : drawScore(p, height);
1205 :
1206 : #ifdef USE_PARTIAL_SORT
1207 1893709 : MoveSorter ms(*this, p, evalData.gp, height, pvsData.cmhPtr, true, pvsData.isInCheck, pvsData.validTTmove ? &e : nullptr,
1208 46204 : refutation != INVALIDMINIMOVE && isCapture(Move2Type(refutation)) ? refutation : INVALIDMINIMOVE);
1209 1038422 : ms.score(moves);
1210 1038422 : size_t offset = 0;
1211 : const Move* it = nullptr;
1212 : //if(!pvsData.cutNode) ms.sort(moves); // we expect one of the first best move to fail high on cutNode
1213 25417272 : while ((it = ms.pickNextLazy(moves, offset/*, !pvsData.cutNode*/)) && !stopFlag) {
1214 : #else
1215 : MoveSorter::scoreAndSort(moves);
1216 : for (auto it = moves.begin(); it != moves.end() && !stopFlag; ++it) {
1217 : #endif
1218 :
1219 12584253 : pvsData.isTTMove = false;
1220 26579120 : pvsData.isQuiet = Move2Type(*it) == T_std && !isNoisy(p,*it);
1221 :
1222 8831172 : if (isSkipMove(*it, skipMoves)) continue; // skipmoves
1223 12557774 : if (pvsData.validTTmove && pvsData.ttMoveTried && sameMove(e.m, *it)) continue; // already tried
1224 :
1225 12328435 : Position p2 = p;
1226 12328435 : const MoveInfo moveInfo(p2,*it);
1227 :
1228 : // do not apply NNUE update here, but later after prunning, right before next call to pvs
1229 12328435 : if (!applyMove(p2, moveInfo, true)) continue;
1230 :
1231 : // prefetch as soon as possible
1232 11192400 : TT::prefetch(computeHash(p2));
1233 11192400 : const Square to = Move2To(*it);
1234 : #ifdef DEBUG_KING_CAP
1235 : if (p.c == Co_White && to == p.king[Co_Black]) return matingScore(height - 1);
1236 : if (p.c == Co_Black && to == p.king[Co_White]) return matingScore(height - 1);
1237 : #endif
1238 :
1239 11192400 : ++pvsData.validMoveCount;
1240 11192400 : if (pvsData.isQuiet) ++pvsData.validQuietMoveCount;
1241 :
1242 11192400 : pvsData.isCheck = isPosInCheck(p2);
1243 :
1244 11192400 : const bool noCheck = !pvsData.isInCheck && !pvsData.isCheck;
1245 :
1246 22384800 : pvsData.isAdvancedPawnPush = PieceTools::getPieceType(p, Move2From(*it)) == P_wp && (SQRANK(to) > 5 || SQRANK(to) < 2);
1247 11192400 : pvsData.earlyMove = pvsData.validMoveCount < (2 /*+2*pvsData.rootnode*/);
1248 :
1249 11192400 : PVList childPV;
1250 : // PVS
1251 11192400 : if (pvsData.earlyMove || !SearchConfig::doPVS){
1252 805068 : stack[p2.halfmoves].p = p2;
1253 805068 : stack[p2.halfmoves].h = p2.h;
1254 : #ifdef WITH_NNUE
1255 805068 : NNUEEvaluator newEvaluator = p.evaluator();
1256 : p2.associateEvaluator(newEvaluator);
1257 805068 : applyMoveNNUEUpdate(p2, moveInfo);
1258 : #endif
1259 : // get depth of next search
1260 : // Remember that if no tt hit, depth has been reduced already (by IIR)
1261 : const auto [nextDepth, extension, reduction] = depthPolicy(p, depth, height, *it, pvsData, evalData, evalScore, extensions, false);
1262 805068 : score = -pvs<pvnode>(-beta, -alpha, p2, nextDepth, height + 1, childPV, seldepth, static_cast<DepthType>(extensions + extension), pvsData.isCheck, false);
1263 805068 : ++pvsData.validNonPrunedCount;
1264 : }
1265 : else {
1266 : // reductions & prunings
1267 10387332 : const bool isPrunable = /*pvsData.isNotPawnEndGame &&*/ !pvsData.isAdvancedPawnPush && !isMateScore(alpha) && !killerT.isKiller(*it, height) && !DynamicConfig::mateFinder;
1268 10387332 : const bool isReductible = SearchConfig::doLMR && depth >= SearchConfig::lmrMinDepth && /*pvsData.isNotPawnEndGame &&*/ !pvsData.isAdvancedPawnPush && !DynamicConfig::mateFinder;
1269 10387332 : const bool isPrunableStd = isPrunable && pvsData.isQuiet;
1270 10387332 : const bool isPrunableStdNoCheck = isPrunableStd && noCheck;
1271 20715573 : const bool isPrunableCap = isPrunable && Move2Type(*it) == T_capture && noCheck;
1272 400989 : const bool isPrunableBadCap = isPrunableCap && isBadCap(*it);
1273 :
1274 : // skip quiet if LMP was triggered
1275 10387332 : if (skipQuiet && pvsData.isQuiet && noCheck && isPrunableStdNoCheck){
1276 : stats.incr(Stats::sid_lmp);
1277 13841942 : continue;
1278 : }
1279 : // skip other bad capture
1280 4149634 : if (skipCap && Move2Type(*it) == T_capture && isPrunableBadCap){
1281 : stats.incr(Stats::sid_see2);
1282 51900 : continue;
1283 : }
1284 :
1285 : // forward pruning heuristic for quiet moves
1286 3932809 : if (isPrunableStdNoCheck){
1287 : // futility
1288 2726557 : if (pvsData.futility) {
1289 : stats.incr(Stats::sid_futility);
1290 : skipQuiet = true;
1291 112670 : continue;
1292 : }
1293 :
1294 : // LMP
1295 2613887 : if (pvsData.lmp && pvsData.validMoveCount > SearchConfig::lmpLimit[pvsData.improving][depth + depthCorrection]) {
1296 : stats.incr(Stats::sid_lmp);
1297 : skipQuiet = true;
1298 220505 : continue;
1299 : }
1300 :
1301 : // History pruning (including CMH)
1302 2577458 : if (pvsData.historyPruning &&
1303 368152 : Move2Score(*it) < SearchConfig::historyPruningCoeff.threshold(depth, evalData.gp, pvsData.improving, pvsData.cutNode)) {
1304 : stats.incr(Stats::sid_historyPruning);
1305 : skipQuiet = true;
1306 33122 : continue;
1307 : }
1308 :
1309 : // CMH pruning alone
1310 2360260 : if (pvsData.CMHPruning ) {
1311 526842 : if (isCMHBad(p, Move2From(*it), Move2To(*it), pvsData.cmhPtr, SearchConfig::CMHMargin)) {
1312 : stats.incr(Stats::sid_CMHPruning);
1313 88589 : continue;
1314 : }
1315 : }
1316 : }
1317 : else{
1318 : // capture history pruning (only std cap)
1319 1440650 : if (pvsData.capHistoryPruning && isPrunableCap &&
1320 234398 : historyT.historyCap[PieceIdx(p.board_const(Move2From(*it)))][to][Abs(p.board_const(to))-1] < SearchConfig::captureHistoryPruningCoeff.threshold(depth, evalData.gp, pvsData.improving, pvsData.cutNode)){
1321 : stats.incr(Stats::sid_capHistPruning);
1322 9358 : continue;
1323 : }
1324 :
1325 : // SEE (capture)
1326 1196894 : if (!pvsData.rootnode && isPrunableBadCap) {
1327 219806 : if (pvsData.futility) {
1328 : stats.incr(Stats::sid_see);
1329 : skipCap = true;
1330 40178 : continue;
1331 : }
1332 267419 : else if ( badCapScore(*it) < - (SearchConfig::seeCaptureInit + SearchConfig::seeCaptureFactor * (depth - 1 + std::max(0, dangerGoodAttack - dangerUnderAttack)/SearchConfig::seeCapDangerDivisor))) {
1333 : stats.incr(Stats::sid_see2);
1334 : skipCap = true;
1335 109700 : continue;
1336 : }
1337 : }
1338 : }
1339 :
1340 : // get depth of next search
1341 : // Remember that if no tt hit, depth has been reduced already (by IIR)
1342 3318687 : auto [nextDepth, extension, reduction] = depthPolicy(p, depth, height, *it, pvsData, evalData, evalScore, extensions, isReductible);
1343 :
1344 : // SEE (quiet)
1345 : ScoreType seeValue = 0;
1346 3318687 : if (isPrunableStdNoCheck) {
1347 2271671 : seeValue = SEE(p, *it);
1348 3234634 : if (!pvsData.rootnode && seeValue < - (SearchConfig::seeQuietInit + SearchConfig::seeQuietFactor * (nextDepth - 1) * (nextDepth + std::max(0, dangerGoodAttack - dangerUnderAttack)/SearchConfig::seeQuietDangerDivisor))){
1349 : stats.incr(Stats::sid_seeQuiet);
1350 370674 : continue;
1351 : }
1352 : // reduce bad quiet more
1353 : //else if (seeValue < 0 && nextDepth > 1) --nextDepth;
1354 : }
1355 :
1356 2948013 : ++pvsData.validNonPrunedCount;
1357 :
1358 : // PVS
1359 2948013 : stack[p2.halfmoves].p = p2;
1360 2948013 : stack[p2.halfmoves].h = p2.h;
1361 : #ifdef WITH_NNUE
1362 2948013 : NNUEEvaluator newEvaluator = p.evaluator();
1363 : p2.associateEvaluator(newEvaluator);
1364 2948013 : applyMoveNNUEUpdate(p2, moveInfo);
1365 : #endif
1366 2948013 : score = -pvs<false>(-alpha - 1, -alpha, p2, nextDepth, height + 1, childPV, seldepth, static_cast<DepthType>(extensions + extension), pvsData.isCheck, true);
1367 2948013 : if (reduction > 0 && score > alpha) {
1368 : stats.incr(Stats::sid_lmrFail);
1369 : childPV.clear();
1370 37143 : score = -pvs<false>(-alpha - 1, -alpha, p2, depth - 1 + extension, height + 1, childPV, seldepth, static_cast<DepthType>(extensions + extension), pvsData.isCheck, !pvsData.cutNode);
1371 : /*
1372 : if (pvsData.isQuiet){
1373 : if (score > alpha) historyT.update<1>(nextDepth, *it, p, pvsData.cmhPtr);
1374 : else historyT.update<-1>(nextDepth, *it, p, pvsData.cmhPtr);
1375 : }
1376 : */
1377 : /*
1378 : else{
1379 : if (score > alpha) historyT.updateCap<1>(nextDepth, *it, p);
1380 : else historyT.updateCap<-1>(nextDepth, *it, p);
1381 : }
1382 : */
1383 : }
1384 2948013 : if ( score > alpha && (pvsData.rootnode || score < beta)) {
1385 : stats.incr(Stats::sid_pvsFail);
1386 : childPV.clear();
1387 : // potential new pv node
1388 2957 : score = -pvs<true>(-beta, -alpha, p2, depth - 1 + extension, height + 1, childPV, seldepth, static_cast<DepthType>(extensions + extension), pvsData.isCheck, false);
1389 : }
1390 : }
1391 :
1392 3753081 : if (stopFlag) return STOPSCORE;
1393 :
1394 3753079 : if (pvsData.rootnode) {
1395 28070 : rootScores.emplace_back(*it, score);
1396 : }
1397 3753079 : if (score > bestScore) {
1398 1202232 : if (pvsData.rootnode) previousBest = *it;
1399 : bestScore = score;
1400 1202232 : bestMove = *it;
1401 1202232 : pvsData.bestMoveIsCheck = pvsData.isCheck;
1402 : //bestScoreUpdated = true;
1403 1202232 : if (score > alpha) {
1404 : if constexpr(pvnode) updatePV(pv, bestMove, childPV);
1405 536525 : pvsData.alphaUpdated = true;
1406 536525 : alpha = score;
1407 : hashBound = TT::B_exact;
1408 536525 : if (score >= beta) {
1409 : stats.incr(Stats::sid_beta);
1410 : #ifdef WITH_BETACUTSTATS
1411 : updateStatBetaCut(p, *it, height);
1412 : #endif
1413 533836 : if (!pvsData.isInCheck){
1414 378760 : const DepthType bonusDepth = depth + (score > beta + SearchConfig::betaMarginDynamicHistory);
1415 378760 : if (pvsData.isQuiet /*&& depth > 1*/ /*&& !( depth == 1 && validQuietMoveCount == 1 )*/) { // quiet move history
1416 : // increase history of this move
1417 107847 : updateTables(*this, p, bonusDepth, height, bestMove, TT::B_beta, pvsData.cmhPtr);
1418 : // reduce history of all previous
1419 285119 : for (auto it2 = moves.begin(); it2 != moves.end() && !sameMove(*it2, bestMove); ++it2) {
1420 177272 : if (Move2Type(*it2) == T_std)
1421 : historyT.update<-1>(bonusDepth, *it2, p, pvsData.cmhPtr);
1422 : }
1423 : }
1424 270913 : else if ( isCapture(bestMove)){ // capture history
1425 : historyT.updateCap<1>(bonusDepth, bestMove, p);
1426 395563 : for (auto it2 = moves.begin(); it2 != moves.end() && !sameMove(*it2, bestMove); ++it2) {
1427 125372 : if (isCapture(*it2))
1428 : historyT.updateCap<-1>(bonusDepth, *it2, p);
1429 : }
1430 : }
1431 : }
1432 : hashBound = TT::B_beta;
1433 : break;
1434 : }
1435 : stats.incr(Stats::sid_alpha);
1436 : }
1437 : }
1438 2550847 : else if (pvsData.rootnode && !pvsData.isInCheck && pvsData.earlyMove && score < alpha - SearchConfig::failLowRootMargin) {
1439 0 : return alpha - SearchConfig::failLowRootMargin;
1440 : }
1441 : }
1442 :
1443 1038420 : if (alphaInit == alpha ) stats.incr(Stats::sid_alphanoupdate);
1444 :
1445 : // check for draw and check mate
1446 1038420 : if (pvsData.validMoveCount == 0){
1447 4015 : return !pvsData.withoutSkipMove ? alpha : pvsData.isInCheck ? matedScore(height) : drawScore(p, height);
1448 : //return (pvsData.isInCheck || !pvsData.withoutSkipMove) ? matedScore(height) : drawScore(p, height);
1449 : }
1450 : // post move loop version
1451 1034405 : else if (is50moves(p,false)){
1452 0 : return drawScore(p, height);
1453 : }
1454 :
1455 : // insert data in TT
1456 1034405 : TT::setEntry(*this, pHash, bestMove, TT::createHashScore(bestScore, height), TT::createHashScore(evalScore, height),
1457 1034405 : static_cast<TT::Bound>(hashBound |
1458 1034405 : (pvsData.ttPV ? TT::B_ttPVFlag : TT::B_none) |
1459 2068810 : (pvsData.bestMoveIsCheck ? TT::B_isCheckFlag : TT::B_none) |
1460 1034405 : (pvsData.isInCheck ? TT::B_isInCheckFlag : TT::B_none)),
1461 : depth,
1462 1034405 : isMainThread());
1463 :
1464 : return bestScore;
1465 : }
|