13#include <pybind11/pybind11.h>
14#include <pybind11/stl.h>
15#include <pybind11/stl/filesystem.h>
25namespace py = pybind11;
39 "Bitbully is a fast Connect-4 solver.";
46 py::enum_<B::Player>(m,
"Player")
52 py::class_<BitBully::BitBully>(m,
"BitBullyCore")
54 .def(py::init<std::filesystem::path>(), py::arg(
"openingBookPath"))
56 py::arg(
"board"), py::arg(
"first_guess"), py::arg(
"max_depth") = -1)
58 py::arg(
"board"), py::arg(
"max_depth") = -1)
60 py::arg(
"board"), py::arg(
"alpha"), py::arg(
"beta"),
61 py::arg(
"depth"), py::arg(
"max_depth") = -1)
63 "evaluate a single move", py::arg(
"board"), py::arg(
"column"),
64 py::arg(
"first_guess"), py::arg(
"max_depth") = -1)
66 py::arg(
"board"), py::arg(
"max_depth") = -1)
68 "Perform a rollout using non-losing moves", py::arg(
"board"))
70 "Convert a solver score to the number of moves until the "
72 py::arg(
"score"), py::arg(
"board"))
73 .def(
"resetTranspositionTable",
75 "Reset the transposition table")
77 "Get the current node counter")
79 "Reset the node counter")
81 "Check, if opening book is loaded")
83 "Load an opening book from a file path. Returns True if loaded.",
84 py::arg(
"bookPath") = std::filesystem::path{})
86 "Unload the currently loaded opening book (if any).");
90 py::class_<B>(m,
"BoardCore")
92 .def(py::init<const B&>())
95 .def(
"canWin", py::overload_cast<int>(&
B::canWin, py::const_),
96 "Check, if current player can win by moving into column.",
98 .def(
"copy", &
B::copy,
"Create a deep copy of the board.")
99 .def(
"canWin", py::overload_cast<>(&
B::canWin, py::const_),
100 "Check, if current player can win with the next move.")
101 .def(
"hash", py::overload_cast<>(&
B::hash, py::const_),
102 "Hash the current position and return hash value.")
104 "Check, if the player who performed the last move has a winning "
105 "position (4 in a row).")
106 .def(
"play", py::overload_cast<int>(&
B::play),
107 "Play a move by column index", py::arg(
"column"))
108 .def(
"play", py::overload_cast<
const std::vector<int>&>(&
B::play),
109 "Play a sequence of moves by column index", py::arg(
"moveSequence"))
110 .def(
"play", py::overload_cast<const std::string&>(&
B::play),
111 "Play a sequence of moves by column index", py::arg(
"moveSequence"))
113 "Play a move on a copy of the board and return the new board",
116 "Popcount of all tokens/bits in the bitboard (for debugging).")
119 "Generate non-losing moves")
121 "Generate possible moves as a vector of column indices",
122 py::arg(
"nonLosing"), py::arg(
"orderMoves"))
126 "Return a string representation of the board")
127 .def(
"movesLeft", &
B::movesLeft,
"Get the number of moves left")
129 "Get the number of Tokens on the board")
131 "Get the mirrored board (mirror around center column)")
133 "Generate all positions that can be reached from the current board "
135 py::arg(
"upToNPly"), py::arg(
"exactlyN"))
141 "Convert the board to a 2D array representation")
142 .def(
"setBoard", py::overload_cast<
const std::vector<int>&>(&
B::setBoard),
143 "Set the board using a list", py::arg(
"moveSequence"))
144 .def(
"setBoard", py::overload_cast<const B::TBoardArray&>(&
B::setBoard),
145 "Set the board using a 2D array", py::arg(
"array"))
146 .def(
"setBoard", py::overload_cast<const B::TBoardArrayT&>(&
B::setBoard),
147 "Set the board using a 2D array", py::arg(
"array"))
148 .def(
"setBoard", py::overload_cast<const std::string&>(&
B::setBoard),
149 "Play a sequence of moves by column index", py::arg(
"moveSequence"))
150 .def_static(
"isValid", &
B::isValid,
"Check, if a board is a valid one.",
153 "Create a random board with n tokens.", py::arg(
"nPly"),
154 py::arg(
"forbidDirectWin"))
156 "Encode position into a huffman-code compressed sequence.")
157 .def(
"uid", &
B::uid,
"Get the unique identifier for the board")
158 .def(
"__eq__", &B::operator==,
"Check if two boards are equal")
159 .def(
"__ne__", &B::operator!=,
"Check if two boards are not equal")
161 "Get the current height of a column", py::arg(
"column"))
167 return py::make_tuple(
168 py::int_(
static_cast<unsigned long long>(s.all_tokens)),
169 py::int_(
static_cast<unsigned long long>(s.active_tokens)),
170 py::int_(s.moves_left));
172 "Return raw internal state: (all_tokens, active_tokens, "
176 [](
B& b,
unsigned long long all_tokens,
177 unsigned long long active_tokens,
int moves_left) {
185 py::arg(
"all_tokens"), py::arg(
"active_tokens"),
186 py::arg(
"moves_left"),
187 "Set raw internal state from (all_tokens, active_tokens, "
188 "moves_left). DANGER: No validity checks are performed!");
191 py::class_<BitBully::OpeningBook>(m,
"OpeningBookCore")
193 .def(py::init<const std::filesystem::path&, bool, bool>(),
194 py::arg(
"bookPath"), py::arg(
"is_8ply"), py::arg(
"with_distances"),
195 "Initialize an OpeningBook with explicit settings.")
196 .def(py::init<const std::filesystem::path&>(), py::arg(
"bookPath"),
197 "Initialize an OpeningBook by inferring database type from file "
202 py::arg(
"is_8ply"), py::arg(
"with_distances"),
203 "Reinitialize the OpeningBook with new settings.")
205 "Get an entry from the book by index.")
207 "Return the raw book table.")
209 "Get the size of the book.")
211 py::arg(
"board"),
"Get the value of a given board.")
213 "Check, if the given board is in the opening book. Note, that "
214 "usually boards are only present in one mirrored variant.")
216 py::arg(
"value"), py::arg(
"board"),
217 "Convert a value to the internal scoring system.")
219 "Get the ply depth of the book.")
223 py::arg(
"filename"), py::arg(
"with_distances") =
true,
224 py::arg(
"is_8ply") =
false,
"Read a book from a file.");
Connect-4 search engine that operates on BitBully::Board.
Bitboard-based representation of a Connect-4 position.
Read-only access to BitBully's pre-computed Connect-4 opening books.
BitBully::Board B
Short-hand alias used throughout the binding definitions.
PYBIND11_MODULE(bitbully_core, m)
Define the bitbully_core extension module.
bool loadBook(const std::filesystem::path &bookPath="")
Load an opening book from disk.
bool isBookLoaded() const
Was an opening book successfully loaded?
void resetNodeCounter()
Reset the visited-node counter to zero.
static int scoreToMovesLeft(const int score, const Board &b) noexcept
Convert a solver score to the number of moves until the game ends.
int mtdf(const Board &b, const int firstGuess, const int maxDepth=-1) noexcept
Solve a position using the MTD(f) driver.
void resetTranspositionTable()
Discard the contents of the transposition table.
auto scoreMove(const Board &b, const int column, const int firstGuess, const int maxDepth=-1)
Evaluate the move that drops a stone in column.
auto getNodeCounter() const
Number of nodes visited since resetNodeCounter().
int nullWindow(const Board &b, const int maxDepth=-1) noexcept
Alternative driver based on a binary search of zero-width windows.
auto scoreMoves(const Board &b, const int maxDepth=-1)
Evaluate every column move from b.
int negamax(Board b, int alpha, int beta, const int depth, const int maxDepth=-1) noexcept
Negamax search with alpha-beta pruning, transposition table and opening book consultation.
static int rollout(Board b) noexcept
Cheap evaluation: play out the game using safe heuristic moves.
void resetBook()
Discard the currently loaded opening book (if any).
Connect-4 position represented as a pair of 64-bit bitboards.
int getColumnHeight(const int column) const
Number of stones currently stacked in column.
void setRawState(const RawState &s) noexcept
Restore an internal state previously captured with rawState().
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.
TMovesCounter countTokens() const
Number of stones currently placed on the board.
TBitBoard doubleThreat(const TBitBoard moves) const
Identify moves that create a "double threat" (forced win).
static uint64_t hash(uint64_t x)
Splittable-mix-style hash for a 64-bit integer.
bool canWin() const
Does the player to move have an immediately winning move?
int TMovesCounter
Type used to count remaining moves on a position.
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).
Board copy() const
Return a deep copy of this board.
static constexpr int N_COLUMNS
Number of columns in a Connect-4 grid.
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.
static constexpr int popCountBoard(uint64_t x)
Software popcount fallback used when no intrinsic is available.
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.
Board playMoveOnCopy(const int mv) const
Apply a move given by column index on a copy of this board.
TMovesCounter movesLeft() const
Number of plies remaining until the board is full.
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.
RawState rawState() const noexcept
Snapshot the internal state.
std::vector< Board > allPositions(const int upToNPly, bool exactlyN) const
Enumerate every reachable position from this board within a depth.
uint64_t uid() const
Unique 64-bit identifier of the position.
int toHuffman() const
Encode the position as a compact Huffman-like integer.
TBoardArray toArray() const
Convert the bitboard representation into a column-major array.
int getNPly() const
Number of stones-on-board the book covers (8 or 12).
static std::vector< std::tuple< key_t, value_t > > readBook(const std::filesystem::path &filename, const bool with_distances=true, const bool is_8ply=false)
Slurp an entire opening book file into memory.
auto getBook() const
Copy of the underlying sorted (key, value) array.
auto getBookSize() const
Number of entries currently held in memory.
int convertValue(const int value, const Board &b) const
Translate a raw book value into the engine's score convention.
auto getEntry(const size_t entryIdx) const
Random-access getter for raw book entries.
void init(const std::filesystem::path &bookPath, const bool is_8ply, const bool with_distances)
Re-initialise the book in place.
bool isInBook(const Board &b) const
Test whether the exact (non-mirrored) position is in the book.
int getBoardValue(const Board &b) const
Retrieve the engine score for a position covered by the book.
Plain-data snapshot of the board's internal state.