BitBully 0.0.78
A fast, perfect-play Connect-4 engine in modern C++
Loading...
Searching...
No Matches
bitbully_core.cpp
Go to the documentation of this file.
1
13#include <pybind11/pybind11.h>
14#include <pybind11/stl.h>
15#include <pybind11/stl/filesystem.h>
16
17#include <array>
18#include <filesystem>
19#include <vector>
20
21#include "BitBully.h"
22#include "Board.h"
23#include "OpeningBook.h"
24
25namespace py = pybind11;
28
37PYBIND11_MODULE(bitbully_core, m) {
38 m.doc() =
39 "Bitbully is a fast Connect-4 solver."; // optional module docstring
40
41 // Board constants (module-level; easy to discover and use from Python)
42 m.attr("N_COLUMNS") = py::int_(B::N_COLUMNS);
43 m.attr("N_ROWS") = py::int_(B::N_ROWS);
44
45 // Player enum (as a proper Python enum)
46 py::enum_<B::Player>(m, "Player")
47 .value("P_EMPTY", B::Player::P_EMPTY)
48 .value("P_YELLOW", B::Player::P_YELLOW)
49 .value("P_RED", B::Player::P_RED)
50 .export_values();
51
52 py::class_<BitBully::BitBully>(m, "BitBullyCore")
53 .def(py::init<>()) // Expose the default constructor
54 .def(py::init<std::filesystem::path>(), py::arg("openingBookPath"))
55 .def("mtdf", &BitBully::BitBully::mtdf, "MTD(f) algorithm",
56 py::arg("board"), py::arg("first_guess"), py::arg("max_depth") = -1)
57 .def("nullWindow", &BitBully::BitBully::nullWindow, "Null-window search",
58 py::arg("board"), py::arg("max_depth") = -1)
59 .def("negamax", &BitBully::BitBully::negamax, "negamax search",
60 py::arg("board"), py::arg("alpha"), py::arg("beta"),
61 py::arg("depth"), py::arg("max_depth") = -1)
62 .def("scoreMove", &BitBully::BitBully::scoreMove,
63 "evaluate a single move", py::arg("board"), py::arg("column"),
64 py::arg("first_guess"), py::arg("max_depth") = -1)
65 .def("scoreMoves", &BitBully::BitBully::scoreMoves, "evaluate all moves",
66 py::arg("board"), py::arg("max_depth") = -1)
67 .def_static("rollout", &BitBully::BitBully::rollout,
68 "Perform a rollout using non-losing moves", py::arg("board"))
69 .def_static("scoreToMovesLeft", &BitBully::BitBully::scoreToMovesLeft,
70 "Convert a solver score to the number of moves until the "
71 "game ends.",
72 py::arg("score"), py::arg("board"))
73 .def("resetTranspositionTable",
75 "Reset the transposition table")
76 .def("getNodeCounter", &BitBully::BitBully::getNodeCounter,
77 "Get the current node counter")
78 .def("resetNodeCounter", &BitBully::BitBully::resetNodeCounter,
79 "Reset the node counter")
80 .def("isBookLoaded", &BitBully::BitBully::isBookLoaded,
81 "Check, if opening book is loaded")
82 .def("loadBook", &BitBully::BitBully::loadBook,
83 "Load an opening book from a file path. Returns True if loaded.",
84 py::arg("bookPath") = std::filesystem::path{})
85 .def("resetBook", &BitBully::BitBully::resetBook,
86 "Unload the currently loaded opening book (if any).");
87
88 // Expose the Board class
89 // TODO: Check functions.... Many not necessary and some might be missing
90 py::class_<B>(m, "BoardCore")
91 .def(py::init<>()) // Default constructor
92 .def(py::init<const B&>()) // Copy-Konstruktor
93 .def("__str__", &B::toString) // Override __str__ in Python
94 .def("__repr__", &B::toString) // Override __repr__ in Python
95 .def("canWin", py::overload_cast<int>(&B::canWin, py::const_),
96 "Check, if current player can win by moving into column.",
97 py::arg("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.")
103 .def("hasWin", &B::hasWin,
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"))
112 .def("playMoveOnCopy", &B::playMoveOnCopy,
113 "Play a move on a copy of the board and return the new board",
114 py::arg("mv"))
115 .def("popCountBoard", py::overload_cast<>(&B::popCountBoard, py::const_),
116 "Popcount of all tokens/bits in the bitboard (for debugging).")
117 .def("legalMovesMask", &B::legalMovesMask, "Generate possible moves")
118 .def("generateNonLosingMoves", &B::generateNonLosingMoves,
119 "Generate non-losing moves")
120 .def("legalMoves", &B::legalMoves,
121 "Generate possible moves as a vector of column indices",
122 py::arg("nonLosing"), py::arg("orderMoves"))
123 .def("isLegalMove", &B::isLegalMove, "Check if a move is legal",
124 py::arg("column"))
125 .def("toString", &B::toString,
126 "Return a string representation of the board")
127 .def("movesLeft", &B::movesLeft, "Get the number of moves left")
128 .def("countTokens", &B::countTokens,
129 "Get the number of Tokens on the board")
130 .def("mirror", &B::mirror,
131 "Get the mirrored board (mirror around center column)")
132 .def("allPositions", &B::allPositions,
133 "Generate all positions that can be reached from the current board "
134 "with n tokens.",
135 py::arg("upToNPly"), py::arg("exactlyN"))
136 .def("findThreats", &B::findThreats, "Find threats on the board",
137 py::arg("moves"))
138 .def("doubleThreat", &B::doubleThreat, "Find double threats",
139 py::arg("moves"))
140 .def("toArray", &B::toArray,
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.",
151 py::arg("board"))
152 .def_static("randomBoard", &B::randomBoard,
153 "Create a random board with n tokens.", py::arg("nPly"),
154 py::arg("forbidDirectWin"))
155 .def("toHuffman", &B::toHuffman,
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")
160 .def("getColumnHeight", &B::getColumnHeight,
161 "Get the current height of a column", py::arg("column"))
162 .def(
163 "rawState",
164 [](const B& b) {
165 const auto s = b.rawState();
166 // Return as Python ints (uint64 fits in Python int)
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));
171 },
172 "Return raw internal state: (all_tokens, active_tokens, "
173 "moves_left).")
174 .def(
175 "setRawState",
176 [](B& b, unsigned long long all_tokens,
177 unsigned long long active_tokens, int moves_left) {
178 B::RawState s{
179 static_cast<B::TBitBoard>(all_tokens),
180 static_cast<B::TBitBoard>(active_tokens),
181 static_cast<B::TMovesCounter>(moves_left),
182 };
183 b.setRawState(s);
184 },
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!");
189
190 // Expose OpeningBook:
191 py::class_<BitBully::OpeningBook>(m, "OpeningBookCore")
192 // Constructors
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 "
198 "size.")
199
200 // Member functions
201 .def("init", &BitBully::OpeningBook::init, py::arg("bookPath"),
202 py::arg("is_8ply"), py::arg("with_distances"),
203 "Reinitialize the OpeningBook with new settings.")
204 .def("getEntry", &BitBully::OpeningBook::getEntry, py::arg("entryIdx"),
205 "Get an entry from the book by index.")
206 .def("getBook", &BitBully::OpeningBook::getBook,
207 "Return the raw book table.")
208 .def("getBookSize", &BitBully::OpeningBook::getBookSize,
209 "Get the size of the book.")
210 .def("getBoardValue", &BitBully::OpeningBook::getBoardValue,
211 py::arg("board"), "Get the value of a given board.")
212 .def("isInBook", &BitBully::OpeningBook::isInBook, py::arg("board"),
213 "Check, if the given board is in the opening book. Note, that "
214 "usually boards are only present in one mirrored variant.")
215 .def("convertValue", &BitBully::OpeningBook::convertValue,
216 py::arg("value"), py::arg("board"),
217 "Convert a value to the internal scoring system.")
218 .def("getNPly", &BitBully::OpeningBook::getNPly,
219 "Get the ply depth of the book.")
220
221 // Static functions
222 .def_static("readBook", &BitBully::OpeningBook::readBook,
223 py::arg("filename"), py::arg("with_distances") = true,
224 py::arg("is_8ply") = false, "Read a book from a file.");
225}
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.
Definition BitBully.h:87
bool isBookLoaded() const
Was an opening book successfully loaded?
Definition BitBully.h:72
void resetNodeCounter()
Reset the visited-node counter to zero.
Definition BitBully.h:251
static int scoreToMovesLeft(const int score, const Board &b) noexcept
Convert a solver score to the number of moves until the game ends.
Definition BitBully.h:115
int mtdf(const Board &b, const int firstGuess, const int maxDepth=-1) noexcept
Solve a position using the MTD(f) driver.
Definition BitBully.h:188
void resetTranspositionTable()
Discard the contents of the transposition table.
Definition BitBully.h:242
auto scoreMove(const Board &b, const int column, const int firstGuess, const int maxDepth=-1)
Evaluate the move that drops a stone in column.
Definition BitBully.h:476
auto getNodeCounter() const
Number of nodes visited since resetNodeCounter().
Definition BitBully.h:248
int nullWindow(const Board &b, const int maxDepth=-1) noexcept
Alternative driver based on a binary search of zero-width windows.
Definition BitBully.h:221
auto scoreMoves(const Board &b, const int maxDepth=-1)
Evaluate every column move from b.
Definition BitBully.h:499
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.
Definition BitBully.h:274
static int rollout(Board b) noexcept
Cheap evaluation: play out the game using safe heuristic moves.
Definition BitBully.h:139
void resetBook()
Discard the currently loaded opening book (if any).
Definition BitBully.h:75
Connect-4 position represented as a pair of 64-bit bitboards.
Definition Board.h:204
int getColumnHeight(const int column) const
Number of stones currently stacked in column.
Definition Board.cpp:512
void setRawState(const RawState &s) noexcept
Restore an internal state previously captured with rawState().
Definition Board.h:760
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
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 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
@ 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
static constexpr int N_COLUMNS
Number of columns in a Connect-4 grid.
Definition Board.h:211
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 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
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
Board playMoveOnCopy(const int mv) const
Apply a move given by column index on a copy of this board.
Definition Board.h:258
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
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
int toHuffman() const
Encode the position as a compact Huffman-like integer.
Definition Board.h:650
TBoardArray toArray() const
Convert the bitboard representation into a column-major array.
Definition Board.cpp:463
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.
Definition Board.h:742