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);
228 static inline TBitBoard lsb(
const TBitBoard x) {
229 const auto mvMask = x - UINT64_C(1);
233 [[nodiscard]] TBitBoard generateNonLosingMoves()
const {
239 TBitBoard moves = legalMovesMask();
240 const TBitBoard threats =
241 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
242 if (
const TBitBoard directThreats = threats & moves) {
244 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
248 return moves & ~(threats >> 1);
251 [[nodiscard]] TBitBoard doubleThreat(
const TBitBoard moves)
const {
252 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
253 const TBitBoard otherThreats =
254 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
255 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
268 [[nodiscard]]
int toHuffman()
const {
271 if (m_movesLeft < 30 || m_movesLeft & 1) {
274 int huff = INT64_C(0);
276 for (
int i = 0; i < N_COLUMNS; ++i) {
277 auto all = m_bAllTokens;
278 auto active = m_bActivePTokens;
279 all >>= (i * COLUMN_BIT_OFFSET);
280 active >>= (i * COLUMN_BIT_OFFSET);
281 for (
int j = 0; j < N_ROWS && (all & 1); j++) {
283 huff |= (active & 1) ? 2 : 3;
293 static std::pair<Board, std::vector<int>> randomBoard(
294 const int nPly,
const bool forbidDirectWin =
true) {
295 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
299 auto [b, mvList] = randomBoardInternal(nPly);
301 while (mvList.size() !=
static_cast<decltype(mvList.size())
>(nPly) ||
302 (forbidDirectWin && b.canWin())) {
303 std::tie(b, mvList) = randomBoardInternal(nPly);
306 return std::make_pair(b, std::move(mvList));
309 [[nodiscard]] std::vector<Board> allPositions(
const int upToNPly,
310 bool exactlyN)
const {
312 std::map<uint64_t, Board> positions;
313 positions.insert({uid(), *
this});
314 addAfterStates(positions, *
this, upToNPly);
316 std::vector<Board> boardVector;
317 boardVector.reserve(positions.size());
319 for (
const auto& [key, board] : positions) {
320 if (!exactlyN || board.countTokens() == upToNPly)
321 boardVector.push_back(board);
337 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
338 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
339 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
340 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
341 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
342 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
343 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
346 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
349 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
350 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
351 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
352 static constexpr TBitBoard BB_MOVES_PRIO5 =
353 getMask({49, 41, 23, 13, 10, 18, 36, 46});
354 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
355 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
356 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
357 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
373 TBitBoard m_bAllTokens, m_bActivePTokens;
374 TMovesCounter m_movesLeft;
376 static TBitBoard winningPositions(TBitBoard x,
bool verticals);
378 auto static inline constexpr getColumnMask(
const int column) {
379 assert(column >= 0 && column < N_COLUMNS);
380 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
381 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
384 auto static inline constexpr getRowMask(
const int row) {
385 assert(row >= 0 && row < N_ROWS);
387 for (
int i = 0; i < N_COLUMNS; ++i) {
388 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
403 auto static constexpr mirrorBitBoard(
const TBitBoard x) {
407 TBitBoard y{UINT64_C(0)};
409 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
410 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
413 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
414 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
417 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
418 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
421 return y | (x & getColumnMask(3));
424 static constexpr uint64_t getMaskColRow(
const int column,
const int row) {
425 assert(column >= 0 && column < N_COLUMNS);
426 assert(row >= 0 && row < N_ROWS);
427 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
430 static constexpr Player opponent(Player p) {
431 return static_cast<Player
>(3 - p);
434 void inline playMoveFastBB(
const TBitBoard mv) {
435 assert(mv != BB_EMPTY);
436 assert((mv & BB_ILLEGAL) == BB_EMPTY);
437 assert((m_bAllTokens & mv) == BB_EMPTY);
438 m_bActivePTokens ^= m_bAllTokens;
446 void inline playMoveFast(
const int column) {
447 assert(column >= 0 && column < N_COLUMNS);
448 const TBitBoard columnMask = getColumnMask(column);
449 assert(uint64_t_popcnt(columnMask) == N_ROWS);
450 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
451 playMoveFastBB(mvMask);
454 static void addAfterStates(std::map<uint64_t, Board>& boardCollection,
455 const Board& b,
const int nPly) {
456 if (b.countTokens() >= nPly) {
460 auto moves = b.legalMovesMask();
463 const auto mv = b.nextMove(moves);
464 assert(uint64_t_popcnt(mv) == 1);
465 if (
auto newB = b.playBitMaskOnCopy(mv);
466 boardCollection.find(newB.uid()) == boardCollection.end() &&
469 boardCollection.insert({newB.uid(), newB});
470 addAfterStates(boardCollection, newB, nPly);
477 static std::pair<Board, std::vector<int>> randomBoardInternal(
479 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
485 static std::random_device rd;
488 static std::mt19937 gen(rd());
491 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
493 std::vector<int> mvSequence;
494 static constexpr int MAX_TRIES = 20;
495 for (
int j = 0; j < nPly; ++j) {
496 int randColumn, tries = 0;
498 randColumn = nextUniform(gen);
500 }
while (tries < MAX_TRIES &&
501 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
502 if (tries >= MAX_TRIES) {
506 mvSequence.emplace_back(randColumn);
509 assert(b.countTokens() == nPly);
511 return {std::move(b), std::move(mvSequence)};
514 static TBoardArray transpose(
const TBoardArrayT& board);
516 std::vector<int> orderedLegalMovesFromMask(TBitBoard mvBits)
const;
518 std::vector<int> legalMovesFromMask(TBitBoard mvBits)
const;