Sanmill/src/search.cpp

454 lines
12 KiB
C++

/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill 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.
Sanmill 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 <algorithm>
#include <cassert>
#include <cmath>
#include <cstring> // For std::memset
#include <iostream>
#include <sstream>
#include "evaluate.h"
#include "misc.h"
#include "movegen.h"
#include "movepick.h"
#include "position.h"
#include "search.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "endgame.h"
#include "types.h"
#include "option.h"
using std::string;
using Eval::evaluate;
using namespace Search;
Value MTDF(Position *pos, Sanmill::Stack<Position> &ss, Value firstguess, Depth depth, Depth originDepth, Move &bestMove);
Value search(Position *pos, Sanmill::Stack<Position> &ss, Depth depth, Depth originDepth, Value alpha, Value beta, Move &bestMove);
/// Search::init() is called at startup to initialize various lookup tables
void Search::init() noexcept
{
return;
}
/// Search::clear() resets search state to its initial value
void Search::clear()
{
Threads.main()->wait_for_search_finished();
#ifdef TRANSPOSITION_TABLE_ENABLE
TT.clear();
#endif
Threads.clear();
}
/// Thread::search() is the main iterative deepening loop. It calls search()
/// repeatedly with increasing depth until the allocated thinking time has been
/// consumed, the user stops the search, or the maximum search depth is reached.
int Thread::search()
{
Sanmill::Stack<Position> ss;
Value value = VALUE_ZERO;
const Depth d = adjustDepth();
adjustedDepth = d;
const time_t time0 = time(nullptr);
srand(static_cast<unsigned int>(time0));
#ifdef TIME_STAT
auto timeStart = chrono::steady_clock::now();
chrono::steady_clock::time_point timeEnd;
#endif
#ifdef CYCLE_STAT
auto cycleStart = stopwatch::rdtscp_clock::now();
chrono::steady_clock::time_point cycleEnd;
#endif
if (rootPos->get_phase() == Phase::moving) {
#ifdef THREEFOLD_REPETITION
if (rootPos->has_game_cycle()) {
return 3;
}
#endif // THREEFOLD_REPETITION
#if defined(UCI_DO_BEST_MOVE) || defined(QT_GUI_LIB)
posKeyHistory.push_back(rootPos->key());
#endif // UCI_DO_BEST_MOVE
assert(posKeyHistory.size() < 256);
}
if (rootPos->get_phase() == Phase::placing) {
posKeyHistory.clear();
}
MoveList<LEGAL>::shuffle();
#ifndef MTDF_AI
Value alpha = -VALUE_INFINITE;
Value beta = VALUE_INFINITE;
#endif
if (gameOptions.getIDSEnabled()) {
loggerDebug("IDS: ");
const Depth depthBegin = 2;
Value lastValue = VALUE_ZERO;
loggerDebug("\n==============================\n");
loggerDebug("==============================\n");
loggerDebug("==============================\n");
for (Depth i = depthBegin; i < d; i += 1) {
#ifdef TRANSPOSITION_TABLE_ENABLE
#ifdef CLEAR_TRANSPOSITION_TABLE
TranspositionTable::clear();
#endif
#endif
#ifdef MTDF_AI
value = MTDF(rootPos, ss, value, i, originDepth, bestMove);
#else
value = search(rootPos, ss, i, originDepth, alpha, beta, bestMove);
#endif
loggerDebug("%d(%d) ", value, value - lastValue);
lastValue = value;
}
#ifdef TIME_STAT
timeEnd = chrono::steady_clock::now();
loggerDebug("\nIDS Time: %llds\n", chrono::duration_cast<chrono::seconds>(timeEnd - timeStart).count());
#endif
}
#ifdef TRANSPOSITION_TABLE_ENABLE
#ifdef CLEAR_TRANSPOSITION_TABLE
TranspositionTable::clear();
#endif
#endif
#ifndef MTDF_AI
if (gameOptions.getIDSEnabled()) {
alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
}
#endif
originDepth = d;
#ifdef MTDF_AI
value = MTDF(rootPos, ss, value, d, originDepth, bestMove);
#else
value = search(rootPos, ss, d, originDepth, alpha, beta, bestMove);
#endif
#ifdef TIME_STAT
timeEnd = chrono::steady_clock::now();
loggerDebug("Total Time: %llus\n", chrono::duration_cast<chrono::seconds>(timeEnd - timeStart).count());
#endif
lastvalue = bestvalue;
bestvalue = value;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
extern ThreadPool Threads;
vector<Key> posKeyHistory;
Value search(Position *pos, Sanmill::Stack<Position> &ss, Depth depth, Depth originDepth, Value alpha, Value beta, Move &bestMove)
{
Value value = VALUE_ZERO;
Value bestValue = -VALUE_INFINITE;
Depth epsilon;
#ifdef THREEFOLD_REPETITION
// Check if we have an upcoming move which draws by repetition, or
// if the opponent had an alternative move earlier to this position.
if (/* alpha < VALUE_DRAW && */
depth != originDepth &&
pos->has_repeated(ss)) {
alpha = VALUE_DRAW;
if (alpha >= beta) {
return alpha;
}
}
#endif // THREEFOLD_REPETITION
#ifdef TT_MOVE_ENABLE
Move ttMove = MOVE_NONE;
#endif // TT_MOVE_ENABLE
#if defined (TRANSPOSITION_TABLE_ENABLE) || defined(ENDGAME_LEARNING)
const Key posKey = pos->key();
#endif
#ifdef ENDGAME_LEARNING
Endgame endgame;
if (gameOptions.isEndgameLearningEnabled() &&
Thread::probeEndgameHash(posKey, endgame)) {
switch (endgame.type) {
case EndGameType::blackWin:
bestValue = VALUE_MATE;
bestValue += depth;
break;
case EndGameType::whiteWin:
bestValue = -VALUE_MATE;
bestValue -= depth;
break;
default:
break;
}
return bestValue;
}
#endif /* ENDGAME_LEARNING */
#ifdef TRANSPOSITION_TABLE_ENABLE
Bound type = BOUND_NONE;
const Value probeVal = TranspositionTable::probe(posKey, depth, alpha, beta, type
#ifdef TT_MOVE_ENABLE
, ttMove
#endif // TT_MOVE_ENABLE
);
if (probeVal != VALUE_UNKNOWN) {
#ifdef TRANSPOSITION_TABLE_DEBUG
Threads.main()->ttHitCount++;
#endif
bestValue = probeVal;
#if 0
// TODO: Need adjust value?
if (position->turn == BLACK)
bestValue += tte.depth - depth;
else
bestValue -= tte.depth - depth;
#endif
#ifdef TT_MOVE_ENABLE
// if (ttMove != MOVE_NONE) {
// bestMove = ttMove;
// }
#endif // TT_MOVE_ENABLE
return bestValue;
}
#ifdef TRANSPOSITION_TABLE_DEBUG
else {
Threads.main()->ttMissCount++;
}
#endif
//hashMapMutex.unlock();
#endif /* TRANSPOSITION_TABLE_ENABLE */
#if 0
if (position->phase == Phase::placing && depth == 1 && pos->pieceToRemoveCount > 0) {
depth--;
}
#endif
// Check for aborted search
// TODO: and immediate draw
if (unlikely(pos->phase == Phase::gameOver) || // TODO: Deal with hash
depth <= 0 ||
Threads.stop.load(std::memory_order_relaxed)) {
bestValue = Eval::evaluate(*pos);
// For win quickly
if (bestValue > 0) {
bestValue += depth;
} else {
bestValue -= depth;
}
#ifdef TRANSPOSITION_TABLE_ENABLE
TranspositionTable::save(bestValue,
depth,
BOUND_EXACT,
posKey
#ifdef TT_MOVE_ENABLE
, MOVE_NONE
#endif // TT_MOVE_ENABLE
);
#endif
return bestValue;
}
MovePicker mp(*pos);
Move nextMove = mp.next_move();
const int moveCount = mp.move_count();
if (moveCount == 1 && depth == originDepth) {
bestMove = nextMove;
bestValue = VALUE_UNIQUE;
return bestValue;
}
#ifdef TRANSPOSITION_TABLE_ENABLE
#ifndef DISABLE_PREFETCH
for (int i = 0; i < moveCount; i++) {
TranspositionTable::prefetch(pos->key_after(mp.moves[i].move));
}
#ifdef PREFETCH_DEBUG
if (posKey << 8 >> 8 == 0x0) {
int pause = 1;
}
#endif // PREFETCH_DEBUG
#endif // !DISABLE_PREFETCH
#endif // TRANSPOSITION_TABLE_ENABLE
for (int i = 0; i < moveCount; i++) {
ss.push(*(pos));
const Color before = pos->sideToMove;
Move move = mp.moves[i].move;
pos->do_move(move);
const Color after = pos->sideToMove;
if (gameOptions.getDepthExtension() == true && moveCount == 1) {
epsilon = 1;
} else {
epsilon = 0;
}
#ifdef PVS_AI
if (i == 0) {
if (after != before) {
value = -search(depth - 1 + epsilon, -beta, -alpha);
} else {
value = search(depth - 1 + epsilon, alpha, beta);
}
} else {
if (after != before) {
value = -search(depth - 1 + epsilon, -alpha - VALUE_PVS_WINDOW, -alpha);
if (value > alpha && value < beta) {
value = -search(depth - 1 + epsilon, -beta, -alpha);
//assert(value >= alpha && value <= beta);
}
} else {
value = search(depth - 1 + epsilon, alpha, alpha + VALUE_PVS_WINDOW);
if (value > alpha && value < beta) {
value = search(depth - 1 + epsilon, alpha, beta);
//assert(value >= alpha && value <= beta);
}
}
}
#else
if (after != before) {
value = -search(pos, ss, depth - 1 + epsilon, originDepth, -beta, -alpha, bestMove);
} else {
value = search(pos, ss, depth - 1 + epsilon, originDepth, alpha, beta, bestMove);
}
#endif // PVS_AI
pos->undo_move(ss);
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
// Check for a new best move
// Finished searching the move. If a stop occurred, the return value of
// the search cannot be trusted, and we return immediately without
// updating best move, PV and TT.
if (Threads.stop.load(std::memory_order_relaxed))
return VALUE_ZERO;
if (value >= bestValue) {
bestValue = value;
if (value > alpha) {
if (depth == originDepth) {
bestMove = move;
}
break;
}
}
}
#ifdef TRANSPOSITION_TABLE_ENABLE
TranspositionTable::save(bestValue,
depth,
bestValue >= beta ? BOUND_LOWER :
BOUND_UPPER,
posKey
#ifdef TT_MOVE_ENABLE
, bestMove
#endif // TT_MOVE_ENABLE
);
#endif /* TRANSPOSITION_TABLE_ENABLE */
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
return bestValue;
}
Value MTDF(Position *pos, Sanmill::Stack<Position> &ss, Value firstguess, Depth depth, Depth originDepth, Move &bestMove)
{
Value g = firstguess;
Value lowerbound = -VALUE_INFINITE;
Value upperbound = VALUE_INFINITE;
Value beta;
while (lowerbound < upperbound) {
if (g == lowerbound) {
beta = g + VALUE_MTDF_WINDOW;
} else {
beta = g;
}
g = search(pos, ss, depth, originDepth, beta - VALUE_MTDF_WINDOW, beta, bestMove);
if (g < beta) {
upperbound = g; // fail low
} else {
lowerbound = g; // fail high
}
}
return g;
}