From 58ab7c438c184ed0529a397a9082b0afc01845b2 Mon Sep 17 00:00:00 2001 From: Vivien Date: Sat, 11 Jul 2020 11:30:31 +0200 Subject: [PATCH] clean up LMR, store more things in TT, less template to get a smaller executable --- Fathom | 2 +- Source/definition.hpp | 2 +- Source/searcher.hpp | 4 +- Source/searcherDriver.cpp | 4 +- Source/searcherPVS.hpp | 65 ++++++++------- Source/searcherQSearch.cpp | 162 ++++++++++++++++++++++++++++++++++++ Source/searcherQSearch.hpp | 165 ------------------------------------- Source/transposition.hpp | 4 +- 8 files changed, 205 insertions(+), 203 deletions(-) diff --git a/Fathom b/Fathom index 9f011e48..401f7205 160000 --- a/Fathom +++ b/Fathom @@ -1 +1 @@ -Subproject commit 9f011e480509611caf2aead4d3a55fee08ef1763 +Subproject commit 401f7205120eece3bb63e5cef8c4d6815c7e2822 diff --git a/Source/definition.hpp b/Source/definition.hpp index 61baf58f..31bd6126 100644 --- a/Source/definition.hpp +++ b/Source/definition.hpp @@ -38,7 +38,7 @@ typedef uint64_t u_int64_t; #include #endif -const std::string MinicVersion = "2.38"; +const std::string MinicVersion = "2.40"; // *** options #define WITH_UCI diff --git a/Source/searcher.hpp b/Source/searcher.hpp index 4cf8b107..7a9a0e75 100644 --- a/Source/searcher.hpp +++ b/Source/searcher.hpp @@ -53,8 +53,8 @@ struct Searcher{ ScoreType drawScore(); - template ScoreType pvs(ScoreType alpha, ScoreType beta, const Position & p, DepthType depth, unsigned int ply, PVList & pv, DepthType & seldepth, bool isInCheck, bool cutNode, const std::vector * skipMoves = nullptr); - template ScoreType qsearch(ScoreType alpha, ScoreType beta, const Position & p, unsigned int ply, DepthType & seldepth, unsigned int qply); + template ScoreType pvs(ScoreType alpha, ScoreType beta, const Position & p, DepthType depth, unsigned int ply, PVList & pv, DepthType & seldepth, bool isInCheck, bool cutNode, bool canPrune, const std::vector * skipMoves = nullptr); + ScoreType qsearch(ScoreType alpha, ScoreType beta, const Position & p, unsigned int ply, DepthType & seldepth, unsigned int qply, bool qRoot, bool pvnode, char isInCheckHint = -1); ScoreType qsearchNoPruning(ScoreType alpha, ScoreType beta, const Position & p, unsigned int ply, DepthType & seldepth); bool SEE_GE(const Position & p, const Move & m, ScoreType threshold)const; ScoreType SEE(const Position & p, const Move & m)const; diff --git a/Source/searcherDriver.cpp b/Source/searcherDriver.cpp index b50c2659..3f2e432e 100644 --- a/Source/searcherDriver.cpp +++ b/Source/searcherDriver.cpp @@ -109,7 +109,7 @@ PVList Searcher::search(const Position & p, Move & m, DepthType & d, ScoreType & && currentMoveMs < INFINITETIME && currentMoveMs > 800 && TimeMan::msecUntilNextTC > 0){ // easy move detection (small open window search) rootScores.clear(); - ScoreType easyScore = pvs(-MATE, MATE, p, easyMoveDetectionDepth, 0, pvOut, seldepth, isInCheck,false); + ScoreType easyScore = pvs(-MATE, MATE, p, easyMoveDetectionDepth, 0, pvOut, seldepth, isInCheck,false,false); std::sort(rootScores.begin(), rootScores.end(), [](const RootScores& r1, const RootScores & r2) {return r1.s > r2.s; }); if (stopFlag) { // no more time, this is strange ... bestScore = easyScore; @@ -166,7 +166,7 @@ PVList Searcher::search(const Position & p, Move & m, DepthType & d, ScoreType & pvLoc.clear(); stack[p.halfmoves].h = p.h; - score = pvs(alpha,beta,p,windowDepth,0,pvLoc,seldepth,isInCheck,false,skipMoves.empty()?nullptr:&skipMoves); + score = pvs(alpha,beta,p,windowDepth,0,pvLoc,seldepth,isInCheck,false,false,skipMoves.empty()?nullptr:&skipMoves); if ( stopFlag ) break; delta += 2 + delta/2; // from xiphos ... if (alpha > -MATE && score <= alpha) { diff --git a/Source/searcherPVS.hpp b/Source/searcherPVS.hpp index f8373bfe..a3e9ac88 100644 --- a/Source/searcherPVS.hpp +++ b/Source/searcherPVS.hpp @@ -16,8 +16,8 @@ #define PERIODICCHECK 1024ull // pvs inspired by Xiphos -template< bool pvnode, bool canPrune> -ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, DepthType depth, unsigned int ply, PVList & pv, DepthType & seldepth, bool isInCheck, bool cutNode, const std::vector* skipMoves){ +template< bool pvnode> +ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, DepthType depth, unsigned int ply, PVList & pv, DepthType & seldepth, bool isInCheck, bool cutNode, bool canPrune, const std::vector* skipMoves){ if (stopFlag) return STOPSCORE; if ( isMainThread() ){ static int periodicCheck = 0; @@ -41,7 +41,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep EvalData data; if (ply >= MAX_DEPTH - 1 || depth >= MAX_DEPTH - 1) return eval(p, data, *this); - if ( depth <= 0 ) return qsearch(alpha,beta,p,ply,seldepth,0); + if ( depth <= 0 ) return qsearch(alpha,beta,p,ply,seldepth,0,true,pvnode,isInCheck); seldepth = std::max((DepthType)ply,seldepth); ++stats.counters[Stats::sid_nodes]; @@ -67,7 +67,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep // probe TT TT::Entry e; bool ttDepthOk = TT::getEntry(*this, p, pHash, depth, e); - TT::Bound bound = TT::Bound(e.b & ~TT::B_ttFlag); + TT::Bound bound = TT::Bound(e.b & ~TT::B_allFlags); if (ttDepthOk) { // if depth of TT entry is enough if ( !rootnode && !pvnode && ( (bound == TT::B_alpha && e.s <= alpha) || (bound == TT::B_beta && e.s >= beta) || (bound == TT::B_exact) ) ) { @@ -79,8 +79,9 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep // if entry hash is not null and entry move is valid, this is a valid TT move (we don't care about depth here !) bool ttHit = e.h != nullHash; bool validTTmove = ttHit && e.m != INVALIDMINIMOVE; - bool ttPV = pvnode || (validTTmove && (e.b&TT::B_ttFlag)); - bool formerPV = ttPV && !pvnode; + bool ttPV = pvnode || (validTTmove && (e.b&TT::B_ttFlag)); ///@todo store more things in TT bound ... + bool ttIsCheck = validTTmove && (e.b&TT::B_isCheckFlag); + //bool formerPV = ttPV && !pvnode; #ifdef WITH_SYZYGY ScoreType tbScore = 0; @@ -150,7 +151,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep ScoreType rAlpha = alpha - SearchConfig::razoringMarginDepthInit[evalScoreIsHashScore] - SearchConfig::razoringMarginDepthCoeff[evalScoreIsHashScore]*marginDepth; if ( SearchConfig::doRazoring && depth <= SearchConfig::razoringMaxDepth[evalScoreIsHashScore] && evalScore <= rAlpha ){ ++stats.counters[Stats::sid_razoringTry]; - const ScoreType qScore = qsearch(alpha,beta,p,ply,seldepth,0); + const ScoreType qScore = qsearch(alpha,beta,p,ply,seldepth,0,true,pvnode,isInCheck); if ( stopFlag ) return STOPSCORE; if ( qScore <= alpha || (depth < 2 && evalScoreIsHashScore) ) return ++stats.counters[Stats::sid_razoring],qScore; } @@ -178,7 +179,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep applyNull(*this,pN); stack[pN.halfmoves].h = pN.h; stack[pN.halfmoves].p = pN; - ScoreType nullscore = -pvs(-beta, -beta + 1, pN, nullDepth, ply + 1, nullPV, seldepth, isInCheck, !cutNode); + ScoreType nullscore = -pvs(-beta, -beta + 1, pN, nullDepth, ply + 1, nullPV, seldepth, isInCheck, !cutNode, false); if (stopFlag) return STOPSCORE; TT::Entry nullEThreat; TT::getEntry(*this, pN, computeHash(pN), 0, nullEThreat); @@ -223,10 +224,10 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep Position p2 = p; if ( ! apply(p2,*it) ) continue; ++probCutCount; - ScoreType scorePC = -qsearch(-betaPC, -betaPC + 1, p2, ply + 1, seldepth,0); + ScoreType scorePC = -qsearch(-betaPC, -betaPC + 1, p2, ply + 1, seldepth,0,true,pvnode); PVList pcPV; if (stopFlag) return STOPSCORE; - if (scorePC >= betaPC) ++stats.counters[Stats::sid_probcutTry2], scorePC = -pvs(-betaPC,-betaPC+1,p2,depth-SearchConfig::probCutMinDepth+1,ply+1,pcPV,seldepth, isAttacked(p2, kingSquare(p2)), !cutNode); + if (scorePC >= betaPC) ++stats.counters[Stats::sid_probcutTry2], scorePC = -pvs(-betaPC,-betaPC+1,p2,depth-SearchConfig::probCutMinDepth+1,ply+1,pcPV,seldepth, isAttacked(p2, kingSquare(p2)), !cutNode, true); if (stopFlag) return STOPSCORE; if (scorePC >= betaPC) return ++stats.counters[Stats::sid_probcut], scorePC; } @@ -237,14 +238,15 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep if ( (!validTTmove /*|| e.d < depth-4*/) && ((pvnode && depth >= SearchConfig::iidMinDepth) || (cutNode && depth >= SearchConfig::iidMinDepth2)) ){ ///@todo try with cutNode only ? ++stats.counters[Stats::sid_iid]; PVList iidPV; - pvs(alpha,beta,p,depth/2,ply,iidPV,seldepth,isInCheck,cutNode,skipMoves); + pvs(alpha,beta,p,depth/2,ply,iidPV,seldepth,isInCheck,cutNode,false,skipMoves); if (stopFlag) return STOPSCORE; TT::getEntry(*this, p, pHash, 0, e); ttHit = e.h != nullHash; validTTmove = ttHit && e.m != INVALIDMINIMOVE; - bound = TT::Bound(e.b & ~TT::B_ttFlag); + bound = TT::Bound(e.b & ~TT::B_allFlags); ttPV = pvnode || (ttHit && (e.b&TT::B_ttFlag)); - formerPV = ttPV && !pvnode; + ttIsCheck = validTTmove && (e.b&TT::B_isCheckFlag); + //formerPV = ttPV && !pvnode; if ( ttHit && !isInCheck && ((bound == TT::B_alpha && e.s < evalScore) || (bound == TT::B_beta && e.s > evalScore) || (bound == TT::B_exact)) ){ evalScore = adjustHashScore(e.s,ply); evalScoreIsHashScore=true; @@ -275,6 +277,8 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep stack[p.halfmoves].threat = refutation; + bool bestMoveIsCheck = false; + // try the tt move before move generation (if not skipped move) if ( validTTmove && !isSkipMove(e.m,skipMoves)) { // should be the case thanks to iid at pvnode bestMove = e.m; // in order to preserve tt move for alpha bound entry @@ -294,7 +298,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep PVList childPV; stack[p2.halfmoves].h = p2.h; stack[p2.halfmoves].p = p2; ///@todo another expensive copy !!!! - const bool isCheck = isAttacked(p2, kingSquare(p2)); + const bool isCheck = ttIsCheck || isAttacked(p2, kingSquare(p2)); if ( isCapture(e.m) ) ttMoveIsCapture = true; //const bool isAdvancedPawnPush = PieceTools::getPieceType(p,Move2From(e.m)) == P_wp && (SQRANK(to) > 5 || SQRANK(to) < 2); // extensions @@ -335,10 +339,10 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep PVList sePV; DepthType seSeldetph = 0; std::vector skip({e.m}); - const ScoreType score = pvs(betaC - 1, betaC, p, depth/2, ply, sePV, seSeldetph, isInCheck, cutNode, &skip); + const ScoreType score = pvs(betaC - 1, betaC, p, depth/2, ply, sePV, seSeldetph, isInCheck, cutNode, false, &skip); if (stopFlag) return STOPSCORE; if (score < betaC) { // TT move is singular - ++stats.counters[Stats::sid_singularExtension],/*ttMoveSingularExt=true,*/++extension; + ++stats.counters[Stats::sid_singularExtension],/*ttMoveSingularExt=!ttMoveIsCapture,*/++extension; // TT move is "very singular" : kind of single reply extension if ( score < betaC - 4*depth) ++stats.counters[Stats::sid_singularExtension2],++extension; } @@ -348,12 +352,12 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep } // if TT move is above beta, we try a reduce search early to see if another move is above beta (from SF) else if ( e.s >= beta ){ - const ScoreType score2 = pvs(beta - 1, beta, p, depth-4, ply, sePV, seSeldetph, isInCheck, cutNode, &skip); + const ScoreType score2 = pvs(beta - 1, beta, p, depth-4, ply, sePV, seSeldetph, isInCheck, cutNode, false, &skip); if ( score2 > beta ) return ++stats.counters[Stats::sid_singularExtension4],beta; // fail-hard } } } - const ScoreType ttScore = -pvs(-beta, -alpha, p2, depth - 1 + extension, ply + 1, childPV, seldepth, isCheck, !cutNode); + const ScoreType ttScore = -pvs(-beta, -alpha, p2, depth - 1 + extension, ply + 1, childPV, seldepth, isCheck, !cutNode, true); if (stopFlag) return STOPSCORE; if (rootnode){ rootScores.push_back({e.m,ttScore}); @@ -362,6 +366,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep if ( ttScore > bestScore ){ bestScore = ttScore; bestMove = e.m; + bestMoveIsCheck = isCheck; if (ttScore > alpha) { hashBound = TT::B_exact; if (pvnode) updatePV(pv, e.m, childPV); @@ -369,7 +374,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep ++stats.counters[Stats::sid_ttbeta]; // increase history bonus of this move if ( !isInCheck && isQuiet /*&& depth > 1*/) updateTables(*this, p, depth + (ttScore > (beta+80)), ply, e.m, TT::B_beta, cmhPtr); - TT::setEntry(*this,pHash,e.m,createHashScore(ttScore,ply),createHashScore(evalScore,ply),TT::Bound(TT::B_beta|(ttPV?TT::B_ttFlag:TT::B_none)),depth); + TT::setEntry(*this,pHash,e.m,createHashScore(ttScore,ply),createHashScore(evalScore,ply),TT::Bound(TT::B_beta|(ttPV?TT::B_ttFlag:TT::B_none)|(bestMoveIsCheck?TT::B_isCheckFlag:TT::B_none)|(isInCheck?TT::B_isInCheckFlag:TT::B_none)),depth); return ttScore; } ++stats.counters[Stats::sid_ttalpha]; @@ -462,7 +467,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep //if (EXTENDMORE(extension) && pvnode && firstMove && (p.pieces_const(p.c) && isQuiet && Move2Type(*it) == T_std && PieceTools::getPieceType(p, Move2From(*it)) == P_wq && isAttacked(p, BBTools::SquareFromBitBoard(p.pieces_const(p.c)))) && SEE_GE(p, *it, 0)) ++stats.counters[Stats::sid_queenThreatExtension], ++extension; } // pvs - if (validMoveCount < (2/*+2*rootnode*/) || !SearchConfig::doPVS ) score = -pvs(-beta,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,!cutNode); + if (validMoveCount < (2/*+2*rootnode*/) || !SearchConfig::doPVS ) score = -pvs(-beta,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,!cutNode,true); else{ // reductions & prunings const bool isPrunable = /*isNotEndGame &&*/ !isAdvancedPawnPush && !isMateScore(alpha) && !DynamicConfig::mateFinder && !killerT.isKiller(*it,ply); @@ -504,18 +509,17 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep // LMR DepthType reduction = 0; if (SearchConfig::doLMR && depth >= SearchConfig::lmrMinDepth - && ( (isReductible && isQuiet) /*|| (isPrunableCap && stack[p.halfmoves].eval <= alpha ) || cutNode*/ ) - /*&& validMoveCount > 1 + 2*rootnode*/ ){ + && ( (isReductible && isQuiet) /*|| isPrunableCap*/ /*|| stack[p.halfmoves].eval <= alpha*/ /*|| cutNode*/ ) + && validMoveCount > 1 + 2*rootnode ){ ++stats.counters[Stats::sid_lmr]; reduction += SearchConfig::lmrReduction[std::min((int)depth,MAX_DEPTH-1)][std::min(validMoveCount,MAX_DEPTH)]; - // more reduction reduction += !improving + ttMoveIsCapture; reduction += (cutNode && evalScore - SearchConfig::failHighReductionThresholdInit[evalScoreIsHashScore] - marginDepth*SearchConfig::failHighReductionThresholdDepth[evalScoreIsHashScore] > beta); ///@todo try without //reduction += moveCountPruning && !formerPV; - // less reduction reduction -= /*std::min(2,*/HISTORY_DIV(2 * Move2Score(*it))/*)*/; //history reduction/extension (beware killers and counter are scored above history max) - reduction -= reduction && (pvnode || isDangerRed || !noCheck /*|| ttMoveSingularExt*/ /*|| isEmergencyDefence*/); - //reduction -= ttPV*2; + //reduction -= (3*data.mobility[p.c] < 2*data.mobility[~p.c]); + reduction -= (ttPV || isDangerRed || !noCheck /*|| ttMoveSingularExt*//*|| isEmergencyDefence*/); + // never extend more than reduce if ( extension - reduction > 0 ) reduction = extension; if ( reduction >= depth - 1 + extension ) reduction = depth - 1 + extension - 1; @@ -528,14 +532,14 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep } // PVS - score = -pvs(-alpha-1,-alpha,p2,nextDepth,ply+1,childPV,seldepth,isCheck,true); + score = -pvs(-alpha-1,-alpha,p2,nextDepth,ply+1,childPV,seldepth,isCheck,true,true); if ( reduction > 0 && score > alpha ){ ++stats.counters[Stats::sid_lmrFail]; childPV.clear(); - score = -pvs(-alpha-1,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,!cutNode); + score = -pvs(-alpha-1,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,!cutNode,true); } if ( pvnode && score > alpha && (rootnode || score < beta) ){ ++stats.counters[Stats::sid_pvsFail]; childPV.clear(); - score = -pvs(-beta ,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,false); + score = -pvs(-beta ,-alpha,p2,depth-1+extension,ply+1,childPV,seldepth,isCheck,false,true); } // potential new pv node } @@ -545,6 +549,7 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep if ( score > bestScore ){ bestScore = score; bestMove = *it; + bestMoveIsCheck = isCheck; //bestScoreUpdated = true; if ( score > alpha ){ if (pvnode) updatePV(pv, *it, childPV); @@ -571,6 +576,6 @@ ScoreType Searcher::pvs(ScoreType alpha, ScoreType beta, const Position & p, Dep } if ( validMoveCount==0 ) return (isInCheck || !withoutSkipMove)?-MATE + ply : 0; - TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(hashBound|(ttPV?TT::B_ttFlag:TT::B_none)),depth); + TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(hashBound|(ttPV?TT::B_ttFlag:TT::B_none)|(bestMoveIsCheck?TT::B_isCheckFlag:TT::B_none)|(isInCheck?TT::B_isInCheckFlag:TT::B_none)),depth); return bestScore; } diff --git a/Source/searcherQSearch.cpp b/Source/searcherQSearch.cpp index c1c4a949..36a6623d 100644 --- a/Source/searcherQSearch.cpp +++ b/Source/searcherQSearch.cpp @@ -32,3 +32,165 @@ ScoreType Searcher::qsearchNoPruning(ScoreType alpha, ScoreType beta, const Posi } return bestScore; } + +inline ScoreType qDeltaMargin(const Position & p) { + ScoreType delta = (p.pieces_const(p.c,P_wp) & seventhRank[p.c]) ? Values[P_wq+PieceShift] : Values[P_wp+PieceShift]; + return delta + Values[P_wq+PieceShift]; +} + +ScoreType Searcher::qsearch(ScoreType alpha, ScoreType beta, const Position & p, unsigned int ply, DepthType & seldepth, unsigned int qply, bool qRoot, bool pvnode, char isInCheckHint){ + if (stopFlag) return STOPSCORE; // no time verification in qsearch, too slow + ++stats.counters[Stats::sid_qnodes]; + + alpha = std::max(alpha, (ScoreType)(-MATE + ply)); + beta = std::min(beta , (ScoreType)( MATE - ply + 1)); + if (alpha >= beta) return alpha; + + if ((int)ply > seldepth) seldepth = ply; + + EvalData data; + if (ply >= MAX_DEPTH - 1) return eval(p, data, *this); + Move bestMove = INVALIDMOVE; + + debug_king_cap(p); + + // probe TT + TT::Entry e; + const Hash pHash = computeHash(p); + const bool ttDepthOk = TT::getEntry(*this, p, pHash, 0, e); + const TT::Bound bound = TT::Bound(e.b & ~TT::B_allFlags); + const bool ttHit = e.h != nullHash; + const bool validTTmove = ttHit && e.m != INVALIDMINIMOVE; + const bool ttPV = pvnode || (validTTmove && (e.b&TT::B_ttFlag)); + const bool ttIsInCheck = validTTmove && (e.b&TT::B_isInCheckFlag); + const bool isInCheck = isInCheckHint!=-1 ? isInCheckHint : ttIsInCheck || isAttacked(p, kingSquare(p)); + const bool specialQSearch = isInCheck || qRoot; + const DepthType hashDepth = specialQSearch ? 0 : -1; + if (ttDepthOk && e.d >= hashDepth) { // if depth of TT entry is enough + if (!pvnode && ((bound == TT::B_alpha && e.s <= alpha) || (bound == TT::B_beta && e.s >= beta) || (bound == TT::B_exact))) { + return adjustHashScore(e.s, ply); + } + } + if ( qRoot && interiorNodeRecognizer(p) == MaterialHash::Ter_Draw) return drawScore(); ///@todo is that gain elo ??? + + if ( validTTmove && (isInCheck || isCapture(e.m)) ) bestMove = e.m; + + // get a static score for the position. + ScoreType evalScore; + if (isInCheck) evalScore = -MATE + ply; + else if ( p.lastMove == NULLMOVE && ply > 0 ) evalScore = 2*ScaleScore(EvalConfig::tempo,stack[p.halfmoves-1].data.gp) - stack[p.halfmoves-1].eval; // skip eval if nullmove just applied ///@todo wrong ! gp is 0 here so tempoMG must be == tempoEG + else{ + if (ttHit){ // if we had a TT hit (with or without associated move), wa can use its eval instead of calling eval() + ++stats.counters[Stats::sid_ttschits]; + evalScore = e.e; + /* + const Hash matHash = MaterialHash::getMaterialHash(p.mat); + if ( matHash ){ + ++stats.counters[Stats::sid_materialTableHits]; + const MaterialHash::MaterialHashEntry & MEntry = MaterialHash::materialHashTable[matHash]; + data.gp = MEntry.gp; + } + else{ + ScoreType matScoreW = 0; + ScoreType matScoreB = 0; + data.gp = gamePhase(p,matScoreW,matScoreB); + ++stats.counters[Stats::sid_materialTableMiss]; + } + */ + data.gp = 0.5; // force mid game value in sorting ... affect only quiet move, so here check evasion ... + } + else { + ++stats.counters[Stats::sid_ttscmiss]; + evalScore = eval(p, data, *this); + } + } + const ScoreType staticScore = evalScore; + bool evalScoreIsHashScore = false; + // use tt score if possible and not in check + if ( !isInCheck){ + if ( ttHit && ((bound == TT::B_alpha && e.s <= evalScore) || (bound == TT::B_beta && e.s >= evalScore) || (bound == TT::B_exact)) ) evalScore = e.s, evalScoreIsHashScore = true; + if ( !ttHit ) TT::setEntry(*this,pHash,INVALIDMOVE,createHashScore(evalScore,ply),createHashScore(evalScore,ply),TT::B_none,-2); // already insert an eval here in case of pruning ... + } + + // early cut-off based on eval score (static or from TT score) + if ( evalScore >= beta ){ + //if ( !ttHit ) TT::setEntry(*this,pHash,INVALIDMOVE,createHashScore(evalScore,ply),createHashScore(evalScore,ply),TT::B_none,-2); + return evalScore; + } + if ( !isInCheck && SearchConfig::doQDeltaPruning && staticScore + qDeltaMargin(p) < alpha ) return ++stats.counters[Stats::sid_delta],alpha; + if ( evalScore > alpha) alpha = evalScore; + + TT::Bound b = TT::B_alpha; + ScoreType bestScore = evalScore; + const ScoreType alphaInit = alpha; + int validMoveCount = 0; + + // try the tt move before move generation + if ( validTTmove && (isInCheck || isCapture(e.m)) ){ + Position p2 = p; + if ( apply(p2,e.m) ){; + ++validMoveCount; + TT::prefetch(computeHash(p2)); + const ScoreType score = -qsearch(-beta,-alpha,p2,ply+1,seldepth,isInCheck?0:qply+1,false,false); + if ( score > bestScore){ + bestMove = e.m; + bestScore = score; + if ( score > alpha ){ + if (score >= beta) { + b = TT::B_beta; + TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(b|(ttPV?TT::B_ttFlag:TT::B_none)|(isInCheck?TT::B_isInCheckFlag:TT::B_none)),hashDepth); + return bestScore; + } + b = TT::B_exact; + alpha = score; + } + } + } + } + + MoveList moves; + if ( isInCheck ) MoveGen::generate(p,moves); ///@todo generate only evasion ! + else MoveGen::generate(p,moves); + CMHPtrArray cmhPtr; + getCMHPtr(p.halfmoves,cmhPtr); + + const Square recapture = VALIDMOVE(p.lastMove) ? Move2To(p.lastMove) : INVALIDSQUARE; + const bool onlyRecapture = qply > 5 && isCapture(p.lastMove) && recapture != INVALIDSQUARE; + +#ifdef USE_PARTIAL_SORT + MoveSorter::score(*this,moves,p,data.gp,ply,cmhPtr,false,isInCheck,validTTmove?&e:NULL); ///@todo warning gp is often = 0.5 here ! + size_t offset = 0; + const Move * it = nullptr; + while( (it = MoveSorter::pickNext(moves,offset))){ +#else + MoveSorter::scoreAndSort(*this,moves,p,data.gp,ply,cmhPtr,false,isInCheck,validTTmove?&e:NULL); ///@todo warning gp is often = 0.5 here ! + for(auto it = moves.begin() ; it != moves.end() ; ++it){ +#endif + if (validTTmove && sameMove(e.m, *it)) continue; // already tried + if (!isInCheck) { + if (onlyRecapture && Move2To(*it) != recapture ) continue; // only recapture now ... + if (!SEE_GE(p,*it,0)) {++stats.counters[Stats::sid_qsee];continue;} + if (SearchConfig::doQFutility && staticScore + SearchConfig::qfutilityMargin[evalScoreIsHashScore] + (isPromotionCap(*it) ? (Values[P_wq+PieceShift]-Values[P_wp+PieceShift]) : 0 ) + (Move2Type(*it)==T_ep ? Values[P_wp+PieceShift] : PieceTools::getAbsValue(p, Move2To(*it))) <= alphaInit) {++stats.counters[Stats::sid_qfutility];continue;} + } + Position p2 = p; + if ( ! apply(p2,*it) ) continue; + ++validMoveCount; + TT::prefetch(computeHash(p2)); + const ScoreType score = -qsearch(-beta,-alpha,p2,ply+1,seldepth,isInCheck?0:qply+1,false,false); + if ( score > bestScore){ + bestMove = *it; + bestScore = score; + if ( score > alpha ){ + if (score >= beta) { + b = TT::B_beta; + break; + } + b = TT::B_exact; + alpha = score; + } + } + } + if ( validMoveCount==0 && isInCheck) bestScore = -MATE + ply; + TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(b|(ttPV?TT::B_ttFlag:TT::B_none)|(isInCheck?TT::B_isInCheckFlag:TT::B_none)),hashDepth); + return bestScore; +} diff --git a/Source/searcherQSearch.hpp b/Source/searcherQSearch.hpp index ee37aa68..8b137891 100644 --- a/Source/searcherQSearch.hpp +++ b/Source/searcherQSearch.hpp @@ -1,166 +1 @@ -#pragma once -inline ScoreType qDeltaMargin(const Position & p) { - ScoreType delta = (p.pieces_const(p.c,P_wp) & seventhRank[p.c]) ? Values[P_wq+PieceShift] : Values[P_wp+PieceShift]; - return delta + Values[P_wq+PieceShift]; -} - -///@todo only recapture after a while - -template < bool qRoot, bool pvnode > -ScoreType Searcher::qsearch(ScoreType alpha, ScoreType beta, const Position & p, unsigned int ply, DepthType & seldepth, unsigned int qply){ - if (stopFlag) return STOPSCORE; // no time verification in qsearch, too slow - ++stats.counters[Stats::sid_qnodes]; - - alpha = std::max(alpha, (ScoreType)(-MATE + ply)); - beta = std::min(beta , (ScoreType)( MATE - ply + 1)); - if (alpha >= beta) return alpha; - - if ((int)ply > seldepth) seldepth = ply; - - EvalData data; - if (ply >= MAX_DEPTH - 1) return eval(p, data, *this); - Move bestMove = INVALIDMOVE; - - const bool isInCheck = isAttacked(p, kingSquare(p)); - const bool specialQSearch = isInCheck || qRoot; - DepthType hashDepth = specialQSearch ? 0 : -1; - - debug_king_cap(p); - - // probe TT - TT::Entry e; - const Hash pHash = computeHash(p); - const bool ttDepthOk = TT::getEntry(*this, p, pHash, hashDepth, e); - TT::Bound bound = TT::Bound(e.b & ~TT::B_ttFlag); - if (ttDepthOk) { // if depth of TT entry is enough - if (!pvnode && ((bound == TT::B_alpha && e.s <= alpha) || (bound == TT::B_beta && e.s >= beta) || (bound == TT::B_exact))) { - return adjustHashScore(e.s, ply); - } - } - if ( qRoot && interiorNodeRecognizer(p) == MaterialHash::Ter_Draw) return drawScore(); ///@todo is that gain elo ??? - - const bool ttHit = e.h != nullHash; - const bool validTTmove = ttHit && e.m != INVALIDMINIMOVE; - const bool ttPV = pvnode || (validTTmove && (e.b&TT::B_ttFlag)); - if ( validTTmove && (isInCheck || isCapture(e.m)) ) bestMove = e.m; - - // get a static score for the position. - ScoreType evalScore; - if (isInCheck) evalScore = -MATE + ply; - else if ( p.lastMove == NULLMOVE && ply > 0 ) evalScore = 2*ScaleScore(EvalConfig::tempo,stack[p.halfmoves-1].data.gp) - stack[p.halfmoves-1].eval; // skip eval if nullmove just applied ///@todo wrong ! gp is 0 here so tempoMG must be == tempoEG - else{ - if (ttHit){ // if we had a TT hit (with or without associated move), wa can use its eval instead of calling eval() - ++stats.counters[Stats::sid_ttschits]; - evalScore = e.e; - /* - const Hash matHash = MaterialHash::getMaterialHash(p.mat); - if ( matHash ){ - ++stats.counters[Stats::sid_materialTableHits]; - const MaterialHash::MaterialHashEntry & MEntry = MaterialHash::materialHashTable[matHash]; - data.gp = MEntry.gp; - } - else{ - ScoreType matScoreW = 0; - ScoreType matScoreB = 0; - data.gp = gamePhase(p,matScoreW,matScoreB); - ++stats.counters[Stats::sid_materialTableMiss]; - } - */ - data.gp = 0.5; // force mid game value in sorting ... affect only quiet move, so here check evasion ... - } - else { - ++stats.counters[Stats::sid_ttscmiss]; - evalScore = eval(p, data, *this); - } - } - const ScoreType staticScore = evalScore; - bool evalScoreIsHashScore = false; - // use tt score if possible and not in check - if ( !isInCheck){ - if ( ttHit && ((bound == TT::B_alpha && e.s <= evalScore) || (bound == TT::B_beta && e.s >= evalScore) || (bound == TT::B_exact)) ) evalScore = e.s, evalScoreIsHashScore = true; - if ( !ttHit ) TT::setEntry(*this,pHash,INVALIDMOVE,createHashScore(evalScore,ply),createHashScore(evalScore,ply),TT::B_none,-2); // already insert an eval here in case of pruning ... - } - - // early cut-off based on eval score (static or from TT score) - if ( evalScore >= beta ){ - //if ( !ttHit ) TT::setEntry(*this,pHash,INVALIDMOVE,createHashScore(evalScore,ply),createHashScore(evalScore,ply),TT::B_none,-2); - return evalScore; - } - if ( !isInCheck && SearchConfig::doQDeltaPruning && staticScore + qDeltaMargin(p) < alpha ) return ++stats.counters[Stats::sid_delta],alpha; - if ( evalScore > alpha) alpha = evalScore; - - TT::Bound b = TT::B_alpha; - ScoreType bestScore = evalScore; - const ScoreType alphaInit = alpha; - int validMoveCount = 0; - - // try the tt move before move generation - if ( validTTmove && (isInCheck || isCapture(e.m)) ){ - Position p2 = p; - if ( apply(p2,e.m) ){; - ++validMoveCount; - TT::prefetch(computeHash(p2)); - const ScoreType score = -qsearch(-beta,-alpha,p2,ply+1,seldepth,isInCheck?0:qply+1); - if ( score > bestScore){ - bestMove = e.m; - bestScore = score; - if ( score > alpha ){ - if (score >= beta) { - b = TT::B_beta; - TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(b|(ttPV?TT::B_ttFlag:TT::B_none)),hashDepth); - return bestScore; - } - b = TT::B_exact; - alpha = score; - } - } - } - } - - MoveList moves; - if ( isInCheck ) MoveGen::generate(p,moves); ///@todo generate only evasion ! - else MoveGen::generate(p,moves); - CMHPtrArray cmhPtr; - getCMHPtr(p.halfmoves,cmhPtr); - - const Square recapture = VALIDMOVE(p.lastMove) ? Move2To(p.lastMove) : INVALIDSQUARE; - const bool onlyRecapture = qply > 5 && isCapture(p.lastMove) && recapture != INVALIDSQUARE; - -#ifdef USE_PARTIAL_SORT - MoveSorter::score(*this,moves,p,data.gp,ply,cmhPtr,false,isInCheck,validTTmove?&e:NULL); ///@todo warning gp is often = 0.5 here ! - size_t offset = 0; - const Move * it = nullptr; - while( (it = MoveSorter::pickNext(moves,offset))){ -#else - MoveSorter::scoreAndSort(*this,moves,p,data.gp,ply,cmhPtr,false,isInCheck,validTTmove?&e:NULL); ///@todo warning gp is often = 0.5 here ! - for(auto it = moves.begin() ; it != moves.end() ; ++it){ -#endif - if (validTTmove && sameMove(e.m, *it)) continue; // already tried - if (!isInCheck) { - if (onlyRecapture && Move2To(*it) != recapture ) continue; // only recapture now ... - if (!SEE_GE(p,*it,0)) {++stats.counters[Stats::sid_qsee];continue;} - if (SearchConfig::doQFutility && staticScore + SearchConfig::qfutilityMargin[evalScoreIsHashScore] + (isPromotionCap(*it) ? (Values[P_wq+PieceShift]-Values[P_wp+PieceShift]) : 0 ) + (Move2Type(*it)==T_ep ? Values[P_wp+PieceShift] : PieceTools::getAbsValue(p, Move2To(*it))) <= alphaInit) {++stats.counters[Stats::sid_qfutility];continue;} - } - Position p2 = p; - if ( ! apply(p2,*it) ) continue; - ++validMoveCount; - TT::prefetch(computeHash(p2)); - const ScoreType score = -qsearch(-beta,-alpha,p2,ply+1,seldepth,isInCheck?0:qply+1); - if ( score > bestScore){ - bestMove = *it; - bestScore = score; - if ( score > alpha ){ - if (score >= beta) { - b = TT::B_beta; - break; - } - b = TT::B_exact; - alpha = score; - } - } - } - if ( validMoveCount==0 && isInCheck) bestScore = -MATE + ply; - TT::setEntry(*this,pHash,bestMove,createHashScore(bestScore,ply),createHashScore(evalScore,ply),TT::Bound(b|(ttPV?TT::B_ttFlag:TT::B_none)),hashDepth); - return bestScore; -} diff --git a/Source/transposition.hpp b/Source/transposition.hpp index 3f52d5dd..e2915a1d 100644 --- a/Source/transposition.hpp +++ b/Source/transposition.hpp @@ -15,7 +15,7 @@ struct Searcher; namespace TT{ extern GenerationType curGen; -enum Bound : unsigned char{ B_none = 0, B_alpha = 1, B_beta = 2, B_exact = 3, B_ttFlag = 4}; +enum Bound : unsigned char{ B_none = 0, B_alpha = 1, B_beta = 2, B_exact = 3, B_ttFlag = 4, B_isCheckFlag = 8, B_isInCheckFlag = 16, B_allFlags = B_ttFlag|B_isCheckFlag|B_isInCheckFlag}; #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" @@ -28,7 +28,7 @@ enum Bound : unsigned char{ B_none = 0, B_alpha = 1, B_beta = 2, B_exact = 3, B_ #endif // defined(__clang__) #pragma pack(push, 1) struct Entry{ - Entry():m(INVALIDMINIMOVE),h(0),s(0),e(0),b(B_none),d(-1)/*,generation(curGen)*/{} + Entry():m(INVALIDMINIMOVE),h(0),s(0),e(0),b(B_none),d(-2)/*,generation(curGen)*/{} Entry(Hash _h, Move _m, ScoreType _s, ScoreType _e, Bound _b, DepthType _d) : h(Hash64to32(_h)), m(Move2MiniMove(_m)), s(_s), e(_e), /*generation(curGen),*/ b(_b), d(_d){} MiniHash h; //32 ScoreType s, e; //16 + 16