/*
 
    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);
 
}