101 friend class BoardTest;
105 static constexpr int N_COLUMNS = 7;
106 static constexpr int N_ROWS = 6;
107 static constexpr int COLUMN_BIT_OFFSET = 9;
108 enum Player { P_EMPTY = 0, P_YELLOW = 1, P_RED = 2 };
109 static constexpr size_t N_VALID_BOARD_VALUES = 3;
110 using TBitBoard = uint64_t;
111 using TMovesCounter = int;
112 using TBoardArray = std::array<std::array<int32_t, N_ROWS>, N_COLUMNS>;
113 using TBoardArrayT = std::array<std::array<int32_t, N_COLUMNS>, N_ROWS>;
115 [[nodiscard]] Board
inline playBitMaskOnCopy(
const TBitBoard mv)
const {
117 b.playMoveFastBB(mv);
121 [[nodiscard]] Board
inline playMoveOnCopy(
const int mv)
const {
124 return b.play(mv) ? b : Board();
127 [[nodiscard]] Board
inline copy()
const {
132 [[nodiscard]] TBitBoard legalMovesMask()
const;
134 [[nodiscard]] std::vector<int> legalMoves(
bool nonLosing,
135 bool orderMoves)
const;
137 [[nodiscard]]
static constexpr int popCountBoard(uint64_t x) {
140 count +=
static_cast<int>(x & 1);
146 [[nodiscard]]
inline auto popCountBoard()
const {
147 return uint64_t_popcnt(m_bAllTokens);
150 [[nodiscard]]
bool isLegalMove(
int column)
const;
152 [[nodiscard]]
static uint64_t hash(uint64_t x) {
153 x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
154 x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
159 [[nodiscard]] uint64_t uid()
const {
162 return m_bActivePTokens + m_bAllTokens;
165 [[nodiscard]] uint64_t hash()
const {
166 return hash(hash(m_bActivePTokens) ^ (hash(m_bAllTokens) << 1));
169 [[nodiscard]]
static TBitBoard nextMove(TBitBoard allMoves) {
170 for (
const auto p : BB_MOVES_PRIO_LIST) {
171 if (
const TBitBoard pvMv = allMoves & p) {
176 return lsb(allMoves);
179 [[nodiscard]]
bool operator==(
const Board& b)
const {
180 const bool equal = (b.m_bAllTokens == m_bAllTokens &&
181 b.m_bActivePTokens == m_bActivePTokens);
184 assert((equal && (b.m_movesLeft == m_movesLeft)) || !equal);
188 [[nodiscard]]
bool operator!=(
const Board& b)
const {
return !(b == *
this); }
190 [[nodiscard]] TBitBoard findOddThreats(TBitBoard moves);
192 [[nodiscard]]
bool setBoard(
const TBoardArray& board);
194 [[nodiscard]]
bool setBoard(
const TBoardArrayT& board);
196 [[nodiscard]]
bool setBoard(
const std::vector<int>& moveSequence);
198 bool play(
int column);
199 [[nodiscard]]
bool play(
const std::vector<int>& moveSequence);
200 [[nodiscard]]
bool play(
const std::string& moveSequence);
202 [[nodiscard]]
bool setBoard(
const std::string& moveSequence);
204 [[nodiscard]] TBoardArray toArray()
const;
206 [[nodiscard]]
static bool isValid(
const TBoardArray& board);
208 [[nodiscard]]
bool canWin()
const;
210 [[nodiscard]]
bool canWin(
int column)
const;
212 [[nodiscard]]
bool hasWin()
const;
214 [[nodiscard]] std::string toString()
const;
216 [[nodiscard]]
inline TMovesCounter movesLeft()
const {
return m_movesLeft; }
218 [[nodiscard]]
inline TMovesCounter countTokens()
const {
219 return N_ROWS * N_COLUMNS - m_movesLeft;
222 [[nodiscard]] Board mirror()
const;
224 [[nodiscard]]
MoveList sortMoves(TBitBoard moves)
const;
226 TBitBoard findThreats(TBitBoard moves);
239 [[nodiscard]]
int getColumnHeight(
const int column)
const;
241 static inline TBitBoard lsb(
const TBitBoard x) {
242 const auto mvMask = x - UINT64_C(1);
246 [[nodiscard]] TBitBoard generateNonLosingMoves()
const {
252 TBitBoard moves = legalMovesMask();
253 const TBitBoard threats =
254 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
255 if (
const TBitBoard directThreats = threats & moves) {
257 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
261 return moves & ~(threats >> 1);
264 [[nodiscard]] TBitBoard doubleThreat(
const TBitBoard moves)
const {
265 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
266 const TBitBoard otherThreats =
267 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
268 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
281 [[nodiscard]]
int toHuffman()
const {
284 if (m_movesLeft < 30 || m_movesLeft & 1) {
287 int huff = INT64_C(0);
289 for (
int i = 0; i < N_COLUMNS; ++i) {
290 auto all = m_bAllTokens;
291 auto active = m_bActivePTokens;
292 all >>= (i * COLUMN_BIT_OFFSET);
293 active >>= (i * COLUMN_BIT_OFFSET);
294 for (
int j = 0; j < N_ROWS && (all & 1); j++) {
296 huff |= (active & 1) ? 2 : 3;
306 static std::pair<Board, std::vector<int>> randomBoard(
307 const int nPly,
const bool forbidDirectWin =
true) {
308 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
312 auto [b, mvList] = randomBoardInternal(nPly);
314 while (mvList.size() !=
static_cast<decltype(mvList.size())
>(nPly) ||
315 (forbidDirectWin && b.canWin())) {
316 std::tie(b, mvList) = randomBoardInternal(nPly);
319 return std::make_pair(b, std::move(mvList));
322 [[nodiscard]] std::vector<Board> allPositions(
const int upToNPly,
323 bool exactlyN)
const {
325 std::map<uint64_t, Board> positions;
326 positions.insert({uid(), *
this});
327 addAfterStates(positions, *
this, upToNPly);
329 std::vector<Board> boardVector;
330 boardVector.reserve(positions.size());
332 for (
const auto& [key, board] : positions) {
333 if (!exactlyN || board.countTokens() == upToNPly)
334 boardVector.push_back(board);
350 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
351 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
352 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
353 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
354 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
355 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
356 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
359 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
362 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
363 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
364 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
365 static constexpr TBitBoard BB_MOVES_PRIO5 =
366 getMask({49, 41, 23, 13, 10, 18, 36, 46});
367 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
368 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
369 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
370 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
386 TBitBoard m_bAllTokens, m_bActivePTokens;
387 TMovesCounter m_movesLeft;
389 static TBitBoard winningPositions(TBitBoard x,
bool verticals);
391 auto static inline constexpr getColumnMask(
const int column) {
392 assert(column >= 0 && column < N_COLUMNS);
393 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
394 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
397 auto static inline constexpr getRowMask(
const int row) {
398 assert(row >= 0 && row < N_ROWS);
400 for (
int i = 0; i < N_COLUMNS; ++i) {
401 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
416 auto static constexpr mirrorBitBoard(
const TBitBoard x) {
420 TBitBoard y{UINT64_C(0)};
422 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
423 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
426 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
427 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
430 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
431 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
434 return y | (x & getColumnMask(3));
437 static constexpr uint64_t getMaskColRow(
const int column,
const int row) {
438 assert(column >= 0 && column < N_COLUMNS);
439 assert(row >= 0 && row < N_ROWS);
440 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
443 static constexpr Player opponent(Player p) {
444 return static_cast<Player
>(3 - p);
447 void inline playMoveFastBB(
const TBitBoard mv) {
448 assert(mv != BB_EMPTY);
449 assert((mv & BB_ILLEGAL) == BB_EMPTY);
450 assert((m_bAllTokens & mv) == BB_EMPTY);
451 m_bActivePTokens ^= m_bAllTokens;
459 void inline playMoveFast(
const int column) {
460 assert(column >= 0 && column < N_COLUMNS);
461 const TBitBoard columnMask = getColumnMask(column);
462 assert(uint64_t_popcnt(columnMask) == N_ROWS);
463 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
464 playMoveFastBB(mvMask);
467 static void addAfterStates(std::map<uint64_t, Board>& boardCollection,
468 const Board& b,
const int nPly) {
469 if (b.countTokens() >= nPly) {
473 auto moves = b.legalMovesMask();
476 const auto mv = b.nextMove(moves);
477 assert(uint64_t_popcnt(mv) == 1);
478 if (
auto newB = b.playBitMaskOnCopy(mv);
479 boardCollection.find(newB.uid()) == boardCollection.end() &&
482 boardCollection.insert({newB.uid(), newB});
483 addAfterStates(boardCollection, newB, nPly);
490 static std::pair<Board, std::vector<int>> randomBoardInternal(
492 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
498 static std::random_device rd;
501 static std::mt19937 gen(rd());
504 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
506 std::vector<int> mvSequence;
507 static constexpr int MAX_TRIES = 20;
508 for (
int j = 0; j < nPly; ++j) {
509 int randColumn, tries = 0;
511 randColumn = nextUniform(gen);
513 }
while (tries < MAX_TRIES &&
514 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
515 if (tries >= MAX_TRIES) {
519 mvSequence.emplace_back(randColumn);
522 assert(b.countTokens() == nPly);
524 return {std::move(b), std::move(mvSequence)};
527 static TBoardArray transpose(
const TBoardArrayT& board);
529 std::vector<int> orderedLegalMovesFromMask(TBitBoard mvBits)
const;
531 std::vector<int> legalMovesFromMask(TBitBoard mvBits)
const;