/*
Texel - A UCI chess engine.
Copyright (C) 2012-2014 Peter Ă–sterlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* search.cpp
*
* Created on: Feb 25, 2012
* Author: petero
*/
#include "search.hpp"
#include "numa.hpp"
#include "tbprobe.hpp"
#include "treeLogger.hpp"
#include "textio.hpp"
#include "util/logger.hpp"
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
using namespace SearchConst;
using namespace Logger;
Search::Search(const Position& pos0, const std::vector<U64>& posHashList0,
int posHashListSize0, SearchTables& st, ParallelData& pd0,
const std::shared_ptr<SplitPoint>& rootSp,
TreeLogger& logFile0)
: eval(st.et), kt(st.kt), ht(st.ht), tt(st.tt), pd(pd0), threadNo(0),
mainNumaNode(true), logFile(logFile0) {
stopHandler = std::make_shared<DefaultStopHandler>(*this);
spVec.push_back(rootSp);
init(pos0, posHashList0, posHashListSize0);
}
void
Search::init(const Position& pos0, const std::vector<U64>& posHashList0,
int posHashListSize0) {
pos = pos0;
posHashList = posHashList0;
posHashListSize = posHashListSize0;
posHashFirstNew = posHashListSize;
initNodeStats();
minTimeMillis = -1;
maxTimeMillis = -1;
searchNeedMoreTime = false;
maxNodes = -1;
minProbeDepth = 0;
nodesBetweenTimeCheck = 10000;
strength = 1000;
weak = false;
randomSeed = 0;
tLastStats = currentTimeMillis();
totalNodes = 0;
tbHits = 0;
nodesToGo = 0;
verbose = false;
}
void
Search::timeLimit(int minTimeLimit, int maxTimeLimit) {
minTimeMillis = minTimeLimit;
maxTimeMillis = maxTimeLimit;
if ((maxTimeMillis >= 0) && (maxTimeMillis < 1000))
nodesBetweenTimeCheck = 1000;
else
nodesBetweenTimeCheck = 10000;
}
void
Search::setStrength(int strength, U64 randomSeed) {
if (strength < 0) strength = 0;
if (strength > 1000) strength = 1000;
this->strength = strength;
weak = strength < 1000;
this->randomSeed = randomSeed;
}
Move
Search::iterativeDeepening(const MoveList& scMovesIn,
int maxDepth, U64 initialMaxNodes,
bool verbose, int maxPV, bool onlyExact,
int minProbeDepth) {
tStart = currentTimeMillis();
totalNodes = 0;
tbHits = 0;
nodesToGo = 0;
if (scMovesIn.size <= 0)
return Move(); // No moves to search
logFile.open("/home/petero/treelog.dmp", pd, threadNo);
const U64 rootNodeIdx = logFile.logPosition(pos, 0, 0, 0);
kt.clear();
pd.npsInfo.reset();
// pd.wq.resetStat();
const bool smp = pd.numHelperThreads() > 0;
maxNodes = initialMaxNodes;
this->minProbeDepth = (TBProbe::tbEnabled() ? minProbeDepth : MAX_SEARCH_DEPTH) * plyScale;
std::vector<MoveInfo> rootMoves;
getRootMoves(scMovesIn, rootMoves, maxDepth);
Position origPos(pos);
bool firstIteration = true;
Move bestMove = rootMoves[0].move; // bestMove is != rootMoves[0].move when there is an unresolved fail high
Move bestExactMove = rootMoves[0].move; // Only updated when new best move has exact score
this->verbose = verbose;
if ((maxDepth < 0) || (maxDepth > MAX_SEARCH_DEPTH))
maxDepth = MAX_SEARCH_DEPTH;
maxPV = std::min(maxPV, (int)rootMoves.size());
for (size_t i = 0; i < COUNT_OF(searchTreeInfo); i++) {
searchTreeInfo[i].allowNullMove = true;
searchTreeInfo[i].singularMove.setMove(0,0,0,0);
}
ht.reScale();
int posHashFirstNew0 = posHashFirstNew;
try {
for (int depthS = plyScale; ; depthS += plyScale, firstIteration = false) {
initNodeStats();
if (listener) listener->notifyDepth(depthS/plyScale);
int aspirationDelta = 0;
int alpha = 0;
UndoInfo ui;
bool needMoreTime = false;
for (int mi = 0; mi < (int)rootMoves.size(); mi++) {
posHashFirstNew = posHashFirstNew0 + ((mi > 0 && maxPV > 1) ? 1 : 0);
if (mi < maxPV)
aspirationDelta = isWinScore(std::abs(rootMoves[mi].score())) ? 3000 : aspirationWindow;
if (firstIteration)
alpha = -MATE0;
else if (mi < maxPV)
alpha = std::max(rootMoves[mi].score() - aspirationDelta, -MATE0);
else
alpha = rootMoves[maxPV-1].score();
searchNeedMoreTime = (mi > 0);
Move& m = rootMoves[mi].move;
pd.topMove = m;
if (currentTimeMillis() - tStart >= 1000)
if (listener) listener->notifyCurrMove(m, mi + 1);
S64 nodesThisMove = -totalNodes;
posHashList[posHashListSize++] = pos.zobristHash();
bool givesCheck = MoveGen::givesCheck(pos, m);
int beta;
if (firstIteration)
beta = MATE0;
else if (mi < maxPV)
beta = std::min(rootMoves[mi].score() + aspirationDelta, MATE0);
else
beta = alpha + 1;
int lmrS = 0;
bool isCapture = (pos.getPiece(m.to()) != Piece::EMPTY);
bool isPromotion = (m.promoteTo() != Piece::EMPTY);
if ((depthS >= 3*plyScale) && !isCapture && !isPromotion &&
!givesCheck && !passedPawnPush(pos, m) && (mi >= rootLMRMoveCount + maxPV)) {
lmrS = plyScale;
}
pos.makeMove(m, ui);
totalNodes++;
SearchTreeInfo& sti = searchTreeInfo[0];
sti.currentMove = m;
sti.currentMoveNo = mi;
sti.lmr = lmrS;
sti.nodeIdx = rootNodeIdx;
int score = -negaScout(smp, true, -beta, -alpha, 1, depthS - lmrS - plyScale, -1, givesCheck);
if ((lmrS > 0) && (score > alpha)) {
sti.lmr = 0;
score = -negaScout(smp, true, -beta, -alpha, 1, depthS - plyScale, -1, givesCheck);
}
nodesThisMove += totalNodes;
posHashListSize--;
pos.unMakeMove(m, ui);
storeSearchResult(rootMoves, mi, depthS, alpha, beta, score);
if ((verbose && firstIteration) || (mi < maxPV) || (score > rootMoves[maxPV-1].score()))
notifyPV(rootMoves, mi, maxPV);
int betaRetryDelta = aspirationDelta;
int alphaRetryDelta = aspirationDelta;
while ((score >= beta) || ((mi < maxPV) && (score <= alpha))) {
nodesThisMove -= totalNodes;
posHashList[posHashListSize++] = pos.zobristHash();
bool fh = score >= beta;
if (fh) {
if (isWinScore(score))
betaRetryDelta = MATE0; // Don't use aspiration window when searching for faster mate
beta = std::min(score + betaRetryDelta, MATE0);
betaRetryDelta = betaRetryDelta * 3 / 2;
if (mi != 0)
needMoreTime = true;
bestMove = m;
} else { // score <= alpha
if (isLoseScore(score))
alphaRetryDelta = MATE0; // Don't use aspiration window when searching for faster mate
alpha = std::max(score - alphaRetryDelta, -MATE0);
alphaRetryDelta = alphaRetryDelta * 3 / 2;
needMoreTime = searchNeedMoreTime = true;
}
pos.makeMove(m, ui);
totalNodes++;
score = -negaScout(smp, true, -beta, -alpha, 1, depthS - plyScale, -1, givesCheck);
nodesThisMove += totalNodes;
posHashListSize--;
pos.unMakeMove(m, ui);
storeSearchResult(rootMoves, mi, depthS, alpha, beta, score);
notifyPV(rootMoves, mi, maxPV);
}
rootMoves[mi].nodes += nodesThisMove;
if ((mi < maxPV) || (score > rootMoves[maxPV-1].move.score())) {
MoveInfo tmp = rootMoves[mi];
int i = mi;
while ((i > 0) && (rootMoves[i-1].score() < tmp.score())) {
rootMoves[i] = rootMoves[i-1];
i--;
}
rootMoves[i] = tmp;
}
bestMove = rootMoves[0].move;
bestExactMove = bestMove;
if (!firstIteration) {
S64 timeLimit = needMoreTime ? maxTimeMillis : minTimeMillis;
if (timeLimit >= 0) {
U64 tNow = currentTimeMillis();
if (tNow - tStart >= (U64)timeLimit)
break;
}
}
}
S64 tNow = currentTimeMillis();
if (verbose) {
for (int i = nodesByPly.minValue(); i < nodesByPly.maxValue(); i++)
std::cout << std::setw(2) << i
<< ' ' << std::setw(7) << nodesByPly.get(i)
<< ' ' << std::setw(7) << nodesByDepth.get(i)
<< std::endl;
std::stringstream ss;
ss.precision(3);
ss << std::fixed << "Time: " << ((tNow - tStart) * .001);
ss.precision(2);
ss << " depth:" << (depthS/(double)plyScale)
<< " nps:" << ((int)(getTotalNodes() / ((tNow - tStart) * .001)));
std::cout << ss.str() << std::endl;
}
if (maxTimeMillis >= 0)
if (tNow - tStart >= minTimeMillis)
break;
if (depthS >= maxDepth * plyScale)
break;
if (maxNodes >= 0)
if (getTotalNodes() >= maxNodes)
break;
bool enoughDepth = true;
for (int i = 0; i < maxPV; i++) {
int plyToMate = MATE0 - std::abs(rootMoves[i].score());
if (depthS < plyToMate * plyScale)
enoughDepth = false;
}
if (enoughDepth)
break;
if (tNow > tStart)
pd.npsInfo.setBaseNps(getTotalNodesThisThread() * 1000.0 / (tNow - tStart));
if (firstIteration) {
std::stable_sort(rootMoves.begin()+maxPV, rootMoves.end(), MoveInfo::SortByScore());
} else {
// Moves that were hard to search should be searched early in the next iteration
std::stable_sort(rootMoves.begin()+maxPV, rootMoves.end(), MoveInfo::SortByNodes());
}
// std::cout << "fhInfo depth:" << depthS / plyScale << std::endl;
// pd.fhInfo.print(std::cout);
// std::cout << "wqStats depth:" << depthS / plyScale << std::endl;
// pd.wq.printStats(std::cout, pd.numHelperThreads() + 1);
// log([&](std::ostream& os){pd.npsInfo.print(os, depthS / plyScale);});
}
} catch (const StopSearch&) {
pos = origPos;
}
notifyStats();
logFile.close();
return onlyExact ? bestExactMove : bestMove;
}
void
Search::storeSearchResult(std::vector<MoveInfo>& scMoves, int mi, int depth,
int alpha, int beta, int score) {
// std::cout << "d:" << depth/plyScale << " mi:" << mi << " a:" << alpha
// << " b:" << beta << " s:" << score << std::endl;
scMoves[mi].depth = depth;
scMoves[mi].alpha = alpha;
scMoves[mi].beta = beta;
scMoves[mi].move.setScore(score);
scMoves[mi].pv.clear();
tt.extractPVMoves(pos, scMoves[mi].move, scMoves[mi].pv);
if ((maxTimeMillis < 0) && SearchConst::isWinScore(std::abs(score)))
TBProbe::extendPV(pos, scMoves[mi].pv);
}
void
Search::notifyPV(const std::vector<MoveInfo>& moveInfo, int mi, int maxPV) {
bool miNotified = false;
int lastReportedDepth = -1;
for (int i = 0, n = 0; n < maxPV; i++) {
if (!miNotified && (moveInfo[mi].score() > moveInfo[i].score()) &&
(moveInfo[mi].score() > moveInfo[mi].alpha)) {
notifyPV(moveInfo[mi], maxPV > 1 ? n : -1);
lastReportedDepth = moveInfo[mi].depth;
miNotified = true;
n++;
if (n >= maxPV)
break;
}
if (i == mi) {
if (!miNotified) {
notifyPV(moveInfo[mi], maxPV > 1 ? n : -1);
lastReportedDepth = moveInfo[mi].depth;
miNotified = true;
n++;
}
} else {
notifyPV(moveInfo[i], maxPV > 1 ? n : -1);
lastReportedDepth = moveInfo[i].depth;
n++;
}
}
if (listener && (moveInfo[mi].depth != lastReportedDepth))
listener->notifyDepth(moveInfo[mi].depth/plyScale);
}
void
Search::notifyPV(const MoveInfo& info, int multiPVIndex) {
if (info.depth <= 0)
return;
bool uBound = info.score() <= info.alpha;
bool lBound = info.score() >= info.beta;
int score = info.move.score();
if (verbose) {
std::stringstream ss;
ss << std::setw(6) << std::left << TextIO::moveToString(pos, info.move, false)
<< ' ' << std::setw(6) << std::right << score
<< ' ' << std::setw(6) << totalNodes;
if (uBound)
ss << " <=";
else if (lBound)
ss << " >=";
else {
std::string PV = TextIO::moveToString(pos, info.move, false) + " ";
UndoInfo ui;
pos.makeMove(info.move, ui);
PV += tt.extractPV(pos);
pos.unMakeMove(info.move, ui);
ss << ' ' << PV;
}
std::cout << ss.str() << std::endl;
}
if (!listener)
return;
bool isMate = false;
if (isWinScore(score)) {
isMate = true;
score = (MATE0 - score) / 2;
} else if (isLoseScore(score)) {
isMate = true;
score = -((MATE0 + score - 1) / 2);
}
U64 tNow = currentTimeMillis();
int time = (int) (tNow - tStart);
S64 totNodes = getTotalNodes();
S64 tbHits = getTbHits();
int nps = (time > 0) ? (int)(totNodes / (time / 1000.0)) : 0;
listener->notifyPV(info.depth/plyScale, score, time, totNodes, nps, isMate,
uBound, lBound, info.pv, multiPVIndex, tbHits);
}
void
Search::notifyStats() {
S64 tNow = currentTimeMillis();
if (listener) {
int time = (int) (tNow - tStart);
S64 totNodes = getTotalNodes();
int nps = (time > 0) ? (int)(totNodes / (time / 1000.0)) : 0;
S64 tbHits = getTbHits();
listener->notifyStats(totNodes, nps, tbHits, time);
}
tLastStats = tNow;
}
bool
Search::shouldStop() {
S64 tNow = currentTimeMillis();
S64 timeLimit = searchNeedMoreTime ? maxTimeMillis : minTimeMillis;
if ( ((timeLimit >= 0) && (tNow - tStart >= timeLimit)) ||
((maxNodes >= 0) && (getTotalNodes() >= maxNodes)))
return true;
if (tNow - tLastStats >= 1000)
notifyStats();
return false;
}
template <bool smp, bool tb>
int
Search::negaScout(int alpha, int beta, int ply, int depth, int recaptureSquare,
const bool inCheck) {
using SplitPointHolder = typename SplitPointTraits<smp>::SpHolder;
// Mate distance pruning
beta = std::min(beta, MATE0-ply-1);
if (alpha >= beta)
return alpha;
if (logFile.isOpened()) {
const SearchTreeInfo& sti = searchTreeInfo[ply-1];
U64 idx = logFile.logNodeStart(sti.nodeIdx, sti.currentMove, alpha, beta, ply, depth);
searchTreeInfo[ply].nodeIdx = idx;
}
if (nodesToGo <= 0) {
nodesToGo = nodesBetweenTimeCheck;
if (stopHandler->shouldStop())
throw StopSearch();
}
// Collect statistics
if (verbose) {
nodesByPly.add(ply);
nodesByDepth.add(depth/plyScale);
}
const U64 hKey = pos.historyHash();
SearchTreeInfo& sti = searchTreeInfo[ply];
sti.currentMove = emptyMove;
sti.currentMoveNo = -1;
// Draw tests
if (canClaimDraw50(pos)) {
if (inCheck) {
MoveList moves;
MoveGen::pseudoLegalMoves(pos, moves);
MoveGen::removeIllegal(pos, moves);
if (moves.size == 0) { // Can't claim draw if already check mated.
int score = -(MATE0-(ply+1));
logFile.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, TType::T_EXACT, UNKNOWN_SCORE, hKey);
return score;
}
}
logFile.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TType::T_EXACT, UNKNOWN_SCORE, hKey);
return 0;
}
if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashFirstNew)) {
logFile.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TType::T_EXACT, UNKNOWN_SCORE, hKey);
return 0; // No need to test for mate here, since it would have been
// discovered the first time the position came up.
}
// Check transposition table
int evalScore = UNKNOWN_SCORE;
TranspositionTable::TTEntry ent;
ent.clear();
const bool singularSearch = !sti.singularMove.isEmpty();
const bool useTT = (mainNumaNode || (depth >= 1 * plyScale)) && // To reduce memory bandwidth
!singularSearch;
if (useTT) tt.probe(hKey, ent);
Move hashMove;
if (ent.getType() != TType::T_EMPTY) {
int score = ent.getScore(ply);
evalScore = ent.getEvalScore();
ent.getMove(hashMove);
if (((beta == alpha + 1) || (depth <= ply*plyScale)) && ent.isCutOff(alpha, beta, ply, depth)) {
if (score >= beta) {
if (!hashMove.isEmpty())
if (pos.getPiece(hashMove.to()) == Piece::EMPTY)
kt.addKiller(ply, hashMove);
}
sti.bestMove = hashMove;
logFile.logNodeEnd(sti.nodeIdx, score, ent.getType(), evalScore, hKey);
return score;
}
}
if (singularSearch)
hashMove = sti.singularMove;
// Probe endgame tablebases
const int illegalScore = -(MATE0-(ply+1));
int tbScore = illegalScore;
if (tb && depth >= minProbeDepth && !singularSearch) {
TranspositionTable::TTEntry tbEnt;
tbEnt.clear();
if (TBProbe::tbProbe(pos, ply, alpha, beta, tbEnt)) {
tbHits++;
nodesToGo -= 1000;
int type = tbEnt.getType();
int score = tbEnt.getScore(ply);
bool cutOff = false;
if (score == 0 && type == TType::T_EXACT) {
const int maxSwindle = 50;
if (depth < 16 * plyScale) {
if (evalScore == UNKNOWN_SCORE)
evalScore = eval.evalPos(pos);
score = Evaluate::swindleScore(evalScore);
cutOff = true;
} else if (alpha >= maxSwindle) {
tbEnt.setType(TType::T_LE);
score = maxSwindle;
cutOff = true;
} else if (beta <= -maxSwindle) {
tbEnt.setType(TType::T_GE);
score = -maxSwindle;
cutOff = true;
}
} else {
if ( (type == TType::T_EXACT) ||
((type == TType::T_GE) && (score >= beta)) ||
((type == TType::T_LE) && (score <= alpha)))
cutOff = true;
}
if (cutOff) {
emptyMove.setScore(score);
if (useTT) tt.insert(hKey, emptyMove, tbEnt.getType(), ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, score, tbEnt.getType(), evalScore, hKey);
return score;
}
if ((type == TType::T_GE) && (score > alpha)) {
tbScore = score;
alpha = score - 1;
}
}
}
int posExtend = inCheck ? plyScale : 0; // Check extension
// If out of depth, perform quiescence search
if (depth + posExtend <= 0) {
q0Eval = evalScore;
sti.bestMove.setMove(0,0,0,0);
int score = quiesce(alpha, beta, ply, 0, inCheck);
int type = TType::T_EXACT;
if (score <= alpha) {
type = TType::T_LE;
} else if (score >= beta) {
type = TType::T_GE;
}
sti.bestMove.setScore(score);
if (useTT) tt.insert(hKey, sti.bestMove, type, ply, depth, q0Eval);
logFile.logNodeEnd(sti.nodeIdx, score, type, q0Eval, hKey);
return score;
}
// Razoring
const bool normalBound = !isLoseScore(alpha) && !isWinScore(beta);
if (normalBound && (depth < 4*plyScale) && (beta == alpha + 1) && !singularSearch) {
if (evalScore == UNKNOWN_SCORE) {
evalScore = eval.evalPos(pos);
}
const int razorMargin = (depth <= plyScale) ? (int)razorMargin1 : (int)razorMargin2; // Pierre-Marie Baty -- added type cast
if (evalScore < beta - razorMargin) {
q0Eval = evalScore;
int score = quiesce(alpha-razorMargin, beta-razorMargin, ply, 0, inCheck);
if (score <= alpha-razorMargin) {
emptyMove.setScore(score);
if (useTT) tt.insert(hKey, emptyMove, TType::T_LE, ply, depth, q0Eval);
logFile.logNodeEnd(sti.nodeIdx, score, TType::T_LE, q0Eval, hKey);
return score;
}
}
}
// Reverse futility pruning
if (!inCheck && (depth < 5*plyScale) && (posExtend == 0) && normalBound && !singularSearch) {
bool mtrlOk;
if (pos.isWhiteMove()) {
mtrlOk = (pos.wMtrl() > pos.wMtrlPawns()) && (pos.wMtrlPawns() > 0);
} else {
mtrlOk = (pos.bMtrl() > pos.bMtrlPawns()) && (pos.bMtrlPawns() > 0);
}
if (mtrlOk) {
int margin;
if (depth <= plyScale) margin = reverseFutilityMargin1;
else if (depth <= 2*plyScale) margin = reverseFutilityMargin2;
else if (depth <= 3*plyScale) margin = reverseFutilityMargin3;
else margin = reverseFutilityMargin4;
if (evalScore == UNKNOWN_SCORE)
evalScore = eval.evalPos(pos);
if (evalScore - margin >= beta) {
emptyMove.setScore(evalScore - margin);
if (useTT) tt.insert(hKey, emptyMove, TType::T_GE, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, evalScore - margin, TType::T_GE, evalScore, hKey);
return evalScore - margin;
}
}
}
// Null-move pruning
if ((depth >= 3*plyScale) && !inCheck && sti.allowNullMove && !isWinScore(beta) && !singularSearch) {
bool nullOk;
if (pos.isWhiteMove()) {
nullOk = (pos.wMtrl() > pos.wMtrlPawns()) && (pos.wMtrlPawns() > 0);
} else {
nullOk = (pos.bMtrl() > pos.bMtrlPawns()) && (pos.bMtrlPawns() > 0);
}
const int R = (depth > 6*plyScale) ? 4*plyScale : 3*plyScale;
if (nullOk) {
if (((ent.getType() == TType::T_EXACT) || (ent.getType() == TType::T_LE)) &&
(ent.getDepth() >= depth - R) && (ent.getScore(ply) < beta))
nullOk = false;
}
if (nullOk) {
if (evalScore == UNKNOWN_SCORE)
evalScore = eval.evalPos(pos);
if (evalScore < beta)
nullOk = false;
}
if (nullOk) {
int score;
{
SplitPointHolder sph(pd, spVec, pending);
if (smp) {
sph.setSp(std::make_shared<SplitPoint>(threadNo, spVec.back(),
searchTreeInfo[ply-1].currentMoveNo,
pos, posHashList, posHashListSize,
sti, kt, ht, alpha, beta, ply, depth / plyScale));
sph.addMove(0, SplitPointMove(Move(), 0, 0, -1, false));
sph.addToQueue();
sph.setOwnerCurrMove(0, alpha);
}
pos.setWhiteMove(!pos.isWhiteMove());
int epSquare = pos.getEpSquare();
pos.setEpSquare(-1);
searchTreeInfo[ply+1].allowNullMove = false;
searchTreeInfo[ply+1].bestMove.setMove(0,0,0,0);
score = -negaScout(smp, tb, -beta, -(beta - 1), ply + 1, depth - R, -1, false);
searchTreeInfo[ply+1].allowNullMove = true;
pos.setEpSquare(epSquare);
pos.setWhiteMove(!pos.isWhiteMove());
}
bool storeInHash = true;
if ((score >= beta) && (depth >= 10 * plyScale)) {
// Null-move verification search
SearchTreeInfo& sti2 = searchTreeInfo[ply-1];
const Move savedMove = sti2.currentMove;
const int savedMoveNo = sti2.currentMoveNo;
const S64 savedNodeIdx2 = sti2.nodeIdx;
sti2.currentMove = Move(1,1,0); // Represents "no move"
sti2.currentMoveNo = -1;
sti2.nodeIdx = sti.nodeIdx;
const S64 savedNodeIdx = sti.nodeIdx;
sti.allowNullMove = false;
score = negaScout(smp, tb, beta - 1, beta, ply, depth - R, recaptureSquare, inCheck);
sti.allowNullMove = true;
sti.nodeIdx = savedNodeIdx;
sti2.currentMove = savedMove;
sti2.currentMoveNo = savedMoveNo;
sti2.nodeIdx = savedNodeIdx2;
searchTreeInfo[ply+1].bestMove.setMove(0,0,0,0);
storeInHash = false;
}
if (smp && (depth - R >= MIN_SMP_DEPTH * plyScale))
pd.fhInfo.addData(-1, searchTreeInfo[ply+1].currentMoveNo, score < beta, false);
if (score >= beta) {
if (isWinScore(score))
score = beta;
emptyMove.setScore(score);
if (storeInHash)
if (useTT) tt.insert(hKey, emptyMove, TType::T_GE, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, score, TType::T_GE, evalScore, hKey);
return score;
}
}
}
bool futilityPrune = false;
int futilityScore = alpha;
if (!inCheck && (depth < 5*plyScale) && (posExtend == 0) && normalBound && !singularSearch) {
int margin;
if (depth <= plyScale) margin = futilityMargin1;
else if (depth <= 2*plyScale) margin = futilityMargin2;
else if (depth <= 3*plyScale) margin = futilityMargin3;
else margin = futilityMargin4;
if (evalScore == UNKNOWN_SCORE)
evalScore = eval.evalPos(pos);
futilityScore = evalScore + margin;
if (futilityScore <= alpha)
futilityPrune = true;
}
// Internal iterative deepening
if ((depth > 4*plyScale) && hashMove.isEmpty()) {
bool isPv = beta > alpha + 1;
if (isPv || (depth > 8 * plyScale)) {
SearchTreeInfo& sti2 = searchTreeInfo[ply-1];
Move savedMove = sti2.currentMove;
int savedMoveNo = sti2.currentMoveNo;
S64 savedNodeIdx2 = sti2.nodeIdx;
sti2.currentMove = Move(1,1,0); // Represents "no move"
sti2.currentMoveNo = -1;
sti2.nodeIdx = sti.nodeIdx;
S64 savedNodeIdx = sti.nodeIdx;
int newDepth = isPv ? depth - 2 * plyScale : depth * 3 / 8;
negaScout(smp, tb, alpha, beta, ply, newDepth, -1, inCheck);
sti.nodeIdx = savedNodeIdx;
sti2.currentMove = savedMove;
sti2.currentMoveNo = savedMoveNo;
sti2.nodeIdx = savedNodeIdx2;
tt.probe(hKey, ent);
if (ent.getType() != TType::T_EMPTY)
ent.getMove(hashMove);
}
}
// Generate move list
MoveList moves;
if (inCheck)
MoveGen::checkEvasions(pos, moves);
else
MoveGen::pseudoLegalMoves(pos, moves);
bool seeDone = false;
bool hashMoveSelected = true;
if (hashMove.isEmpty() || !selectHashMove(moves, hashMove)) {
scoreMoveList(moves, ply);
seeDone = true;
hashMoveSelected = false;
}
// Handle singular extension
bool singularExtend = false;
if ((depth > 6 * plyScale) && (posExtend == 0) &&
hashMoveSelected && sti.singularMove.isEmpty() &&
(ent.getType() != TType::T_LE) &&
(ent.getDepth() >= depth - 3 * plyScale) &&
(getMoveExtend(hashMove, recaptureSquare) <= 0) &&
(ply + depth / plyScale < MAX_SEARCH_DEPTH) &&
MoveGen::isLegal(pos, hashMove, inCheck)) {
SearchTreeInfo& sti2 = searchTreeInfo[ply-1];
Move savedMove = sti2.currentMove;
int savedMoveNo = sti2.currentMoveNo;
S64 savedNodeIdx2 = sti2.nodeIdx;
sti2.currentMove = Move(1,1,0); // Represents "no move"
sti2.currentMoveNo = -1;
sti2.nodeIdx = sti.nodeIdx;
S64 savedNodeIdx = sti.nodeIdx;
int newDepth = depth / 2;
int newBeta = ent.getScore(ply) - 25;
sti.singularMove = hashMove;
int singScore = negaScout(smp, tb, newBeta-1, newBeta, ply, newDepth,
recaptureSquare, inCheck);
sti.singularMove.setMove(0,0,0,0);
if (singScore <= newBeta-1)
singularExtend = true;
sti.nodeIdx = savedNodeIdx;
sti2.currentMove = savedMove;
sti2.currentMoveNo = savedMoveNo;
sti2.nodeIdx = savedNodeIdx2;
}
SplitPointHolder sph(pd, spVec, pending);
if (smp) {
sph.setSp(std::make_shared<SplitPoint>(threadNo, spVec.back(),
searchTreeInfo[ply-1].currentMoveNo,
pos, posHashList, posHashListSize,
sti, kt, ht, alpha, beta, ply, depth/plyScale));
for (int mi = 0; mi < moves.size; mi++) {
if ((mi == 1) && !seeDone) {
scoreMoveList(moves, ply, 1);
seeDone = true;
}
if ((mi > 0) || !hashMoveSelected)
selectBest(moves, mi);
}
}
// Determine late move pruning (LMP) limit
int lmpMoveCountLimit = 256;
if (beta == alpha + 1) {
bool lmpOk;
if (pos.isWhiteMove())
lmpOk = (pos.wMtrl() > pos.wMtrlPawns()) && (pos.wMtrlPawns() > 0);
else
lmpOk = (pos.bMtrl() > pos.bMtrlPawns()) && (pos.bMtrlPawns() > 0);
if (lmpOk) {
if (depth <= plyScale) lmpMoveCountLimit = lmpMoveCountLimit1;
else if (depth <= 2 * plyScale) lmpMoveCountLimit = lmpMoveCountLimit2;
else if (depth <= 3 * plyScale) lmpMoveCountLimit = lmpMoveCountLimit3;
else if (depth <= 4 * plyScale) lmpMoveCountLimit = lmpMoveCountLimit4;
}
}
// Start searching move alternatives
UndoInfo ui;
for (int pass = (smp?0:1); pass < 2; pass++) {
bool haveLegalMoves = false;
int b = beta;
int bestScore = illegalScore;
int bestMove = -1;
int lmrCount = 0;
if (tb && pass == 1 && tbScore != illegalScore) {
bestScore = tbScore - 1;
bestMove = -2;
sti.bestMove.setMove(0, 0, 0, 0);
}
for (int mi = 0; mi < moves.size; mi++) {
if (!smp) {
if ((mi == 1) && !seeDone) {
scoreMoveList(moves, ply, 1);
seeDone = true;
}
if ((mi < lmpMoveCountLimit) || (lmrCount <= lmrMoveCountLimit1))
if ((mi > 0) || !hashMoveSelected)
selectBest(moves, mi);
}
Move& m = moves[mi];
int newCaptureSquare = -1;
bool isCapture = (pos.getPiece(m.to()) != Piece::EMPTY);
bool isPromotion = (m.promoteTo() != Piece::EMPTY);
int sVal = std::numeric_limits<int>::min();
bool mayReduce = (m.score() < 53) && (!isCapture || m.score() < 0) && !isPromotion;
bool givesCheck = MoveGen::givesCheck(pos, m);
bool negSEECheck = (depth > 4*plyScale) && givesCheck && negSEE(m);
bool doFutility = false;
if (mayReduce && haveLegalMoves && !givesCheck && !passedPawnPush(pos, m)) {
if (normalBound && !isLoseScore(bestScore) && (mi >= lmpMoveCountLimit))
continue; // Late move pruning
if (futilityPrune)
doFutility = true;
}
int score = illegalScore;
if (doFutility) {
score = futilityScore;
} else {
#ifdef HAS_PREFETCH
if (pass == 1)
tt.prefetch(pos.hashAfterMove(m));
#endif
if ((mi == 0) && m.equals(sti.singularMove))
continue;
if (!MoveGen::isLegal(pos, m, inCheck))
continue;
int moveExtend = (posExtend > 0) ? 0 : getMoveExtend(m, recaptureSquare);
int extend = std::max(posExtend, moveExtend);
if (singularExtend && (mi == 0))
extend = 1*plyScale;
int lmr = 0;
if ((depth >= 2*plyScale) && mayReduce && (extend == 0)) {
if (!givesCheck && !passedPawnPush(pos, m)) {
lmrCount++;
if ((lmrCount > lmrMoveCountLimit2) && (depth > 5*plyScale) && !isCapture) {
lmr = 3*plyScale;
} else if ((lmrCount > lmrMoveCountLimit1) && (depth > 3*plyScale) && !isCapture) {
lmr = 2*plyScale;
} else {
lmr = 1*plyScale;
}
}
}
int newDepth = depth - plyScale + extend - lmr - (negSEECheck ? plyScale : 0);
if (isCapture && (givesCheck || (depth + extend) > plyScale)) {
// Compute recapture target square, but only if we are not going
// into q-search at the next ply.
int fVal = ::pieceValue[pos.getPiece(m.from())];
int tVal = ::pieceValue[pos.getPiece(m.to())];
const int pV = ::pV;
if (std::abs(tVal - fVal) < pV / 2) { // "Equal" capture
sVal = SEE(m);
if (std::abs(sVal) < pV / 2)
newCaptureSquare = m.to();
}
}
if (pass == 0) {
sph.addMove(mi, SplitPointMove(m, lmr, newDepth, newCaptureSquare, givesCheck));
} else {
posHashList[posHashListSize++] = pos.zobristHash();
pos.makeMove(m, ui);
totalNodes++;
nodesToGo--;
sti.currentMove = m;
sti.currentMoveNo = mi;
sti.lmr = lmr;
// S64 n1 = totalNodes; int nomDepth = newDepth;
if (smp) {
int helperScore = sph.setOwnerCurrMove(mi, alpha);
if (helperScore != UNKNOWN_SCORE)
score = helperScore;
}
if (!smp || (score == illegalScore)) {
score = -negaScout(smp, tb, -b, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck);
if (((lmr > 0) && (score > alpha)) ||
((score > alpha) && (score < beta) && (b != beta))) {
sti.lmr = 0;
newDepth += lmr;
score = -negaScout(smp, tb, -beta, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck);
}
}
if (smp) {
// if (sph.hasHelperThread())
// log([&](std::ostream& os){os << "main seqNo:" << sph.getSeqNo() << " ply:" << ply << " m:" << mi
// << " a:" << alpha << " b:" << beta << " s:" << score
// << " d:" << nomDepth/plyScale << " n:" << (totalNodes-n1);});
if (beta > alpha + 1) {
pd.fhInfo.addPvData(mi, score > alpha);
} else {
pd.fhInfo.addData(mi, searchTreeInfo[ply+1].currentMoveNo,
score <= alpha, !sph.isAllNode());
}
}
posHashListSize--;
pos.unMakeMove(m, ui);
}
}
if (pass == 1) {
if (weak && haveLegalMoves)
if (weakPlaySkipMove(pos, m, ply))
score = illegalScore;
m.setScore(score);
if (score != illegalScore)
haveLegalMoves = true;
bestScore = std::max(bestScore, score);
if (score > alpha) {
alpha = score;
bestMove = mi;
sti.bestMove.setMove(m.from(), m.to(), m.promoteTo(), sti.bestMove.score());
}
if (alpha >= beta) {
if (pos.getPiece(m.to()) == Piece::EMPTY) {
kt.addKiller(ply, m);
ht.addSuccess(pos, m, depth/plyScale);
for (int mi2 = mi - 1; mi2 >= 0; mi2--) {
Move m2 = moves[mi2];
if (pos.getPiece(m2.to()) == Piece::EMPTY)
ht.addFail(pos, m2, depth/plyScale);
}
}
if (useTT) tt.insert(hKey, m, TType::T_GE, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, alpha, TType::T_GE, evalScore, hKey);
return alpha;
}
b = alpha + 1;
}
}
if (pass == 0) {
sph.addToQueue();
} else {
if (!haveLegalMoves && !inCheck) {
if (singularSearch) {
logFile.logNodeEnd(sti.nodeIdx, alpha, TType::T_LE, evalScore, hKey);
return alpha; // Only one legal move, fail low to trigger singular extension
}
emptyMove.setScore(0);
if (useTT) tt.insert(hKey, emptyMove, TType::T_EXACT, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, 0, TType::T_EXACT, evalScore, hKey);
return 0; // Stale-mate
}
if (tb && bestMove == -2) { // TB win, unknown move
bestScore = tbScore;
emptyMove.setScore(bestScore);
if (useTT) tt.insert(hKey, emptyMove, TType::T_EXACT, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, bestScore, TType::T_EXACT, evalScore, hKey);
} else if (bestMove >= 0) {
if (useTT) tt.insert(hKey, moves[bestMove], TType::T_EXACT, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, bestScore, TType::T_EXACT, evalScore, hKey);
} else {
emptyMove.setScore(bestScore);
if (useTT) tt.insert(hKey, emptyMove, TType::T_LE, ply, depth, evalScore);
logFile.logNodeEnd(sti.nodeIdx, bestScore, TType::T_LE, evalScore, hKey);
}
return bestScore;
}
}
assert(false);
return 0;
}
int
Search::getMoveExtend(const Move& m, int recaptureSquare) {
if ((m.to() == recaptureSquare)) {
int sVal = SEE(m);
int tVal = ::pieceValue[pos.getPiece(m.to())];
if (sVal > tVal - pV / 2)
return plyScale;
}
bool isCapture = (pos.getPiece(m.to()) != Piece::EMPTY);
if (isCapture && (pos.wMtrlPawns() + pos.bMtrlPawns() > pV)) {
// Extend if going into pawn endgame
int capVal = ::pieceValue[pos.getPiece(m.to())];
if (pos.isWhiteMove()) {
if ((pos.wMtrl() == pos.wMtrlPawns()) && (pos.bMtrl() - pos.bMtrlPawns() == capVal))
return plyScale;
} else {
if ((pos.bMtrl() == pos.bMtrlPawns()) && (pos.wMtrl() - pos.wMtrlPawns() == capVal))
return plyScale;
}
}
return 0;
}
void
Search::getRootMoves(const MoveList& rootMovesIn,
std::vector<MoveInfo>& rootMovesOut,
int maxDepth) {
MoveList rootMoves(rootMovesIn);
if ((maxTimeMillis >= 0) || (maxNodes >= 0) || (maxDepth >= 0)) {
MoveList legalMoves;
MoveGen::pseudoLegalMoves(pos, legalMoves);
MoveGen::removeIllegal(pos, legalMoves);
if (rootMoves.size == legalMoves.size) {
// Game mode, handle missing TBs
std::vector<Move> movesToSearch;
if (TBProbe::getSearchMoves(pos, legalMoves, movesToSearch))
rootMoves.filter(movesToSearch);
}
}
// If strength is < 10%, only include a subset of the root moves.
// At least one move is always included though.
std::vector<bool> includedMoves(rootMoves.size);
U64 rndL = pos.zobristHash() ^ randomSeed;
includedMoves[(int)(rndL % rootMoves.size)] = true;
double pIncl = (strength < 100) ? strength * strength * 1e-4 : 1.0;
for (int mi = 0; mi < rootMoves.size; mi++) {
rndL = 6364136223846793005ULL * rndL + 1442695040888963407ULL;
double rnd = ((rndL & 0x7fffffffffffffffULL) % 1000000000) / 1e9;
if (!includedMoves[mi] && (rnd < pIncl))
includedMoves[mi] = true;
}
for (int mi = 0; mi < rootMoves.size; mi++) {
if (includedMoves[mi]) {
const Move& m = rootMoves[mi];
rootMovesOut.push_back(MoveInfo(m, 0));
}
}
}
bool
Search::weakPlaySkipMove(const Position& pos, const Move& m, int ply) const {
U64 rndL = pos.zobristHash() ^ Position::getHashKey(Piece::EMPTY, m.from()) ^
Position::getHashKey(Piece::EMPTY, m.to()) ^ randomSeed;
double rnd = ((rndL & 0x7fffffffffffffffULL) % 1000000000) / 1e9;
double s = strength * 1e-3;
double offs = (17 - 50 * s) / 3;
double effPly = ply * Evaluate::interpolate(pos.wMtrl() + pos.bMtrl(), 0, 30, ::qV * 4, 100) * 1e-2;
double t = effPly + offs;
double p = 1/(1+exp(t)); // Probability to "see" move
bool easyMove = ((pos.getPiece(m.to()) != Piece::EMPTY) ||
(ply < 2) || (searchTreeInfo[ply-2].currentMove.to() == m.from()));
if (easyMove)
p = 1-(1-p)*(1-p);
if (rnd > p)
return true;
return false;
}
int
Search::quiesce(int alpha, int beta, int ply, int depth, const bool inCheck) {
int score;
if (inCheck) {
score = -(MATE0 - (ply+1));
} else {
if ((depth == 0) && (q0Eval != UNKNOWN_SCORE)) {
score = q0Eval;
} else {
score = eval.evalPos(pos);
if (depth == 0)
q0Eval = score;
}
}
if (score >= beta)
return score;
const int evalScore = score;
if (score > alpha)
alpha = score;
int bestScore = score;
const bool tryChecks = (depth > -1);
MoveList moves;
if (inCheck) {
MoveGen::checkEvasions(pos, moves);
} else if (tryChecks) {
MoveGen::pseudoLegalCapturesAndChecks(pos, moves);
} else {
MoveGen::pseudoLegalCaptures(pos, moves);
}
bool realInCheckComputed = false;
bool realInCheck = false;
if (depth > -2) {
realInCheckComputed = true;
realInCheck = inCheck;
}
scoreMoveListMvvLva(moves);
UndoInfo ui;
for (int mi = 0; mi < moves.size; mi++) {
if (mi < quiesceMaxSortMoves) {
// If the first N moves didn't fail high this is probably an ALL-node,
// so spending more effort on move ordering is probably wasted time.
selectBest(moves, mi);
}
const Move& m = moves[mi];
bool givesCheck = false;
bool givesCheckComputed = false;
if (inCheck) {
// Allow all moves
} else {
if ((pos.getPiece(m.to()) == Piece::EMPTY) && (m.promoteTo() == Piece::EMPTY)) {
// Non-capture
if (!tryChecks)
continue;
givesCheck = MoveGen::givesCheck(pos, m);
givesCheckComputed = true;
if (!givesCheck)
continue;
if (negSEE(m)) // Needed because m.score() is not computed for non-captures
continue;
} else {
if (negSEE(m))
continue;
int capt = ::pieceValue[pos.getPiece(m.to())];
int prom = ::pieceValue[m.promoteTo()];
int optimisticScore = evalScore + capt + prom + deltaPruningMargin;
if (optimisticScore < alpha) { // Delta pruning
if ((pos.wMtrlPawns() > 0) && (pos.wMtrl() > capt + pos.wMtrlPawns()) &&
(pos.bMtrlPawns() > 0) && (pos.bMtrl() > capt + pos.bMtrlPawns())) {
if (depth -1 > -2) {
givesCheck = MoveGen::givesCheck(pos, m);
givesCheckComputed = true;
}
if (!givesCheck) {
if (optimisticScore > bestScore)
bestScore = optimisticScore;
continue;
}
}
}
}
}
if (!realInCheckComputed) {
realInCheck = MoveGen::inCheck(pos);
realInCheckComputed = true;
}
if (!MoveGen::isLegal(pos, m, realInCheck))
continue;
if (!givesCheckComputed && (depth - 1 > -2))
givesCheck = MoveGen::givesCheck(pos, m);
const bool nextInCheck = (depth - 1) > -2 ? givesCheck : false;
pos.makeMove(m, ui);
totalNodes++;
nodesToGo--;
score = -quiesce(-beta, -alpha, ply + 1, depth - 1, nextInCheck);
pos.unMakeMove(m, ui);
if (score > bestScore) {
bestScore = score;
if (score > alpha) {
if (depth == 0) {
SearchTreeInfo& sti = searchTreeInfo[ply];
sti.bestMove.setMove(m.from(), m.to(), m.promoteTo(), score);
}
alpha = score;
if (alpha >= beta)
return alpha;
}
}
}
return bestScore;
}
int
Search::SEE(const Move& m) {
int captures[64]; // Value of captured pieces
const int kV = ::kV;
const int square = m.to();
if (square == pos.getEpSquare()) {
captures[0] = ::pV;
} else {
captures[0] = ::pieceValue[pos.getPiece(square)];
if (captures[0] == kV)
return kV;
}
int nCapt = 1; // Number of entries in captures[]
UndoInfo ui;
pos.makeSEEMove(m, ui);
bool white = pos.isWhiteMove();
int valOnSquare = ::pieceValue[pos.getPiece(square)];
U64 occupied = pos.occupiedBB();
while (true) {
int bestValue = std::numeric_limits<int>::max();
U64 atk;
if (white) {
atk = BitBoard::bPawnAttacks[square] & pos.pieceTypeBB(Piece::WPAWN) & occupied;
if (atk != 0) {
bestValue = ::pV;
} else {
atk = BitBoard::knightAttacks[square] & pos.pieceTypeBB(Piece::WKNIGHT) & occupied;
if (atk != 0) {
bestValue = ::nV;
} else {
U64 bAtk = BitBoard::bishopAttacks(square, occupied) & occupied;
atk = bAtk & pos.pieceTypeBB(Piece::WBISHOP);
if (atk != 0) {
bestValue = ::bV;
} else {
U64 rAtk = BitBoard::rookAttacks(square, occupied) & occupied;
atk = rAtk & pos.pieceTypeBB(Piece::WROOK);
if (atk != 0) {
bestValue = ::rV;
} else {
atk = (bAtk | rAtk) & pos.pieceTypeBB(Piece::WQUEEN);
if (atk != 0) {
bestValue = ::qV;
} else {
atk = BitBoard::kingAttacks[square] & pos.pieceTypeBB(Piece::WKING) & occupied;
if (atk != 0) {
bestValue = kV;
} else {
break;
}
}
}
}
}
}
} else {
atk = BitBoard::wPawnAttacks[square] & pos.pieceTypeBB(Piece::BPAWN) & occupied;
if (atk != 0) {
bestValue = ::pV;
} else {
atk = BitBoard::knightAttacks[square] & pos.pieceTypeBB(Piece::BKNIGHT) & occupied;
if (atk != 0) {
bestValue = ::nV;
} else {
U64 bAtk = BitBoard::bishopAttacks(square, occupied) & occupied;
atk = bAtk & pos.pieceTypeBB(Piece::BBISHOP);
if (atk != 0) {
bestValue = ::bV;
} else {
U64 rAtk = BitBoard::rookAttacks(square, occupied) & occupied;
atk = rAtk & pos.pieceTypeBB(Piece::BROOK);
if (atk != 0) {
bestValue = ::rV;
} else {
atk = (bAtk | rAtk) & pos.pieceTypeBB(Piece::BQUEEN);
if (atk != 0) {
bestValue = ::qV;
} else {
atk = BitBoard::kingAttacks[square] & pos.pieceTypeBB(Piece::BKING) & occupied;
if (atk != 0) {
bestValue = kV;
} else {
break;
}
}
}
}
}
}
}
captures[nCapt++] = valOnSquare;
if (valOnSquare == kV)
break;
valOnSquare = bestValue;
occupied &= ~(atk & (0-atk)); // Pierre-Marie Baty -- signedness fix
white = !white;
}
pos.unMakeSEEMove(m, ui);
int score = 0;
for (int i = nCapt - 1; i > 0; i--)
score = std::max(0, captures[i] - score);
return captures[0] - score;
}
void
Search::scoreMoveList(MoveList& moves, int ply, int startIdx) {
for (int i = startIdx; i < moves.size; i++) {
Move& m = moves[i];
bool isCapture = (pos.getPiece(m.to()) != Piece::EMPTY) || (m.promoteTo() != Piece::EMPTY);
int score = 0;
if (isCapture) {
int seeScore = signSEE(m);
int v = pos.getPiece(m.to());
int a = pos.getPiece(m.from());
score = Evaluate::pieceValueOrder[v] * 8 - Evaluate::pieceValueOrder[a];
if (seeScore > 0)
score += 100;
else if (seeScore == 0)
score += 50;
else
score -= 50;
score *= 100;
}
int ks = kt.getKillerScore(ply, m);
if (ks > 0) {
score += ks + 50;
} else {
int hs = ht.getHistScore(pos, m);
score += hs;
}
m.setScore(score);
}
}
bool
Search::selectHashMove(MoveList& moves, const Move& hashMove) {
for (int i = 0; i < moves.size; i++) {
Move& m = moves[i];
if (m.equals(hashMove)) {
m.setScore(10000);
std::swap(moves[i], moves[0]);
return true;
}
}
return false;
}
void
Search::initNodeStats() {
nodesByPly.clear();
nodesByDepth.clear();
}
void
Search::setThreadNo(int tNo) {
threadNo = tNo;
if (threadNo > 0)
nodesBetweenTimeCheck = 1000;
mainNumaNode = Numa::instance().isMainNode(threadNo);
}