/*
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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#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;
}