From 60ef1acf6e7383a664ecbebc5900aa9755701b49 Mon Sep 17 00:00:00 2001 From: SXKA Date: Sun, 22 Oct 2023 13:56:14 +0800 Subject: [PATCH] Update file --- Qt-Gomoku/src/gomoku/engine.cpp | 259 +++++++++---------- Qt-Gomoku/src/gomoku/engine.h | 7 +- Qt-Gomoku/src/gomoku/movesgenerator.cpp | 6 +- Qt-Gomoku/src/windows/gamewindow.cpp | 13 +- Qt-Gomoku/src/zobrist/transpositiontable.cpp | 94 +++++-- Qt-Gomoku/src/zobrist/transpositiontable.h | 20 +- 6 files changed, 226 insertions(+), 173 deletions(-) diff --git a/Qt-Gomoku/src/gomoku/engine.cpp b/Qt-Gomoku/src/gomoku/engine.cpp index b8e0560..f9bd0ca 100644 --- a/Qt-Gomoku/src/gomoku/engine.cpp +++ b/Qt-Gomoku/src/gomoku/engine.cpp @@ -6,9 +6,9 @@ aho_corasick::trie Engine::trie = aho_corasick::trie(); QCache Engine::largeCache = QCache(16777216); QCache Engine::smallCache = QCache(65536); const QHash Engine::shapeScoreHash = { - {"001000", One}, {"000100", One}, - {"010100", Two}, {"001010", Two}, {"001100", Two}, - {"011100", Three}, {"001110", Three}, {"010110", Three}, {"011010", Three}, + {"00100", One}, + {"01010", Two}, {"001100", Two}, + {"01110", Three}, {"010110", Three}, {"011010", Three}, {"11110", Four}, {"01111", Four}, {"10111", Four}, {"11011", Four}, {"11101", Four}, {"011110", OpenFours}, {"11111", Five} @@ -24,6 +24,7 @@ Engine::Engine() , board({}) , blackScores({}) , whiteScores({}) +, checkSum(transpositionTable.hash()) , blackTotalScore(0) , whiteTotalScore(0) , cutNodeCount(0) @@ -112,34 +113,35 @@ State Engine::gameState(const QPoint &point, const Stone &stone) const QPoint Engine::bestMove(const Stone &stone) { - const auto &last = lastPoint(); + const auto &last = lastMove(); if (movesHistory.empty() || (movesHistory.size() == 1 && last != QPoint(7, 7) && checkStone(last) != stone)) { return {7, 7}; } - if (movesHistory.size() == 1 && last == QPoint(7, 7)) { - std::random_device device; - std::default_random_engine engine(device()); - std::uniform_int_distribution distribution(-1, 1); + checkSum = transpositionTable.hash(); - int x; - const auto y = distribution(engine); + const auto point = bestPoint; + const QTime &time = QTime::currentTime(); + const auto &score = pvs(stone, Min, Max, LIMIT_DEPTH, PVNode); + const auto &elapsedTime = time.msecsTo(QTime::currentTime()); - do { - x = distribution(engine); - } while (x == 0 && y == 0); + if (bestPoint == point) { + QPair pair{{-1, -1}, {-1, -1}}; - return {x + 7, y + 7}; + if (transpositionTable.probe(transpositionTable.hash(), Min, Max, 0, pair) != Zobrist::MISS) { + if (pair.first != QPoint(-1, -1)) { + bestPoint = pair.first; + } else if (pair.second != QPoint(-1, -1)) { + bestPoint = pair.second; + } + } } - const QTime time = QTime::currentTime(); - const auto score = pvs(stone, Min, Max, LIMIT_DEPTH, PVNode); - const auto elapsedTime = time.msecsTo(QTime::currentTime()); - + qInfo() << bestPoint; qInfo() << "Score: " << score; - qInfo() << "Node numbers: " << --nodeCount; + qInfo() << "Node numbers: " << nodeCount; qInfo() << "Cut node numbers: " << cutNodeCount << " (" << 100 * cutNodeCount / nodeCount << "%)"; qInfo() << "Hit node numbers: " << hitNodeCount << " (" << 100 * hitNodeCount / nodeCount << "%)"; qInfo() << "Elapsed time: " << 0.001 * elapsedTime << 's'; @@ -153,9 +155,9 @@ QPoint Engine::bestMove(const Stone &stone) return bestPoint; } -QPoint Engine::lastPoint() const +QPoint Engine::lastMove() const { - return movesHistory.empty() ? QPoint() : movesHistory.top(); + return movesHistory.empty() ? QPoint(-1, -1) : movesHistory.top(); } inline void Engine::restoreScore() @@ -193,8 +195,8 @@ void Engine::updateScore(const QPoint &point) break; } }; - const auto x = point.x(); - const auto y = point.y(); + const auto &x = point.x(); + const auto &y = point.y(); for (int i = 0; i < 15; ++i) { insertToLine(0, QPoint(i, y)); @@ -217,18 +219,10 @@ void Engine::updateScore(const QPoint &point) std::array whiteLineScores{}; for (int i = 0; i < 4; ++i) { - if (blackLines[i].size() < 15) { - blackLines[i].append(15 - blackLines[i].size(), ' '); - } - - if (whiteLines[i].size() < 15) { - whiteLines[i].append(15 - whiteLines[i].size(), ' '); - } - - if (const auto cacheScore = largeCache[blackLines[i]]) { + if (const auto &cacheScore = largeCache[blackLines[i]]) { blackLineScores[i] = *cacheScore; } else { - const auto shapes = trie.parse_text(blackLines[i]); + const auto &shapes = trie.parse_text(blackLines[i]); for (const auto &shape : shapes) { blackLineScores[i] += shapeScoreHash[shape.get_keyword()]; @@ -237,10 +231,10 @@ void Engine::updateScore(const QPoint &point) largeCache.insert(blackLines[i], new int(blackLineScores[i])); } - if (const auto cacheScore = largeCache[whiteLines[i]]) { + if (const auto &cacheScore = largeCache[whiteLines[i]]) { whiteLineScores[i] = *cacheScore; } else { - const auto shapes = trie.parse_text(whiteLines[i]); + const auto &shapes = trie.parse_text(whiteLines[i]); for (const auto &shape : shapes) { whiteLineScores[i] += shapeScoreHash[shape.get_keyword()]; @@ -279,34 +273,23 @@ void Engine::updateScore(const QPoint &point) int Engine::evaluatePoint(const QPoint &point) const { int score = 0; - int blackThreeCount = 0; - int whiteThreeCount = 0; constexpr std::array dx = {1, 0, 1, 1}; constexpr std::array dy = {0, 1, 1, -1}; for (int i = 0; i < 4; ++i) { - const auto scores = lineScores(point, dx[i], dy[i]); - - if (scores.first >= Three && ++blackThreeCount >= 2) { - return OpenFours; - } - - if (scores.second >= Three && ++whiteThreeCount >= 2) { - return OpenFours; - } - - score += scores.first + scores.second; + score += lineScores(point, dx[i], dy[i]); } return score; } -QPair Engine::lineScores(const QPoint &point, const int &dx, const int &dy) const +int Engine::lineScores(const QPoint &point, const int &dx, const int &dy) const { + bool isolated = true; std::array stones{Empty, Empty, Empty, Empty, Empty}; for (int i = -2; i <= 2; ++i) { - const auto neighborhood = QPoint(point.x() + dx * i, point.y() + dy * i); + const auto &neighborhood = QPoint(point.x() + dx * i, point.y() + dy * i); if (!isLegal(neighborhood)) { continue; @@ -315,19 +298,32 @@ QPair Engine::lineScores(const QPoint &point, const int &dx, const int const auto &stone = checkStone(neighborhood); if (stone != Empty) { + isolated = false; stones[i + 2] = stone; } } - if (!(stones[0] + stones[1] || stones[3] + stones[4])) { - return {0, 0}; + if (isolated) { + return 0; } - std::string blackLine(10, ' '); - std::string whiteLine(10, ' '); + if (!(stones[0] + stones[1] || stones[3] + stones[4]) && stones[0] != stones[1] + && stones[3] != stones[4]) { + return 0; + } + + std::string blackLine; + std::string whiteLine; + + for (int i = -4; i <= 4; ++i) { + const auto &neighborhood = QPoint(point.x() + dx * i, point.y() + dy * i); - for (int i = -5; i <= 5; ++i) { - const auto neighborhood = QPoint(point.x() + dx * i, point.y() + dy * i); + if (!i) { + blackLine.push_back('1'); + whiteLine.push_back('1'); + + continue; + } if (!isLegal(neighborhood)) { continue; @@ -335,31 +331,30 @@ QPair Engine::lineScores(const QPoint &point, const int &dx, const int switch (checkStone(neighborhood)) { case Empty: - blackLine[i + 5] = '0'; - whiteLine[i + 5] = '0'; + blackLine.push_back('0'); + whiteLine.push_back('0'); break; case Black: - blackLine[i + 5] = '1'; + blackLine.push_back('1'); + whiteLine.push_back(' '); break; case White: - whiteLine[i + 5] = '1'; + blackLine.push_back(' '); + whiteLine.push_back('1'); break; } } - blackLine[5] = '1'; - whiteLine[5] = '1'; - - int blackScore = 0; + int score = 0; if (const auto &cacheScore = smallCache[blackLine]) { - blackScore = *cacheScore; + score += *cacheScore; } else { const auto accumulateScore = new int(0); - const auto shapes = trie.parse_text(blackLine); + const auto &shapes = trie.parse_text(blackLine); for (const auto &shape : shapes) { *accumulateScore += shapeScoreHash[shape.get_keyword()]; @@ -367,16 +362,14 @@ QPair Engine::lineScores(const QPoint &point, const int &dx, const int smallCache.insert(blackLine, accumulateScore); - blackScore += *accumulateScore; + score += *accumulateScore; } - int whiteScore = 0; - if (const auto &cacheScore = smallCache[whiteLine]) { - whiteScore = *cacheScore; + score += *cacheScore; } else { const auto accumulateScore = new int(0); - const auto shapes = trie.parse_text(whiteLine); + const auto &shapes = trie.parse_text(whiteLine); for (const auto &shape : shapes) { *accumulateScore += shapeScoreHash[shape.get_keyword()]; @@ -384,13 +377,12 @@ QPair Engine::lineScores(const QPoint &point, const int &dx, const int smallCache.insert(whiteLine, accumulateScore); - whiteScore += *accumulateScore; + score += *accumulateScore; } - return {blackScore, whiteScore}; + return score; } - inline int Engine::evaluate(const Stone &stone) const { return stone == Black ? blackTotalScore : whiteTotalScore; @@ -401,35 +393,14 @@ int Engine::pvs(const Stone &stone, int alpha, const int &beta, const int &depth { ++nodeCount; - if (depth != LIMIT_DEPTH && transpositionTable.contains(transpositionTable.hash(), depth)) { - const auto &entry = transpositionTable.at(transpositionTable.hash()); - - switch (entry.type) { - case Zobrist::HashEntry::Empty: - qWarning() << "Hashing error!"; - - break; - case Zobrist::HashEntry::Exact: - ++hitNodeCount; - - return entry.score; - case Zobrist::HashEntry::LowerBound: - if (entry.score >= beta) { - ++hitNodeCount; - - return entry.score; - } - - break; - case Zobrist::HashEntry::UpperBound: - if (entry.score <= alpha) { - ++hitNodeCount; + QPair goodMovePair{{-1, -1}, {-1, -1}}; + const auto &probeValue = transpositionTable.probe(transpositionTable.hash(), alpha, beta, depth, + goodMovePair); - return entry.score; - } + if (probeValue != Zobrist::MISS) { + ++hitNodeCount; - break; - } + return probeValue; } const auto &firstScore = evaluate(stone); @@ -438,28 +409,24 @@ int Engine::pvs(const Stone &stone, int alpha, const int &beta, const int &depth if (firstScore >= Five) { ++cutNodeCount; - return Max - (LIMIT_DEPTH - depth); + return Max - (LIMIT_DEPTH - depth) - 1; } if (secondScore >= Five) { ++cutNodeCount; - return Min + (LIMIT_DEPTH - depth); + return Min + (LIMIT_DEPTH - depth) + 1; } if (depth <= 0 || generator.empty()) { - const auto score = firstScore - secondScore; - - transpositionTable.insert(transpositionTable.hash(), Zobrist::HashEntry::Exact, depth, score); - - return score; + return firstScore - secondScore; } if (nodeType != PVNode && nullOk) { R = depth >= 6 ? 3 : 2; - const auto score = -pvs(static_cast(-stone), -beta, -beta + 1, depth - R - 1, - nodeType, false); + const auto &score = -pvs(static_cast(-stone), -beta, -beta + 1, depth - R - 1, + nodeType, false); if (score >= beta) { ++cutNodeCount; @@ -469,47 +436,75 @@ int Engine::pvs(const Stone &stone, int alpha, const int &beta, const int &depth } QList> candidates; - const auto &moves = generator.generate(); + auto moves = generator.generate(); + + moves.remove(goodMovePair.first); + moves.remove(goodMovePair.second); for (const auto &move : moves) { - candidates.emplace_back(evaluatePoint(move), move); + candidates.emplaceBack(evaluatePoint(move), move); } std::sort(candidates.begin(), candidates.end(), std::greater()); + if (goodMovePair.second != QPoint(-1, -1)) { + candidates.emplaceFront(evaluatePoint(goodMovePair.second), goodMovePair.second); + } + + if (goodMovePair.first != QPoint(-1, -1)) { + candidates.emplaceFront(evaluatePoint(goodMovePair.first), goodMovePair.first); + } + if (candidates.size() > LIMIT_WIDTH) { candidates.resize(LIMIT_WIDTH); } + bool extend = false; + + if (candidates.front().first >= Five) { + extend = true; + } + move(candidates.front().second, stone); - auto bestScore = -pvs(static_cast(-stone), -beta, -alpha, depth - 1, + auto bestScore = -pvs(static_cast(-stone), -beta, -alpha, depth + extend - 1, static_cast(-nodeType)); undo(1); - if (depth == LIMIT_DEPTH) { - bestPoint = candidates.front().second; - } - if (bestScore >= beta) { transpositionTable.insert(transpositionTable.hash(), Zobrist::HashEntry::LowerBound, depth, - bestScore); + bestScore, candidates.front().second); ++cutNodeCount; return bestScore; } - candidates.pop_front(); - + QPoint pvNode{-1, -1}; auto valueType = Zobrist::HashEntry::UpperBound; + if (bestScore > alpha) { + alpha = bestScore; + pvNode = candidates.front().second; + valueType = Zobrist::HashEntry::Exact; + + if (transpositionTable.hash() == checkSum) { + bestPoint = candidates.front().second; + } + } + + candidates.pop_front(); + for (const auto& [score, candidate] : candidates) { - alpha = qMax(alpha, bestScore); + extend = false; + + if (score >= Five) { + extend = true; + } move(candidate, stone); - auto candidateScore = -pvs(static_cast(-stone), -alpha - 1, -alpha, depth - 1, + auto candidateScore = -pvs(static_cast(-stone), -alpha - 1, -alpha, depth + extend - 1, nodeType == CutNode ? AllNode : CutNode); undo(1); @@ -522,7 +517,7 @@ int Engine::pvs(const Stone &stone, int alpha, const int &beta, const int &depth move(candidate, stone); - candidateScore = -pvs(static_cast(-stone), -beta, -candidateScore, depth - 1, + candidateScore = -pvs(static_cast(-stone), -beta, -candidateScore, depth + extend - 1, nodeType); undo(1); @@ -533,27 +528,29 @@ int Engine::pvs(const Stone &stone, int alpha, const int &beta, const int &depth if (bestScore >= beta) { transpositionTable.insert(transpositionTable.hash(), Zobrist::HashEntry::LowerBound, depth, - bestScore); + bestScore, candidate); ++cutNodeCount; return bestScore; } - if (depth == LIMIT_DEPTH) { - bestPoint = candidate; - } + if (bestScore > alpha) { + alpha = bestScore; + pvNode = candidate; + valueType = Zobrist::HashEntry::Exact; - valueType = Zobrist::HashEntry::Exact; + if (transpositionTable.hash() == checkSum) { + bestPoint = candidate; + } + } } } if (nodeType == CutNode && bestScore == alpha) { - ++cutNodeCount; - return bestScore; } - transpositionTable.insert(transpositionTable.hash(), valueType, depth, bestScore); + transpositionTable.insert(transpositionTable.hash(), valueType, depth, bestScore, pvNode); return bestScore; } diff --git a/Qt-Gomoku/src/gomoku/engine.h b/Qt-Gomoku/src/gomoku/engine.h index b602fbe..50ea2b6 100644 --- a/Qt-Gomoku/src/gomoku/engine.h +++ b/Qt-Gomoku/src/gomoku/engine.h @@ -27,7 +27,7 @@ namespace Gomoku { inline auto R = 3; constexpr auto LIMIT_DEPTH = 10; -constexpr auto LIMIT_WIDTH = 12; +constexpr auto LIMIT_WIDTH = 8; enum NodeType { AllNode = -1, PVNode, CutNode @@ -64,6 +64,7 @@ class Engine std::array, 15> board; std::array blackScores; std::array whiteScores; + unsigned long long checkSum; int blackTotalScore; int whiteTotalScore; int cutNodeCount; @@ -77,13 +78,13 @@ class Engine [[nodiscard]] Stone checkStone(const QPoint &point) const; [[nodiscard]] State gameState(const QPoint &point, const Stone &stone) const; QPoint bestMove(const Stone &stone); - [[nodiscard]] QPoint lastPoint() const; + [[nodiscard]] QPoint lastMove() const; private: void restoreScore(); void updateScore(const QPoint &point); int evaluatePoint(const QPoint &point) const; - QPair lineScores(const QPoint &point, const int &dx, const int &dy) const; + int lineScores(const QPoint &point, const int &dx, const int &dy) const; int evaluate(const Stone &stone) const; int pvs(const Stone &stone, int alpha, const int &beta, const int &depth, const NodeType &nodeType, const bool &nullOk = true); diff --git a/Qt-Gomoku/src/gomoku/movesgenerator.cpp b/Qt-Gomoku/src/gomoku/movesgenerator.cpp index 17b1172..c0f9b7b 100644 --- a/Qt-Gomoku/src/gomoku/movesgenerator.cpp +++ b/Qt-Gomoku/src/gomoku/movesgenerator.cpp @@ -15,7 +15,7 @@ void MovesGenerator::move(const QPoint &point) for (int i = 0; i < 2; ++i) { for (int j = 0; j < 4; ++j) { for (int k = 1; k <= 2; ++k) { - auto neighborhood = point + QPoint(d[i] * dx[j] * k, d[i] * dy[j] * k); + const auto &neighborhood = point + QPoint(d[i] * dx[j] * k, d[i] * dy[j] * k); if (Engine::isLegal(neighborhood) && (*board)[neighborhood.x()][neighborhood.y()] == Empty) { const auto size = moves.count(); @@ -30,7 +30,7 @@ void MovesGenerator::move(const QPoint &point) } } - auto removedPoint = QPoint(); + QPoint removedPoint{-1, -1}; if (moves.remove(point)) { removedPoint = point; @@ -47,7 +47,7 @@ void MovesGenerator::undo(const QPoint &point) moves.remove(p); } - if (!hist.second.isNull()) { + if (hist.second != QPoint(-1, -1)) { moves.insert(hist.second); } diff --git a/Qt-Gomoku/src/windows/gamewindow.cpp b/Qt-Gomoku/src/windows/gamewindow.cpp index 8ab88ae..bc3e302 100644 --- a/Qt-Gomoku/src/windows/gamewindow.cpp +++ b/Qt-Gomoku/src/windows/gamewindow.cpp @@ -2,6 +2,7 @@ GameWindow::GameWindow(QWidget *parent) : QMainWindow(parent) + , last(QPoint(-1, -1)) , playerStone(Gomoku::Black) , step(0) , gameOver(false) @@ -62,7 +63,7 @@ void GameWindow::mouseReleaseEvent(QMouseEvent *event) } ui.undo->setEnabled(true); - last = engine.lastPoint(); + last = engine.lastMove(); ++step; repaint(); @@ -100,7 +101,7 @@ void GameWindow::mouseReleaseEvent(QMouseEvent *event) engine.move(engine.bestMove(stone), stone); - last = engine.lastPoint(); + last = engine.lastMove(); }); watcher.setFuture(future); @@ -199,7 +200,7 @@ void GameWindow::paintEvent(QPaintEvent *event) (move.x() + 1) * 40 + 30); } - if (!last.isNull()) { + if (last != QPoint(-1, -1)) { painter.drawLine((last.y() + 1) * 40 - 1, (last.x() + 1) * 40 + 20, (last.y() + 1) * 40 - 6, (last.x() + 1) * 40 + 20); painter.drawLine((last.y() + 1) * 40 + 1, (last.x() + 1) * 40 + 20, (last.y() + 1) * 40 + 6, @@ -219,7 +220,7 @@ void GameWindow::setGame(const Gomoku::Stone &stone, const bool &type) if (playerStone == Gomoku::White && gameType == PVC) { engine.move(engine.bestMove(static_cast(-playerStone)), Gomoku::Black); - last = engine.lastPoint(); + last = engine.lastMove(); } } @@ -231,7 +232,7 @@ void GameWindow::on_async_finished() repaint(); const auto stone = static_cast(-playerStone); - const auto gameState = engine.gameState(engine.lastPoint(), stone); + const auto gameState = engine.gameState(engine.lastMove(), stone); if (gameState == Gomoku::Draw || gameState == Gomoku::Win) { gameOver = true; @@ -292,7 +293,7 @@ void GameWindow::on_undo_released() playerStone = static_cast(-playerStone); } - last = engine.lastPoint(); + last = engine.lastMove(); update(); } diff --git a/Qt-Gomoku/src/zobrist/transpositiontable.cpp b/Qt-Gomoku/src/zobrist/transpositiontable.cpp index 0cb3d0a..7a4e436 100644 --- a/Qt-Gomoku/src/zobrist/transpositiontable.cpp +++ b/Qt-Gomoku/src/zobrist/transpositiontable.cpp @@ -2,17 +2,24 @@ using namespace Zobrist; -TranspositionTable::TranspositionTable() : TranspositionTable(1048576) +TranspositionTable::TranspositionTable() : TranspositionTable(65536) { }; TranspositionTable::TranspositionTable(const int &size) - : hashTable(QVarLengthArray(size)) + : innerTable(QVarLengthArray(size)) + , outerTable(QVarLengthArray(size)) + , mask(size - 1) { std::random_device device; std::default_random_engine engine(device()); std::uniform_int_distribution distribution; + for (int i = 0; i < size; ++i) { + innerTable[i] = HashEntry{0, HashEntry::Empty, 0, 0, {-1, -1}}; + outerTable[i] = HashEntry{0, HashEntry::Empty, 0, 0, {-1, -1}}; + } + for (int i = 0; i < 15; ++i) { for (int j = 0; j < 15; ++j) { blackRandomTable[i][j] = distribution(engine); @@ -20,45 +27,86 @@ TranspositionTable::TranspositionTable(const int &size) } } - boardHash = distribution(engine); + checkSum = distribution(engine); } void TranspositionTable::insert(const unsigned long long &hashKey, const HashEntry::Type &type, - const int &depth, - const int &score) + const int &depth, const int &score, const QPoint &point) { - auto &entry = hashTable[hashKey & (hashTable.size() - 1)]; + const auto &index = hashKey & mask; + auto &innerEntry = innerTable[index]; + auto &outerEntry = outerTable[index]; - if (entry.type == HashEntry::Empty && entry.depth <= depth) { - entry.checkSum = hashKey; - entry.type = type; - entry.depth = depth; - entry.score = score; + if (outerEntry.depth <= depth) { + innerEntry = outerEntry; + outerEntry.lock = hashKey; + outerEntry.type = type; + outerEntry.depth = depth; + outerEntry.score = score; + outerEntry.move = point; + } else { + innerEntry.lock = hashKey; + innerEntry.type = type; + innerEntry.depth = depth; + innerEntry.score = score; + innerEntry.move = point; } } -bool TranspositionTable::contains(const unsigned long long &hashKey, const int &depth) const +void TranspositionTable::transpose(const QPoint &point, const Gomoku::Stone &stone) { - auto &entry = hashTable[hashKey & (hashTable.size() - 1)]; + const auto &randomTable = stone == Gomoku::Black ? blackRandomTable : whiteRandomTable; - return entry.checkSum == hashKey && entry.depth >= depth; + checkSum ^= randomTable[point.x()][point.y()]; } unsigned long long TranspositionTable::hash() const { - return boardHash; + return checkSum; } -unsigned long long TranspositionTable::transpose(const QPoint &point, const Gomoku::Stone &stone) +int TranspositionTable::probe(const unsigned long long &hashKey, const int &alpha, const int &beta, + const int &depth, QPair &pair) const { - const auto randomTable = stone == Gomoku::Black ? blackRandomTable : whiteRandomTable; + HashEntry entry{0, HashEntry::Empty, 0, 0, {-1, -1}}; + const auto &index = hashKey & mask; + const auto &innerEntry = innerTable[index]; + const auto &outerEntry = outerTable[index]; - boardHash ^= randomTable[point.x()][point.y()]; + if (outerEntry.type != HashEntry::Empty && outerEntry.lock == hashKey) { + if (outerEntry.depth >= depth) { + entry = outerEntry; + } - return boardHash; -} + pair.first = outerEntry.move; + } -HashEntry TranspositionTable::at(const unsigned long long &hashKey) const -{ - return hashTable[hashKey & (hashTable.size() - 1)]; + if (innerEntry.type != HashEntry::Empty && innerEntry.lock == hashKey) { + if (entry.type == HashEntry::Empty && innerEntry.depth >= depth) { + entry = innerEntry; + } + + pair.second = innerEntry.move; + } + + switch (entry.type) { + case HashEntry::Empty: + break; + case HashEntry::Exact: + return entry.score; + case HashEntry::LowerBound: + if (entry.score >= beta) { + return entry.score; + } + + break; + case HashEntry::UpperBound: + if (entry.score <= alpha) { + return entry.score; + } + + break; + } + + return MISS; } diff --git a/Qt-Gomoku/src/zobrist/transpositiontable.h b/Qt-Gomoku/src/zobrist/transpositiontable.h index c94d88c..059442e 100644 --- a/Qt-Gomoku/src/zobrist/transpositiontable.h +++ b/Qt-Gomoku/src/zobrist/transpositiontable.h @@ -6,31 +6,37 @@ #include #include #include +#include namespace Zobrist { +constexpr auto MISS = INT_MAX; + struct HashEntry { - unsigned long long checkSum; + unsigned long long lock; enum Type { Empty, Exact, LowerBound, UpperBound } type; int score; int depth; + QPoint move; }; class TranspositionTable { private: + QVarLengthArray innerTable; + QVarLengthArray outerTable; std::array, 15> blackRandomTable; std::array, 15> whiteRandomTable; - QVarLengthArray hashTable; - unsigned long long boardHash; + unsigned long long checkSum; + long long mask; public: TranspositionTable(); TranspositionTable(const int &size); void insert(const unsigned long long &hashKey, const HashEntry::Type &type, const int &depth, - const int &score); - bool contains(const unsigned long long &hashKey, const int &depth) const; + const int &score, const QPoint &point); + void transpose(const QPoint &point, const Gomoku::Stone &stone); unsigned long long hash() const; - unsigned long long transpose(const QPoint &point, const Gomoku::Stone &stone); - HashEntry at(const unsigned long long &hashKey) const; + int probe(const unsigned long long &hashKey, const int &alpha, const int &beta, const int &depth, + QPair &pair) const; }; }