73 friend class BoardTest;
77 static constexpr int N_COLUMNS = 7;
78 static constexpr int N_ROWS = 6;
79 static constexpr int COLUMN_BIT_OFFSET = 9;
80 enum Player { P_EMPTY = 0, P_YELLOW = 1, P_RED = 2 };
81 static constexpr size_t N_VALID_BOARD_VALUES = 3;
82 using TBitBoard = uint64_t;
83 using TMovesCounter = int;
84 using TBoardArray = std::array<std::array<int32_t, N_ROWS>, N_COLUMNS>;
85 using TBoardArrayT = std::array<std::array<int32_t, N_COLUMNS>, N_ROWS>;
87 void inline playMoveFastBB(
const TBitBoard mv) {
88 assert(mv != BB_EMPTY);
89 assert((mv & BB_ILLEGAL) == BB_EMPTY);
90 assert((m_bAllTokens & mv) == BB_EMPTY);
91 m_bActivePTokens ^= m_bAllTokens;
99 [[nodiscard]] Board
inline playMoveOnCopy(
const TBitBoard mv)
const {
101 b.playMoveFastBB(mv);
105 [[nodiscard]] Board
inline copy()
const {
110 [[nodiscard]] TBitBoard generateMoves()
const;
112 static constexpr int popCountBoard(uint64_t x) {
115 count +=
static_cast<int>(x & 1);
121 [[nodiscard]]
inline auto popCountBoard()
const {
122 return uint64_t_popcnt(m_bAllTokens);
125 [[nodiscard]]
bool isLegalMove(
int column)
const;
127 static uint64_t hash(uint64_t x) {
128 x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
129 x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
134 [[nodiscard]] uint64_t uid()
const {
137 return m_bActivePTokens + m_bAllTokens;
140 [[nodiscard]] uint64_t hash()
const {
141 return hash(hash(m_bActivePTokens) ^ (hash(m_bAllTokens) << 1));
144 static TBitBoard nextMove(TBitBoard allMoves) {
145 for (
const auto p : BB_MOVES_PRIO_LIST) {
146 if (
const TBitBoard pvMv = allMoves & p) {
151 return lsb(allMoves);
154 bool operator==(
const Board& b)
const {
155 const bool equal = (b.m_bAllTokens == m_bAllTokens &&
156 b.m_bActivePTokens == m_bActivePTokens);
159 assert((equal && (b.m_movesLeft == m_movesLeft)) || !equal);
163 bool operator!=(
const Board& b)
const {
return !(b == *
this); }
165 TBitBoard findOddThreats(TBitBoard moves);
167 bool setBoard(
const TBoardArray& board);
169 bool setBoard(
const TBoardArrayT& board);
171 bool setBoard(
const std::vector<int>& moveSequence);
173 bool setBoard(
const std::string& moveSequence);
175 [[nodiscard]] TBoardArray toArray()
const;
177 static bool isValid(
const TBoardArray& board);
179 bool playMove(
int column);
181 [[nodiscard]]
bool canWin()
const;
183 [[nodiscard]]
bool canWin(
int column)
const;
185 [[nodiscard]]
bool hasWin()
const;
187 [[nodiscard]] std::string toString()
const;
189 [[nodiscard]]
inline TMovesCounter movesLeft()
const {
return m_movesLeft; }
191 [[nodiscard]]
inline TMovesCounter countTokens()
const {
192 return N_ROWS * N_COLUMNS - m_movesLeft;
195 [[nodiscard]] Board mirror()
const;
197 [[nodiscard]]
MoveList sortMoves(TBitBoard moves)
const;
199 TBitBoard findThreats(TBitBoard moves);
201 static inline TBitBoard lsb(
const TBitBoard x) {
202 const auto mvMask = x - UINT64_C(1);
206 [[nodiscard]] TBitBoard generateNonLosingMoves()
const {
210 TBitBoard moves = generateMoves();
211 const TBitBoard threats =
212 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
213 if (
const TBitBoard directThreats = threats & moves) {
215 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
219 return moves & ~(threats >> 1);
222 [[nodiscard]] TBitBoard doubleThreat(
const TBitBoard moves)
const {
223 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
224 const TBitBoard otherThreats =
225 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
226 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
239 [[nodiscard]]
int toHuffman()
const {
242 if (m_movesLeft < 30 || m_movesLeft & 1) {
245 int huff = INT64_C(0);
247 for (
int i = 0; i < N_COLUMNS; ++i) {
248 auto all = m_bAllTokens;
249 auto active = m_bActivePTokens;
250 all >>= (i * COLUMN_BIT_OFFSET);
251 active >>= (i * COLUMN_BIT_OFFSET);
252 for (
int j = 0; j < N_ROWS && (all & 1); j++) {
254 huff |= (active & 1) ? 2 : 3;
264 static std::pair<Board, std::vector<int>> randomBoard(
265 const int nPly,
const bool forbidDirectWin =
true) {
266 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
270 auto [b, mvList] = randomBoardInternal(nPly);
272 while (mvList.size() !=
static_cast<decltype(mvList.size())
>(nPly) ||
273 (forbidDirectWin && b.canWin())) {
274 std::tie(b, mvList) = randomBoardInternal(nPly);
277 return std::make_pair(b, std::move(mvList));
280 [[nodiscard]] std::vector<Board> allPositions(
const int upToNPly,
281 bool exactlyN)
const {
283 std::map<uint64_t, Board> positions;
284 positions.insert({uid(), *
this});
285 addAfterStates(positions, *
this, upToNPly);
287 std::vector<Board> boardVector;
288 boardVector.reserve(positions.size());
290 for (
const auto& [key, board] : positions) {
291 if (!exactlyN || board.countTokens() == upToNPly)
292 boardVector.push_back(board);
308 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
309 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
310 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
311 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
312 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
313 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
314 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
317 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
320 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
321 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
322 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
323 static constexpr TBitBoard BB_MOVES_PRIO5 =
324 getMask({49, 41, 23, 13, 10, 18, 36, 46});
325 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
326 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
327 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
328 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
344 TBitBoard m_bAllTokens, m_bActivePTokens;
345 TMovesCounter m_movesLeft;
347 static TBitBoard winningPositions(TBitBoard x,
bool verticals);
349 auto static inline constexpr getColumnMask(
const int column) {
350 assert(column >= 0 && column < N_COLUMNS);
351 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
352 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
355 auto static inline constexpr getRowMask(
const int row) {
356 assert(row >= 0 && row < N_ROWS);
358 for (
int i = 0; i < N_COLUMNS; ++i) {
359 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
374 auto static constexpr mirrorBitBoard(
const TBitBoard x) {
378 TBitBoard y{UINT64_C(0)};
380 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
381 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
384 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
385 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
388 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
389 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
392 return y | (x & getColumnMask(3));
395 static constexpr uint64_t getMaskColRow(
const int column,
const int row) {
396 assert(column >= 0 && column < N_COLUMNS);
397 assert(row >= 0 && row < N_ROWS);
398 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
401 static constexpr Player opponent(Player p) {
402 return static_cast<Player
>(3 - p);
405 void inline playMoveFast(
const int column) {
406 assert(column >= 0 && column < N_COLUMNS);
407 const TBitBoard columnMask = getColumnMask(column);
408 assert(uint64_t_popcnt(columnMask) == N_ROWS);
409 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
410 playMoveFastBB(mvMask);
413 static void addAfterStates(std::map<uint64_t, Board>& boardCollection,
414 const Board& b,
const int nPly) {
415 if (b.countTokens() >= nPly) {
419 auto moves = b.generateMoves();
422 const auto mv = b.nextMove(moves);
423 assert(uint64_t_popcnt(mv) == 1);
424 if (
auto newB = b.playMoveOnCopy(mv);
425 boardCollection.find(newB.uid()) == boardCollection.end() &&
428 boardCollection.insert({newB.uid(), newB});
429 addAfterStates(boardCollection, newB, nPly);
436 static std::pair<Board, std::vector<int>> randomBoardInternal(
438 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
444 static std::random_device rd;
447 static std::mt19937 gen(rd());
450 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
452 std::vector<int> mvSequence;
453 static constexpr int MAX_TRIES = 20;
454 for (
int j = 0; j < nPly; ++j) {
455 int randColumn, tries = 0;
457 randColumn = nextUniform(gen);
459 }
while (tries < MAX_TRIES &&
460 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
461 if (tries >= MAX_TRIES) {
464 b.playMove(randColumn);
465 mvSequence.emplace_back(randColumn);
468 assert(b.countTokens() == nPly);
470 return {std::move(b), std::move(mvSequence)};
473 static TBoardArray transpose(
const TBoardArrayT& board);