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>;
86 void inline playMoveFastBB(
const TBitBoard mv) {
87 assert(mv != BB_EMPTY);
88 assert((mv & BB_ILLEGAL) == BB_EMPTY);
89 assert((m_bAllTokens & mv) == BB_EMPTY);
90 m_bActivePTokens ^= m_bAllTokens;
98 [[nodiscard]] Board
inline playMoveOnCopy(
const TBitBoard mv)
const {
100 b.playMoveFastBB(mv);
104 [[nodiscard]] TBitBoard generateMoves()
const;
106 static constexpr int popCountBoard(uint64_t x) {
109 count +=
static_cast<int>(x & 1);
115 [[nodiscard]]
inline auto popCountBoard()
const {
116 return uint64_t_popcnt(m_bAllTokens);
119 [[nodiscard]]
bool isLegalMove(
int column)
const;
121 static uint64_t hash(uint64_t x) {
122 x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
123 x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
128 [[nodiscard]] uint64_t uid()
const {
131 return m_bActivePTokens + m_bAllTokens;
134 [[nodiscard]] uint64_t hash()
const {
135 return hash(hash(m_bActivePTokens) ^ (hash(m_bAllTokens) << 1));
138 static TBitBoard nextMove(TBitBoard allMoves) {
139 for (
const auto p : BB_MOVES_PRIO_LIST) {
140 if (
const TBitBoard pvMv = allMoves & p) {
145 return lsb(allMoves);
148 bool operator==(
const Board &b)
const {
149 const bool equal = (b.m_bAllTokens == m_bAllTokens &&
150 b.m_bActivePTokens == m_bActivePTokens);
153 assert((equal && (b.m_movesLeft == m_movesLeft)) || !equal);
157 bool operator!=(
const Board &b)
const {
return !(b == *
this); }
159 TBitBoard findOddThreats(TBitBoard moves);
161 bool setBoard(
const TBoardArray &board);
163 bool setBoard(
const std::vector<int> &moveSequence);
165 [[nodiscard]] TBoardArray toArray()
const;
167 static bool isValid(
const TBoardArray &board);
169 bool playMove(
int column);
171 [[nodiscard]]
bool canWin()
const;
173 [[nodiscard]]
bool canWin(
int column)
const;
175 [[nodiscard]]
bool hasWin()
const;
177 [[nodiscard]] std::string toString()
const;
179 [[nodiscard]]
inline TMovesCounter movesLeft()
const {
return m_movesLeft; }
181 [[nodiscard]]
inline TMovesCounter countTokens()
const {
182 return N_ROWS * N_COLUMNS - m_movesLeft;
185 [[nodiscard]] Board mirror()
const;
187 [[nodiscard]]
MoveList sortMoves(TBitBoard moves)
const;
189 TBitBoard findThreats(TBitBoard moves);
191 static inline TBitBoard lsb(
const TBitBoard x) {
192 const auto mvMask = x - UINT64_C(1);
196 [[nodiscard]] TBitBoard generateNonLosingMoves()
const {
200 TBitBoard moves = generateMoves();
201 const TBitBoard threats =
202 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
203 if (
const TBitBoard directThreats = threats & moves) {
205 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
209 return moves & ~(threats >> 1);
212 [[nodiscard]] TBitBoard doubleThreat(
const TBitBoard moves)
const {
213 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
214 const TBitBoard otherThreats =
215 winningPositions(m_bActivePTokens ^ m_bAllTokens,
true);
216 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
229 [[nodiscard]]
int toHuffman()
const {
232 if (m_movesLeft < 30 || m_movesLeft & 1) {
235 int huff = INT64_C(0);
237 for (
int i = 0; i < N_COLUMNS; ++i) {
238 auto all = m_bAllTokens;
239 auto active = m_bActivePTokens;
240 all >>= (i * COLUMN_BIT_OFFSET);
241 active >>= (i * COLUMN_BIT_OFFSET);
242 for (
int j = 0; j < N_ROWS && (all & 1); j++) {
244 huff |= (active & 1) ? 2 : 3;
254 static std::pair<Board, std::vector<int>> randomBoard(
255 const int nPly,
const bool forbidDirectWin =
true) {
256 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
260 auto [b, mvList] = randomBoardInternal(nPly);
262 while (mvList.size() !=
static_cast<decltype(mvList.size())
>(nPly) ||
263 (forbidDirectWin && b.canWin())) {
264 std::tie(b, mvList) = randomBoardInternal(nPly);
267 return std::make_pair(b, std::move(mvList));
270 [[nodiscard]] std::vector<Board> allPositions(
const int upToNPly,
271 bool exactlyN)
const {
273 std::map<uint64_t, Board> positions;
274 positions.insert({uid(), *
this});
275 addAfterStates(positions, *
this, upToNPly);
277 std::vector<Board> boardVector;
278 boardVector.reserve(positions.size());
280 for (
const auto &[key, board] : positions) {
281 if (!exactlyN || board.countTokens() == upToNPly)
282 boardVector.push_back(board);
298 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
299 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
300 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
301 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
302 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
303 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
304 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
307 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
310 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
311 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
312 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
313 static constexpr TBitBoard BB_MOVES_PRIO5 =
314 getMask({49, 41, 23, 13, 10, 18, 36, 46});
315 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
316 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
317 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
318 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
334 TBitBoard m_bAllTokens, m_bActivePTokens;
335 TMovesCounter m_movesLeft;
337 static TBitBoard winningPositions(TBitBoard x,
bool verticals);
339 auto static inline constexpr getColumnMask(
const int column) {
340 assert(column >= 0 && column < N_COLUMNS);
341 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
342 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
345 auto static inline constexpr getRowMask(
const int row) {
346 assert(row >= 0 && row < N_ROWS);
348 for (
int i = 0; i < N_COLUMNS; ++i) {
349 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
364 auto static constexpr mirrorBitBoard(
const TBitBoard x) {
368 TBitBoard y{UINT64_C(0)};
370 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
371 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
374 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
375 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
378 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
379 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
382 return y | (x & getColumnMask(3));
385 static constexpr uint64_t getMaskColRow(
const int column,
const int row) {
386 assert(column >= 0 && column < N_COLUMNS);
387 assert(row >= 0 && row < N_ROWS);
388 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
391 static constexpr Player opponent(Player p) {
392 return static_cast<Player
>(3 - p);
395 void inline playMoveFast(
const int column) {
396 assert(column >= 0 && column < N_COLUMNS);
397 const TBitBoard columnMask = getColumnMask(column);
398 assert(uint64_t_popcnt(columnMask) == N_ROWS);
399 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
400 playMoveFastBB(mvMask);
403 static void addAfterStates(std::map<uint64_t, Board> &boardCollection,
404 const Board &b,
const int nPly) {
405 if (b.countTokens() >= nPly) {
409 auto moves = b.generateMoves();
412 const auto mv = b.nextMove(moves);
413 assert(uint64_t_popcnt(mv) == 1);
414 if (
auto newB = b.playMoveOnCopy(mv);
415 boardCollection.find(newB.uid()) == boardCollection.end() &&
418 boardCollection.insert({newB.uid(), newB});
419 addAfterStates(boardCollection, newB, nPly);
426 static std::pair<Board, std::vector<int>> randomBoardInternal(
428 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
434 static std::random_device rd;
437 static std::mt19937 gen(rd());
440 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
442 std::vector<int> mvSequence;
443 static constexpr int MAX_TRIES = 20;
444 for (
int j = 0; j < nPly; ++j) {
445 int randColumn, tries = 0;
447 randColumn = nextUniform(gen);
449 }
while (tries < MAX_TRIES &&
450 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
451 if (tries >= MAX_TRIES) {
454 b.playMove(randColumn);
455 mvSequence.emplace_back(randColumn);
458 assert(b.countTokens() == nPly);
460 return {std::move(b), std::move(mvSequence)};