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 playMoveOnCopy(
const TBitBoard mv)
const {
117 b.playMoveFastBB(mv);
121 [[nodiscard]] Board
inline copy()
const {
126 [[nodiscard]] TBitBoard legalMovesMask()
const;
128 [[nodiscard]] std::vector<int> legalMoves(
bool nonLosing,
129 bool orderMoves)
const;
131 [[nodiscard]]
static constexpr int popCountBoard(uint64_t x) {
134 count +=
static_cast<int>(x & 1);
140 [[nodiscard]]
inline auto popCountBoard()
const {
141 return uint64_t_popcnt(m_bAllTokens);
144 [[nodiscard]]
bool isLegalMove(
int column)
const;
146 [[nodiscard]]
static uint64_t hash(uint64_t x) {
147 x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
148 x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
153 [[nodiscard]] uint64_t uid()
const {
156 return m_bActivePTokens + m_bAllTokens;
159 [[nodiscard]] uint64_t hash()
const {
160 return hash(hash(m_bActivePTokens) ^ (hash(m_bAllTokens) << 1));
163 [[nodiscard]]
static TBitBoard nextMove(TBitBoard allMoves) {
164 for (
const auto p : BB_MOVES_PRIO_LIST) {
165 if (
const TBitBoard pvMv = allMoves & p) {
170 return lsb(allMoves);
173 [[nodiscard]]
bool operator==(
const Board& b)
const {
174 const bool equal = (b.m_bAllTokens == m_bAllTokens &&
175 b.m_bActivePTokens == m_bActivePTokens);
178 assert((equal && (b.m_movesLeft == m_movesLeft)) || !equal);
182 [[nodiscard]]
bool operator!=(
const Board& b)
const {
return !(b == *
this); }
184 [[nodiscard]] TBitBoard findOddThreats(TBitBoard moves);
186 [[nodiscard]]
bool setBoard(
const TBoardArray& board);
188 [[nodiscard]]
bool setBoard(
const TBoardArrayT& board);
190 [[nodiscard]]
bool setBoard(
const std::vector<int>& moveSequence);
192 bool play(
int column);
193 [[nodiscard]]
bool play(
const std::vector<int>& moveSequence);
194 [[nodiscard]]
bool play(
const std::string& moveSequence);
196 [[nodiscard]]
bool setBoard(
const std::string& moveSequence);
198 [[nodiscard]] TBoardArray toArray()
const;
200 [[nodiscard]]
static bool isValid(
const TBoardArray& board);
202 [[nodiscard]]
bool canWin()
const;
204 [[nodiscard]]
bool canWin(
int column)
const;
206 [[nodiscard]]
bool hasWin()
const;
208 [[nodiscard]] std::string toString()
const;
210 [[nodiscard]]
inline TMovesCounter movesLeft()
const {
return m_movesLeft; }
212 [[nodiscard]]
inline TMovesCounter countTokens()
const {
213 return N_ROWS * N_COLUMNS - m_movesLeft;
216 [[nodiscard]] Board mirror()
const;
218 [[nodiscard]]
MoveList sortMoves(TBitBoard moves)
const;
220 TBitBoard findThreats(TBitBoard moves);
222 static inline TBitBoard lsb(
const TBitBoard x) {
223 const auto mvMask = x - UINT64_C(1);
227 [[nodiscard]] TBitBoard generateNonLosingMoves()
const {
231 TBitBoard moves = legalMovesMask();
232 const TBitBoard threats =
233 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
234 if (
const TBitBoard directThreats = threats & moves) {
236 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
240 return moves & ~(threats >> 1);
243 [[nodiscard]] TBitBoard doubleThreat(
const TBitBoard moves)
const {
244 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
245 const TBitBoard otherThreats =
246 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
247 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
260 [[nodiscard]]
int toHuffman()
const {
263 if (m_movesLeft < 30 || m_movesLeft & 1) {
266 int huff = INT64_C(0);
268 for (
int i = 0; i < N_COLUMNS; ++i) {
269 auto all = m_bAllTokens;
270 auto active = m_bActivePTokens;
271 all >>= (i * COLUMN_BIT_OFFSET);
272 active >>= (i * COLUMN_BIT_OFFSET);
273 for (
int j = 0; j < N_ROWS && (all & 1); j++) {
275 huff |= (active & 1) ? 2 : 3;
285 static std::pair<Board, std::vector<int>> randomBoard(
286 const int nPly,
const bool forbidDirectWin =
true) {
287 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
291 auto [b, mvList] = randomBoardInternal(nPly);
293 while (mvList.size() !=
static_cast<decltype(mvList.size())
>(nPly) ||
294 (forbidDirectWin && b.canWin())) {
295 std::tie(b, mvList) = randomBoardInternal(nPly);
298 return std::make_pair(b, std::move(mvList));
301 [[nodiscard]] std::vector<Board> allPositions(
const int upToNPly,
302 bool exactlyN)
const {
304 std::map<uint64_t, Board> positions;
305 positions.insert({uid(), *
this});
306 addAfterStates(positions, *
this, upToNPly);
308 std::vector<Board> boardVector;
309 boardVector.reserve(positions.size());
311 for (
const auto& [key, board] : positions) {
312 if (!exactlyN || board.countTokens() == upToNPly)
313 boardVector.push_back(board);
329 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
330 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
331 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
332 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
333 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
334 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
335 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
338 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
341 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
342 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
343 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
344 static constexpr TBitBoard BB_MOVES_PRIO5 =
345 getMask({49, 41, 23, 13, 10, 18, 36, 46});
346 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
347 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
348 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
349 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
365 TBitBoard m_bAllTokens, m_bActivePTokens;
366 TMovesCounter m_movesLeft;
368 static TBitBoard winningPositions(TBitBoard x,
bool verticals);
370 auto static inline constexpr getColumnMask(
const int column) {
371 assert(column >= 0 && column < N_COLUMNS);
372 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
373 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
376 auto static inline constexpr getRowMask(
const int row) {
377 assert(row >= 0 && row < N_ROWS);
379 for (
int i = 0; i < N_COLUMNS; ++i) {
380 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
395 auto static constexpr mirrorBitBoard(
const TBitBoard x) {
399 TBitBoard y{UINT64_C(0)};
401 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
402 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
405 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
406 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
409 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
410 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
413 return y | (x & getColumnMask(3));
416 static constexpr uint64_t getMaskColRow(
const int column,
const int row) {
417 assert(column >= 0 && column < N_COLUMNS);
418 assert(row >= 0 && row < N_ROWS);
419 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
422 static constexpr Player opponent(Player p) {
423 return static_cast<Player
>(3 - p);
426 void inline playMoveFastBB(
const TBitBoard mv) {
427 assert(mv != BB_EMPTY);
428 assert((mv & BB_ILLEGAL) == BB_EMPTY);
429 assert((m_bAllTokens & mv) == BB_EMPTY);
430 m_bActivePTokens ^= m_bAllTokens;
438 void inline playMoveFast(
const int column) {
439 assert(column >= 0 && column < N_COLUMNS);
440 const TBitBoard columnMask = getColumnMask(column);
441 assert(uint64_t_popcnt(columnMask) == N_ROWS);
442 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
443 playMoveFastBB(mvMask);
446 static void addAfterStates(std::map<uint64_t, Board>& boardCollection,
447 const Board& b,
const int nPly) {
448 if (b.countTokens() >= nPly) {
452 auto moves = b.legalMovesMask();
455 const auto mv = b.nextMove(moves);
456 assert(uint64_t_popcnt(mv) == 1);
457 if (
auto newB = b.playMoveOnCopy(mv);
458 boardCollection.find(newB.uid()) == boardCollection.end() &&
461 boardCollection.insert({newB.uid(), newB});
462 addAfterStates(boardCollection, newB, nPly);
469 static std::pair<Board, std::vector<int>> randomBoardInternal(
471 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
477 static std::random_device rd;
480 static std::mt19937 gen(rd());
483 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
485 std::vector<int> mvSequence;
486 static constexpr int MAX_TRIES = 20;
487 for (
int j = 0; j < nPly; ++j) {
488 int randColumn, tries = 0;
490 randColumn = nextUniform(gen);
492 }
while (tries < MAX_TRIES &&
493 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
494 if (tries >= MAX_TRIES) {
498 mvSequence.emplace_back(randColumn);
501 assert(b.countTokens() == nPly);
503 return {std::move(b), std::move(mvSequence)};
506 static TBoardArray transpose(
const TBoardArrayT& board);
508 std::vector<int> orderedLegalMovesFromMask(TBitBoard mvBits)
const;
510 std::vector<int> legalMovesFromMask(TBitBoard mvBits)
const;