|
| None | __init__ (self, OpeningBookName|None opening_book="default", *, TieBreakStrategy|None tie_break=None, random.Random|None rng=None, int max_depth=-1) |
| str | __repr__ (self) |
| bool | is_book_loaded (self) |
| None | reset_transposition_table (self) |
| int | get_node_counter (self) |
| None | reset_node_counter (self) |
| int | score_move (self, Board board, int column, int first_guess=0, *, int|None max_depth=None) |
| dict[int, int] | score_all_moves (self, Board board, *, int|None max_depth=None) |
| int | best_move (self, Board board, *, TieBreakStrategy|None tie_break=None, random.Random|None rng=None, int|None max_depth=None) |
| int | negamax (self, Board board, int alpha=-1000, int beta=1000, int depth=0, *, int|None max_depth=None) |
| int | null_window (self, Board board, *, int|None max_depth=None) |
| int | mtdf (self, Board board, int first_guess=0, *, int|None max_depth=None) |
| None | load_book (self, OpeningBookName|os.PathLike[str]|str book) |
| None | reset_book (self) |
| tuple[OpeningBookName,...] | available_opening_books (cls) |
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`][src.bitbully.bitbully_core.BitBullyCore].
It integrates the packaged *BitBully Databases* opening books and
operates on [`bitbully.Board`][src.bitbully.board.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:
```python
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:
```python
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:
```
{3: 1, 2: 0, 4: 0, 1: -1, 5: -1, 0: -2, 6: -2}
```
Definition at line 42 of file solver.py.
| int bitbully.solver.BitBully.best_move |
( |
| self, |
|
|
Board | board, |
|
|
* | , |
|
|
TieBreakStrategy | None | tie_break = None, |
|
|
random.Random | None | rng = None, |
|
|
int | None | max_depth = None ) |
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 optional
``rng`` can be provided for reproducibility.
Args:
board (Board): The current board state.
tie_break (TieBreakStrategy | None):
Strategy used to resolve ties between equally scoring moves.
rng (random.Random | None):
Random number generator used when ``tie_break="random"``.
If ``None``, the agent's (`self.rng`) RNG is used.
max_depth (int | None): Maximum search depth override.
``None`` uses the instance default (``self.max_depth``).
Returns:
int: The selected column index (0-6).
Raises:
ValueError: If there are no legal moves (board is full) or
if an unknown tie-breaking strategy is specified.
Example:
```python
from bitbully import BitBully, Board
import random
agent = BitBully()
board = Board()
best_col = agent.best_move(board)
assert best_col == 3 # Center column is best on an empty board
```
Example:
```python
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}
```
Expected Output:
```
_ _ _ _ _ _ _
_ _ _ _ _ _ _
_ _ _ _ _ _ _
_ _ _ _ _ _ _
_ _ _ _ _ _ _
_ X _ X O _ _
```
Example:
Use depth-limited search for a fast (approximate) best move:
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board()
col = agent.best_move(board, max_depth=12)
assert 0 <= col <= 6
```
Definition at line 297 of file solver.py.
| dict[int, int] bitbully.solver.BitBully.score_all_moves |
( |
| self, |
|
|
Board | board, |
|
|
* | , |
|
|
int | None | max_depth = None ) |
Score all legal moves for the given board state.
Args:
board (Board): The current board state.
max_depth (int | None): Maximum search depth override.
``None`` uses the instance default (``self.max_depth``).
Returns:
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
```
Example:
Use depth-limited search for faster (approximate) scoring:
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board()
scores = agent.score_all_moves(board, max_depth=12)
assert len(scores) == 7 # all columns playable on an empty board
```
Definition at line 244 of file solver.py.
| int bitbully.solver.BitBully.score_move |
( |
| self, |
|
|
Board | board, |
|
|
int | column, |
|
|
int | first_guess = 0, |
|
|
* | , |
|
|
int | None | max_depth = None ) |
Evaluate a single move for the given board state.
Args:
board (Board): The current board state.
column (int): Column index (0-6) of the move to evaluate.
first_guess (int): Initial guess for the score (often 0).
max_depth (int | None): Maximum search depth override.
``None`` uses the instance default (``self.max_depth``).
Returns:
int: The evaluation score of the move.
Example:
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board()
score = agent.score_move(board, column=3)
assert score == 1 # Score for the center column on an empty board
```
Example:
Score a move using depth-limited search (faster, approximate):
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board()
score = agent.score_move(board, column=3, max_depth=12)
assert isinstance(score, int)
```
Raises:
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`][src.bitbully.bitbully_core.BitBullyCore.scoreMove].
Definition at line 197 of file solver.py.
| int bitbully.solver.BitBully.score_to_moves_left |
( |
int | score, |
|
|
Board | board ) |
|
static |
Convert a solver score to the number of moves until the game ends.
Given a score returned by the solver (e.g., from
:meth:`mtdf` or :meth:`score_all_moves`) and the board state,
returns how many moves remain until the game concludes under
perfect play.
Args:
score (int): The solver score. Positive means the current player
wins; negative means the current player loses; 0 means draw.
board (Board): The board state for which the score was computed.
Returns:
int: Number of moves remaining until the game ends.
Example:
A draw uses all remaining moves:
```python
from bitbully import BitBully, Board
board = Board() # empty board, 42 moves left
assert BitBully.score_to_moves_left(0, board) == 42
```
Example:
Maximum score means the current player wins on the very next move:
```python
from bitbully import BitBully, Board
board = Board()
assert BitBully.score_to_moves_left(21, board) == 1
```
Example:
Combining with the solver to find how many moves until
the game is decided:
```python
from bitbully import BitBully, Board
agent = BitBully()
board = Board()
score = agent.mtdf(board)
moves_until_end = BitBully.score_to_moves_left(score, board)
assert moves_until_end == 41 # yellow barely wins
```
Definition at line 535 of file solver.py.