// principal variation search int SimplePVSearch::pvSearch(Board& board, SearchInfo& si) { if (stop(si)) { return 0; } if (si.ply>maxPlySearched) { maxPlySearched=si.ply; } if (si.depth<=0) { si.update(0,PV_NODE); return qSearch(board, si); } if (board.isDraw() || si.ply >= maxSearchPly-1) { return drawScore; } const int oldAlpha = si.alpha; int score = -maxScore; int currentScore = -maxScore; bool okToPrune=false; si.alpha = std::max(-maxScore+si.ply, si.alpha); si.beta = std::min(maxScore-(si.ply+1), si.beta); if (si.alpha>=si.beta) { return si.alpha; } TranspositionTable::HashData hashData; MoveIterator::Move hashMove; const Key key = si.partialSearch?board.getPartialSearchKey():board.getKey(); // tt retrieve & prunning bool hashOk = agent->hashGet(okToPrune, key, hashData, si.ply, si.depth); if (hashOk) { hashMove = hashData.move(); if (okToPrune) { return hashData.value(); } } const bool isKingAttacked = board.isInCheck(); bool isLazyEval=false; if (!isKingAttacked) { if (hashOk && (hashData.flag() & TranspositionTable::NODE_EVAL)) { currentScore = hashData.evalValue(); } else { currentScore = evaluator.evaluate(board,si.alpha,si.beta); isLazyEval = evaluator.isLazyEval(); } } //iid if (si.depth > allowIIDAtPV && hashMove.none()) { SearchInfo newSi(false,emptyMove,si.alpha,si.beta,si.depth-2,si.ply,PV_NODE,si.splitPoint); score = pvSearch(board,newSi); hashOk = agent->hashGet(key, hashData, si.ply); if (hashOk) { hashMove = hashData.move(); } } MoveIterator moves = MoveIterator(); MoveIterator::Move bestMove=emptyMove; MoveIterator::Move move; int moveCounter=0; int bestScore=-maxScore; bool nmMateScore=false; while (true) { move = selectMove<false>(board, moves, hashMove, si.ply, si.depth); if (move.none()) { break; } if (si.partialSearch && move == si.move) { continue; } const bool isHashMove = move.type==MoveIterator::TT_MOVE; int extension=0; if (isKingAttacked) { extension++; } else if (isHashMove && si.depth > sePVDepth && hashOk && !hashMove.none() && !si.partialSearch && hashData.depth() >= si.depth-3 && (hashData.flag() & TranspositionTable::LOWER)) { if (abs(hashData.value()) < winningScore) { const int seValue = hashData.value() - seMargin; SearchInfo seSi(false, hashMove, true, seValue-1, seValue, si.depth/2,si.ply, si.nodeType, si.splitPoint); const int partialScore = zwSearch(board,seSi); if (partialScore < seValue) { extension++; } } } MoveBackup backup; board.doMove(move,backup); moveCounter++; const bool givingCheck = board.setInCheck(board.getSideToMove()); const bool passedPawnPush = isPawnPush(board,move.to); const bool pawnOn7thRank = move.promotionPiece!=EMPTY; int newDepth=si.depth-1+extension; SearchInfo newSi(true,move,-si.beta,-si.alpha,newDepth,si.ply+1,PV_NODE,si.splitPoint); if (moveCounter==1 || isHashMove) { newSi.update(newDepth,PV_NODE,-si.beta,-si.alpha,move); score = -pvSearch(board, newSi); } else { int reduction=0; if (extension==0 && !isKingAttacked && !givingCheck && !passedPawnPush && !pawnOn7thRank && si.depth>lmrDepthThreshold && move.type == MoveIterator::NON_CAPTURE && killer[si.ply][0] != move && killer[si.ply][1] != move) { reduction=getReduction(true, si.depth, moveCounter); if (agent->getHistory(board.getPiece(move.from), move.to) <= 0) { reduction++; } } newSi.update(newDepth-reduction,CUT_NODE,-si.beta,-si.alpha,move); score = -zwSearch(board, newSi); if (score > si.alpha && score < si.beta) { if (reduction>0) { bool research=true; if (reduction>2) { newSi.update(newDepth-1,CUT_NODE,-si.beta,-si.alpha,move); score = -zwSearch(board, newSi); research=(score >= si.beta); } if (research) { newSi.update(newDepth,CUT_NODE,-si.beta,-si.alpha,move); score = -zwSearch(board, newSi); } } if (score > si.alpha && score < si.beta) { newSi.update(newDepth,PV_NODE,-si.beta,-si.alpha,move); score = -pvSearch(board, newSi); } } } board.undoMove(backup); if (stop(si)) { return 0; } nodes++; if (score>=si.beta) { bestScore=score; bestMove=move; break; } if (score>bestScore) { bestScore=score; if(score>si.alpha ) { si.alpha = score; bestMove=move; } } if (!stop(si) && agent->getThreadNumber()>1 && agent->getFreeThreads()>0 && si.depth>minSplitDepth) { if (agent->spawnThreads(board, &si, getThreadId(), &moves, &move, &hashMove, &bestScore, &si.alpha, ¤tScore, &moveCounter, &nmMateScore)) { if (bestScore>=si.beta) { break; } } } } if (!moveCounter) { return si.partialSearch?oldAlpha:isKingAttacked?-maxScore+si.ply:drawScore; } TranspositionTable::NodeFlag flag; if (bestScore>=si.beta) { flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::LOWER_EVAL:TranspositionTable::LOWER ; agent->updateHistory(board,bestMove,si.depth); updateKillers(board,bestMove,si.ply); } else if (bestScore>oldAlpha) { flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::EXACT_EVAL:TranspositionTable::EXACT; } else { flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::UPPER_EVAL:TranspositionTable::UPPER; bestMove=emptyMove; } agent->hashPut(key,bestScore,currentScore,si.depth,si.ply,flag,bestMove); return bestScore; }
// zero window search - non pv nodes int SimplePVSearch::zwSearch(Board& board, SearchInfo& si) { if (stop(si)) { return 0; } if (si.ply>maxPlySearched) { maxPlySearched=si.ply; } if (si.depth<=0) { si.update(0,si.nodeType,si.beta-1, si.beta, si.move); return qSearch(board, si); } if (board.isDraw() || si.ply >= maxSearchPly-1) { return drawScore; } if (-maxScore+si.ply >= si.beta) { return si.beta; } if (maxScore-(si.ply+1) < si.beta) { return si.beta-1; } const bool isKingAttacked = board.isInCheck(); bool isLazyEval = false; bool nmMateScore=false; bool okToPrune=false; int score = 0; int currentScore = -maxScore; TranspositionTable::HashData hashData; MoveIterator::Move hashMove; const Key key = si.partialSearch?board.getPartialSearchKey():board.getKey(); // tt retrieve & prunning bool hashOk = agent->hashGet(okToPrune, key, hashData, si.ply, si.depth, si.allowNullMove, si.beta-1, si.beta); if (hashOk) { hashMove = hashData.move(); if (okToPrune) { return hashData.value(); } } if (!isKingAttacked) { if (hashOk && (hashData.flag() & TranspositionTable::NODE_EVAL)) { currentScore = hashData.evalValue(); } else { currentScore = evaluator.evaluate(board,si.beta-1,si.beta); isLazyEval = evaluator.isLazyEval(); } } //razoring if (si.depth < razorDepth && hashMove.none() && si.allowNullMove && !isKingAttacked && !isMateScore(si.beta) && !board.isPawnPromoting() && !si.move.none() && currentScore < si.beta-getRazorMargin(si.depth)) { const int newBeta = si.beta-getRazorMargin(si.depth); SearchInfo newSi(si.allowNullMove,si.move,newBeta-1,newBeta,0, si.ply,CUT_NODE,si.splitPoint); score = qSearch(board, newSi); if (score < newBeta) { return score; } } //futility if (!isKingAttacked && si.allowNullMove && si.depth < futilityDepth && !board.isPawnFinal() && !isMateScore(si.beta) && !si.move.none() && currentScore >= si.beta+getFutilityMargin(si.depth,0)) { return currentScore-getFutilityMargin(si.depth,0); } // null move if (si.depth>1 && !isKingAttacked && si.allowNullMove && !board.isPawnFinal() && !isMateScore(si.beta) && currentScore >= si.beta-(si.depth>=nullMoveDepth?nullMoveMargin:0)) { const int reduction = 3 + (si.depth > 4 ? si.depth/4 : 0); MoveBackup backup; board.doNullMove(backup); SearchInfo newSi(false,emptyMove,si.beta,1-si.beta, si.depth-reduction, si.ply+1,CUT_NODE,si.splitPoint); score = -zwSearch(board, newSi); board.undoNullMove(backup); if (stop(si)) { return 0; } if (score >= si.beta) { if (score >= maxScore-maxSearchPly) { score = si.beta; } bool okToPrune = true; if (si.depth>11) { SearchInfo newSi(false,emptyMove,si.alpha,si.beta,si.depth-reduction, si.ply+1,CUT_NODE,si.splitPoint); const int newScore = zwSearch(board, newSi); if (newScore<si.beta) { okToPrune = false; } } if (okToPrune) { const TranspositionTable::NodeFlag flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::NM_LOWER_EVAL:TranspositionTable::NM_LOWER; agent->hashPut(key,score,currentScore,si.depth,si.ply,flag,emptyMove); return score; } } else { if (score == -maxScore+si.ply+2) { nmMateScore=true; } } } //iid if (si.depth > allowIIDAtNormal && hashMove.none() && currentScore >= si.beta-iidMargin) { SearchInfo newSi(false,emptyMove,si.alpha,si.beta,si.depth/2,si.ply,ALL_NODE,si.splitPoint); score = zwSearch(board,newSi); hashOk=agent->hashGet(key, hashData, si.ply); if (hashOk) { hashMove = hashData.move(); } } MoveIterator moves = MoveIterator(); MoveIterator::Move move; MoveIterator::Move bestMove=emptyMove; int moveCounter=0; int bestScore=-maxScore; while (true) { move = selectMove<false>(board, moves, hashMove, si.ply, si.depth); if (move.none()) { break; } if (si.partialSearch && move == si.move) { continue; } const bool isHashMove = move.type==MoveIterator::TT_MOVE; int extension=0; if (isKingAttacked) { extension++; } else if (isHashMove && si.depth > seNonPVDepth && hashOk && !hashMove.none() && !si.partialSearch && hashData.depth() >= si.depth-3 && (hashData.flag() & TranspositionTable::LOWER)) { if (abs(hashData.value()) < winningScore) { const int seValue = hashData.value() - seMargin; SearchInfo seSi(false, hashMove, true, seValue-1, seValue, si.depth/2, si.ply, si.nodeType, si.splitPoint); const int partialScore = zwSearch(board,seSi); if (partialScore < seValue) { extension++; } } } MoveBackup backup; board.doMove(move,backup); moveCounter++; const bool givingCheck = board.setInCheck(board.getSideToMove()); const bool passedPawnPush = isPawnPush(board,move.to); const bool pawnOn7thRank = move.promotionPiece!=EMPTY; //futility if ( move.type == MoveIterator::NON_CAPTURE && !isKingAttacked && !givingCheck && !passedPawnPush && !pawnOn7thRank && !nmMateScore && extension == 0) { if (getMoveCountMargin(si.depth) < moveCounter && si.depth < moveCountDepth && !isMateScore(bestScore) ) { board.undoMove(backup); continue; } if (si.depth < futilityDepth) { const int futilityScore = currentScore + getFutilityMargin(si.depth,moveCounter); if (futilityScore < si.beta) { if (futilityScore>bestScore) { bestScore=futilityScore; } board.undoMove(backup); continue; } } } //reductions int reduction=0; if (extension==0 && !isKingAttacked && !givingCheck && si.depth>lmrDepthThreshold && !nmMateScore && move.type == MoveIterator::NON_CAPTURE && !isHashMove && moveCounter != 1 && killer[si.ply][0] != move && killer[si.ply][1] != move) { reduction=getReduction(false, si.depth, moveCounter); if (si.nodeType == CUT_NODE || agent->getHistory(board.getPiece(move.from), move.to) <= 0) { reduction++; } } int newDepth=si.depth-1+extension; SearchInfo newSi(true,move,si.beta,1-si.beta,newDepth-reduction, si.ply+1,si.nodeType==CUT_NODE?ALL_NODE:CUT_NODE,si.splitPoint); score = -zwSearch(board, newSi); if (score >= si.beta && reduction>0) { bool research=true; if (reduction>2) { newSi.update(newDepth-1,si.nodeType==CUT_NODE?ALL_NODE:CUT_NODE, si.beta,1-si.beta,move); score = -zwSearch(board, newSi); research=(score >= si.beta); } if (research) { newSi.update(newDepth,si.nodeType==CUT_NODE?ALL_NODE:CUT_NODE, si.beta,1-si.beta,move); score = -zwSearch(board, newSi); } } board.undoMove(backup); if (stop(si)) { return 0; } nodes++; if (score>=si.beta) { bestScore=score; bestMove=move; break; } if (score>bestScore) { bestScore=score; bestMove=move; } if (!stop(si) && agent->getThreadNumber()>1 && agent->getFreeThreads()>0 && si.depth>minSplitDepth) { if (agent->spawnThreads(board, &si, getThreadId(), &moves, &move, &hashMove, &bestScore, &si.alpha, ¤tScore, &moveCounter, &nmMateScore)) { if (bestScore>=si.beta) { break; } } } } if (!moveCounter) { return si.partialSearch?si.beta-1:isKingAttacked?-maxScore+si.ply:drawScore; } TranspositionTable::NodeFlag flag; if (bestScore>=si.beta) { flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::LOWER_EVAL:TranspositionTable::LOWER; agent->updateHistory(board,bestMove,si.depth); updateKillers(board,bestMove,si.ply); } else { flag = currentScore!=-maxScore && !isLazyEval? TranspositionTable::UPPER_EVAL:TranspositionTable::UPPER; bestMove=emptyMove; } agent->hashPut(key,bestScore,currentScore,si.depth,si.ply,flag,bestMove); return bestScore; }
int Solve (int nodeType, Position& board, int ply, int alpha, int beta, int depth) { // return score for terminal state if (board.HasWon(board.get_arrayOfBitboard((board.get_nPlies() - 1) & 1))) { return -WIN + ply; } else if (board.get_nPlies() == 42) { return DRAW; } // Mate distance pruning alpha = std::max (ply - WIN, alpha); beta = std::min (WIN - (ply + 1), beta); if (alpha >= beta) { return alpha; } TTEntry entry = TranspositionTable.probeTTable(board.get_key()); if (entry.flag == EXACT || entry.flag == L_BOUND && entry.evaluationScore >= beta || entry.flag == U_BOUND && entry.evaluationScore <= alpha) { if (entry.evaluationScore >= beta) { updateKillers(entry.move, ply); } return entry.evaluationScore; } int hashMove = (entry.flag == L_BOUND && entry.evaluationScore < beta) ? entry.move : NO_MOVE; int bestScore = -INF; int movesMade = 0; bool raisedAlpha = false; MovePicker mPicker(board, ply, hashMove); int bestMove = NO_MOVE; for (int i=0; i < 7; i++) { int move = mPicker.getNextMove(); if (move == NO_MOVE) { break; } board.MakeMove(move); int score = -Solve(NON_ROOT, board, ply + 1, -beta, -alpha, depth - 1); board.UnmakeMove(); nodesVisited++; movesMade++; if (score >= beta) { TTEntry newEntry = {board.get_key(), L_BOUND, depth, score, move}; TranspositionTable.storeTTable(board.get_key(), newEntry); updateKillers(move, ply); updateHistory(depth, ply, move); if (movesMade == 1) { fh1++; } else { fh++; } return score; } else if (score > bestScore) { bestScore = score; bestMove = move; if (score > alpha) { alpha = score; raisedAlpha = true; } } } if (raisedAlpha) { TTEntry newEntry = {board.get_key(), EXACT, depth, alpha, bestMove}; TranspositionTable.storeTTable(board.get_key(), newEntry); } else { TTEntry newEntry = {board.get_key(), U_BOUND, depth, bestScore, NO_MOVE}; TranspositionTable.storeTTable(board.get_key(), newEntry); } return bestScore; }