BitBully 0.0.71
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"))
36 .def("nullWindow", &BitBully::BitBully::nullWindow, "Null-window search",
37 py::arg("board"))
38 .def("negamax", &BitBully::BitBully::negamax, "negamax search",
39 py::arg("board"), py::arg("alpha"), py::arg("beta"),
40 py::arg("depth"))
41 .def("scoreMove", &BitBully::BitBully::scoreMove,
42 "evaluate a single move", py::arg("board"), py::arg("column"),
43 py::arg("first_guess"))
44 .def("scoreMoves", &BitBully::BitBully::scoreMoves, "evaluate all moves",
45 py::arg("board"))
46 .def("resetTranspositionTable",
47 &BitBully::BitBully::resetTranspositionTable,
48 "Reset the transposition table")
49 .def("getNodeCounter", &BitBully::BitBully::getNodeCounter,
50 "Get the current node counter")
51 .def("resetNodeCounter", &BitBully::BitBully::resetNodeCounter,
52 "Reset the node counter")
53 .def("isBookLoaded", &BitBully::BitBully::isBookLoaded,
54 "Check, if opening book is loaded")
55 .def("loadBook", &BitBully::BitBully::loadBook,
56 "Load an opening book from a file path. Returns True if loaded.",
57 py::arg("bookPath") = std::filesystem::path{})
58 .def("resetBook", &BitBully::BitBully::resetBook,
59 "Unload the currently loaded opening book (if any).");
60
61 // Expose the Board class
62 // TODO: Check functions.... Many not necessary and some might be missing
63 py::class_<B>(m, "BoardCore")
64 .def(py::init<>()) // Default constructor
65 .def(py::init<const B&>()) // Copy-Konstruktor
66 .def("__str__", &B::toString) // Override __str__ in Python
67 .def("__repr__", &B::toString) // Override __repr__ in Python
68 .def("canWin", py::overload_cast<int>(&B::canWin, py::const_),
69 "Check, if current player can win by moving into column.",
70 py::arg("column"))
71 .def("copy", &B::copy, "Create a deep copy of the board.")
72 .def("canWin", py::overload_cast<>(&B::canWin, py::const_),
73 "Check, if current player can win with the next move.")
74 .def("hash", py::overload_cast<>(&B::hash, py::const_),
75 "Hash the current position and return hash value.")
76 .def("hasWin", &B::hasWin,
77 "Check, if the player who performed the last move has a winning "
78 "position (4 in a row).")
79 .def("play", py::overload_cast<int>(&B::play),
80 "Play a move by column index", py::arg("column"))
81 .def("play", py::overload_cast<const std::vector<int>&>(&B::play),
82 "Play a sequence of moves by column index", py::arg("moveSequence"))
83 .def("play", py::overload_cast<const std::string&>(&B::play),
84 "Play a sequence of moves by column index", py::arg("moveSequence"))
85 .def("playMoveOnCopy", &B::playMoveOnCopy,
86 "Play a move on a copy of the board and return the new board",
87 py::arg("mv"))
88 .def("popCountBoard", py::overload_cast<>(&B::popCountBoard, py::const_),
89 "Popcount of all tokens/bits in the bitboard (for debugging).")
90 .def("legalMovesMask", &B::legalMovesMask, "Generate possible moves")
91 .def("generateNonLosingMoves", &B::generateNonLosingMoves,
92 "Generate non-losing moves")
93 .def("legalMoves", &B::legalMoves,
94 "Generate possible moves as a vector of column indices",
95 py::arg("nonLosing"), py::arg("orderMoves"))
96 .def("isLegalMove", &B::isLegalMove, "Check if a move is legal",
97 py::arg("column"))
98 .def("toString", &B::toString,
99 "Return a string representation of the board")
100 .def("movesLeft", &B::movesLeft, "Get the number of moves left")
101 .def("countTokens", &B::countTokens,
102 "Get the number of Tokens on the board")
103 .def("mirror", &B::mirror,
104 "Get the mirrored board (mirror around center column)")
105 .def("allPositions", &B::allPositions,
106 "Generate all positions that can be reached from the current board "
107 "with n tokens.",
108 py::arg("upToNPly"), py::arg("exactlyN"))
109 .def("findThreats", &B::findThreats, "Find threats on the board",
110 py::arg("moves"))
111 .def("doubleThreat", &B::doubleThreat, "Find double threats",
112 py::arg("moves"))
113 .def("toArray", &B::toArray,
114 "Convert the board to a 2D array representation")
115 .def("setBoard", py::overload_cast<const std::vector<int>&>(&B::setBoard),
116 "Set the board using a list", py::arg("moveSequence"))
117 .def("setBoard", py::overload_cast<const B::TBoardArray&>(&B::setBoard),
118 "Set the board using a 2D array", py::arg("array"))
119 .def("setBoard", py::overload_cast<const B::TBoardArrayT&>(&B::setBoard),
120 "Set the board using a 2D array", py::arg("array"))
121 .def("setBoard", py::overload_cast<const std::string&>(&B::setBoard),
122 "Play a sequence of moves by column index", py::arg("moveSequence"))
123 .def_static("isValid", &B::isValid, "Check, if a board is a valid one.",
124 py::arg("board"))
125 .def_static("randomBoard", &B::randomBoard,
126 "Create a random board with n tokens.", py::arg("nPly"),
127 py::arg("forbidDirectWin"))
128 .def("toHuffman", &B::toHuffman,
129 "Encode position into a huffman-code compressed sequence.")
130 .def("uid", &B::uid, "Get the unique identifier for the board")
131 .def("__eq__", &B::operator==, "Check if two boards are equal")
132 .def("__ne__", &B::operator!=, "Check if two boards are not equal");
133
134 // Expose OpeningBook:
135 py::class_<BitBully::OpeningBook>(m, "OpeningBookCore")
136 // Constructors
137 .def(py::init<const std::filesystem::path&, bool, bool>(),
138 py::arg("bookPath"), py::arg("is_8ply"), py::arg("with_distances"),
139 "Initialize an OpeningBook with explicit settings.")
140 .def(py::init<const std::filesystem::path&>(), py::arg("bookPath"),
141 "Initialize an OpeningBook by inferring database type from file "
142 "size.")
143
144 // Member functions
145 .def("init", &BitBully::OpeningBook::init, py::arg("bookPath"),
146 py::arg("is_8ply"), py::arg("with_distances"),
147 "Reinitialize the OpeningBook with new settings.")
148 .def("getEntry", &BitBully::OpeningBook::getEntry, py::arg("entryIdx"),
149 "Get an entry from the book by index.")
150 .def("getBook", &BitBully::OpeningBook::getBook,
151 "Return the raw book table.")
152 .def("getBookSize", &BitBully::OpeningBook::getBookSize,
153 "Get the size of the book.")
154 .def("getBoardValue", &BitBully::OpeningBook::getBoardValue,
155 py::arg("board"), "Get the value of a given board.")
156 .def("isInBook", &BitBully::OpeningBook::isInBook, py::arg("board"),
157 "Check, if the given board is in the opening book. Note, that "
158 "usually boards are only present in one mirrored variant.")
159 .def("convertValue", &BitBully::OpeningBook::convertValue,
160 py::arg("value"), py::arg("board"),
161 "Convert a value to the internal scoring system.")
162 .def("getNPly", &BitBully::OpeningBook::getNPly,
163 "Get the ply depth of the book.")
164
165 // Static functions
166 .def_static("readBook", &BitBully::OpeningBook::readBook,
167 py::arg("filename"), py::arg("with_distances") = true,
168 py::arg("is_8ply") = false, "Read a book from a file.");
169}