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;
24 MoveList sortMoves(Board::TBitBoard moves);
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();
49 int mtdf(
const Board &b,
const int firstGuess) {
55 int upperBound = INT32_MAX;
56 int lowerBound = INT32_MIN;
58 while (lowerBound < upperBound) {
59 const auto beta = std::max(g, lowerBound + 1);
60 g = negamax(b, beta - 1, beta, 0);
71 int nullWindow(
const Board &b) {
72 int min = -b.movesLeft() / 2;
73 int max = (b.movesLeft() + 1) / 2;
76 int mid = min + (max - min) / 2;
77 if (mid <= 0 && min / 2 < mid)
79 else if (mid >= 0 && max / 2 > mid)
81 int r = negamax(b, mid, mid + 1, 0);
91 void resetTranspositionTable() {
93 USE_TRANSPOSITION_TABLE ? DEFAULT_LOG_TRANSPOSITION_SIZE : 0};
96 [[nodiscard]]
auto getNodeCounter()
const {
return nodeCounter; }
98 void resetNodeCounter() { nodeCounter = 0ULL; }
100 int negamax(
Board b,
int alpha,
int beta,
int depth) {
102 assert(alpha < beta);
105 if (isBookLoaded() && b.countTokens() == m_openingBook->getNPly()) {
106 return m_openingBook->getBoardValue(b);
113 if (!depth && b.canWin()) {
114 return (b.movesLeft() + 1) / 2;
117 if (alpha >= (b.movesLeft() + 1) / 2) {
124 if (
const int min = -b.movesLeft() / 2; alpha < min) {
126 if (alpha >= beta)
return alpha;
128 if (
const int max = (b.movesLeft() - 1) / 2; beta > max) {
130 if (alpha >= beta)
return beta;
133 if (!b.movesLeft()) {
134 assert(!b.generateMoves());
135 assert(b.popCountBoard() == Board::N_COLUMNS * Board::N_ROWS);
139 int oldAlpha = alpha;
141 auto moves = b.generateNonLosingMoves();
143 return -b.movesLeft() / 2;
146 assert(uint64_t_popcnt(moves) <= Board::N_COLUMNS);
147 assert(uint64_t_popcnt(moves) > 0);
149 if (depth < 20 && b.doubleThreat(moves)) {
150 return (b.movesLeft() - 1) / 2;
155 if constexpr (USE_TRANSPOSITION_TABLE) {
156 if (b.movesLeft() > 6 && b.movesLeft() % 2 == 0) {
157 ttEntry = transpositionTable.get(b);
158 if (ttEntry && ttEntry->b == b.uid()) {
159 if (ttEntry->flag == TranspositionTable::Entry::EXACT) {
160 return ttEntry->value;
161 }
else if (ttEntry->flag == TranspositionTable::Entry::LOWER) {
162 alpha = std::max(alpha, ttEntry->value);
163 }
else if (ttEntry->flag == TranspositionTable::Entry::UPPER) {
164 beta = std::min(beta, ttEntry->value);
167 return ttEntry->value;
172 else if (depth < 22 && b.movesLeft() % 2) {
173 auto etcMoves = b.generateMoves();
175 auto mv = b.nextMove(etcMoves);
176 assert(uint64_t_popcnt(mv) == 1);
177 auto bETC = b.playMoveOnCopy(mv);
178 auto etcEntry = transpositionTable.get(bETC);
180 if (etcEntry->b == bETC.uid() &&
181 etcEntry->flag != TranspositionTable::Entry::LOWER &&
182 -etcEntry->value >= beta) {
183 return -etcEntry->value;
193 if (b.movesLeft() > 20) {
194 const auto bMirror = b.mirror();
195 auto ttEntryMirror = transpositionTable.get(bMirror);
196 if (ttEntryMirror && ttEntryMirror->b == bMirror.uid()) {
197 if (ttEntryMirror->flag == TranspositionTable::Entry::EXACT) {
198 return ttEntryMirror->value;
199 }
else if (ttEntryMirror->flag == TranspositionTable::Entry::LOWER) {
200 alpha = std::max(alpha, ttEntryMirror->value);
201 }
else if (ttEntryMirror->flag == TranspositionTable::Entry::UPPER) {
202 beta = std::min(beta, ttEntryMirror->value);
205 return ttEntryMirror->value;
219 int value = -(1 << 10);
221 auto mvList = b.sortMoves(moves);
224 auto mv = mvList.pop();
225 for (; mv && alpha < beta; mv = mvList.pop()) {
227 assert(uint64_t_popcnt(mv) == 1);
229 -negamax(b.playMoveOnCopy(mv), -beta, -alpha, depth + 1);
230 value = std::max(value, moveValue);
231 alpha = std::max(alpha, value);
234 auto threats = depth < 22 ? b.findThreats(moves) : UINT64_C(0);
235 assert((threats & moves) == threats);
238 while (moves && alpha < beta) {
240 const auto mv = (threats ? b.nextMove(threats) : b.nextMove(moves));
241 assert(uint64_t_popcnt(mv) == 1);
243 -negamax(b.playMoveOnCopy(mv), -beta, -alpha, depth + 1);
244 value = std::max(value, moveValue);
245 alpha = std::max(alpha, value);
251 if constexpr (USE_TRANSPOSITION_TABLE) {
252 if (!ttEntry)
return value;
253 assert(ttEntry !=
nullptr);
262 ttEntry->b = b.uid();
263 ttEntry->value = value;
265 if (value <= oldAlpha) {
266 ttEntry->flag = TranspositionTable::Entry::UPPER;
267 }
else if (value >= beta) {
268 ttEntry->flag = TranspositionTable::Entry::LOWER;
270 ttEntry->flag = TranspositionTable::Entry::EXACT;
276 auto scoreMoves(
const Board &b) {
277 std::vector scores(Board::N_COLUMNS, -1000);
278 for (
auto col = 0UL; col < scores.size(); col++) {
279 if (
auto afterB = b; afterB.playMove(col)) {
280 if (afterB.hasWin()) {
281 scores[col] = (afterB.movesLeft()) / 2 + 1;
285 scores[col] = -mtdf(afterB, !col ? 0 : scores.at(col - 1));