solver
This module provides the Connect Four AI agent "BitBully" with opening book support.
Classes:
| Name | Description |
|---|---|
BitBully |
A Connect Four AI agent with optional opening book support. |
Attributes:
| Name | Type | Description |
|---|---|---|
OpeningBookName |
TypeAlias
|
Name of the opening book used by the BitBully engine. |
TieBreakStrategy |
TypeAlias
|
Strategy for breaking ties between equally good moves. |
OpeningBookName
module-attribute
Name of the opening book used by the BitBully engine.
Possible values:
- "default": Alias for "12-ply-dist".
- "8-ply": 8-ply opening book (win/loss only).
- "12-ply": 12-ply opening book (win/loss only).
- "12-ply-dist": 12-ply opening book with distance-to-win information.
TieBreakStrategy
module-attribute
BitBully
BitBully(opening_book: OpeningBookName | None = 'default', *, tie_break: TieBreakStrategy | None = None, rng: Random | None = None)
A Connect Four AI agent with optional opening book support.
Todo: - We have to describe the scoring scheme (range of values and their meaning).
This class is a high-level Python wrapper around
bitbully_core.BitBullyCore.
It integrates the packaged BitBully Databases opening books and
operates on bitbully.Board objects.
Notes
- If an opening book is enabled, it is used automatically for early-game positions.
- For deeper positions or positions outside the database horizon, the engine falls back to search-based evaluation.
Example
from bitbully import BitBully, Board
agent = BitBully()
board, _ = Board.random_board(n_ply=14, forbid_direct_win=True)
print(board)
# All three search methods should agree on the score
score_mtdf = agent.mtdf(board)
score_negamax = agent.negamax(board)
score_null_window = agent.null_window(board)
assert score_negamax == score_null_window == score_mtdf
Example
from bitbully import BitBully, Board
board = Board() # empty board
agent = BitBully()
scores = agent.score_all_moves(board) # get scores for all moves
assert len(scores) == 7 # there are 7 columns
assert scores == {3: 1, 2: 0, 4: 0, 1: -1, 5: -1, 0: -2, 6: -2} # center column is best
print(scores)
Expected Output:
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
OpeningBookName | None
|
Which opening book to load.
|
'default'
|
|
TieBreakStrategy | None
|
Default strategy for breaking ties between equally scoring moves.
If |
None
|
|
Random | None
|
Optional RNG for reproducible "random" tie-breaking. |
None
|
TODO: Example for initialization with different books.
Methods:
| Name | Description |
|---|---|
__repr__ |
Return a concise string representation of the BitBully agent. |
available_opening_books |
Return the available opening book identifiers. |
best_move |
Return the best legal move (column index) for the current player. |
get_node_counter |
Return the number of nodes visited since the last reset. |
is_book_loaded |
Check whether an opening book is loaded. |
load_book |
Load an opening book from a file path. |
mtdf |
Evaluate a position using the MTD(f) algorithm. |
negamax |
Evaluate a position using negamax search. |
null_window |
Evaluate a position using a null-window search. |
reset_book |
Unload the currently loaded opening book (if any). |
reset_node_counter |
Reset the internal node counter. |
reset_transposition_table |
Clear the internal transposition table. |
score_all_moves |
Score all legal moves for the given board state. |
score_move |
Evaluate a single move for the given board state. |
Attributes:
| Name | Type | Description |
|---|---|---|
opening_book_type |
OpeningBookName | None
|
|
rng |
|
|
tie_break |
|
Source code in src/bitbully/solver.py
available_opening_books
classmethod
available_opening_books() -> tuple[OpeningBookName, ...]
Return the available opening book identifiers.
Returns:
| Type | Description |
|---|---|
tuple[OpeningBookName, ...]
|
tuple[OpeningBookName, ...]:
All supported opening book names, including |
Example
Source code in src/bitbully/solver.py
best_move
best_move(board: Board, *, tie_break: TieBreakStrategy | None = None, rng: Random | None = None) -> int
Return the best legal move (column index) for the current player.
All legal moves are scored using :meth:score_all_moves. The move(s)
with the highest score are considered best, and ties are resolved
according to tie_break.
Tie-breaking strategies
None(default): Use the agent's default tie-breaking strategy (self.tie_break)."center"(default): Prefer the move closest to the center column (3). If still tied, choose the smaller column index."leftmost": Choose the smallest column index among tied moves."random": Choose uniformly at random among tied moves. An optionalrngcan be provided for reproducibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The current board state. |
required |
|
TieBreakStrategy | None
|
Strategy used to resolve ties between equally scoring moves. |
None
|
|
Random | None
|
Random number generator used when |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The selected column index (0-6). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If there are no legal moves (board is full) or if an unknown tie-breaking strategy is specified. |
Example
Example
from bitbully import BitBully, Board
import random
agent = BitBully()
board = Board("341") # some arbitrary position
print(board)
assert agent.best_move(board, tie_break="center") == 3 # Several moves are tied; center is preferred
assert agent.best_move(board, tie_break="leftmost") == 1 # Leftmost among tied moves
assert agent.best_move(board, tie_break="random") in {1, 3, 4} # Random among tied moves
rng = random.Random(42) # use own random number generator
assert agent.best_move(board, tie_break="random", rng=rng) in {1, 3, 4}
Source code in src/bitbully/solver.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | |
get_node_counter
get_node_counter() -> int
Return the number of nodes visited since the last reset.
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Number of visited nodes. |
Example
-
BitBully API Reference
solverBitBullyreset_node_counter
Source code in src/bitbully/solver.py
is_book_loaded
is_book_loaded() -> bool
Check whether an opening book is loaded.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Example
Source code in src/bitbully/solver.py
load_book
load_book(book: OpeningBookName | PathLike[str] | str) -> None
Load an opening book from a file path.
This is a thin wrapper around
bitbully_core.BitBullyCore.loadBook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
OpeningBookName | PathLike[str] | str
|
Name/Identifier (see |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the book identifier/path is invalid or if loading the book fails. |
Example
from bitbully import BitBully
from pathlib import Path
which_book = BitBully.available_opening_books()[0] # e.g., "default"
agent = BitBully(opening_book=None) # start without book
assert agent.is_book_loaded() is False
agent.load_book(which_book) # load "default" book
assert agent.is_book_loaded() is True
Example
from bitbully import BitBully
from pathlib import Path
import bitbully_databases as bbd
which_book = BitBully.available_opening_books()[2] # e.g., "12-ply"
db_path = bbd.BitBullyDatabases.get_database_path(which_book)
agent = BitBully(opening_book=None) # start without book
assert agent.is_book_loaded() is False
agent.load_book(db_path)
assert agent.is_book_loaded() is True
Source code in src/bitbully/solver.py
mtdf
mtdf(board: Board, first_guess: int = 0) -> int
Evaluate a position using the MTD(f) algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The board position to evaluate. |
required |
|
int
|
Initial guess for the score (often 0). |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The evaluation score. |
Example
Source code in src/bitbully/solver.py
negamax
Evaluate a position using negamax search.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The board position to evaluate. |
required |
|
int
|
Alpha bound. |
-1000
|
|
int
|
Beta bound. |
1000
|
|
int
|
Search depth in plies. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The evaluation score returned by the engine. |
Example
Source code in src/bitbully/solver.py
null_window
Evaluate a position using a null-window search.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The board position to evaluate. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The evaluation score. |
Example
Source code in src/bitbully/solver.py
reset_book
Unload the currently loaded opening book (if any).
This resets the engine to search-only mode until another opening book is loaded.
Example
Source code in src/bitbully/solver.py
reset_node_counter
Reset the internal node counter.
See Also: get_node_counter for usage.
reset_transposition_table
score_all_moves
Score all legal moves for the given board state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The current board state. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, int]
|
dict[int, int]: A dictionary of up to 7 column-value pairs, one per reachable column (0-6). Higher values generally indicate better moves for the player to move. If a column is full, it will not be listed in the returned dictionary. |
Example
```python from bitbully import BitBully, Board
agent = BitBully() board = Board() scores = agent.score_all_moves(board) assert scores == {3: 1, 2: 0, 4: 0, 1: -1, 5: -1, 0: -2, 6: -2} # Center column is best on an empty board
Example:
When a column is full, it is omitted from the results:
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board(6 * "3") # fill center column
scores = agent.score_all_moves(board)
assert scores == {2: 1, 4: 1, 1: 0, 5: 0, 0: -1, 6: -1} # Column 3 is full and thus omitted
Source code in src/bitbully/solver.py
score_move
Evaluate a single move for the given board state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Board
|
The current board state. |
required |
|
int
|
Column index (0-6) of the move to evaluate. |
required |
|
int
|
Initial guess for the score (often 0). |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The evaluation score of the move. |
Example
Raises:
| Type | Description |
|---|---|
ValueError
|
If the column is outside the valid range or if the column is full. |
Notes
- This is a wrapper around
bitbully_core.BitBullyCore.scoreMove.