16 unsigned long long int nodeCounter;
17 static bool constexpr USE_TRANSPOSITION_TABLE =
true;
18 static auto constexpr DEFAULT_LOG_TRANSPOSITION_SIZE = 22;
21 std::unique_ptr<OpeningBook> m_openingBook;
26 explicit BitBully(
const std::filesystem::path& bookPath =
"")
29 USE_TRANSPOSITION_TABLE ? DEFAULT_LOG_TRANSPOSITION_SIZE : 0} {
33 inline bool isBookLoaded()
const {
return m_openingBook !=
nullptr; }
35 inline void resetBook() { m_openingBook.reset(); }
37 inline bool loadBook(
const std::filesystem::path& bookPath =
"") {
41 if (!bookPath.empty()) {
42 m_openingBook = std::make_unique<OpeningBook>(bookPath);
43 assert(isBookLoaded());
45 return isBookLoaded();
58 const int p = (b.movesLeft() + 1) % 2;
59 const int sgnScore = score < 0 ? 1 : 0;
60 const int absScore = score < 0 ? -score : score;
61 const int mvFinalLeft = 2 * (absScore - 1) + (sgnScore ^ p);
62 return b.movesLeft() - mvFinalLeft;
65 static int rollout(
Board b)
noexcept {
74 const int score = (b.movesLeft() + 1) / 2;
75 return (ply % 2 == 0) ? score : -score;
82 auto moves = b.generateNonLosingMoves();
84 const int score = -(b.movesLeft() / 2);
85 return (ply % 2 == 0) ? score : -score;
88 const auto mv = Board::nextMove(moves);
95 b = b.playBitMaskOnCopy(mv);
101 int mtdf(
const Board& b,
const int firstGuess,
102 const int maxDepth = -1) noexcept {
108 int upperBound = INT32_MAX;
109 int lowerBound = INT32_MIN;
111 while (lowerBound < upperBound) {
112 const auto beta = std::max(g, lowerBound + 1);
113 g = negamax(b, beta - 1, beta, 0, maxDepth);
124 int nullWindow(
const Board& b,
const int maxDepth = -1) noexcept {
125 int min = -b.movesLeft() / 2;
126 int max = (b.movesLeft() + 1) / 2;
129 int mid = min + (max - min) / 2;
130 if (mid <= 0 && min / 2 < mid)
132 else if (mid >= 0 && max / 2 > mid)
134 int r = negamax(b, mid, mid + 1, 0, maxDepth);
144 void resetTranspositionTable() {
146 USE_TRANSPOSITION_TABLE ? DEFAULT_LOG_TRANSPOSITION_SIZE : 0};
149 [[nodiscard]]
auto getNodeCounter()
const {
return nodeCounter; }
151 void resetNodeCounter() { nodeCounter = 0ULL; }
153 int negamax(
Board b,
int alpha,
int beta,
const int depth,
154 const int maxDepth = -1) noexcept {
156 assert(alpha < beta);
159 if (maxDepth >= 0 && depth >= maxDepth) {
163 const int8_t remainingBudget =
164 (maxDepth < 0) ? INT8_MAX : static_cast<int8_t>(maxDepth - depth);
166 if (isBookLoaded() && b.countTokens() == m_openingBook->getNPly()) {
167 return m_openingBook->getBoardValue(b);
174 if (!depth && b.canWin()) {
175 return (b.movesLeft() + 1) / 2;
178 if (alpha >= (b.movesLeft() + 1) / 2) {
185 if (
const int min = -b.movesLeft() / 2; alpha < min) {
187 if (alpha >= beta)
return alpha;
189 if (
const int max = (b.movesLeft() - 1) / 2; beta > max) {
191 if (alpha >= beta)
return beta;
194 if (!b.movesLeft()) {
195 assert(!b.legalMovesMask());
196 assert(b.popCountBoard() == Board::N_COLUMNS * Board::N_ROWS);
200 int oldAlpha = alpha;
202 auto moves = b.generateNonLosingMoves();
204 return -b.movesLeft() / 2;
207 assert(uint64_t_popcnt(moves) <= Board::N_COLUMNS);
208 assert(uint64_t_popcnt(moves) > 0);
210 if (depth < 20 && b.doubleThreat(moves)) {
211 return (b.movesLeft() - 1) / 2;
216 if constexpr (USE_TRANSPOSITION_TABLE) {
217 if (b.movesLeft() > 6 && b.movesLeft() % 2 == 0) {
218 ttEntry = transpositionTable.get(b);
219 if (ttEntry && ttEntry->b == b.uid() &&
220 ttEntry->searchDepth >= remainingBudget) {
221 if (ttEntry->flag == TranspositionTable::Entry::EXACT) {
222 return ttEntry->value;
223 }
else if (ttEntry->flag == TranspositionTable::Entry::LOWER) {
224 alpha = std::max(alpha, ttEntry->value);
225 }
else if (ttEntry->flag == TranspositionTable::Entry::UPPER) {
226 beta = std::min(beta, ttEntry->value);
229 return ttEntry->value;
234 else if (depth < 22 && b.movesLeft() % 2) {
235 auto etcMoves = b.legalMovesMask();
237 auto mv = b.nextMove(etcMoves);
238 assert(uint64_t_popcnt(mv) == 1);
239 auto bETC = b.playBitMaskOnCopy(mv);
240 auto etcEntry = transpositionTable.get(bETC);
242 if (etcEntry->b == bETC.uid() &&
243 etcEntry->searchDepth >= remainingBudget &&
244 etcEntry->flag != TranspositionTable::Entry::LOWER &&
245 -etcEntry->value >= beta) {
246 return -etcEntry->value;
256 if (b.movesLeft() > 20) {
257 const auto bMirror = b.mirror();
258 auto ttEntryMirror = transpositionTable.get(bMirror);
259 if (ttEntryMirror && ttEntryMirror->b == bMirror.uid() &&
260 ttEntryMirror->searchDepth >= remainingBudget) {
261 if (ttEntryMirror->flag == TranspositionTable::Entry::EXACT) {
262 return ttEntryMirror->value;
263 }
else if (ttEntryMirror->flag == TranspositionTable::Entry::LOWER) {
264 alpha = std::max(alpha, ttEntryMirror->value);
265 }
else if (ttEntryMirror->flag == TranspositionTable::Entry::UPPER) {
266 beta = std::min(beta, ttEntryMirror->value);
269 return ttEntryMirror->value;
283 int value = -(1 << 10);
285 auto mvList = b.sortMoves(moves);
288 auto mv = mvList.pop();
289 for (; mv && alpha < beta; mv = mvList.pop()) {
291 assert(uint64_t_popcnt(mv) == 1);
292 auto moveValue = -negamax(b.playBitMaskOnCopy(mv), -beta, -alpha,
293 depth + 1, maxDepth);
294 value = std::max(value, moveValue);
295 alpha = std::max(alpha, value);
298 auto threats = depth < 22 ? b.findThreats(moves) : UINT64_C(0);
299 assert((threats & moves) == threats);
302 while (moves && alpha < beta) {
304 const auto mv = (threats ? b.nextMove(threats) : b.nextMove(moves));
305 assert(uint64_t_popcnt(mv) == 1);
306 auto moveValue = -negamax(b.playBitMaskOnCopy(mv), -beta, -alpha,
307 depth + 1, maxDepth);
308 value = std::max(value, moveValue);
309 alpha = std::max(alpha, value);
315 if constexpr (USE_TRANSPOSITION_TABLE) {
316 if (!ttEntry)
return value;
317 assert(ttEntry !=
nullptr);
326 ttEntry->b = b.uid();
327 ttEntry->value = value;
328 ttEntry->searchDepth = remainingBudget;
330 if (value <= oldAlpha) {
331 ttEntry->flag = TranspositionTable::Entry::UPPER;
332 }
else if (value >= beta) {
333 ttEntry->flag = TranspositionTable::Entry::LOWER;
335 ttEntry->flag = TranspositionTable::Entry::EXACT;
341 auto scoreMove(
const Board& b,
const int column,
const int firstGuess,
342 const int maxDepth = -1) {
344 if (
auto afterB = b; afterB.play(column)) {
345 if (afterB.hasWin()) {
346 return (afterB.movesLeft()) / 2 + 1;
349 score = -mtdf(afterB, firstGuess, maxDepth);
354 auto scoreMoves(
const Board& b,
const int maxDepth = -1) {
355 std::vector scores(Board::N_COLUMNS, -1000);
356 for (
auto col = 0UL; col < scores.size(); col++) {
368 scores[col] = scoreMove(b,
static_cast<int>(col),
369 !col ? 0 : scores.at(col - 1), maxDepth);