BitBully 0.0.78
A fast, perfect-play Connect-4 engine in modern C++
Loading...
Searching...
No Matches
Board.h
Go to the documentation of this file.
1
14#ifndef XBITBULLY__BOARD_H_
15#define XBITBULLY__BOARD_H_
16
17#include <array>
18#include <cassert>
19#include <cstddef>
20#include <cstdint>
21#include <iostream>
22#include <map>
23#include <random>
24#include <sstream>
25#include <vector>
26
27#include "MoveList.h"
28
29// TODO: Move function definitions to .cpp file!
30/*
31 * // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
32 * A generalization of the best bit counting method to integers of bit-widths
33upto 128 (parameterized by type T) is this:
34
35v = v - ((v >> 1) & (T)~(T)0/3); // temp
36v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3); // temp
37v = (v + (v >> 4)) & (T)~(T)0/255*15; // temp
38c = (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * CHAR_BIT; // count
39*/
40#if __GNUC__
42#define uint64_t_popcnt __builtin_popcountll
43#else
44#if _MSC_VER
45#include <intrin.h>
47#define uint64_t_popcnt __popcnt64
48#else
50#define uint64_t_popcnt popCountBoard
51#endif
52#endif
53
66inline int ctz_u64(uint64_t x) {
67#if defined(_MSC_VER)
68 unsigned long index;
69 _BitScanForward64(&index, x);
70 return static_cast<int>(index);
71#elif defined(__GNUC__) || defined(__clang__)
72 return __builtin_ctzll(x);
73#else
74 int idx = 0;
75 while ((x & 1u) == 0u) {
76 x >>= 1u;
77 ++idx;
78 }
79 return idx;
80#endif
81}
82
93inline std::vector<int> bits_set(uint64_t x) {
94 std::vector<int> result;
95 result.reserve(uint64_t_popcnt(x));
96 while (x) {
97 int bit = ctz_u64(x);
98 result.push_back(bit);
99 x &= x - UINT64_C(1);
100 }
101 return result;
102}
103
106namespace BitBully {
107
108#ifndef CHAR_BIT
110constexpr int CHAR_BIT = 8;
111#endif
112
124static constexpr uint64_t getMask(const std::initializer_list<int> bits) {
125 uint64_t bb{UINT64_C(0)};
126 for (const auto i : bits) {
127 // return 0, if index is out of range (0-63)
128 if (i < 0 || i >= 64) {
129 return UINT64_C(0);
130 }
131 bb |= (UINT64_C(1) << i);
132 }
133 return bb;
134}
135
148static constexpr bool isIllegalBit(const int bitIdx) {
149 constexpr int COLUMN_BIT_OFFSET = 9; // TODO: redundant in class below. Fix??
150 constexpr int N_ROWS = 6; // TODO: redundant in class below. Fix??
151 constexpr int COLUMNS = 7; // TODO: redundant in class below. Fix??
152 return bitIdx >= COLUMN_BIT_OFFSET * COLUMNS ||
153 (bitIdx % COLUMN_BIT_OFFSET) / N_ROWS;
154}
155
165static constexpr uint64_t illegalBitMask() {
166 uint64_t bb{UINT64_C(0)};
167 for (size_t i = 0; i < CHAR_BIT * sizeof(uint64_t); ++i) {
168 bb ^= (isIllegalBit(i) ? UINT64_C(1) << i : UINT64_C(0));
169 }
170 return bb;
171}
172
204class Board {
205 friend class BoardTest;
206
207 public:
209 Board();
211 static constexpr int N_COLUMNS = 7;
213 static constexpr int N_ROWS = 6;
219 static constexpr int COLUMN_BIT_OFFSET = 9;
220
222 enum Player {
225 P_RED = 2
226 };
227
228 static constexpr size_t N_VALID_BOARD_VALUES = 3;
230 using TBitBoard = uint64_t;
232 using TMovesCounter = int;
234 using TBoardArray = std::array<std::array<int32_t, N_ROWS>, N_COLUMNS>;
236 using TBoardArrayT = std::array<std::array<int32_t, N_COLUMNS>, N_ROWS>;
237
245 [[nodiscard]] Board inline playBitMaskOnCopy(const TBitBoard mv) const {
246 Board b = *this;
247 b.playMoveFastBB(mv);
248 return b;
249 }
250
258 [[nodiscard]] Board inline playMoveOnCopy(const int mv) const {
259 // Returns an empty board in case the move is illegal.
260 Board b = *this;
261 return b.play(mv) ? b : Board();
262 }
263
265 [[nodiscard]] Board inline copy() const {
266 Board b = *this;
267 return b;
268 }
269
277 [[nodiscard]] TBitBoard legalMovesMask() const;
278
289 [[nodiscard]] std::vector<int> legalMoves(bool nonLosing,
290 bool orderMoves) const;
291
298 [[nodiscard]] static constexpr int popCountBoard(uint64_t x) {
299 int count = 0;
300 while (x) {
301 count += static_cast<int>(x & 1);
302 x >>= 1;
303 }
304 return count;
305 }
306
313 [[nodiscard]] inline auto popCountBoard() const {
314 return uint64_t_popcnt(m_bAllTokens);
315 }
316
323 [[nodiscard]] bool isLegalMove(int column) const;
324
334 [[nodiscard]] static uint64_t hash(uint64_t x) {
335 x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
336 x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
337 x = x ^ (x >> 31);
338 return x;
339 }
340
350 [[nodiscard]] uint64_t uid() const { return m_bActivePTokens + m_bAllTokens; }
351
358 [[nodiscard]] uint64_t hash() const {
359 return hash(hash(m_bActivePTokens) ^ (hash(m_bAllTokens) << 1));
360 }
361
376 [[nodiscard]] static TBitBoard nextMove(TBitBoard allMoves) {
377 for (const auto p : BB_MOVES_PRIO_LIST) {
378 if (const TBitBoard pvMv = allMoves & p) {
379 allMoves = pvMv;
380 break;
381 }
382 }
383 return lsb(allMoves);
384 }
385
387 [[nodiscard]] bool operator==(const Board& b) const {
388 const bool equal = (b.m_bAllTokens == m_bAllTokens &&
389 b.m_bActivePTokens == m_bActivePTokens);
390
391 // Assert that if board is equal that also movesLeft are equal
392 assert((equal && (b.m_movesLeft == m_movesLeft)) || !equal);
393 return equal;
394 }
395
397 [[nodiscard]] bool operator!=(const Board& b) const { return !(b == *this); }
398
409 [[nodiscard]] TBitBoard findOddThreats(TBitBoard moves);
410
419 [[nodiscard]] bool setBoard(const TBoardArray& board);
420
422 [[nodiscard]] bool setBoard(const TBoardArrayT& board);
423
431 [[nodiscard]] bool setBoard(const std::vector<int>& moveSequence);
432
438 bool play(int column);
445 [[nodiscard]] bool play(const std::vector<int>& moveSequence);
455 [[nodiscard]] bool play(const std::string& moveSequence);
456
458 [[nodiscard]] bool setBoard(const std::string& moveSequence);
459
464 [[nodiscard]] TBoardArray toArray() const;
465
475 [[nodiscard]] static bool isValid(const TBoardArray& board);
476
478 [[nodiscard]] bool canWin() const;
479
485 [[nodiscard]] bool canWin(int column) const;
486
492 [[nodiscard]] bool hasWin() const;
493
500 [[nodiscard]] std::string toString() const;
501
503 [[nodiscard]] inline TMovesCounter movesLeft() const { return m_movesLeft; }
504
506 [[nodiscard]] inline TMovesCounter countTokens() const {
507 return N_ROWS * N_COLUMNS - m_movesLeft;
508 }
509
517 [[nodiscard]] Board mirror() const;
518
530 [[nodiscard]] MoveList sortMoves(TBitBoard moves) const;
531
545
546 /*
547 * [ *, *, *, *, *, *, *]
548 * [ *, *, *, *, *, *, *]
549 * [ *, *, *, *, *, *, *]
550 * [ 5, 14, 23, 32, 41, 50, 59],
551 * [ 4, 13, 22, 31, 40, 49, 58],
552 * [ 3, 12, 21, 30, 39, 48, 57],
553 * [ 2, 11, 20, 29, 38, 47, 56],
554 * [ 1, 10, 19, 28, 37, 46, 55],
555 * [ 0, 9, 18, 27, 36, 45, 54]
556 */
562 [[nodiscard]] int getColumnHeight(const int column) const;
563
570 static inline TBitBoard lsb(const TBitBoard x) {
571 const auto mvMask = x - UINT64_C(1);
572 return ~mvMask & x;
573 }
574
591 [[nodiscard]] TBitBoard generateNonLosingMoves() const {
592 // Mostly inspired by Pascal's Code
593 // This function might return an empty bitboard. In this case, the active
594 // player will lose, since all possible moves will lead to a defeat.
595 // NOTE: This function will not return immediate winning moves in those
596 // cases where the opposing player has a double threat (or threat)
597 TBitBoard moves = legalMovesMask();
598 const TBitBoard threats =
599 winningPositions(m_bActivePTokens ^ m_bAllTokens, true);
600 if (const TBitBoard directThreats = threats & moves) {
601 // no way we can neutralize more than one direct threat...
602 moves = directThreats & (directThreats - 1) ? UINT64_C(0) : directThreats;
603 }
604
605 // No token under an opponent's threat.
606 return moves & ~(threats >> 1);
607 }
608
619 [[nodiscard]] TBitBoard doubleThreat(const TBitBoard moves) const {
620 const TBitBoard ownThreats = winningPositions(m_bActivePTokens, false);
621 const TBitBoard otherThreats =
622 winningPositions(m_bActivePTokens ^ m_bAllTokens, true);
623 return moves & (ownThreats >> 1) & (ownThreats >> 2) & ~(otherThreats >> 1);
624 }
625
626 /* [ *, *, *, *, *, *, *]
627 * [ *, *, *, *, *, *, *]
628 * [ *, *, *, *, *, *, *]
629 * [ 5, 14, 23, 32, 41, 50, 59],
630 * [ 4, 13, 22, 31, 40, 49, 58],
631 * [ 3, 12, 21, 30, 39, 48, 57],
632 * [ 2, 11, 20, 29, 38, 47, 56],
633 * [ 1, 10, 19, 28, 37, 46, 55],
634 * [ 0, 9, 18, 27, 36, 45, 54]
635 */
650 [[nodiscard]] int toHuffman() const {
651 // This function is only defined for positions with an even number of tokens
652 // and for positions with less or equal than 12 tokens.
653 if (m_movesLeft < 30 || m_movesLeft & 1) {
654 return 0;
655 }
656 int huff = INT64_C(0);
657
658 for (int i = 0; i < N_COLUMNS; ++i) {
659 auto all = m_bAllTokens;
660 auto active = m_bActivePTokens;
661 all >>= (i * COLUMN_BIT_OFFSET);
662 active >>= (i * COLUMN_BIT_OFFSET);
663 for (int j = 0; j < N_ROWS && (all & 1); j++) {
664 huff <<= 2; // we will insert 2 bits for yellow or red
665 huff |= (active & 1) ? 2 : 3; // yellow-> 10b, red -> 11b
666 all >>= 1;
667 active >>= 1;
668 }
669 huff <<= 1; // insert 0 to indicate the end of the column
670 }
671 // length until here (for 12-ply position): 12*2+7 = 31
672 return huff << 1; // add one 0-bit to fill up to a full byte
673 }
674
689 static std::pair<Board, std::vector<int>> randomBoard(
690 const int nPly, const bool forbidDirectWin = true) {
691 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
692 return {};
693 }
694
695 auto [b, mvList] = randomBoardInternal(nPly);
696
697 while (mvList.size() != static_cast<decltype(mvList.size())>(nPly) ||
698 (forbidDirectWin && b.canWin())) {
699 std::tie(b, mvList) = randomBoardInternal(nPly);
700 }
701
702 return std::make_pair(b, std::move(mvList));
703 }
704
719 [[nodiscard]] std::vector<Board> allPositions(const int upToNPly,
720 bool exactlyN) const {
721 // https://oeis.org/A212693
722 std::map<uint64_t, Board> positions;
723 positions.insert({uid(), *this}); // add empty board
724 addAfterStates(positions, *this, upToNPly);
725
726 std::vector<Board> boardVector;
727 boardVector.reserve(positions.size()); // Optimize memory allocation
728
729 for (const auto& [key, board] : positions) {
730 if (!exactlyN || board.countTokens() == upToNPly)
731 boardVector.push_back(board); // Copy each board into the vector
732 }
733 return boardVector;
734 }
735
747
749 [[nodiscard]] inline RawState rawState() const noexcept {
750 return RawState{m_bAllTokens, m_bActivePTokens, m_movesLeft};
751 }
752
760 inline void setRawState(const RawState& s) noexcept {
761 m_bAllTokens = s.all_tokens;
762 m_bActivePTokens = s.active_tokens;
763 m_movesLeft = s.moves_left;
764 }
765
766 private:
767 /* [ *, *, *, *, *, *, *]
768 * [ *, *, *, *, *, *, *]
769 * [ *, *, *, *, *, *, *]
770 * [ 5, 14, 23, 32, 41, 50, 59],
771 * [ 4, 13, 22, 31, 40, 49, 58],
772 * [ 3, 12, 21, 30, 39, 48, 57],
773 * [ 2, 11, 20, 29, 38, 47, 56],
774 * [ 1, 10, 19, 28, 37, 46, 55],
775 * [ 0, 9, 18, 27, 36, 45, 54]
776 */
777 static constexpr auto BOTTOM_ROW_BITS = {54, 45, 36, 27, 18, 9, 0};
778 static constexpr TBitBoard BB_BOTTOM_ROW = getMask(BOTTOM_ROW_BITS);
779 static constexpr auto TOP_ROW_BITS = {59, 50, 41, 32, 23, 14, 5};
780 static constexpr TBitBoard BB_TOP_ROW = getMask(TOP_ROW_BITS);
781 static constexpr TBitBoard BB_ILLEGAL = illegalBitMask();
782 static constexpr TBitBoard BB_ALL_LEGAL_TOKENS = ~BB_ILLEGAL;
783 static constexpr TBitBoard BB_EMPTY{UINT64_C(0)};
784
785 // These two center fields generally are the most promising ones:
786 static constexpr TBitBoard BB_MOVES_PRIO1 = getMask({29, 30});
787
788 // After {29, 30}, we should consider these moves, and so on:
789 static constexpr TBitBoard BB_MOVES_PRIO2 = getMask({31, 21, 20, 28, 38, 39});
790 static constexpr TBitBoard BB_MOVES_PRIO3 = getMask({40, 32, 22, 19, 27, 37});
791 static constexpr TBitBoard BB_MOVES_PRIO4 = getMask({47, 48, 11, 12});
792 static constexpr TBitBoard BB_MOVES_PRIO5 =
793 getMask({49, 41, 23, 13, 10, 18, 36, 46});
794 static constexpr TBitBoard BB_MOVES_PRIO6 = getMask({45, 50, 14, 9});
795 static constexpr auto BB_MOVES_PRIO_LIST = {BB_MOVES_PRIO1, BB_MOVES_PRIO2,
796 BB_MOVES_PRIO3, BB_MOVES_PRIO4,
797 BB_MOVES_PRIO5, BB_MOVES_PRIO6};
798
799 /* Having a bitboard that contains all stones and another one representing the
800 * current active player has the advantage that we do not have to do any
801 * branching to figure out which player's turn it is. After each move we
802 * simply apply an XOR-operation to switch players. */
803 /* [ *, *, *, *, *, *, *]
804 * [ *, *, *, *, *, *, *]
805 * [ *, *, *, *, *, *, *]
806 * [ 5, 14, 23, 32, 41, 50, 59],
807 * [ 4, 13, 22, 31, 40, 49, 58],
808 * [ 3, 12, 21, 30, 39, 48, 57],
809 * [ 2, 11, 20, 29, 38, 47, 56],
810 * [ 1, 10, 19, 28, 37, 46, 55],
811 * [ 0, 9, 18, 27, 36, 45, 54]
812 */
813 TBitBoard m_bAllTokens;
814 TBitBoard m_bActivePTokens;
815 TMovesCounter m_movesLeft;
816
827 static TBitBoard winningPositions(TBitBoard x, bool verticals);
828
829 auto static inline constexpr getColumnMask(const int column) {
830 assert(column >= 0 && column < N_COLUMNS);
831 return (UINT64_C(1) << (column * COLUMN_BIT_OFFSET + N_ROWS)) -
832 (UINT64_C(1) << (column * COLUMN_BIT_OFFSET));
833 }
834
835 auto static inline constexpr getRowMask(const int row) {
836 assert(row >= 0 && row < N_ROWS);
837 TBitBoard mask{0};
838 for (int i = 0; i < N_COLUMNS; ++i) {
839 mask |= (UINT64_C(1) << (i * COLUMN_BIT_OFFSET + row));
840 }
841 return mask;
842 }
843
844 /* [ *, *, *, *, *, *, *]
845 * [ *, *, *, *, *, *, *]
846 * [ *, *, *, *, *, *, *]
847 * [ 5, 14, 23, 32, 41, 50, 59],
848 * [ 4, 13, 22, 31, 40, 49, 58],
849 * [ 3, 12, 21, 30, 39, 48, 57],
850 * [ 2, 11, 20, 29, 38, 47, 56],
851 * [ 1, 10, 19, 28, 37, 46, 55],
852 * [ 0, 9, 18, 27, 36, 45, 54]
853 */
854 auto static constexpr mirrorBitBoard(const TBitBoard x) {
855 // TODO: It should be possible to do it in x only (using XORS). But,
856 // premature optimization is the root of all evil. Try this later.
857 // TODO: Any difference using XOR instead of OR? (probably not)...
858 TBitBoard y{UINT64_C(0)};
859 // move left-most column to right-most and vice versa:
860 y |= ((x & getColumnMask(6)) >> 6 * COLUMN_BIT_OFFSET);
861 y |= ((x & getColumnMask(0)) << 6 * COLUMN_BIT_OFFSET);
862
863 // Same with columns 1 & 5...
864 y |= ((x & getColumnMask(5)) >> 4 * COLUMN_BIT_OFFSET);
865 y |= ((x & getColumnMask(1)) << 4 * COLUMN_BIT_OFFSET);
866
867 // Same with columns 2 & 4
868 y |= ((x & getColumnMask(4)) >> 2 * COLUMN_BIT_OFFSET);
869 y |= ((x & getColumnMask(2)) << 2 * COLUMN_BIT_OFFSET);
870
871 // column 3 stays where it is...
872 return y | (x & getColumnMask(3));
873 }
874
875 static constexpr uint64_t getMaskColRow(const int column, const int row) {
876 assert(column >= 0 && column < N_COLUMNS);
877 assert(row >= 0 && row < N_ROWS);
878 return UINT64_C(1) << (column * COLUMN_BIT_OFFSET + row);
879 }
880
881 static constexpr Player opponent(Player p) {
882 return static_cast<Player>(3 - p);
883 }
884
885 void inline playMoveFastBB(const TBitBoard mv) {
886 assert(mv != BB_EMPTY);
887 assert((mv & BB_ILLEGAL) == BB_EMPTY);
888 assert((m_bAllTokens & mv) == BB_EMPTY);
889 m_bActivePTokens ^= m_bAllTokens; // Already, switch player
890
891 // However, move is performed for current player (assuming, above switch is
892 // not yet performed)
893 m_bAllTokens ^= mv; // bitwise xor and bitwise or are equivalent here
894 m_movesLeft--;
895 }
896
897 void inline playMoveFast(const int column) {
898 assert(column >= 0 && column < N_COLUMNS);
899 const TBitBoard columnMask = getColumnMask(column);
900 assert(uint64_t_popcnt(columnMask) == N_ROWS);
901 const auto mvMask = (m_bAllTokens + BB_BOTTOM_ROW) & columnMask;
902 playMoveFastBB(mvMask);
903 }
904
905 static void addAfterStates(std::map<uint64_t, Board>& boardCollection,
906 const Board& b, const int nPly) {
907 if (b.countTokens() >= nPly) {
908 return;
909 }
910
911 auto moves = b.legalMovesMask();
912
913 while (moves) {
914 const auto mv = b.nextMove(moves);
915 assert(uint64_t_popcnt(mv) == 1);
916 if (auto newB = b.playBitMaskOnCopy(mv);
917 boardCollection.find(newB.uid()) == boardCollection.end() &&
918 !b.hasWin()) {
919 // We have not reached this position yet
920 boardCollection.insert({newB.uid(), newB});
921 addAfterStates(boardCollection, newB, nPly);
922 }
923
924 moves ^= mv;
925 }
926 }
927
928 static std::pair<Board, std::vector<int>> randomBoardInternal(
929 const int nPly) {
930 if (nPly < 0 || nPly > N_COLUMNS * N_ROWS) {
931 return {};
932 }
933 Board b;
934
935 // Create a random device to seed the random number generator
936 static std::random_device rd;
937
938 // Create a Mersenne Twister random number generator
939 static std::mt19937 gen(rd());
940
941 // Create a uniform integer distribution for the desired range
942 static std::uniform_int_distribution<> nextUniform(0, N_COLUMNS);
943
944 std::vector<int> mvSequence;
945 static constexpr int MAX_TRIES = 20;
946 for (int j = 0; j < nPly; ++j) {
947 int randColumn, tries = 0;
948 do {
949 randColumn = nextUniform(gen);
950 tries++;
951 } while (tries < MAX_TRIES &&
952 (!b.isLegalMove(randColumn) || b.canWin(randColumn)));
953 if (tries >= MAX_TRIES) {
954 return {};
955 }
956 b.play(randColumn);
957 mvSequence.emplace_back(randColumn);
958 }
959
960 assert(b.countTokens() == nPly);
961
962 return {std::move(b), std::move(mvSequence)};
963 }
964
965 static TBoardArray transpose(const TBoardArrayT& board);
966
967 std::vector<int> orderedLegalMovesFromMask(TBitBoard mvBits) const;
968
969 std::vector<int> legalMovesFromMask(TBitBoard mvBits) const;
970};
971
972} // namespace BitBully
973
974#endif // XBITBULLY__BOARD_H_
#define uint64_t_popcnt
Portable fallback popcount when no compiler intrinsic is available.
Definition Board.h:50
std::vector< int > bits_set(uint64_t x)
Decompose a bitboard into the indices of its set bits.
Definition Board.h:93
int ctz_u64(uint64_t x)
Count trailing zero bits of a 64-bit integer.
Definition Board.h:66
Tiny priority queue used by the engine for move ordering.
Connect-4 position represented as a pair of 64-bit bitboards.
Definition Board.h:204
bool operator!=(const Board &b) const
Inequality counterpart of operator==.
Definition Board.h:397
int getColumnHeight(const int column) const
Number of stones currently stacked in column.
Definition Board.cpp:512
MoveList sortMoves(TBitBoard moves) const
Sort candidate moves into a MoveList using a threat-based heuristic.
Definition Board.cpp:285
void setRawState(const RawState &s) noexcept
Restore an internal state previously captured with rawState().
Definition Board.h:760
Board()
Construct an empty board with player Yellow to move.
Definition Board.cpp:18
static constexpr int N_ROWS
Number of rows in a Connect-4 grid.
Definition Board.h:213
uint64_t TBitBoard
64-bit type used for any bitboard value.
Definition Board.h:230
TBitBoard legalMovesMask() const
Bitboard whose set bits mark the next reachable cell of every non-full column.
Definition Board.cpp:418
bool isLegalMove(int column) const
Check whether dropping a stone in column is legal.
Definition Board.cpp:170
TBitBoard findOddThreats(TBitBoard moves)
Identify candidate moves that introduce a new "odd" threat.
Definition Board.cpp:344
TMovesCounter countTokens() const
Number of stones currently placed on the board.
Definition Board.h:506
TBitBoard doubleThreat(const TBitBoard moves) const
Identify moves that create a "double threat" (forced win).
Definition Board.h:619
static constexpr size_t N_VALID_BOARD_VALUES
Number of distinct cell values: empty, yellow, red.
Definition Board.h:228
static uint64_t hash(uint64_t x)
Splittable-mix-style hash for a 64-bit integer.
Definition Board.h:334
bool canWin() const
Does the player to move have an immediately winning move?
Definition Board.cpp:407
int TMovesCounter
Type used to count remaining moves on a position.
Definition Board.h:232
bool play(int column)
Drop a stone of the player to move into column.
Definition Board.cpp:137
Player
Player identifiers used by the board's array view.
Definition Board.h:222
@ P_EMPTY
Empty cell.
Definition Board.h:223
@ P_RED
Red stone (player who moves second).
Definition Board.h:225
@ P_YELLOW
Yellow stone (player who moves first).
Definition Board.h:224
Board copy() const
Return a deep copy of this board.
Definition Board.h:265
Board playBitMaskOnCopy(const TBitBoard mv) const
Apply a move (already encoded as a single-bit mask) on a copy of this board.
Definition Board.h:245
static constexpr int N_COLUMNS
Number of columns in a Connect-4 grid.
Definition Board.h:211
static TBitBoard nextMove(TBitBoard allMoves)
Pick the best-ranked move from a candidate set using a fixed priority order.
Definition Board.h:376
bool hasWin() const
Did the player who just moved form a four-in-a-row?
Definition Board.cpp:254
static bool isValid(const TBoardArray &board)
Validate a board layout.
Definition Board.cpp:97
bool operator==(const Board &b) const
Compare two positions for equality (same stones, same side to move).
Definition Board.h:387
bool setBoard(const TBoardArray &board)
Replace the current position with the one described by board.
Definition Board.cpp:67
TBitBoard generateNonLosingMoves() const
Compute moves that do not lose immediately.
Definition Board.h:591
auto popCountBoard() const
Number of stones currently on the board.
Definition Board.h:313
Board mirror() const
Mirror the board around its central column.
Definition Board.cpp:501
std::string toString() const
Pretty-printed ASCII representation of the board.
Definition Board.cpp:481
static constexpr int popCountBoard(uint64_t x)
Software popcount fallback used when no intrinsic is available.
Definition Board.h:298
TBitBoard findThreats(TBitBoard moves)
Identify candidate moves that yield a tactical advantage.
Definition Board.cpp:313
std::vector< int > legalMoves(bool nonLosing, bool orderMoves) const
Enumerate legal moves as column indices.
Definition Board.cpp:448
uint64_t hash() const
64-bit hash of the position derived from uid().
Definition Board.h:358
Board playMoveOnCopy(const int mv) const
Apply a move given by column index on a copy of this board.
Definition Board.h:258
static TBitBoard lsb(const TBitBoard x)
Isolate the least significant set bit of a bitboard.
Definition Board.h:570
TMovesCounter movesLeft() const
Number of plies remaining until the board is full.
Definition Board.h:503
static std::pair< Board, std::vector< int > > randomBoard(const int nPly, const bool forbidDirectWin=true)
Generate a random reachable position with a fixed number of plies.
Definition Board.h:689
std::array< std::array< int32_t, N_ROWS >, N_COLUMNS > TBoardArray
Column-major 2D representation of the board (board[col][row]).
Definition Board.h:234
RawState rawState() const noexcept
Snapshot the internal state.
Definition Board.h:749
std::vector< Board > allPositions(const int upToNPly, bool exactlyN) const
Enumerate every reachable position from this board within a depth.
Definition Board.h:719
uint64_t uid() const
Unique 64-bit identifier of the position.
Definition Board.h:350
std::array< std::array< int32_t, N_COLUMNS >, N_ROWS > TBoardArrayT
Row-major 2D representation of the board (board[row][col]).
Definition Board.h:236
int toHuffman() const
Encode the position as a compact Huffman-like integer.
Definition Board.h:650
static constexpr int COLUMN_BIT_OFFSET
Bit-offset between two columns inside the 64-bit board representation.
Definition Board.h:219
TBoardArray toArray() const
Convert the bitboard representation into a column-major array.
Definition Board.cpp:463
Fixed-capacity priority queue tailored for Connect-4 move ordering.
Definition MoveList.h:24
Top-level namespace for the BitBully Connect-4 engine.
Definition BitBully.h:26
constexpr int CHAR_BIT
Number of bits per byte (defined locally if not provided by the platform).
Definition Board.h:110
Plain-data snapshot of the board's internal state.
Definition Board.h:742
TBitBoard all_tokens
Bitboard of all occupied cells.
Definition Board.h:743
TBitBoard active_tokens
Bitboard of cells held by the side to move.
Definition Board.h:744
TMovesCounter moves_left
Plies left until the board is full.
Definition Board.h:745