/* Protector -- a UCI chess engine Copyright (C) 2009-2010 Raimund Heid (Raimund_Heid@yahoo.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 . */ #include #include #include #include #include #include "search.h" #include "matesearch.h" #include "io.h" #include "movegeneration.h" #include "hash.h" #include "evaluation.h" #include "book.h" #include "coordination.h" #ifdef INCLUDE_TABLEBASE_ACCESS #include "tablebase.h" #endif /* #define DEBUG_THREAD_COORDINATION */ /* #define CRV_CHECK */ #define QUIESCENCE_FUTILITY_CUT #define FUTILITY_CUTOFF_HISTORY /* #define EXTRA_BONUS_BETA_CUT */ #define EXTEND_PIECE_SWAP #define AGRESSIVE_HISTORY_PRUNING #define EXTEND_ON_THREATS extern bool resetGlobalHashtable; const int FAIL_LOW_MARGIN = 50; /* was 20 */ const int FUTILITY_CUT_LIMIT_PCT = 60; /* was 60 */ int THREAT_LIMIT = 90; Move crv[MAX_DEPTH]; UINT64 crvHash; INT32 checkTimeCount = 0; static int searchBest(Variation * variation, int alpha, int beta, const int ply, const int restDepth, const bool pvNode, const bool reduction, PrincipalVariation * pv); INLINE static void checkTime(Variation * variation) { variation->timestamp = getTimestamp(); if (variation->timeLimit != 0 && variation->timestamp - variation->startTime >= variation->timeLimit && variation->searchStatus == SEARCH_STATUS_RUNNING && variation->ponderMode == FALSE) { variation->searchStatus = SEARCH_STATUS_TERMINATE; #ifdef DEBUG_THREAD_COORDINATION logDebug ("Time limit reached (time=%lu ms, limit=%lu ms)).\n", variation->timestamp - variation->startTime, variation->timeLimit); #endif } if ((checkTimeCount++ % 10) == 0 && variation->handleSearchEvent != 0) { variation->handleSearchEvent(SEARCHEVENT_STATISTICS_UPDATE, variation); } } INLINE static void initPlyInfo(PlyInfo * info) { info->quietMove = FALSE; } INLINE static bool hasDangerousPawns(const Position * position, const Color color) { if (color == WHITE) { return (bool) ((position->piecesOfType[WHITE_PAWN] & squaresOfRank[RANK_7]) != EMPTY_BITBOARD); } else { return (bool) ((position->piecesOfType[BLACK_PAWN] & squaresOfRank[RANK_2]) != EMPTY_BITBOARD); } } static int searchBestQuiescence(Variation * variation, int alpha, int beta, const int ply, const int restDepth, const bool pvNode, PrincipalVariation * pv) { Position *position = &variation->singlePosition; int best = VALUE_MATED, currentValue = VALUE_MATED, historyLimit, i; const int VALUE_MATE_HERE = -VALUE_MATED - ply + 1; const int VALUE_MATED_HERE = VALUE_MATED + ply; Movelist movelist; Move currentMove; const bool inCheck = variation->plyInfo[ply - 1].currentMoveIsCheck; PrincipalVariation newPv; assert(alpha >= VALUE_MATED && alpha <= -VALUE_MATED); assert(beta >= VALUE_MATED && beta <= -VALUE_MATED); assert(alpha < beta); assert(ply > 0 && ply < MAX_DEPTH); assert(restDepth <= 0); assert(passiveKingIsSafe(position)); assert((inCheck != FALSE) == (activeKingIsSafe(position) == FALSE)); newPv.length = pv->length = 0; variation->nodes++; if (variation->nodes == (variation->nodes & ~((UINT64) 0xFFFF))) { checkTime(variation); } if (variation->terminate && movesAreEqual(variation->bestBaseMove, NO_MOVE) == FALSE) { variation->searchStatus = SEARCH_STATUS_TERMINATE; } if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } /* Check for a draw by repetition. */ /* ------------------------------- */ historyLimit = POSITION_HISTORY_OFFSET + variation->ply - position->halfMoveClock; assert(historyLimit >= 0); for (i = POSITION_HISTORY_OFFSET + variation->ply - 4; i >= historyLimit; i -= 2) { if (position->hashValue == variation->positionHistory[i]) { return variation->drawScore[position->activeColor]; } } if (inCheck == FALSE) { assert(flipTest(position, variation->pawnHashtable, variation->kingsafetyHashtable) != FALSE); best = getValue(position, alpha, beta, variation->pawnHashtable, variation->kingsafetyHashtable); if (best > alpha) { alpha = best; if (best >= beta) { return best; } } currentValue = best; } if (ply >= MAX_DEPTH) { assert(flipTest(position, variation->pawnHashtable, variation->kingsafetyHashtable) != FALSE); return getValue(position, alpha, beta, variation->pawnHashtable, variation->kingsafetyHashtable); } if (alpha < VALUE_MATED_HERE && inCheck == FALSE) { alpha = VALUE_MATED_HERE; if (VALUE_MATED_HERE >= beta) { return VALUE_MATED_HERE; } } if (beta > VALUE_MATE_HERE) { beta = VALUE_MATE_HERE; if (VALUE_MATE_HERE <= alpha) { return VALUE_MATE_HERE; } } initQuiescenceMovelist(&movelist, &variation->singlePosition, &variation->plyInfo[ply], &variation->historyValue[0], restDepth, inCheck); initializePlyInfo(variation); while ((currentMove = getNextMove(&movelist)) != NO_MOVE) { int value, newDepth = (inCheck ? restDepth : restDepth - 1); #ifdef QUIESCENCE_FUTILITY_CUT const int delta = 50; int optValue = currentValue + delta + basicValue[position->piece[getToSquare(currentMove)]]; if (pvNode == FALSE && inCheck == FALSE && optValue < alpha && pieceType(position->piece[getToSquare(currentMove)]) != QUEEN && getNewPiece(currentMove) != QUEEN && (pieceType(position->piece[getFromSquare(currentMove)]) != PAWN || colorRank(position->activeColor, getToSquare(currentMove)) != RANK_7)) { if (getNewPiece(currentMove) != NO_PIECE) { optValue += basicValue[getNewPiece(currentMove)] - basicValue[PAWN]; } if (getToSquare(currentMove) == position->enPassantSquare && pieceType(position->piece[getFromSquare(currentMove)]) == PAWN) { optValue += basicValue[PAWN]; } if (optValue < alpha && moveIsCheck(currentMove, position) == FALSE) { best = max(best, optValue); continue; } } #endif assert(moveIsPseudoLegal(position, currentMove)); if (makeMoveFast(variation, currentMove) != 0 || passiveKingIsSafe(&variation->singlePosition) == FALSE) { unmakeLastMove(variation); continue; } variation->plyInfo[ply].currentMoveIsCheck = activeKingIsSafe(&variation->singlePosition) == FALSE; assert(position->piece[getToSquare(currentMove)] != NO_PIECE || (getToSquare(currentMove) == position->enPassantSquare && position->piece[getFromSquare(currentMove)] == (PAWN | position->activeColor)) || getNewPiece(currentMove) != NO_PIECE || inCheck || variation->plyInfo[ply].currentMoveIsCheck); assert(inCheck != FALSE || basicValue[position->piece[getFromSquare(currentMove)]] <= basicValue[position->piece[getToSquare(currentMove)]] || seeMove(position, currentMove) >= 0); value = -searchBestQuiescence(variation, -beta, -alpha, ply + 1, newDepth, pvNode, &newPv); unmakeLastMove(variation); if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } if (value > best) { best = value; if (variation->handleSearchEvent != 0) { appendMoveToPv(&newPv, pv, currentMove); } if (best > alpha) { alpha = best; if (best >= beta) { return best; } } } } if (best == VALUE_MATED) { /* mate */ assert(inCheck != FALSE); best = VALUE_MATED + ply; } return best; } static int searchBestWithoutNullmove(Variation * variation, int alpha, int beta, const int ply, const int restDepth, const bool pvNode, const Move hashmove, PrincipalVariation * pv) { Position *position = &variation->singlePosition; int best = VALUE_MATED; Movelist movelist; int i, historyLimit; Move currentMove; const bool inCheck = variation->plyInfo[ply - 1].currentMoveIsCheck; PrincipalVariation newPv; assert(alpha >= VALUE_MATED && alpha <= -VALUE_MATED); assert(beta >= VALUE_MATED && beta <= -VALUE_MATED); assert(alpha < beta); assert(ply > 0 && ply < MAX_DEPTH); assert(restDepth > 0); assert(passiveKingIsSafe(position)); assert(inCheck == FALSE); initPlyInfo(&variation->plyInfo[ply]); newPv.length = pv->length = 0; variation->nodes++; if (variation->nodes == (variation->nodes & ~((UINT64) 0xFFFF))) { checkTime(variation); } if (variation->terminate && movesAreEqual(variation->bestBaseMove, NO_MOVE) == FALSE) { variation->searchStatus = SEARCH_STATUS_TERMINATE; } if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } /* Check for a draw by repetition. */ /* ------------------------------- */ historyLimit = POSITION_HISTORY_OFFSET + variation->ply - position->halfMoveClock; assert(historyLimit >= 0); for (i = POSITION_HISTORY_OFFSET + variation->ply - 4; i >= historyLimit; i -= 2) { if (position->hashValue == variation->positionHistory[i]) { return variation->drawScore[position->activeColor]; } } initStandardMovelist(&movelist, &variation->singlePosition, &variation->plyInfo[ply], &variation->historyValue[0], hashmove, inCheck); initializePlyInfo(variation); /* Loop through all moves in this node. */ /* ------------------------------------ */ while ((currentMove = getNextMove(&movelist)) != NO_MOVE) { int value, newDepth; bool check; assert(moveIsPseudoLegal(position, currentMove)); /* Execute the current move and check if it is legal. */ /* -------------------------------------------------- */ if (makeMoveFast(variation, currentMove) != 0 || passiveKingIsSafe(&variation->singlePosition) == FALSE) { unmakeLastMove(variation); continue; } /* Check the conditions for search extensions and finally */ /* calculate the rest depth for the next ply. */ /* ------------------------------------------------------ */ variation->plyInfo[ply].currentMoveIsCheck = check = activeKingIsSafe(&variation->singlePosition) == FALSE; newDepth = (check ? restDepth : restDepth - 1); /* Recursive search */ /* ---------------- */ value = -searchBest(variation, -beta, -alpha, ply + 1, newDepth, pvNode, FALSE, &newPv); unmakeLastMove(variation); if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } /* Calculate the parameters controlling the search tree. */ /* ----------------------------------------------------- */ if (value > best) { best = value; if (variation->handleSearchEvent != 0) { appendMoveToPv(&newPv, pv, currentMove); } if (best > alpha) { alpha = best; if (best >= beta) { return best; } } } } /* No legal move was found. Check if it's mate or stalemate. */ /* --------------------------------------------------------- */ if (best == VALUE_MATED) { /* stalemate */ assert(inCheck == FALSE); best = variation->drawScore[position->activeColor]; } return best; } INLINE static bool moveIsQuiet(const Move move, const Position * position) { return (bool) (position->piece[getToSquare(move)] == NO_PIECE && getNewPiece(move) == NO_PIECE && (getToSquare(move) != position->enPassantSquare || pieceType(position->piece[getFromSquare(move)]) != PAWN)); } INLINE static bool moveAttacksSquare(const Move move, const Square square, const Position * position) { const Piece piece = position->piece[getFromSquare(move)]; Bitboard orginalMoves = getCaptureMoves(getFromSquare(move), piece, position->allPieces); Bitboard movesFromTarget = getCaptureMoves(getToSquare(move), piece, position->allPieces); return testSquare(orginalMoves, square) == FALSE && testSquare(movesFromTarget, square); } /* * Check the connection of two consecutive moves. * Source of idea: Stockfish, thx folks */ INLINE static bool moveIsDefence(const Move move, const Move threatMove, Position * position) { const Square threatTarget = getToSquare(threatMove); if (getFromSquare(move) == threatTarget && seeMove(position, move) >= 0) { return TRUE; /* threatened piece was moved */ } #ifdef PASSIVE_DEFENCE_MOVES else { const Square threatFromSquare = getFromSquare(threatMove); const PieceType threateningPiece = pieceType(position->piece[threatFromSquare]); Bitboard sliderCorridor; switch (threateningPiece) { case BISHOP: case ROOK: case QUEEN: sliderCorridor = squaresBetween[threatFromSquare][threatTarget]; break; default: sliderCorridor = EMPTY_BITBOARD; /* treat pawn advances? */ } if (testSquare(sliderCorridor, getToSquare(move)) && seeMove(position, move) >= 0) { return TRUE; /* move blocks slider */ } } if (basicValue[position->piece[getFromSquare(threatMove)]] >= basicValue[position->piece[getFromSquare(move)]]) { if (moveAttacksSquare(move, threatTarget, position) && seeMove(position, move) >= 0) { return TRUE; /* move defends target */ } } #endif return FALSE; } static INLINE bool isPassedPawnMove(const Square pawnSquare, const Square targetSquare, const Position * position) { const Piece piece = position->piece[pawnSquare]; if (pieceType(piece) == PAWN) { return pawnIsPassed(position, targetSquare, pieceColor(piece)); } else { return FALSE; } } static int searchBest(Variation * variation, int alpha, int beta, const int ply, const int restDepth, const bool pvNode, const bool reduction, PrincipalVariation * pv) { const int oldAlpha = alpha; Position *position = &variation->singlePosition; int best = VALUE_MATED; const int VALUE_MATE_HERE = -VALUE_MATED - ply + 1; const int VALUE_MATED_HERE = VALUE_MATED + ply; Movelist movelist; Hashentry *tableHit = NULL; UINT8 hashentryFlag; int i, historyLimit, numMovesPlayed = 0, numQuietMoves = 0; Move hashmove = NO_MOVE, bestMove = NO_MOVE, threatMove = NO_MOVE; Move currentMove; Move lastMove = variation->plyInfo[ply - 1].currentMove; const bool inCheck = variation->plyInfo[ply - 1].currentMoveIsCheck; PrincipalVariation newPv; int historyIndexPlayed[MAX_MOVES_PER_POSITION]; bool futilityCut = FALSE; const bool dangerousPawns = hasDangerousPawns(position, position->activeColor); bool oppThreat = FALSE; #ifdef CRV_CHECK bool crvFail = FALSE; for (i = 0; crv[i] != NO_MOVE; i++) { if (movesAreEqual(variation->plyInfo[i].currentMove, crv[i]) == FALSE) { crvFail = TRUE; break; } } if (crvFail == FALSE && i != ply) { crvFail = TRUE; } #endif /* CRV_CHECK */ if (ply + 1 > variation->selDepth) { variation->selDepth = ply + 1; } assert(alpha >= VALUE_MATED && alpha <= -VALUE_MATED); assert(beta >= VALUE_MATED && beta <= -VALUE_MATED); assert(alpha < beta); assert(ply > 0 && ply < MAX_DEPTH); assert(restDepth >= -3); assert(passiveKingIsSafe(position)); assert((inCheck != FALSE) == (activeKingIsSafe(position) == FALSE)); initPlyInfo(&variation->plyInfo[ply]); newPv.length = pv->length = 0; newPv.move[0] = (UINT16) NO_MOVE; if (restDepth <= 0) { /* 63% */ return searchBestQuiescence(variation, alpha, beta, ply, 0, pvNode, pv); } variation->nodes++; if (variation->nodes == (variation->nodes & ~((UINT64) 0xFFFF))) { checkTime(variation); } if (variation->terminate && movesAreEqual(variation->bestBaseMove, NO_MOVE) == FALSE) { variation->searchStatus = SEARCH_STATUS_TERMINATE; } if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } /* Check for a draw according to the 50-move-rule */ /* ---------------------------------------------- */ if (position->halfMoveClock >= 100) { return variation->drawScore[position->activeColor]; } /* Check for a draw by repetition. */ /* ------------------------------- */ historyLimit = POSITION_HISTORY_OFFSET + variation->ply - position->halfMoveClock; assert(historyLimit >= 0); for (i = POSITION_HISTORY_OFFSET + variation->ply - 4; i >= historyLimit; i -= 2) { if (position->hashValue == variation->positionHistory[i]) { return variation->drawScore[position->activeColor]; } } #ifdef INCLUDE_TABLEBASE_ACCESS /* Probe the tablebases in case of reduced material */ /* ------------------------------------------------ */ if (ply >= 2 && (pvNode || restDepth >= 6) && position->numberOfPieces[WHITE] + position->numberOfPieces[BLACK] <= 5 && tbAvailable != FALSE && variation->handleSearchEvent != 0) { int tbValue; tbValue = probeTablebase(position); if (tbValue != TABLEBASE_ERROR) { variation->tbHits++; if (tbValue == 0) { return variation->drawScore[position->activeColor]; } return (tbValue > 0 ? tbValue - ply : tbValue + ply); } } #endif /* Probe the transposition table */ /* ----------------------------- */ tableHit = getDatedEntry(variation->hashtable, variation->singlePosition.hashValue); if (tableHit != NULL) { /* 45% */ const int importance = getHashentryImportance(tableHit); hashmove = (Move) getHashentryMove(tableHit); if (hashmove != NO_MOVE) { /* 81% */ assert(moveIsPseudoLegal(position, hashmove)); if (moveIsPseudoLegal(position, hashmove)) { assert(moveIsLegal(position, hashmove)); if (variation->handleSearchEvent != 0) { appendMoveToPv(&newPv, pv, hashmove); } } else { hashmove = NO_MOVE; } } if (pvNode == FALSE && restDepth <= importance) { /* 99% */ const int value = getHashentryValue(tableHit); const int hashValue = calcEffectiveValue(value, ply); const int flag = getHashentryFlag(tableHit); switch (flag) { case HASHVALUE_LOWER: if (hashValue <= alpha) { return hashValue; } break; case HASHVALUE_EXACT: return hashValue; case HASHVALUE_HIGHER: if (hashValue >= beta) { return hashValue; } break; default:; } } } if (ply >= MAX_DEPTH) { assert(flipTest(position, variation->pawnHashtable, variation->kingsafetyHashtable) != FALSE); return getValue(position, alpha, beta, variation->pawnHashtable, variation->kingsafetyHashtable); } if (alpha < VALUE_MATED_HERE && inCheck == FALSE) { alpha = VALUE_MATED_HERE; if (VALUE_MATED_HERE >= beta) { return VALUE_MATED_HERE; } } if (beta > VALUE_MATE_HERE) { beta = VALUE_MATE_HERE; if (VALUE_MATE_HERE <= alpha) { return VALUE_MATE_HERE; } } initializePlyInfo(variation); if (restDepth >= 2 && inCheck == FALSE && pvNode == FALSE && numberOfNonPawnPieces(position, position->activeColor) >= 2 && getNumberOfPieceMoves(position, position->activeColor, 6) >= 6) { /* 16-32% */ const int verificationReduction = 5; const int newDepth = restDepth - 4; int nullValue; assert(flipTest(position, variation->pawnHashtable, variation->kingsafetyHashtable) != FALSE); makeMoveFast(variation, NULLMOVE); variation->plyInfo[ply].currentMoveIsCheck = FALSE; nullValue = -searchBest(variation, -beta, -beta + 1, ply + 1, newDepth, pvNode, FALSE, &newPv); unmakeLastMove(variation); threatMove = (Move) newPv.move[0]; if (restDepth > verificationReduction && nullValue >= beta) { /* 2% */ nullValue = searchBestWithoutNullmove(variation, alpha, beta, ply, restDepth - verificationReduction, FALSE, hashmove, &newPv); if (nullValue >= beta) { /* 99% */ best = nullValue; bestMove = (Move) newPv.move[0]; *pv = newPv; if (moveIsQuiet(bestMove, position)) { historyIndexPlayed[numQuietMoves++] = historyIndex(bestMove, position); } goto finish; } } if (nullValue >= beta) { /* 70% */ best = min(nullValue, -VALUE_ALMOST_MATED); goto finish; } else if (reduction) { const int passiveValue = getValue(position, alpha, beta, variation->pawnHashtable, variation->kingsafetyHashtable); const int gap = passiveValue - nullValue; oppThreat = (bool) (gap >= THREAT_LIMIT); } } /* Internal iterative deepening. */ /* ----------------------------- */ if (restDepth >= 3 && hashmove == NO_MOVE && pvNode) { const int value = searchBest(variation, alpha, beta, ply, restDepth - 2, TRUE, FALSE, &newPv); if (value <= alpha) { searchBest(variation, VALUE_MATED, beta, ply, restDepth - 2, TRUE, FALSE, &newPv); } if (newPv.length > 0 && moveIsPseudoLegal(position, (Move) newPv.move[0])) { hashmove = (Move) newPv.move[0]; } } if (pvNode == FALSE && restDepth <= 6 && inCheck == FALSE && dangerousPawns == FALSE) { const static int delta[6] = { 150, 150, 350, 350, 550, 550 }; const int limit = alpha - delta[restDepth - 1]; int nodeValue; if (basicPositionalBalance(position) < limit && (nodeValue = getValue(position, limit, beta, variation->pawnHashtable, variation->kingsafetyHashtable)) < limit) { best = nodeValue; futilityCut = TRUE; } } if (ply >= 2) { variation->plyInfo[ply].killerMove3 = variation->plyInfo[ply - 2].killerMove1; variation->plyInfo[ply].killerMove4 = variation->plyInfo[ply - 2].killerMove2; } else { variation->plyInfo[ply].killerMove3 = NO_MOVE; variation->plyInfo[ply].killerMove4 = NO_MOVE; } if (futilityCut) { initPreQuiescenceMovelist(&movelist, &variation->singlePosition, &variation->plyInfo[ply], &variation->historyValue[0], hashmove, inCheck); } else { initStandardMovelist(&movelist, &variation->singlePosition, &variation->plyInfo[ply], &variation->historyValue[0], hashmove, inCheck); } /* Loop through all moves in this node. */ /* ------------------------------------ */ while ((currentMove = getNextMove(&movelist)) != NO_MOVE) { const int historyIdx = historyIndex(currentMove, position); const int stage = moveGenerationStage[movelist.currentStage]; int value, newDepth; bool extend = (bool) ((ply <= variation->nominalDepth || pvNode) && inCheck && movelist.numberOfMoves <= 1); bool check, historyCut = FALSE; const bool quietMove = moveIsQuiet(currentMove, position); const Square toSquare = getToSquare(currentMove); const bool capture = (bool) (position->piece[toSquare] != NO_PIECE); const bool pieceCapture = (bool) (capture && pieceType(position->piece[toSquare]) != PAWN); const bool recapture = (bool) (toSquare == getToSquare(lastMove) && capture && (variation->plyInfo[ply - 1].captured != NO_PIECE || (pieceType(position->piece[toSquare]) == PAWN && variation->plyInfo[ply - 1].enPassantSquare == toSquare))); const bool pieceRecapture = (bool) (recapture && pieceCapture && pieceType(variation->plyInfo[ply - 1].captured) != PAWN && variation->plyInfo[ply - 1].captured != NO_PIECE); bool defenceMove = FALSE; #ifndef NDEBUG bool simpleMoveCheck = FALSE; if (moveGenerationStage[movelist.currentStage] == MGS_REST && simpleMoveIsCheck(position, currentMove)) { simpleMoveCheck = TRUE; } #endif #ifdef EXTEND_ON_THREATS variation->plyInfo[ply].quietMove = quietMove; #endif assert(moveIsPseudoLegal(position, currentMove)); assert(hashmove == NO_MOVE || numMovesPlayed > 0 || movesAreEqual(currentMove, hashmove)); if (restDepth >= 3 && threatMove != NO_MOVE && quietMove && inCheck == FALSE && oppThreat == FALSE) { defenceMove = moveIsDefence(currentMove, threatMove, position); /*if (defenceMove) { dumpMove(threatMove); dumpMove(currentMove); dumpVariation(variation); } */ } #ifdef FUTILITY_CUTOFF_HISTORY if (restDepth <= 6 && pvNode == FALSE && inCheck == FALSE && numQuietMoves >= restDepth + (restDepth & 1) && stage != MGS_GOOD_CAPTURES_AND_PROMOTIONS && !isPassedPawnMove(getFromSquare(currentMove), toSquare, position)) { const int depthDivisor = (restDepth + 1) / 2; const int limit = FUTILITY_CUT_LIMIT_PCT / depthDivisor; const int historyValue = (variation->historyHitCount[historyIdx] * 100) / variation->historyTotalCount[historyIdx]; if (historyValue < limit && simpleMoveIsCheck(position, currentMove) == FALSE) { continue; } } #endif /* Execute the current move and check if it is legal. */ /* -------------------------------------------------- */ if (makeMoveFast(variation, currentMove) != 0 || passiveKingIsSafe(&variation->singlePosition) == FALSE) { unmakeLastMove(variation); continue; } /* Check the conditions for search extensions and finally */ /* calculate the rest depth for the next ply. */ /* ------------------------------------------------------ */ variation->plyInfo[ply].currentMoveIsCheck = check = activeKingIsSafe(&variation->singlePosition) == FALSE; assert(stage != MGS_REST || simpleMoveCheck == FALSE || check == TRUE); if (check || (pieceRecapture && ply <= variation->nominalDepth)) { extend = TRUE; } else if (pvNode) { const Color passiveColor = opponent(position->activeColor); if (pieceType(position->piece[toSquare]) == PAWN && (colorRank(passiveColor, toSquare) >= RANK_6 || pawnIsPassed(position, toSquare, passiveColor))) { extend = TRUE; } else if (recapture) { extend = TRUE; } else if (pieceCapture) { if (restDepth >= 6) { extend = TRUE; } else if (numberOfNonPawnPieces(position, WHITE) <= 2 && numberOfNonPawnPieces(position, WHITE) == numberOfNonPawnPieces(position, BLACK)) { extend = TRUE; } } } newDepth = (extend ? restDepth : restDepth - 1); /* History pruning */ /* --------------- */ if (inCheck == FALSE && extend == FALSE && numMovesPlayed >= 3 && restDepth >= 3 && stage != MGS_GOOD_CAPTURES_AND_PROMOTIONS && isPassedPawnMove(toSquare, toSquare, position) == FALSE && pvNode == FALSE && oppThreat == FALSE && defenceMove == FALSE) { newDepth--; historyCut = TRUE; } /* Recursive search */ /* ---------------- */ if (pvNode == FALSE || best == VALUE_MATED) { value = -searchBest(variation, -beta, -alpha, ply + 1, newDepth, pvNode, historyCut, &newPv); if (historyCut && value > alpha) { value = -searchBest(variation, -beta, -alpha, ply + 1, restDepth - 1, pvNode, FALSE, &newPv); } } else /* pvNode == TRUE && best > VALUE_MATED */ { value = -searchBest(variation, -alpha - 1, -alpha, ply + 1, newDepth, FALSE, FALSE, &newPv); if (value > alpha) { /* 2% */ value = -searchBest(variation, -beta, -alpha, ply + 1, newDepth, TRUE, FALSE, &newPv); } } assert(value >= VALUE_MATED && value <= -VALUE_MATED); unmakeLastMove(variation); numMovesPlayed++; if (quietMove) { historyIndexPlayed[numQuietMoves++] = historyIndex(currentMove, position); } if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { return 0; } /* Calculate the parameters controlling the search tree. */ /* ----------------------------------------------------- */ if (value > best) { /* 32% */ best = value; if (variation->handleSearchEvent != 0) { appendMoveToPv(&newPv, pv, currentMove); } if (best > alpha) { /* 63% */ alpha = best; bestMove = currentMove; if (best >= beta) { /* 99% */ goto finish; } } } } /* No legal move was found. Check if it's mate or stalemate. */ /* --------------------------------------------------------- */ if (best == VALUE_MATED) { if (inCheck) { /* mate */ best = VALUE_MATED + ply; } else { /* stalemate */ best = variation->drawScore[position->activeColor]; } } finish: #ifdef CRV_CHECK if (crvFail == FALSE) { logDebug ("\nbestValue=%d beta=%d, rd=%d extensions=%d pvNode=%d\nbest move: ", best, beta, restDepth, (ply + restDepth) - variation->nominalDepth, pvNode); dumpMove(bestMove); dumpVariation(variation); } #endif /* CRV_CHECK */ /* Calculate the parameters controlling the move ordering. */ /* ------------------------------------------------------- */ if (bestMove != NO_MOVE && moveIsQuiet(bestMove, position)) { Move killerMove = bestMove; const int historyBonus = restDepth; const Piece movingPiece = position->piece[getFromSquare(killerMove)]; const int index = historyIndex(bestMove, position); assert(numQuietMoves >= 1); variation->historyValue[index] = (UINT16) (variation->historyValue[index] + historyBonus); #ifdef EXTRA_BONUS_BETA_CUT if (best > beta + 20) { historyValue[index] += historyBonus; } #endif if (variation->historyValue[index] > HISTORY_MAX) { shrinkHistoryValues(variation); } setMoveValue(&killerMove, movingPiece); registerKillerMove(&variation->plyInfo[ply], killerMove); if (best >= beta) { int i; for (i = 0; i < numQuietMoves; i++) { const int playedIndex = historyIndexPlayed[i]; variation->historyTotalCount[playedIndex]++; if (variation->historyTotalCount[playedIndex] >= HISTORY_MAX) { shrinkHistoryHitValues(variation); } } variation->historyHitCount[index]++; } } /* Store the value in the transposition table. */ /* ------------------------------------------- */ if (best > oldAlpha) { hashentryFlag = (best >= beta ? HASHVALUE_HIGHER : HASHVALUE_EXACT); } else { hashentryFlag = HASHVALUE_LOWER; } setDatedEntry(variation->hashtable, position->hashValue, calcHashtableValue(best, ply), (INT8) restDepth, packedMove(bestMove), hashentryFlag); return best; } static void pvToHashtable(Variation * variation, const int pvIndex) { Move move = (Move) variation->pv.move[pvIndex]; if (moveIsLegal(&variation->singlePosition, move)) { /* Store the value in the transposition table. */ /* ------------------------------------------- */ setDatedEntry(variation->hashtable, variation->singlePosition.hashValue, -VALUE_MATED, 0, (UINT16) move, HASHVALUE_LOWER); makeMove(variation, move); pvToHashtable(variation, pvIndex + 1); unmakeLastMove(variation); } } void registerNewBestMove(Variation * variation, Move * move, const int value, PrincipalVariation * pv) { if (variation->searchStatus == SEARCH_STATUS_RUNNING) { setMoveValue(move, value); variation->pv.score = value; if (variation->handleSearchEvent != 0) { appendMoveToPv(pv, &variation->pv, *move); } variation->bestBaseMove = *move; if (variation->nominalDepth > 4 && variation->numberOfCurrentBaseMove > 1) { variation->bestMoveChange = TRUE; variation->easyMove = FALSE; } if (variation->handleSearchEvent != 0) { variation->handleSearchEvent(SEARCHEVENT_NEW_PV, variation); } } } static int getExactValue(Variation * variation, PrincipalVariation * pv, Move * move, const int alpha, const int depth, const int estimation) { const int INITIAL_WINDOW_SIZE = 20; int window = INITIAL_WINDOW_SIZE, value; bool valueFound = FALSE; int tempAlpha = max(alpha, estimation - window); int tempBeta = min(-VALUE_MATED, estimation + window); int count = 0; do { value = -searchBest(variation, -tempBeta, -tempAlpha, 1, depth - 1, TRUE, FALSE, pv); if (value > alpha) { if (value >= tempBeta) { registerNewBestMove(variation, move, value, pv); } } else { return VALUE_MATED; } if (value > tempAlpha && value < tempBeta) { valueFound = TRUE; } else { if (++count == 5) { tempAlpha = VALUE_MATED; tempBeta = -VALUE_MATED; } else { window += 10; tempAlpha = max(alpha, value - window); tempBeta = min(-VALUE_MATED, value + window); } } } while (valueFound == FALSE); return value; } static void exploreBaseMoves(Variation * variation, Movelist * basemoves) { int best = VALUE_MATED, alpha = best, beta = -alpha, restDepth = variation->selDepth = variation->nominalDepth, ply = 0; const bool fullWindow = (bool) (variation->nominalDepth <= 1); PrincipalVariation pv; initializeMoveValues(basemoves); variation->failingHighOrLow = FALSE; initPlyInfo(&variation->plyInfo[ply]); for (variation->numberOfCurrentBaseMove = 1; variation->numberOfCurrentBaseMove <= basemoves->numberOfMoves; variation->numberOfCurrentBaseMove++) { int value, newDepth = restDepth; const int icm = variation->numberOfCurrentBaseMove - 1; variation->currentBaseMove = basemoves->moves[icm]; makeMoveFast(variation, basemoves->moves[icm]); if (variation->handleSearchEvent != 0) { variation->handleSearchEvent(SEARCHEVENT_NEW_BASEMOVE, variation); } if (activeKingIsSafe(&variation->singlePosition) == FALSE) { variation->plyInfo[ply].currentMoveIsCheck = TRUE; newDepth++; } else { variation->plyInfo[ply].currentMoveIsCheck = FALSE; } if (best == VALUE_MATED || fullWindow) { if (fullWindow == FALSE) { value = getExactValue(variation, &pv, &basemoves->moves[icm], alpha, newDepth, variation->previousBest); } else { value = -searchBest(variation, -beta, -alpha, ply + 1, newDepth - 1, TRUE, FALSE, &pv); } } else { value = -searchBest(variation, -alpha - 1, -alpha, ply + 1, newDepth - 1, FALSE, FALSE, &pv); if (value > alpha) { variation->failingHighOrLow = TRUE; value = -searchBest(variation, -alpha - 1, -alpha, ply + 1, newDepth - 1, TRUE, FALSE, &pv); if (value > alpha) { value = getExactValue(variation, &pv, &basemoves->moves[icm], alpha, newDepth, value); } } } unmakeLastMove(variation); if (variation->searchStatus != SEARCH_STATUS_RUNNING && variation->nominalDepth > 1) { break; } if (value > best || fullWindow) { setMoveValue(&basemoves->moves[icm], value); } if (value > best) { best = value; if (best > alpha) { if (fullWindow == FALSE) { alpha = best; } registerNewBestMove(variation, &basemoves->moves[icm], best, &pv); if (best >= beta && fullWindow == FALSE) { goto finish; } } } variation->failingHighOrLow = (bool) (best < variation->previousBest - FAIL_LOW_MARGIN); } finish: sortMoves(basemoves); if (variation->handleSearchEvent != 0) { pvToHashtable(variation, 0); } if (fullWindow && (basemoves->numberOfMoves < 2 || getMoveValue(basemoves->moves[0]) > getMoveValue(basemoves->moves[1]) + 150)) { variation->easyMove = TRUE; } } static void initializePawnHashtable(PawnHashInfo * pawnHashtable) { int i; for (i = 0; i < PAWN_HASHTABLE_SIZE; i++) { pawnHashtable[i].hashValue = 0; } } static void initializeKingsafetyHashtable(KingSafetyHashInfo * kingsafetyHashtable) { int i; for (i = 0; i < KINGSAFETY_HASHTABLE_SIZE; i++) { kingsafetyHashtable[i].hashValue = 0; } } Move search(Variation * variation, Movelist * acceptableSolutions) { Movelist movelist; int iteration; long timeTarget; int stableIterationCount = 0; int stableBestMoveCount = 0; Move bestMove = NO_MOVE; UINT64 nodeCount = 0; bool failLow = FALSE; int loScore = -VALUE_MATED, hiScore = VALUE_MATED; if (resetGlobalHashtable) { resetHashtable(variation->hashtable); initializePawnHashtable(variation->pawnHashtable); initializeKingsafetyHashtable(variation->kingsafetyHashtable); resetGlobalHashtable = FALSE; } resetHistoryValues(variation); resetHistoryHitValues(variation); variation->startTime = getTimestamp(); variation->startTimeProcess = getProcessTimestamp(); variation->timestamp = variation->startTime + 1; variation->tbHits = 0; variation->numPvUpdates = 0; variation->easyMove = FALSE; variation->terminatePondering = FALSE; variation->previousBest = VALUE_MATED; variation->bestBaseMove = NO_MOVE; getLegalMoves(variation, &movelist); initializePlyInfo(variation); #ifdef TRACE_EVAL getValue(&variation->singlePosition, VALUE_MATED, -VALUE_MATED, variation->pawnHashtable, variation->kingsafetyHashtable); #endif #ifdef USE_BOOK if (globalBook.indexFile != NULL && globalBook.moveFile != NULL && &variation->singlePosition->moveNumber <= 12) { Move bookMove = getBookmove(&globalBook, &variation->singlePosition->hashValue, &movelist); if (bookMove != NO_MOVE) { variation->bestBaseMove = bookMove; variation->searchStatus = SEARCH_STATUS_TERMINATE; variation->finishTime = getTimestamp(); if (variation->handleSearchEvent != 0) { variation->handleSearchEvent(SEARCHEVENT_SEARCH_FINISHED, variation); } variation->nodes = 0; return variation->bestBaseMove; } } #endif variation->numberOfBaseMoves = movelist.numberOfMoves; setMoveValue(&variation->bestBaseMove, VALUE_MATED); for (iteration = 1; iteration <= MAX_DEPTH; iteration++) { long calculationTime; int iterationValue, referenceValue; int scoreWidth; variation->bestMoveChange = FALSE; variation->nominalDepth = iteration; exploreBaseMoves(variation, &movelist); calculationTime = (unsigned long) (getTimestamp() - variation->startTime); if (movesAreEqual(variation->bestBaseMove, bestMove)) { stableBestMoveCount++; } else { stableBestMoveCount = 0; } bestMove = variation->bestBaseMove; iterationValue = getMoveValue(variation->bestBaseMove); loScore = (calculationTime >= variation->timeTarget / 32 ? min(loScore, iterationValue) : loScore); hiScore = (calculationTime >= variation->timeTarget / 32 ? max(hiScore, iterationValue) : hiScore); scoreWidth = max(hiScore, variation->expectedScore) - min(loScore, variation->expectedScore); failLow = (bool) (iterationValue < variation->previousBest - FAIL_LOW_MARGIN); referenceValue = max(variation->previousBest, variation->expectedScore); variation->previousBest = iterationValue; assert(calculationTime >= 0); if (acceptableSolutions != 0 && listContainsMove(acceptableSolutions, variation->bestBaseMove)) { stableIterationCount++; if (stableIterationCount == 1) { nodeCount = variation->nodes; } } else { stableIterationCount = 0; nodeCount = variation->nodes; } /* Check for a fail low. */ /* --------------------- */ if (variation->numberOfBaseMoves == 1) { timeTarget = variation->timeTarget / 16; } else if (failLow) { timeTarget = 4 * variation->timeTarget; if (calculationTime >= variation->timeTarget / 32) { variation->easyMove = FALSE; } } else if (variation->bestMoveChange || (iterationValue < referenceValue && scoreWidth >= FAIL_LOW_MARGIN)) { timeTarget = variation->timeTarget; } else if (variation->easyMove && iterationValue >= referenceValue) { timeTarget = variation->timeTarget / 4; } else { timeTarget = variation->timeTarget / 2; } if (variation->searchStatus == SEARCH_STATUS_RUNNING && variation->timeLimit != 0 && calculationTime >= timeTarget) { #ifdef DEBUG_THREAD_COORDINATION logDebug ("Time target reached (%lu/%lu ms, %lu%%)).\n", calculationTime, variation->timeTarget, (calculationTime * 100) / variation->timeTarget); #endif if (variation->ponderMode) { variation->terminatePondering = TRUE; #ifdef DEBUG_THREAD_COORDINATION logDebug("Setting ponder termination flag.\n"); #endif } else { variation->searchStatus = SEARCH_STATUS_TERMINATE; #ifdef DEBUG_THREAD_COORDINATION logDebug("Terminating search.\n"); #endif } } if (variation->searchStatus == SEARCH_STATUS_RUNNING && (getMoveValue(variation->bestBaseMove) <= VALUE_MATED + iteration || getMoveValue(variation->bestBaseMove) >= -VALUE_MATED - iteration)) { #ifdef DEBUG_THREAD_COORDINATION logDebug("Best value out of bounds (%d). Terminating search.\n", getMoveValue(variation->bestBaseMove)); #endif variation->searchStatus = SEARCH_STATUS_TERMINATE; } if (variation->searchStatus == SEARCH_STATUS_RUNNING && iteration == MAX_DEPTH) { #ifdef DEBUG_THREAD_COORDINATION logDebug("Max depth reached. Terminating search.\n"); #endif variation->searchStatus = SEARCH_STATUS_TERMINATE; } if (variation->searchStatus != SEARCH_STATUS_RUNNING) { #ifdef DEBUG_THREAD_COORDINATION logReport ("search status != SEARCH_STATUS_RUNNING -> exiting search.\n", getMoveValue(variation->bestBaseMove)); #endif break; } if (acceptableSolutions != 0 && stableIterationCount >= 1 && (getMoveValue(variation->bestBaseMove) > 20000 || (stableIterationCount >= 2 && (getMoveValue(variation->bestBaseMove) >= 25 || (getTimestamp() - variation->startTime) >= 3000)))) { #ifdef DEBUG_THREAD_COORDINATION logDebug("Solution found (value=%d). Terminating search.\n", getMoveValue(variation->bestBaseMove)); #endif variation->searchStatus = SEARCH_STATUS_TERMINATE; break; } } incrementDate(variation->hashtable); variation->finishTime = getTimestamp(); variation->finishTimeProcess = getProcessTimestamp(); if (variation->handleSearchEvent != 0) { variation->handleSearchEvent(SEARCHEVENT_SEARCH_FINISHED, variation); } variation->nodes = nodeCount; if (statCount1 != 0 || statCount2 != 0) { logDebug("statCount1=%lld statCount2=%lld (%lld%%) \n", statCount1, statCount2, (statCount2 * 100) / max(1, statCount1)); } return variation->bestBaseMove; } int initializeModuleSearch() { const Move m1 = getPackedMove(F3, H5, NO_PIECE); const Move m2 = getPackedMove(G7, G6, NO_PIECE); Move ccrv[3]; int i = 0; ccrv[0] = m1; ccrv[1] = m2; ccrv[2] = NO_MOVE; do { crv[i] = ccrv[i]; } while (ccrv[i++] != NO_MOVE); return 0; } int testModuleSearch() { return 0; }