BitBully 0.0.76
Loading...
Searching...
No Matches
bitbully_core.cpp
1#include <pybind11/pybind11.h>
2#include <pybind11/stl.h>
3#include <pybind11/stl/filesystem.h>
4
5#include <array>
6#include <filesystem>
7#include <vector>
8
9#include "BitBully.h"
10#include "Board.h"
11#include "OpeningBook.h"
12
13namespace py = pybind11;
14using B = BitBully::Board;
15
16PYBIND11_MODULE(bitbully_core, m) {
17 m.doc() =
18 "Bitbully is a fast Connect-4 solver."; // optional module docstring
19
20 // Board constants (module-level; easy to discover and use from Python)
21 m.attr("N_COLUMNS") = py::int_(B::N_COLUMNS);
22 m.attr("N_ROWS") = py::int_(B::N_ROWS);
23
24 // Player enum (as a proper Python enum)
25 py::enum_<B::Player>(m, "Player")
26 .value("P_EMPTY", B::Player::P_EMPTY)
27 .value("P_YELLOW", B::Player::P_YELLOW)
28 .value("P_RED", B::Player::P_RED)
29 .export_values();
30
31 py::class_<BitBully::BitBully>(m, "BitBullyCore")
32 .def(py::init<>()) // Expose the default constructor
33 .def(py::init<std::filesystem::path>(), py::arg("openingBookPath"))
34 .def("mtdf", &BitBully::BitBully::mtdf, "MTD(f) algorithm",
35 py::arg("board"), py::arg("first_guess"), py::arg("max_depth") = -1)
36 .def("nullWindow", &BitBully::BitBully::nullWindow, "Null-window search",
37 py::arg("board"), py::arg("max_depth") = -1)
38 .def("negamax", &BitBully::BitBully::negamax, "negamax search",
39 py::arg("board"), py::arg("alpha"), py::arg("beta"),
40 py::arg("depth"), py::arg("max_depth") = -1)
41 .def("scoreMove", &BitBully::BitBully::scoreMove,
42 "evaluate a single move", py::arg("board"), py::arg("column"),
43 py::arg("first_guess"), py::arg("max_depth") = -1)
44 .def("scoreMoves", &BitBully::BitBully::scoreMoves, "evaluate all moves",
45 py::arg("board"), py::arg("max_depth") = -1)
46 .def_static("rollout", &BitBully::BitBully::rollout,
47 "Perform a rollout using non-losing moves", py::arg("board"))
48 .def_static("scoreToMovesLeft", &BitBully::BitBully::scoreToMovesLeft,
49 "Convert a solver score to the number of moves until the "
50 "game ends.",
51 py::arg("score"), py::arg("board"))
52 .def("resetTranspositionTable",
53 &BitBully::BitBully::resetTranspositionTable,
54 "Reset the transposition table")
55 .def("getNodeCounter", &BitBully::BitBully::getNodeCounter,
56 "Get the current node counter")
57 .def("resetNodeCounter", &BitBully::BitBully::resetNodeCounter,
58 "Reset the node counter")
59 .def("isBookLoaded", &BitBully::BitBully::isBookLoaded,
60 "Check, if opening book is loaded")
61 .def("loadBook", &BitBully::BitBully::loadBook,
62 "Load an opening book from a file path. Returns True if loaded.",
63 py::arg("bookPath") = std::filesystem::path{})
64 .def("resetBook", &BitBully::BitBully::resetBook,
65 "Unload the currently loaded opening book (if any).");
66
67 // Expose the Board class
68 // TODO: Check functions.... Many not necessary and some might be missing
69 py::class_<B>(m, "BoardCore")
70 .def(py::init<>()) // Default constructor
71 .def(py::init<const B&>()) // Copy-Konstruktor
72 .def("__str__", &B::toString) // Override __str__ in Python
73 .def("__repr__", &B::toString) // Override __repr__ in Python
74 .def("canWin", py::overload_cast<int>(&B::canWin, py::const_),
75 "Check, if current player can win by moving into column.",
76 py::arg("column"))
77 .def("copy", &B::copy, "Create a deep copy of the board.")
78 .def("canWin", py::overload_cast<>(&B::canWin, py::const_),
79 "Check, if current player can win with the next move.")
80 .def("hash", py::overload_cast<>(&B::hash, py::const_),
81 "Hash the current position and return hash value.")
82 .def("hasWin", &B::hasWin,
83 "Check, if the player who performed the last move has a winning "
84 "position (4 in a row).")
85 .def("play", py::overload_cast<int>(&B::play),
86 "Play a move by column index", py::arg("column"))
87 .def("play", py::overload_cast<const std::vector<int>&>(&B::play),
88 "Play a sequence of moves by column index", py::arg("moveSequence"))
89 .def("play", py::overload_cast<const std::string&>(&B::play),
90 "Play a sequence of moves by column index", py::arg("moveSequence"))
91 .def("playMoveOnCopy", &B::playMoveOnCopy,
92 "Play a move on a copy of the board and return the new board",
93 py::arg("mv"))
94 .def("popCountBoard", py::overload_cast<>(&B::popCountBoard, py::const_),
95 "Popcount of all tokens/bits in the bitboard (for debugging).")
96 .def("legalMovesMask", &B::legalMovesMask, "Generate possible moves")
97 .def("generateNonLosingMoves", &B::generateNonLosingMoves,
98 "Generate non-losing moves")
99 .def("legalMoves", &B::legalMoves,
100 "Generate possible moves as a vector of column indices",
101 py::arg("nonLosing"), py::arg("orderMoves"))
102 .def("isLegalMove", &B::isLegalMove, "Check if a move is legal",
103 py::arg("column"))
104 .def("toString", &B::toString,
105 "Return a string representation of the board")
106 .def("movesLeft", &B::movesLeft, "Get the number of moves left")
107 .def("countTokens", &B::countTokens,
108 "Get the number of Tokens on the board")
109 .def("mirror", &B::mirror,
110 "Get the mirrored board (mirror around center column)")
111 .def("allPositions", &B::allPositions,
112 "Generate all positions that can be reached from the current board "
113 "with n tokens.",
114 py::arg("upToNPly"), py::arg("exactlyN"))
115 .def("findThreats", &B::findThreats, "Find threats on the board",
116 py::arg("moves"))
117 .def("doubleThreat", &B::doubleThreat, "Find double threats",
118 py::arg("moves"))
119 .def("toArray", &B::toArray,
120 "Convert the board to a 2D array representation")
121 .def("setBoard", py::overload_cast<const std::vector<int>&>(&B::setBoard),
122 "Set the board using a list", py::arg("moveSequence"))
123 .def("setBoard", py::overload_cast<const B::TBoardArray&>(&B::setBoard),
124 "Set the board using a 2D array", py::arg("array"))
125 .def("setBoard", py::overload_cast<const B::TBoardArrayT&>(&B::setBoard),
126 "Set the board using a 2D array", py::arg("array"))
127 .def("setBoard", py::overload_cast<const std::string&>(&B::setBoard),
128 "Play a sequence of moves by column index", py::arg("moveSequence"))
129 .def_static("isValid", &B::isValid, "Check, if a board is a valid one.",
130 py::arg("board"))
131 .def_static("randomBoard", &B::randomBoard,
132 "Create a random board with n tokens.", py::arg("nPly"),
133 py::arg("forbidDirectWin"))
134 .def("toHuffman", &B::toHuffman,
135 "Encode position into a huffman-code compressed sequence.")
136 .def("uid", &B::uid, "Get the unique identifier for the board")
137 .def("__eq__", &B::operator==, "Check if two boards are equal")
138 .def("__ne__", &B::operator!=, "Check if two boards are not equal")
139 .def("getColumnHeight", &B::getColumnHeight,
140 "Get the current height of a column", py::arg("column"))
141 .def(
142 "rawState",
143 [](const B& b) {
144 const auto s = b.rawState();
145 // Return as Python ints (uint64 fits in Python int)
146 return py::make_tuple(
147 py::int_(static_cast<unsigned long long>(s.all_tokens)),
148 py::int_(static_cast<unsigned long long>(s.active_tokens)),
149 py::int_(s.moves_left));
150 },
151 "Return raw internal state: (all_tokens, active_tokens, "
152 "moves_left).")
153 .def(
154 "setRawState",
155 [](B& b, unsigned long long all_tokens,
156 unsigned long long active_tokens, int moves_left) {
157 B::RawState s{
158 static_cast<B::TBitBoard>(all_tokens),
159 static_cast<B::TBitBoard>(active_tokens),
160 static_cast<B::TMovesCounter>(moves_left),
161 };
162 b.setRawState(s);
163 },
164 py::arg("all_tokens"), py::arg("active_tokens"),
165 py::arg("moves_left"),
166 "Set raw internal state from (all_tokens, active_tokens, "
167 "moves_left). DANGER: No validity checks are performed!");
168
169 // Expose OpeningBook:
170 py::class_<BitBully::OpeningBook>(m, "OpeningBookCore")
171 // Constructors
172 .def(py::init<const std::filesystem::path&, bool, bool>(),
173 py::arg("bookPath"), py::arg("is_8ply"), py::arg("with_distances"),
174 "Initialize an OpeningBook with explicit settings.")
175 .def(py::init<const std::filesystem::path&>(), py::arg("bookPath"),
176 "Initialize an OpeningBook by inferring database type from file "
177 "size.")
178
179 // Member functions
180 .def("init", &BitBully::OpeningBook::init, py::arg("bookPath"),
181 py::arg("is_8ply"), py::arg("with_distances"),
182 "Reinitialize the OpeningBook with new settings.")
183 .def("getEntry", &BitBully::OpeningBook::getEntry, py::arg("entryIdx"),
184 "Get an entry from the book by index.")
185 .def("getBook", &BitBully::OpeningBook::getBook,
186 "Return the raw book table.")
187 .def("getBookSize", &BitBully::OpeningBook::getBookSize,
188 "Get the size of the book.")
189 .def("getBoardValue", &BitBully::OpeningBook::getBoardValue,
190 py::arg("board"), "Get the value of a given board.")
191 .def("isInBook", &BitBully::OpeningBook::isInBook, py::arg("board"),
192 "Check, if the given board is in the opening book. Note, that "
193 "usually boards are only present in one mirrored variant.")
194 .def("convertValue", &BitBully::OpeningBook::convertValue,
195 py::arg("value"), py::arg("board"),
196 "Convert a value to the internal scoring system.")
197 .def("getNPly", &BitBully::OpeningBook::getNPly,
198 "Get the ply depth of the book.")
199
200 // Static functions
201 .def_static("readBook", &BitBully::OpeningBook::readBook,
202 py::arg("filename"), py::arg("with_distances") = true,
203 py::arg("is_8ply") = false, "Read a book from a file.");
204}
static int scoreToMovesLeft(const int score, const Board &b) noexcept
Definition BitBully.h:54