19 : m_bAllTokens{BB_EMPTY},
20 m_bActivePTokens{BB_EMPTY},
26 assert(getRowMask(0) == getMask({0, 9, 18, 27, 36, 45, 54}));
27 assert(getRowMask(5) == getMask({5, 14, 23, 32, 41, 50, 59}));
33 if (!b.
play(moveSequence)) {
45 if (!b.
play(moveSequence))
return false;
54 for (std::size_t c = 0; c <
N_COLUMNS; ++c) {
55 for (std::size_t r = 0; r <
N_ROWS; ++r) {
56 result[c][
N_ROWS - r - 1] = board[r][c];
63 const auto boardT = transpose(board);
71 TBitBoard allTokens{BB_EMPTY}, yellowTokens{BB_EMPTY};
73 for (
auto r = 0; r <
N_ROWS; ++r) {
74 const auto val = board[c][r];
75 const auto mask = getMaskColRow(c, r);
78 }
else if (val !=
P_RED) {
82 assert((allTokens & mask) == BB_EMPTY);
91 m_bActivePTokens = yellowTokens ^ (m_movesLeft & 1 ? allTokens : BB_EMPTY);
92 m_bAllTokens = allTokens;
99 std::array<int, N_VALID_BOARD_VALUES> valCounts = {0UL};
100 for (
auto c =
static_cast<TBoardArray::size_type
>(0); c <
N_COLUMNS; ++c) {
101 bool columnComplete =
false;
102 for (
auto r =
static_cast<TBoardArray::size_type
>(0); r <
N_ROWS; ++r) {
103 const auto val = board[c][r];
109 columnComplete =
true;
111 if (columnComplete)
return false;
118 if (
const auto diff = valCounts[
P_YELLOW] - valCounts[
P_RED];
119 diff < 0 || diff > 1) {
141 playMoveFast(column);
149 for (
const auto mv : moveSequence) {
150 if (!b.
play(mv))
return false;
157 for (
const char c : moveSequence) {
158 if (c <
'0' || c >=
'0' +
N_COLUMNS)
return false;
161 std::vector<int> moveSequenceInt;
162 moveSequenceInt.reserve(
163 moveSequence.size());
164 std::transform(moveSequence.begin(), moveSequence.end(),
165 std::back_inserter(moveSequenceInt),
166 [](
const char c) { return c -
'0'; });
167 return play(moveSequenceInt);
171 if (column < 0 || column >=
N_COLUMNS)
return false;
173 TBitBoard columnMask = getColumnMask(column);
175 return !(m_bAllTokens & columnMask & BB_TOP_ROW);
223 const bool verticals) {
225 TBitBoard wins = verticals ? (x << 1) & (x << 2) & (x << 3) : UINT64_C(0);
230 auto tmp = (x << b) & (x << 2 * b);
231 wins |= tmp & (x << 3 * b);
232 wins |= tmp & (x >> b);
235 tmp = (x >> b) & (x >> 2 * b);
236 wins |= tmp & (x << b);
237 wins |= tmp & (x >> 3 * b);
240 return wins & BB_ALL_LEGAL_TOKENS;
256 const auto y = m_bActivePTokens ^ m_bAllTokens;
257 auto x = y & (y << 2);
258 if (x & (x << 1))
return true;
289 const TBitBoard ownThreats = winningPositions(m_bActivePTokens,
false);
299 winningPositions(m_bActivePTokens ^ mv,
true) & ~(m_bAllTokens ^ mv);
304 if (ownThreats & (mv << 1)) numThreats--;
306 mvList.
insert(mv, numThreats);
314 auto threats = winningPositions(m_bActivePTokens,
true) & ~m_bAllTokens;
318 auto threatMoves = UINT64_C(0);
320 auto mv =
lsb(moves);
329 winningPositions(m_bActivePTokens ^ mv,
true) & ~(m_bAllTokens ^ mv);
335 numThreats > curNumThreats) {
345 constexpr auto ODD_ROWS = getRowMask(2) | getRowMask(4);
346 auto threats = winningPositions(m_bActivePTokens,
true) & ~m_bAllTokens;
350 auto threatMoves = UINT64_C(0);
352 const auto mv =
lsb(moves);
355 winningPositions(m_bActivePTokens ^ mv,
true) & ~(m_bAllTokens ^ mv);
357 numThreats > curNumThreats) {
408 return winningPositions(m_bActivePTokens,
true) &
409 (m_bAllTokens + BB_BOTTOM_ROW);
414 (winningPositions(m_bActivePTokens,
true) &
415 (m_bAllTokens + BB_BOTTOM_ROW) & getColumnMask(column));
419 return (m_bAllTokens + BB_BOTTOM_ROW) & BB_ALL_LEGAL_TOKENS;
424std::vector<int> Board::orderedLegalMovesFromMask(
425 const TBitBoard mvBits)
const {
428 std::vector<int> cols;
429 cols.reserve(sortedMoves.getSize());
431 while (
const auto mv = sortedMoves.pop()) {
439std::vector<int> Board::legalMovesFromMask(
const TBitBoard mvBits)
const {
442 std::transform(bits.begin(), bits.end(), bits.begin(),
443 [](
int bit) { return bit / COLUMN_BIT_OFFSET; });
449 const bool orderMoves)
const {
455 (winningPositions(m_bActivePTokens,
true) &
456 (m_bAllTokens + BB_BOTTOM_ROW))
459 return orderMoves ? orderedLegalMovesFromMask(mvBits)
460 : legalMovesFromMask(mvBits);
465 const auto activePlayer = (m_movesLeft & 1 ?
P_RED :
P_YELLOW);
466 const auto inactivePlayer = opponent(activePlayer);
468 for (
auto r = 0; r <
N_ROWS; ++r) {
469 if (
const auto mask = getMaskColRow(c, r); m_bActivePTokens & mask) {
470 board[c][r] = activePlayer;
471 }
else if (m_bAllTokens & mask) {
472 board[c][r] = inactivePlayer;
482 std::stringstream ss;
485 for (
int r =
N_ROWS - 1; r >= 0; r--) {
487 if (arr[c][r] ==
P_RED) {
503 mB.m_movesLeft = m_movesLeft;
504 mB.m_bActivePTokens = mirrorBitBoard(m_bActivePTokens);
505 mB.m_bAllTokens = mirrorBitBoard(m_bAllTokens);
513 assert(column >= 0 && column <
N_COLUMNS);
514 TBitBoard columnMask = getColumnMask(column);
516 TBitBoard occupied = m_bAllTokens & columnMask;
Bitboard-based representation of a Connect-4 position.
#define uint64_t_popcnt
Portable fallback popcount when no compiler intrinsic is available.
std::vector< int > bits_set(uint64_t x)
Decompose a bitboard into the indices of its set bits.
int ctz_u64(uint64_t x)
Count trailing zero bits of a 64-bit integer.
int getColumnHeight(const int column) const
Number of stones currently stacked in column.
MoveList sortMoves(TBitBoard moves) const
Sort candidate moves into a MoveList using a threat-based heuristic.
Board()
Construct an empty board with player Yellow to move.
static constexpr int N_ROWS
Number of rows in a Connect-4 grid.
uint64_t TBitBoard
64-bit type used for any bitboard value.
TBitBoard legalMovesMask() const
Bitboard whose set bits mark the next reachable cell of every non-full column.
bool isLegalMove(int column) const
Check whether dropping a stone in column is legal.
TBitBoard findOddThreats(TBitBoard moves)
Identify candidate moves that introduce a new "odd" threat.
bool canWin() const
Does the player to move have an immediately winning move?
bool play(int column)
Drop a stone of the player to move into column.
@ P_RED
Red stone (player who moves second).
@ P_YELLOW
Yellow stone (player who moves first).
static constexpr int N_COLUMNS
Number of columns in a Connect-4 grid.
static TBitBoard nextMove(TBitBoard allMoves)
Pick the best-ranked move from a candidate set using a fixed priority order.
bool hasWin() const
Did the player who just moved form a four-in-a-row?
static bool isValid(const TBoardArray &board)
Validate a board layout.
bool setBoard(const TBoardArray &board)
Replace the current position with the one described by board.
TBitBoard generateNonLosingMoves() const
Compute moves that do not lose immediately.
Board mirror() const
Mirror the board around its central column.
std::string toString() const
Pretty-printed ASCII representation of the board.
TBitBoard findThreats(TBitBoard moves)
Identify candidate moves that yield a tactical advantage.
std::vector< int > legalMoves(bool nonLosing, bool orderMoves) const
Enumerate legal moves as column indices.
static TBitBoard lsb(const TBitBoard x)
Isolate the least significant set bit of a bitboard.
std::array< std::array< int32_t, N_ROWS >, N_COLUMNS > TBoardArray
Column-major 2D representation of the board (board[col][row]).
std::array< std::array< int32_t, N_COLUMNS >, N_ROWS > TBoardArrayT
Row-major 2D representation of the board (board[row][col]).
static constexpr int COLUMN_BIT_OFFSET
Bit-offset between two columns inside the 64-bit board representation.
TBoardArray toArray() const
Convert the bitboard representation into a column-major array.
Fixed-capacity priority queue tailored for Connect-4 move ordering.
void insert(const TBitBoard move, const int score)
Insert a (move, score) pair while keeping the queue sorted.
Top-level namespace for the BitBully Connect-4 engine.
constexpr int CHAR_BIT
Number of bits per byte (defined locally if not provided by the platform).