// 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 Engine::AlphaBeta(int depth, int alpha, int beta, vector<Move>* variation, bool cannull, bool dopv) { #ifdef BLITZKRIEG_DEBUG Bitset tablekey = pos.PawnKey; if (alpha > beta || alpha < CONS_NEGINF || beta > CONS_INF) { cout << "info string ERROR: alpha > beta" << alpha << " " << beta << " " << ply << endl; } #endif if (isDraw()) return 0; ///Quiescence if (depth == 0) { int value = QuiescenceSearch(alpha, beta); //go to quiescence /*if (value > alpha && value < beta) PvSize = ply - 1;*/ //Table.Save(pos.TTKey,0,value,TT_EXACT,CONS_NULLMOVE); return value; } nodes++; if (nodes%CheckupNodeCount == 0) { checkup(); //nodes = 0; } ///Repetition if (ply != 0 && pos.isRepetition()) //check for repetition { //Table.Save(pos.TTKey,depth,0,TT_EXACT,CONS_NULLMOVE); return 0; } ///Probe int probe = Table.Probe(pos.TTKey, depth, alpha, beta); Move ttbestmove = createNullMove(pos.epsquare); if (probe != CONS_TTUNKNOWN) { //cout << probe << " found " << pos.TTKey << endl; if (ply != 0) { tthitcount++; return probe; } /*else { ttbestmove = Table.getBestMove(pos.TTKey); if (!ttbestmove.isNullMove()) { variation->push_back(ttbestmove); tthitcount++; return probe; } }*/ } //if (probe.avoidnull) cannull = false; int leafevalprobe = Table.Probe(pos.TTKey, 0, alpha, beta); if (leafevalprobe!=CONS_TTUNKNOWN //&& leafevalprobe.entry->bound==TT_EXACT ) { Evaluation[ply] = leafevalprobe; //use TT probe as a better leafeval } else { if (ply > 0 && currentVariation[ply] == CONS_NULLMOVE) //if last move was a nullmove, just invert score { Evaluation[ply] = -Evaluation[ply-1]; assert(!cannull); } else { Evaluation[ply] = LeafEval<false>(); } } ///Razoring if (!dopv && ply != 0 && depth < 4 && !incheck[ply] && (((Evaluation[ply] + getRazorMargin(depth)) <= alpha))) { prunednodes++; if (depth <= 1 && (Evaluation[ply] + getRazorMargin(3)) <= alpha) return QuiescenceSearch(alpha, beta); int ralpha = alpha - getRazorMargin(depth); int v = QuiescenceSearch(ralpha, ralpha + 1); if (v <= ralpha) return v; } ///Futility if (depth < 5 && ply != 0 && !incheck[ply] && ((Evaluation[ply] - getFutilityMargin(depth)) >= beta)) { futilitynodes++; return (Evaluation[ply] - getFutilityMargin(depth)); } int bound = TT_ALPHA; /*vector<Move> dummyline; dummyline.reserve(128);*/ /*vector<Move> lineptr; lineptr.reserve(128);*/ vector<Move> line; line.reserve(128); Move m; int score = 0; ///Null Move bool madenullmove = false; Bitset Pieces = pos.OccupiedSq ^ pos.Pieces[COLOR_WHITE][PIECE_PAWN] ^ pos.Pieces[COLOR_BLACK][PIECE_PAWN]; int pieceCount = popcnt(Pieces); if (cannull && !dopv && depth >= 3 && incheck[ply] == false && (pieceCount>2) //side to move does not have only pawns(to avoid zugzwang) //&& Evaluation[ply] >= beta ) { madenullmove = true; //int R = depth > 5 ? 3 : 2; //dynamic depth-based reduction int R = ((823 + 67 * depth) / 256 + std::min(max(0, Evaluation[ply] - beta) / PieceMaterial[PIECE_PAWN], 3)); m = createNullMove(pos.epsquare); ply++; #ifdef BLITZKRIEG_DEBUG Bitset ttkeynull = pos.TTKey; #endif pos.forceMove(m); /*bool fullnullmovesearch = true; if (depth >= 7) { score = -QuiescenceSearchStandPat(-beta, -beta+1); if (score < beta) fullnullmovesearch = false; } if(fullnullmovesearch)*/ score = -AlphaBeta(max(0, depth - R), -beta, -beta+1, &line, false, false); //make a null-window search (we don't care by how much it fails high, if it does) ply--; pos.unmakeMove(m); if (line.empty()!=true) Threats[ply] = line.at(line.size() - 1); #ifdef BLITZKRIEG_DEBUG if (ttkeynull != pos.TTKey) { cout << "info string ERROR: Null TT fail" << endl; _getch(); } #endif if (score >= beta) { //cout << "Null move cutoff " << beta << endl; /*if (probe.avoidnull) badavoidnull++;*/ nullcutoffs++; return score; } //if (score < alpha - 100) //score is so bad, we are in danger, so increase depth //{ // depth++; //} } //futility pruning //bool futilityprune = false; //if (depth < 4 && !underCheck && // (((leafeval + FutilityMargin[depth]) <= alpha))) //futility pruning //{ // futilitynodes++; // futilityprune = true; //} //movesort(vec,depth); bool alpharaised = false; bool foundlegal = false; Move alphamove = CONS_NULLMOVE; int finalalpha = -1; int firstalpha = -1; //vec = pos.generateMoves(); vector<Move> vec; vec.reserve(128); //movegentime.Start(); //if (futilityprune) //{ // pos.generateCaptures(vec); //search only captures in futility pruning //} //else //{ pos.generateMoves(vec); //} //movegentime.Stop(); /*vector<Move> line; line.reserve(128);*/ /*vector<int> scores; scores.reserve(128); generateCaptureScores(vec, scores);*/ if (probe==CONS_TTUNKNOWN && (dopv || Evaluation[ply] + 256 >= beta) && depth >= 2) //internal iterative deepening { int score = AlphaBeta(depth-2, alpha, beta, &line, false, dopv); } int evaldiff = ply >= 2 ? Evaluation[ply] - Evaluation[ply - 2] : 0; vector<Move> quietmoves; quietmoves.reserve(128); int bestscore = CONS_NEGINF; //int oldsortphase = SORTPHASE_NONE; for (unsigned int i = 0;i < vec.size();i++) //search { line.clear(); //dummyline.clear(); //m = vec.at(i); //int tablekey2 = pos.TTKey; m = getHighestScoringMove(vec, i); #ifdef BLITZKRIEG_DEBUG if (SortPhase == SORTPHASE_NONE) cout << "info string Sort Phase error" << endl; #endif int capturedpiece = m.getCapturedPiece(); int special = m.getSpecial(); int movingpiece = m.getMovingPiece(); int moveto = m.getTo(); int movefrom = m.getFrom(); int iscapture = isCapture(m); /*int see = 0; int evade_see = 0; Move smallestattckr = pos.getSmallestAttacker(getOpponent(pos.turn), movefrom); if (iscapture) { see = StaticExchangeEvaluation(moveto, movefrom, movingpiece, capturedpiece); } if (!smallestattckr.isNullMove()) { evade_see = StaticExchangeEvaluation(movefrom, smallestattckr.getTo(), smallestattckr.getMovingPiece(), movingpiece); }*/ //if (iscapture && depth <= 1 && see < 0) //{ //prune bad captures at low depths // continue; //} //if (depth < 8 // && !alpharaised // && noMaterialGain(m) // //&& !incheck[ply] // && !incheck[ply - 1] // && movingpiece!=PIECE_PAWN // ) //{ // if (i >= 2+depth*depth) // { // continue; // } //} if (!pos.makeMove(m)) { continue; } //if (vec.size() == 1 || (foundlegal==false && i==vec.size()-1)) //singular extension, only 1 legal move, so extend //{ // depth++; //} foundlegal = true; ply++; currentVariation[ply] = m; score = 0; int reductiondepth = 1; ///Check Extension if (pos.underCheck(pos.turn)) { incheck[ply] = true; reductiondepth--; } else { incheck[ply] = false; } ///Recapture Extension /*if (ply > 1 && m.getTo() == currentVariation[ply - 1].getTo() && iscapture && isCapture(currentVariation[ply - 1])) { reductiondepth--; }*/ //extend when capturing the last piece /*if (isCapture(m) && getSquare2Piece(capturedpiece) != PIECE_PAWN && popcnt(pos.Pieces[COLOR_WHITE][PIECE_PAWN]) + popcnt(pos.Pieces[COLOR_BLACK][PIECE_PAWN]) + 2 == popcnt(pos.OccupiedSq)) { reductiondepth -= 3; }*/ /*if (depth < 16) { if (!incheck[ply - 1] && !incheck[ply] && movingpiece != PIECE_PAWN && noMaterialGain(m)) { if(i > 3 + (1 << (depth-1))) { pos.unmakeMove(m); incheck[ply] = false; ply--; continue; } } }*/ //if (!dopv && depth < 4 && ((Evaluation[ply] + getSmallRazorMargin(depth)) <= alpha)) //small forward razoring //{ // reductiondepth++; //} ///Latemove Reduction if (!alpharaised //&& i >= 4 && depth >= 4 //&& special!=PIECE_QUEEN //&& (see < 0 || !iscapture) && SortPhase >= SORTPHASE_HISTORY //&& noMaterialGain(m) //&& (KillerMoves[0][ply].getTo() != moveto || KillerMoves[0][ply].getFrom() != movefrom) //&& (KillerMoves[1][ply].getTo() != moveto || KillerMoves[1][ply].getFrom() != movefrom) && !incheck[ply] && !incheck[ply-1] //&& (movingpiece!=PIECE_PAWN || getRank(getColorMirror(getOpponent(pos.turn), moveto))<6) //dont reduce pawn moves past 6th rank //&& m!=Threats[ply] ) { if (evaldiff > 0) reductiondepth += min(depth - 4, min((int)i,4)); else reductiondepth += min(depth - 4, min((int)i+1,5)); assert((depth - reductiondepth) >= 3); if (!dopv && HistoryScores[movingpiece][moveto] < 0) //history reduction { reductiondepth++; } if (m == Threats[ply] && reductiondepth > 0) //decrease reduction if move is a threat { reductiondepth = max(reductiondepth - 1, 0); } //if (noMaterialGain(m) && !smallestattckr.isNullMove() && evade_see < 0) //decrease reduction if move evades a capture //{ // reductiondepth = max(reductiondepth - 1, 0); //} } //if (isCapture(m) && !dopv && see > 400 && depth>=5) //prune really good captures //{ // reductiondepth += 4; //} //if (alpha_counter != 0 && (depth-reductiondepth)>=3 && i>((double)alphalast_sum/alpha_counter) && capturedpiece == SQUARE_EMPTY && special == PIECE_NONE // && !pos.underCheck(pos.turn) // && (KillerMoves[0][ply].getTo() != m.getTo() || KillerMoves[0][ply].getFrom() != m.getFrom()) // && (KillerMoves[1][ply].getTo() != m.getTo() || KillerMoves[1][ply].getFrom() != m.getFrom())) //latemove reduction //{ // reductiondepth += 2; // /*reductiondepth ++; // if (i >= 8 && depth >= 6) // { // reductiondepth++; // if (i >= 12 && depth >= 9) // reductiondepth++; // }*/ //} ///Search if(dopv && i>0 && depth>=3) //principal variation search { score = -AlphaBeta(max(depth - reductiondepth, 0), -alpha - 1, -alpha, &line, true, false); if(score > alpha && score < beta) //check for failure { line.clear(); score = -AlphaBeta(depth - 1, -beta, -alpha, &line, true, true); //research alpharaised = false; pvresearch++; //cout << "pv research" << endl; } } else { score = -AlphaBeta(max(depth - reductiondepth,0), -beta, -alpha, &line, true, dopv); //cout << "latemove" << endl; if(score > alpha && score < beta && reductiondepth>1) { line.clear(); score = -AlphaBeta(depth - 1, -beta, -alpha, &line, true, dopv); latemoveresearch++; } } currentVariation[ply] = CONS_NULLMOVE; incheck[ply] = false; ply--; pos.unmakeMove(m); assert(score > CONS_NEGINF && score < CONS_INF); assert(score >= CONS_MATED && score <= -CONS_MATED); if(score>=beta) { if(noMaterialGain(m)) { //if(Table.getBestMove(pos.TTKey)!=m) //dont store hash move as a killer setKiller(m, depth, score); int bonus = depth*depth; HistoryScores[movingpiece][m.getTo()] += bonus; if (HistoryScores[movingpiece][m.getTo()] > 200000) //prevent overflow of history values { for (int i = 0;i < 6;i++) { for (int j = 0;j < 64;j++) { HistoryScores[i][j] /= 2; } } } for (int i = 0;i < quietmoves.size();i++) { /*if(HistoryScores[quietmoves.at(i).getMovingPiece()][quietmoves.at(i).getTo()] <= bonus) HistoryScores[quietmoves.at(i).getMovingPiece()][quietmoves.at(i).getTo()] = 0; else*/ HistoryScores[quietmoves.at(i).getMovingPiece()][quietmoves.at(i).getTo()] -= bonus; } } Table.Save(pos.TTKey, depth, score, TT_BETA, m); #ifdef BLITZKRIEG_STATS betacutoff_counter++; betacutoff_sum += i+1; if (i == 0) firstbetacutoffcount++; #endif return score; //fail soft beta cutoff } else if(score>bestscore) { bestscore = score; if (score > alpha) { bound = TT_EXACT; alpha = score; alpharaised = true; alphamove = m; *variation = line; if (noMaterialGain(m)) HistoryScores[movingpiece][m.getTo()] += depth; #ifdef BLITZKRIEG_STATS if (firstalpha == -1) { firstalpha = i; } finalalpha = i; #endif } } if (noMaterialGain(m)) { quietmoves.push_back(m); } } if(!foundlegal) { //if(futilityprune) //{ // //movegentime.Start(); // vec.clear(); // pos.generateMoves(vec); // //movegentime.Stop(); // int flag = 1; // for(int i = 0;i<vec.size();i++) // { // if(pos.makeMove(m)) // { // unmakeMove(m); // flag = 0; // break; // } // } // if(flag) // { // if(pos.underCheck(pos.turn)) // { // return CONS_MATED-ply; // } // else // { // return CONS_DRAW; // } // } //} //else { if(pos.underCheck(pos.turn)) { return CONS_MATED+ply; } else { return CONS_DRAW; } } } if(!alphamove.isNullMove()) { variation->push_back(alphamove); //if(dopv) // PrincipalVariation[ply] = alphamove; /*if (depth == 1) { PvSize = ply; PvPly = ply; }*/ /*else if (ply == PvPly - 1) { PrincipalVariation[ply] = alphamove; PvPly = ply; }*/ //HistoryScores[alphamove.getFrom()][alphamove.getTo()] += depth+finalalpha; #ifdef BLITZKRIEG_STATS alpha_counter++; alphalast_sum += (finalalpha + 1); alphafirst_sum += (firstalpha + 1); #endif } Table.Save(pos.TTKey, depth, bestscore, bound, alphamove); /*if (ply == 0) { cout << "info string Stored: " << alphamove.toString() << " " << bound << " " << depth << endl; }*/ #ifdef BLITZKRIEG_DEBUG if (pos.PawnKey != tablekey) { cout << "info string ERROR: Pawn TT key doesnt match" << endl; } #endif return bestscore; }