BitBully 0.0.59-a2
Loading...
Searching...
No Matches
bitbully.board.Board Class Reference

Public Member Functions

None __init__ (self, Sequence[Sequence[int]]|Sequence[int]|str|None init_with=None)
bool __eq__ (self, object value)
bool __ne__ (self, object value)
str __repr__ (self)
str __str__ (self)
list[Boardall_positions (self, int up_to_n_ply, bool exactly_n)
bool can_win_next (self, int|None move=None)
Board copy (self)
int count_tokens (self)
bool has_win (self)
int __hash__ (self)
bool is_legal_move (self, int move)
Board mirror (self)
int moves_left (self)
bool play (self, int|Sequence[int]|str move)
Board play_on_copy (self, int move)
bool reset_board (self, Sequence[int]|Sequence[Sequence[int]]|str|None board=None)
list[list[int]] to_array (self, bool column_major_layout=True)
str to_string (self)
int uid (self)
int current_player (self)
bool is_full (self)
bool is_game_over (self)
int|None winner (self)
Board from_moves (cls, Sequence[int]|str moves)
Board from_array (cls, Sequence[Sequence[int]] arr)
int to_huffman (self)
list[int] legal_moves (self, bool non_losing=False, bool order_moves=False)

Static Public Member Functions

tuple[Board, list[int]] random_board (int n_ply, bool forbid_direct_win)

Public Attributes

int current_player = 1 else 1

Protected Attributes

 _board = bitbully_core.BoardCore()

Detailed Description

Represents the state of a Connect Four board. Mostly a thin wrapper around BoardCore.

Definition at line 11 of file board.py.

Constructor & Destructor Documentation

◆ __init__()

None bitbully.board.Board.__init__ ( self,
Sequence[Sequence[int]] | Sequence[int] | str | None init_with = None )
Initializes a Board instance.

Args:
    init_with (Sequence[Sequence[int]] | Sequence[int] | str | None):
        Optional initial board state. Accepts:
        - 2D array (list, tuple, numpy-array) with shape 7x6 or 6x7
        - 1D sequence of ints: a move sequence of columns (e.g., [0, 0, 2, 2, 3, 3])
        - String: A move sequence of columns as string (e.g., "002233")
        - None for an empty board

Raises:
    ValueError: If the provided initial board state is invalid.

Example:
    You can initialize an empty board in multiple ways:
    ```python
    import bitbully as bb

    # Create an empty board using the default constructor.
    board = bb.Board()  # Starts with no tokens placed.

    # Alternatively, initialize the board explicitly from a 2D list.
    # Each inner list represents a column (7 columns total, 6 rows each).
    # A value of 0 indicates an empty cell; 1 and 2 would represent player tokens.
    board = bb.Board([[0] * 6 for _ in range(7)])  # Equivalent to an empty board.

    # You can also set up a specific board position manually using a 6 x 7 layout,
    # where each inner list represents a row instead of a column.
    # (Both layouts are accepted by BitBully for convenience.)
    # For more complex examples using 2D arrays, see the examples below.
    board = bb.Board([[0] * 7 for _ in range(6)])  # Also equivalent to an empty board.

    # Display the board in text form.
    # The __repr__ method shows the current state (useful for debugging or interactive use).
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    ```

The recommended way to initialize an empty board is simply `Board()`.

Example:
    You can also initialize a board with a sequence of moves:
    ```python
    import bitbully as bb

    # Initialize a board with a sequence of moves played in the center column.

    # The list [3, 3, 3] represents three moves in column index 3 (zero-based).
    # Moves alternate automatically between Player 1 (yellow, X) and Player 2 (red, O).
    # After these three moves, the center column will contain:
    #   - Row 0: Player 1 token (bottom)
    #   - Row 1: Player 2 token
    #   - Row 2: Player 1 token
    board = bb.Board([3, 3, 3])

    # Display the resulting board.
    # The textual output shows the tokens placed in the center column.
    board
    ```

    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    ```

Example:
    You can also initialize a board using a string containing a move sequence:
    ```python
    import bitbully as bb

    # Initialize a board using a compact move string.

    # The string "33333111" represents a sequence of eight moves:
    #   3 3 3 3 3 → five moves in the center column (index 3)
    #   1 1 1 → three moves in the second column (index 1)
    #
    # Moves are applied in order, alternating automatically between Player 1 (yellow, X)
    # and Player 2 (red, O), just as if you had called `board.play()` repeatedly.
    #
    # This shorthand is convenient for reproducing board states or test positions
    # without having to provide long move lists.

    board = bb.Board("33333111")

    # Display the resulting board.
    # The printed layout shows how the tokens stack in each column.
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Example:
    You can also initialize a board using a 2D array (list of lists):
    ```python
    import bitbully as bb

    # Use a 6 x 7 list (rows x columns) to set up a specific board position manually.

    # Each inner list represents a row of the Connect-4 grid.
    # Convention:
    #   - 0 → empty cell
    #   - 1 → Player 1 token (yellow, X)
    #   - 2 → Player 2 token (red, O)
    #
    # The top list corresponds to the *top row* (row index 5),
    # and the bottom list corresponds to the *bottom row* (row index 0).
    # This layout matches the typical visual display of the board.

    board_array = [
        [0, 0, 0, 0, 0, 0, 0],  # Row 5 (top)
        [0, 0, 0, 1, 0, 0, 0],  # Row 4: Player 1 token in column 3
        [0, 0, 0, 2, 0, 0, 0],  # Row 3: Player 2 token in column 3
        [0, 2, 0, 1, 0, 0, 0],  # Row 2: tokens in columns 1 and 3
        [0, 1, 0, 2, 0, 0, 0],  # Row 1: tokens in columns 1 and 3
        [0, 2, 0, 1, 0, 0, 0],  # Row 0 (bottom): tokens stacked lowest
    ]

    # Create a Board instance directly from the 2D list.
    # This allows reconstructing arbitrary positions (e.g., from test data or saved states)
    # without replaying the move sequence.
    board = bb.Board(board_array)

    # Display the resulting board state in text form.
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Example:
    You can also initialize a board using a 2D (7 x 6) array with columns as inner lists:
    ```python
    import bitbully as bb

    # Use a 7 x 6 list (columns x rows) to set up a specific board position manually.

    # Each inner list represents a **column** of the Connect-4 board, from left (index 0)
    # to right (index 6). Each column contains six entries — one for each row, from
    # bottom (index 0) to top (index 5).
    #
    # Convention:
    #   - 0 → empty cell
    #   - 1 → Player 1 token (yellow, X)
    #   - 2 → Player 2 token (red, O)
    #
    # This column-major layout matches the internal representation used by BitBully,
    # where tokens are dropped into columns rather than filled row by row.

    board_array = [
        [0, 0, 0, 0, 0, 0],  # Column 0 (leftmost)
        [2, 1, 2, 0, 0, 0],  # Column 1
        [0, 0, 0, 0, 0, 0],  # Column 2
        [1, 2, 1, 2, 1, 0],  # Column 3 (center)
        [0, 0, 0, 0, 0, 0],  # Column 4
        [0, 0, 0, 0, 0, 0],  # Column 5
        [0, 0, 0, 0, 0, 0],  # Column 6 (rightmost)
    ]

    # Create a Board instance directly from the 2D list.
    # This allows reconstructing any arbitrary position (e.g., test cases, saved games)
    # without replaying all moves individually.
    board = bb.Board(board_array)

    # Display the resulting board.
    # The text output shows tokens as they would appear in a real Connect-4 grid.
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Definition at line 14 of file board.py.

Member Function Documentation

◆ __eq__()

bool bitbully.board.Board.__eq__ ( self,
object value )
Checks equality between two Board instances.

Notes:
    - Equality checks in BitBully compare the *exact board state* (bit patterns),
      not just the move history.
    - Two different move sequences can still yield the same position if they
      result in identical token configurations.
    - This is useful for comparing solver states, verifying test positions,
      or detecting transpositions in search algorithms.

Args:
    value (object): The other Board instance to compare against.

Returns:
    bool: True if both boards are equal, False otherwise.

Raises:
    NotImplementedError: If the other value is not a Board instance.

Example:
    ```python
    import bitbully as bb

    # Create two boards that should represent *identical* game states.
    board1 = bb.Board()
    assert board1.play("33333111")

    board2 = bb.Board()
    # Play the same position step by step using a different but equivalent sequence.
    # Internally, the final bitboard state will match `board1`.
    assert board2.play("31133331")

    # Boards with identical token placements are considered equal.
    # Equality (`==`) and inequality (`!=`) operators are overloaded for convenience.
    assert board1 == board2
    assert not (board1 != board2)

    # ------------------------------------------------------------------------------

    # Create two boards that differ by one move.
    board1 = bb.Board("33333111")
    board2 = bb.Board("33333112")  # One extra move in the last column (index 2)

    # Since the token layout differs, equality no longer holds.
    assert board1 != board2
    assert not (board1 == board2)
    ```

Definition at line 224 of file board.py.

◆ __hash__()

int bitbully.board.Board.__hash__ ( self)
Returns a hash of the Board instance for use in hash-based collections.

Returns:
    int: The hash value of the Board instance.

Example:
    ```python
    import bitbully as bb

    # Create two boards that represent the same final position.
    # The first board is initialized directly from a move string.
    board1 = bb.Board("33333111")

    # The second board is built incrementally by playing an equivalent sequence of moves.
    # Even though the order of intermediate plays differs, the final layout of tokens
    # (and thus the internal bitboard state) will be identical to `board1`.
    board2 = bb.Board()
    board2.play("31133331")

    # Boards with identical configurations produce the same hash value.
    # This allows them to be used efficiently as keys in dictionaries or members of sets.
    assert hash(board1) == hash(board2)

    # Display the board's hash value.
    hash(board1)
    ```
    Expected output:
    ```text
    971238920548618160
    ```

Definition at line 639 of file board.py.

◆ __ne__()

bool bitbully.board.Board.__ne__ ( self,
object value )
Checks inequality between two Board instances.

See the documentation for [`bitbully.Board.__eq__`][src.bitbully.Board.__eq__] for details.

Args:
    value (object): The other Board instance to compare against.

Returns:
    bool: True if both boards are not equal, False otherwise.

Definition at line 277 of file board.py.

◆ __repr__()

str bitbully.board.Board.__repr__ ( self)
Returns a string representation of the Board instance.

Definition at line 290 of file board.py.

◆ __str__()

str bitbully.board.Board.__str__ ( self)
Return a human-readable ASCII representation (same as to_string()).

See the documentation for [`bitbully.Board.to_string`][src.bitbully.Board.to_string] for details.

Definition at line 294 of file board.py.

◆ all_positions()

list[Board] bitbully.board.Board.all_positions ( self,
int up_to_n_ply,
bool exactly_n )
Find all positions reachable from the current position up to a given ply.

This is a high-level wrapper around
`bitbully_core.BoardCore.allPositions`.

Starting from the **current** board, it generates all positions that can be
reached by playing additional moves such that the resulting position has:

- At most ``up_to_n_ply`` tokens on the board, if ``exactly_n`` is ``False``.
- Exactly ``up_to_n_ply`` tokens on the board, if ``exactly_n`` is ``True``.

Note:
    The number of tokens already present in the current position is taken
    into account. If ``up_to_n_ply`` is smaller than
    ``self.count_tokens()``, the result is typically empty.

    This function can grow combinatorially with ``up_to_n_ply`` and the
    current position, so use it with care for large depths.

Args:
    up_to_n_ply (int):
        The maximum total number of tokens (ply) for generated positions.
        Must be between 0 and 42 (inclusive).
    exactly_n (bool):
        If ``True``, only positions with exactly ``up_to_n_ply`` tokens
        are returned. If ``False``, all positions with a token count
        between the current number of tokens and ``up_to_n_ply`` are
        included.

Returns:
    list[Board]: A list of :class:`Board` instances representing all
    reachable positions that satisfy the ply constraint.

Raises:
    ValueError: If ``up_to_n_ply`` is outside the range ``[0, 42]``.

Example:
    Compute all positions at exactly 3 ply from the empty board:

    ```python
    import bitbully as bb

    # Start from an empty board.
    board = bb.Board()

    # Generate all positions that contain exactly 3 tokens.
    positions = board.all_positions(3, exactly_n=True)

    # According to OEIS A212693, there are exactly 238 distinct
    # reachable positions with 3 played moves in standard Connect-4.
    assert len(positions) == 238
    ```

    Reference:
        - Number of distinct positions at ply *n*:
          https://oeis.org/A212693

Definition at line 301 of file board.py.

◆ can_win_next()

bool bitbully.board.Board.can_win_next ( self,
int | None move = None )
Checks if the current player can win in the next move.

Args:
    move (int | None): Optional column to check for an immediate win. If None, checks all columns.

Returns:
    bool: True if the current player can win next, False otherwise.

See also: [`bitbully.Board.has_win`][src.bitbully.Board.has_win].

Example:
    ```python
    import bitbully as bb

    # Create a board from a move string.
    # The string "332311" represents a short sequence of alternating moves
    # that results in a nearly winning position for Player 1 (yellow, X).
    board = bb.Board("332311")

    # Display the current board state (see below)
    print(board)

    # Player 1 (yellow, X) — who is next to move — can win immediately
    # by placing a token in either column 0 or column 4.
    assert board.can_win_next(0)
    assert board.can_win_next(4)

    # However, playing in other columns does not result in an instant win.
    assert not board.can_win_next(2)
    assert not board.can_win_next(3)

    # You can also call `can_win_next()` without arguments to perform a general check.
    # It returns True if the current player has *any* winning move available.
    assert board.can_win_next()
    ```
    The board we created above looks like this:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  O  _  _  _
    _  O  _  O  _  _  _
    _  X  X  X  _  _  _
    ```

Definition at line 375 of file board.py.

◆ copy()

Board bitbully.board.Board.copy ( self)
Creates a copy of the current Board instance.

The `copy()` method returns a new `Board` object that represents the
*same position* as the original at the time of copying. Subsequent
changes to one board do **not** affect the other — they are completely
independent.

Returns:
    Board: A new Board instance that is a copy of the current one.

Example:
    Create a board, copy it, and verify that both represent the same position:
    ```python
    import bitbully as bb

    # Create a board from a compact move string.
    board = bb.Board("33333111")

    # Create an independent copy of the current position.
    board_copy = board.copy()

    # Both boards represent the same position and are considered equal.
    assert board == board_copy
    assert hash(board) == hash(board_copy)
    assert board.to_string() == board_copy.to_string()

    # Display the board state.
    print(board)
    ```
    Expected output (both boards print the same position):
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Example:
    Modifying the copy does not affect the original:
    ```python
    import bitbully as bb

    board = bb.Board("33333111")

    # Create a copy of the current position.
    board_copy = board.copy()

    # Play an additional move on the copied board only.
    assert board_copy.play(0)  # Drop a token into the leftmost column.

    # Now the boards represent different positions.
    assert board != board_copy

    # The original board remains unchanged.
    print("Original:")
    print(board)

    print("Modified copy:")
    print(board_copy)
    ```
    Expected output:
    ```text
    Original:

    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _

    Modified copy:

    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    X  O  _  X  _  _  _
    ```

Definition at line 425 of file board.py.

◆ count_tokens()

int bitbully.board.Board.count_tokens ( self)
Counts the total number of tokens currently placed on the board.

This method simply returns how many moves have been played so far in the
current position — that is, the number of occupied cells on the 7x6 grid.

It does **not** distinguish between players; it only reports the total
number of tokens, regardless of whether they belong to Player 1 or Player 2.

Returns:
    int: The total number of tokens on the board (between 0 and 42).

Example:
    Count tokens on an empty board:
    ```python
    import bitbully as bb

    board = bb.Board()  # No moves played yet.
    assert board.count_tokens() == 0

    # The board is completely empty.
    print(board)
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    ```

Example:
    Count tokens after a few moves:
    ```python
    import bitbully as bb

    # Play three moves in the center column (index 3).
    board = bb.Board()
    assert board.play([3, 3, 3])

    # Three tokens have been placed on the board.
    assert board.count_tokens() == 3

    print(board)
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    ```

Example:
    Relation to the length of a move sequence:
    ```python
    import bitbully as bb

    moves = "33333111"  # 8 moves in total
    board = bb.Board(moves)

    # The number of tokens on the board always matches
    # the number of moves that have been played.
    # (as long as the input was valid)
    assert board.count_tokens() == len(moves)
    ```

Definition at line 513 of file board.py.

◆ current_player()

int bitbully.board.Board.current_player ( self)
Returns the player whose turn it is to move.

The current player is derived from the **parity** of the number of tokens
on the board:

- Player 1 (yellow, ``X``) moves first on an empty board.
- After an even number of moves → it is Player 1's turn.
- After an odd  number of moves → it is Player 2's turn.

Returns:
    int:
        The player to move:

        - ``1`` → Player 1 (yellow, ``X``)
        - ``2`` → Player 2 (red, ``O``)

Example:
    ```python
    import bitbully as bb

    # Empty board → Player 1 starts.
    board = bb.Board()
    assert board.current_player == 1
    assert board.count_tokens() == 0

    # After one move, it's Player 2's turn.
    assert board.play(3)
    assert board.count_tokens() == 1
    assert board.current_player == 2

    # After a second move, it's again Player 1's turn.
    assert board.play(4)
    assert board.count_tokens() == 2
    assert board.current_player == 1
    ```

Definition at line 1379 of file board.py.

◆ from_array()

Board bitbully.board.Board.from_array ( cls,
Sequence[Sequence[int]] arr )
Creates a board directly from a 2D array representation.

This is a convenience wrapper around the main constructor
[`bitbully.Board.__init__`][src.bitbully.Board.__init__]
and accepts the same array formats:

- **Row-major**: 6 x 7 (``[row][column]``), top row first.
- **Column-major**: 7 x 6 (``[column][row]``), left column first.

Values must follow the usual convention:

- ``0`` → empty cell
- ``1`` → Player 1 token (yellow, ``X``)
- ``2`` → Player 2 token (red, ``O``)

Args:
    arr (Sequence[Sequence[int]]):
        A 2D array describing the board state, either in row-major or
        column-major layout. See the examples in
        [`bitbully.Board.__init__`][src.bitbully.Board.__init__] for details.

Returns:
    Board:
        A new `Board` instance representing the given layout.

Example:
    Using a 6 x 7 row-major layout:
    ```python
    import bitbully as bb

    board_array = [
        [0, 0, 0, 0, 0, 0, 0],  # Row 5 (top)
        [0, 0, 0, 1, 0, 0, 0],  # Row 4
        [0, 0, 0, 2, 0, 0, 0],  # Row 3
        [0, 2, 0, 1, 0, 0, 0],  # Row 2
        [0, 1, 0, 2, 0, 0, 0],  # Row 1
        [0, 2, 0, 1, 0, 0, 0],  # Row 0 (bottom)
    ]

    board = bb.Board.from_array(board_array)
    print(board)
    ```

Example:
    Using a 7 x 6 column-major layout:
    ```python
    import bitbully as bb

    board_array = [
        [0, 0, 0, 0, 0, 0],  # Column 0
        [2, 1, 2, 1, 0, 0],  # Column 1
        [0, 0, 0, 0, 0, 0],  # Column 2
        [1, 2, 1, 2, 1, 0],  # Column 3
        [0, 0, 0, 0, 0, 0],  # Column 4
        [2, 1, 2, 0, 0, 0],  # Column 5
        [0, 0, 0, 0, 0, 0],  # Column 6
    ]

    board = bb.Board.from_array(board_array)

    # Round-trip via to_array:
    assert board.to_array() == board_array
    ```

Definition at line 1600 of file board.py.

◆ from_moves()

Board bitbully.board.Board.from_moves ( cls,
Sequence[int] | str moves )
Creates a board by replaying a sequence of moves from the empty position.

This is a convenience constructor around [`bitbully.Board.play`][src.bitbully.Board.play].
It starts from an empty board and applies the given move sequence, assuming
it is **legal** (no out-of-range columns, no moves in full columns, etc.).

Args:
    moves (Sequence[int] | str):
        The move sequence to replay from the starting position. Accepts:

        - A sequence of integers (e.g. ``[3, 3, 3, 1]``)
        - A string of digits (e.g. ``"3331"``)

        Each value represents a column index (0-6). Players alternate
        automatically between moves.

Returns:
    Board:
        A new `Board` instance representing the final position
        after all moves have been applied.

Example:
    ```python
    import bitbully as bb

    # Create a position directly from a compact move string.
    board = bb.Board.from_moves("33333111")

    # Equivalent to:
    # board = bb.Board()
    # assert board.play("33333111")

    print(board)
    assert board.count_tokens() == 8
    assert not board.has_win()
    ```

Definition at line 1557 of file board.py.

◆ has_win()

bool bitbully.board.Board.has_win ( self)
Checks if the current player has a winning position.

Returns:
    bool: True if the current player has a winning position (4-in-a-row), False otherwise.

Unlike `can_win_next()`, which checks whether the current player *could* win
on their next move, the `has_win()` method determines whether a winning
condition already exists on the board.
This method is typically used right after a move to verify whether the game
has been won.

See also: [`bitbully.Board.can_win_next`][src.bitbully.Board.can_win_next].

Example:
    ```python
    import bitbully as bb

    # Initialize a board from a move sequence.
    # The string "332311" represents a position where Player 1 (yellow, X)
    # is one move away from winning.
    board = bb.Board("332311")

    # At this stage, Player 1 has not yet won, but can win immediately
    # by placing a token in either column 0 or column 4.
    assert not board.has_win()
    assert board.can_win_next(0)  # Check column 0
    assert board.can_win_next(4)  # Check column 4
    assert board.can_win_next()  # General check (any winning move)

    # Simulate Player 1 playing in column 4 — this completes
    # a horizontal line of four tokens and wins the game.
    assert board.play(4)

    # Display the updated board to visualize the winning position.
    print(board)

    # The board now contains a winning configuration:
    # Player 1 (yellow, X) has achieved a Connect-4.
    assert board.has_win()
    ```
    Board from above, expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  O  _  _  _
    _  O  _  O  _  _  _
    _  X  X  X  X  _  _
    ```

Definition at line 586 of file board.py.

◆ is_full()

bool bitbully.board.Board.is_full ( self)
Checks whether the board has any empty cells left.

A Connect Four board has 42 cells in total (7 columns x 6 rows).
This method returns ``True`` if **all** cells are occupied, i.e.
when  [`bitbully.Board.moves_left`][src.bitbully.Board.moves_left] returns ``0``.

Returns:
    bool:
        ``True`` if the board is completely full
        (no more legal moves possible), otherwise ``False``.

Example:
    ```python
    import bitbully as bb

    board = bb.Board()
    assert not board.is_full()
    assert board.moves_left() == 42
    assert board.count_tokens() == 0

    # Fill the board column by column.
    for _ in range(6):
        assert board.play("0123456")  # one token per column, per row

    # Now every cell is occupied.
    assert board.is_full()
    assert board.moves_left() == 0
    assert board.count_tokens() == 42
    ```

Definition at line 1419 of file board.py.

◆ is_game_over()

bool bitbully.board.Board.is_game_over ( self)
Checks whether the game has ended (win or draw).

A game of Connect Four is considered **over** if:

- One of the players has a winning position
  (see [`bitbully.Board.has_win`][src.bitbully.Board.has_win]), **or**
- The board is completely full and no further moves can be played
  (see [`bitbully.Board.is_full`][src.bitbully.Board.is_full]).

This method does **not** indicate *who* won; for that, use
[`bitbully.Board.winner`][src.bitbully.Board.winner].

Returns:
    bool:
        ``True`` if the game is over (win or draw), otherwise ``False``.

Example:
    Game over by a win:
    ```python
    import bitbully as bb

    # Player 1 (X) wins horizontally on the bottom row.
    board = bb.Board()
    assert board.play("0101010")

    assert board.has_win()
    assert board.is_game_over()
    assert board.winner() == 1
    ```

Example:
    Game over by a draw (full board, no winner):
    ```python
    import bitbully as bb

    board, _ = bb.Board.random_board(42, forbid_direct_win=False)

    assert board.is_full()
    assert not board.has_win()
    assert board.is_game_over()
    assert board.winner() is None
    ```

Definition at line 1452 of file board.py.

◆ is_legal_move()

bool bitbully.board.Board.is_legal_move ( self,
int move )
Checks if a move (column) is legal in the current position.

A move is considered *legal* if:

- The column index is within the valid range (0-6), **and**
- The column is **not full** (i.e. it still has at least one empty cell).

This method does **not** check for tactical consequences such as
leaving an immediate win to the opponent, nor does it stop being
usable once a player has already won. It purely validates whether a
token can be dropped into the given column according to the basic
rules of Connect Four. You have to check for wins separately using
[`bitbully.Board.has_win`][src.bitbully.Board.has_win].


Args:
    move (int): The column index (0-6) to check.

Returns:
    bool: True if the move is legal, False otherwise.

Example:
    All moves are legal on an empty board:
    ```python
    import bitbully as bb

    board = bb.Board()  # Empty 7x6 board

    # Every column index from 0 to 6 is a valid move.
    for col in range(7):
        assert board.is_legal_move(col)

    # Out-of-range indices are always illegal.
    assert not board.is_legal_move(-1)
    assert not board.is_legal_move(7)
    ```

Example:
    Detecting an illegal move in a full column:
    ```python
    import bitbully as bb

    # Fill the center column (index 3) with six tokens.
    board = bb.Board()
    assert board.play([3, 3, 3, 3, 3, 3])

    # The center column is now full, so another move in column 3 is illegal.
    assert not board.is_legal_move(3)

    # Other columns are still available (as long as they are not full).
    assert board.is_legal_move(0)
    assert board.is_legal_move(6)

    print(board)
    ```
    Expected output:
    ```text
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    ```

Example:
    This function only checks legality, not for situations where a player has won:
    ```python
    import bitbully as bb

    # Player 1 (yellow, X) wins  the game.
    board = bb.Board()
    assert board.play("1122334")

    # Even though Player 1 has already won, moves in non-full columns are still legal.
    for col in range(7):
        assert board.is_legal_move(col)

    print(board)
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  O  O  O  _  _  _
    _  X  X  X  X  _  _
    ```

Definition at line 673 of file board.py.

◆ legal_moves()

list[int] bitbully.board.Board.legal_moves ( self,
bool non_losing = False,
bool order_moves = False )
Returns a list of all legal moves (non-full columns) for the current board state.

Args:
    non_losing (bool):
        If ``True``, only returns moves that do **not** allow the opponent
        to win immediately on their next turn. The list might be empty
        If ``False``, all legal moves are returned.
    order_moves (bool):
        If ``True``, the returned list is ordered to prioritize moves (potentially more promising first).

Returns:
    list[int]: A list of column indices (0-6) where a token can be legally dropped.

Example:
    ```python
    import bitbully as bb

    board = bb.Board()
    legal_moves = board.legal_moves()
    assert set(legal_moves) == set(range(7))  # All columns are initially legal
    assert set(legal_moves) == set(board.legal_moves(order_moves=True))
    board.legal_moves(order_moves=True) == [3, 2, 4, 1, 5, 0, 6]  # Center column prioritized
    ```

Definition at line 1809 of file board.py.

◆ mirror()

Board bitbully.board.Board.mirror ( self)
Returns a new Board instance that is the mirror image of the current board.

This method reflects the board **horizontally** around its vertical center column:
- Column 0 <-> Column 6
- Column 1 <-> Column 5
- Column 2 <-> Column 4
- Column 3 stays in the center

The player to move is not changed - only the spatial
arrangement of the tokens is mirrored. The original board remains unchanged;
`mirror()` always returns a **new** `Board` instance.

Returns:
    Board: A new Board instance that is the mirror image of the current one.

Example:
    Mirroring a simple asymmetric position:
    ```python
    import bitbully as bb

    # Play four moves along the bottom row.
    board = bb.Board()
    assert board.play("0123")  # Columns: 0, 1, 2, 3

    # Create a mirrored copy of the board.
    mirrored = board.mirror()

    print("Original:")
    print(board)

    print("Mirrored:")
    print(mirrored)
    ```

    Expected output:
    ```text
    Original:

    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    X  O  X  O  _  _  _

    Mirrored:

    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  O  X  O  X
    ```

Example:
    Mirroring a position that is already symmetric:
    ```python
    import bitbully as bb

    # Central symmetry: one token in each outer column and in the center.
    board = bb.Board([1, 3, 5])

    mirrored = board.mirror()

    # The mirrored position is identical to the original.
    assert board == mirrored
    assert hash(board) == hash(mirrored)

    print(board)
    ```
     Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  X  _  O  _  X  _
    ```

Definition at line 766 of file board.py.

◆ moves_left()

int bitbully.board.Board.moves_left ( self)
Returns the number of moves left until the board is full.

This is simply the number of *empty* cells remaining on the 7x6 grid.
On an empty board there are 42 free cells, so:

- At the start of the game: `moves_left() == 42`
- After `n` valid moves: `moves_left() == 42 - n`
- On a completely full board: `moves_left() == 0`

This method is equivalent to:
```
42 - board.count_tokens()
```
but implemented efficiently in the underlying C++ core.

Returns:
    int: The number of moves left (0-42).

Example:
    Moves left on an empty board:
    ```python
    import bitbully as bb

    board = bb.Board()  # No tokens placed yet.
    assert board.moves_left() == 42
    assert board.count_tokens() == 0
    ```

Example:
    Relation to the number of moves played:
    ```python
    import bitbully as bb

    # Play five moves in various columns.
    moves = [3, 3, 1, 4, 6]
    board = bb.Board()
    assert board.play(moves)

    # Five tokens have been placed, so 42 - 5 = 37 moves remain.
    assert board.count_tokens() == 5
    assert board.moves_left() == 37
    assert board.moves_left() + board.count_tokens() == 42
    ```

Definition at line 852 of file board.py.

◆ play()

bool bitbully.board.Board.play ( self,
int | Sequence[int] | str move )
Plays one or more moves for the current player.

The method updates the internal board state by dropping tokens
into the specified columns. Input can be:
- a single integer (column index 0 to 6),
- an iterable sequence of integers (e.g., `[3, 1, 3]` or `range(7)`),
- or a string of digits (e.g., `"33333111"`) representing the move order.

Args:
    move (int | Sequence[int] | str):
        The column index or sequence of column indices where tokens should be placed.

Returns:
    bool: True if the move was played successfully, False if the move was illegal.


Example:
    Play a sequence of moves into the center column (column index 3):
    ```python
    import bitbully as bb

    board = bb.Board()
    assert board.play([3, 3, 3])  # returns True on successful move
    board
    ```

    Expected output:

    ```
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    ```

Example:
    Play a sequence of moves across all columns:
    ```python
    import bitbully as bb

    board = bb.Board()
    assert board.play(range(7))  # returns True on successful move
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    X  O  X  O  X  O  X
    ```

Example:
    Play a sequence using a string:
    ```python
    import bitbully as bb

    board = bb.Board()
    assert board.play("33333111")  # returns True on successful move
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Definition at line 899 of file board.py.

◆ play_on_copy()

Board bitbully.board.Board.play_on_copy ( self,
int move )
Return a new board with the given move applied, leaving the current board unchanged.

Args:
    move (int):
        The column index (0-6) in which to play the move.

Returns:
    Board:
        A new Board instance representing the position after the move.

Raises:
    ValueError: If the move is illegal (e.g. column is full or out of range).

Example:
    ```python
    import bitbully as bb

    board = bb.Board("333")  # Some existing position
    new_board = board.play_on_copy(4)

    # The original board is unchanged.
    assert board.count_tokens() == 3

    # The returned board includes the new move.
    assert new_board.count_tokens() == 4
    assert new_board != board
    ```

Definition at line 987 of file board.py.

◆ random_board()

tuple[Board, list[int]] bitbully.board.Board.random_board ( int n_ply,
bool forbid_direct_win )
static
Generates a random board state by playing a specified number of random moves.

If ``forbid_direct_win`` is ``True``, the generated position is guaranteed
**not** to contain an immediate winning move for the player to move.

Args:
    n_ply (int):
        Number of random moves to simulate (0-42).
    forbid_direct_win (bool):
        If ``True``, ensures the resulting board has **no immediate winning move**.

Returns:
    tuple[Board, list[int]]:
        A pair ``(board, moves)`` where ``board`` is the generated position
        and ``moves`` are the exact random moves performed.

Raises:
    ValueError: If `n_ply` is outside the valid range [0, 42].

Example:
    Basic usage:
    ```python
    import bitbully as bb

    board, moves = bb.Board.random_board(10, forbid_direct_win=True)

    print("Moves:", moves)
    print("Board:")
    print(board)

    # The move list must match the requested ply.
    assert len(moves) == 10

    # No immediate winning move when forbid_direct_win=True.
    assert not board.can_win_next()
    ```

Example:
    Using random boards in tests or simulations:
    ```python
    import bitbully as bb

    # Generate 50 random 10-ply positions.
    for _ in range(50):
        board, moves = bb.Board.random_board(10, forbid_direct_win=True)
        assert len(moves) == 10
        assert not board.has_win()  # Game should not be over
        assert board.count_tokens() == 10  # All generated boards contain exactly 10 tokens
        assert not board.can_win_next()  # Since `forbid_direct_win=True`, no immediate threat
    ```

Example:
    Reconstructing the board manually from the move list:
    ```python
    import bitbully as bb

    b1, moves = bb.Board.random_board(8, forbid_direct_win=True)

    # Recreate the board using the move sequence:
    b2 = bb.Board(moves)

    assert b1 == b2
    assert b1.to_string() == b2.to_string()
    assert b1.uid() == b2.uid()
    ```

Example:
    Ensure randomness by generating many distinct sequences:
    ```python
    import bitbully as bb

    seen = set()
    for _ in range(20):
        _, moves = bb.Board.random_board(5, False)
        seen.add(tuple(moves))

    # Very likely to see more than one unique sequence.
    assert len(seen) > 1
    ```

Definition at line 1668 of file board.py.

◆ reset_board()

bool bitbully.board.Board.reset_board ( self,
Sequence[int] | Sequence[Sequence[int]] | str | None board = None )
Resets the board or sets (overrides) the board to a specific state.

Args:
    board (Sequence[int] | Sequence[Sequence[int]] | str | None):
        The new board state. Accepts:
        - 2D array (list, tuple, numpy-array) with shape 7x6 or 6x7
        - 1D sequence of ints: a move sequence of columns (e.g., [0, 0, 2, 2, 3, 3])
        - String: A move sequence of columns as string (e.g., "002233...")
        - None: to reset to an empty board

Returns:
    bool: True if the board was set successfully, False otherwise.

Example:
    Reset the board to an empty state:
    ```python
    import bitbully as bb

    # Create a temporary board position from a move string.
    # The string "0123456" plays one token in each column (0-6) in sequence.
    board = bb.Board("0123456")

    # Reset the board to an empty state.
    # Calling `reset_board()` clears all tokens and restores the starting position.
    # No moves → an empty board.
    assert board.reset_board()
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    ```

Example:
    (Re-)Set the board using a move sequence string:
    ```python
    import bitbully as bb

    # This is just a temporary setup; it will be replaced below.
    board = bb.Board("0123456")

    # Set the board state directly from a move sequence.
    # The list [3, 3, 3] represents three consecutive moves in the center column (index 3).
    # Moves alternate automatically between Player 1 (yellow) and Player 2 (red).
    #
    # The `reset_board()` method clears the current position and replays the given moves
    # from an empty board — effectively overriding any existing board state.
    assert board.reset_board([3, 3, 3])

    # Display the updated board to verify the new position.
    board
    ```
    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  _  _  X  _  _  _
    ```

Example:
    You can also set the board using other formats, such as a 2D array or a string.
    See the examples in the [`bitbully.Board.__init__`][src.bitbully.Board.__init__] docstring for details.

    ```python
    # Briefly demonstrate the different input formats accepted by `reset_board()`.
    import bitbully as bb

    # Create an empty board instance
    board = bb.Board()

    # Variant 1: From a list of moves (integers)
    # Each number represents a column index (0-6); moves alternate between players.
    assert board.reset_board([3, 3, 3])

    # Variant 2: From a compact move string
    # Equivalent to the list above — useful for quick testing or serialization.
    assert board.reset_board("33333111")

    # Variant 3: From a 2D list in row-major format (6 x 7)
    # Each inner list represents a row (top to bottom).
    # 0 = empty, 1 = Player 1, 2 = Player 2.
    board_array = [
        [0, 0, 0, 0, 0, 0, 0],  # Row 5 (top)
        [0, 0, 0, 1, 0, 0, 0],  # Row 4
        [0, 0, 0, 2, 0, 0, 0],  # Row 3
        [0, 2, 0, 1, 0, 0, 0],  # Row 2
        [0, 1, 0, 2, 0, 0, 0],  # Row 1
        [0, 2, 0, 1, 0, 0, 0],  # Row 0 (bottom)
    ]
    assert board.reset_board(board_array)

    # Variant 4: From a 2D list in column-major format (7 x 6)
    # Each inner list represents a column (left to right); this matches BitBully's internal layout.
    board_array = [
        [0, 0, 0, 0, 0, 0],  # Column 0 (leftmost)
        [2, 1, 2, 1, 0, 0],  # Column 1
        [0, 0, 0, 0, 0, 0],  # Column 2
        [1, 2, 1, 2, 1, 0],  # Column 3 (center)
        [0, 0, 0, 0, 0, 0],  # Column 4
        [2, 1, 2, 0, 0, 0],  # Column 5
        [0, 0, 0, 0, 0, 0],  # Column 6 (rightmost)
    ]
    assert board.reset_board(board_array)

    # Display the final board state in text form
    board
    ```

    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  O  _
    _  X  _  O  _  X  _
    _  O  _  X  _  O  _
    ```

Definition at line 1028 of file board.py.

◆ to_array()

list[list[int]] bitbully.board.Board.to_array ( self,
bool column_major_layout = True )
Returns the board state as a 2D array (list of lists).

This layout is convenient for printing, serialization, or converting
to a NumPy array for further analysis.

Args:
    column_major_layout (bool): Use column-major format if set to `True`,
        otherwise the row-major-layout is used.

Returns:
    list[list[int]]: A 7x6 2D list representing the board state.

Raises:
    NotImplementedError: If `column_major_layout` is set to `False`.

Example:
    === "Column-major Format:"

        The returned array is in **column-major** format with shape `7 x 6`
        (`[column][row]`):

        - There are 7 inner lists, one for each column of the board.
        - Each inner list has 6 integers, one for each row.
        - Row index `0` corresponds to the **bottom row**,
        row index `5` to the **top row**.
        - Convention:
        - `0` -> empty cell
        - `1` -> Player 1 token (yellow, X)
        - `2` -> Player 2 token (red, O)

        ```python
        import bitbully as bb
        from pprint import pprint

        # Create a position from a move sequence.
        board = bb.Board("33333111")

        # Extract the board as a 2D list (rows x columns).
        arr = board.to_array()

        # Reconstruct the same position from the 2D array.
        board2 = bb.Board(arr)

        # Both boards represent the same position.
        assert board == board2
        assert board.to_array() == board2.to_array()

        # print ther result of `board.to_array()`:
        pprint(board.to_array())
        ```
        Expected output:
        ```text
        [[0, 0, 0, 0, 0, 0],
        [2, 1, 2, 0, 0, 0],
        [0, 0, 0, 0, 0, 0],
        [1, 2, 1, 2, 1, 0],
        [0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0]]
        ```

    === "Row-major Format:"

        ``` markdown
        TODO: This is not supported yet
        ```

Definition at line 1172 of file board.py.

◆ to_huffman()

int bitbully.board.Board.to_huffman ( self)
Encode the current board position into a Huffman-compressed byte sequence.

This is a high-level wrapper around
`bitbully_core.BoardCore.toHuffman`. The returned int encodes the
exact token layout **and** the side to move using the same format as
the BitBully opening databases.

The encoding is:

- Deterministic: the same position always yields the same byte sequence.
- Compact: suitable for storage (of positions with little number of tokens),
  or lookups in the BitBully database format.

Returns:
    int: A Huffman-compressed representation of the current board
    state.

Raises:
    NotImplementedError:
        If the position does not contain exactly 8 or 12 tokens, as the
          Huffman encoding is only defined for these cases.

Example:
    Encode a position and verify that equivalent positions have the
    same Huffman code:

    ```python
    import bitbully as bb

    # Two different move sequences leading to the same final position.
    b1 = bb.Board("01234444")
    b2 = bb.Board("44440123")

    h1 = b1.to_huffman()
    h2 = b2.to_huffman()

    # Huffman encoding is purely position-based.
    assert h1 == h2

    print(f"Huffman code: {h1}")
    ```
Expected output:
    ```text
    Huffman code: 10120112
    ```

Definition at line 1757 of file board.py.

◆ to_string()

str bitbully.board.Board.to_string ( self)
Returns a human-readable ASCII representation of the board.

The returned string shows the **current board position** as a 6x7 grid,
laid out exactly as it would appear when you print a `Board` instance:

- 6 lines of text, one per row (top row first, bottom row last)
- 7 entries per row, separated by two spaces
- `_` represents an empty cell
- `X` represents a token from Player 1 (yellow)
- `O` represents a token from Player 2 (red)

This is useful when you want to explicitly capture the board as a string
(e.g., for logging, debugging, or embedding into error messages) instead
of relying on `print(board)` or `repr(board)`.

Returns:
    str: A multi-line ASCII string representing the board state.

Example:
    Using `to_string()` on an empty board:
    ```python
    import bitbully as bb

    board = bb.Board("33333111")

    s = board.to_string()
    print(s)
    ```

    Expected output:
    ```text
    _  _  _  _  _  _  _
    _  _  _  X  _  _  _
    _  _  _  O  _  _  _
    _  O  _  X  _  _  _
    _  X  _  O  _  _  _
    _  O  _  X  _  _  _
    ```

Definition at line 1246 of file board.py.

◆ uid()

int bitbully.board.Board.uid ( self)
Returns a unique identifier for the current board state.

The UID is a deterministic integer computed from the internal bitboard
representation of the position. It is **stable**, **position-based**, and
uniquely tied to the exact token layout **and** the side to move.

Key properties:

- Boards with the **same** configuration (tokens + player to move) always
  produce the **same** UID.
- Any change to the board (e.g., after a legal move) will almost always
  result in a **different** UID.
- Copies of a board created via the copy constructor or `Board.copy()`
  naturally share the same UID as long as their states remain identical.

Unlike `__hash__()`, the UID is not optimized for hash-table dispersion.
For use in transposition tables, caching, or dictionary/set keys,
prefer `__hash__()` since it provides a higher-quality hash distribution.

Returns:
    int: A unique integer identifier for the board state.

Example:
    UID is an integer and not None:
    ```python
    import bitbully as bb

    board = bb.Board()
    u = board.uid()

    assert isinstance(u, int)
    # Empty board has a well-defined, stable UID.
    assert board.uid() == u
    ```

Example:
    UID changes when the position changes:
    ```python
    import bitbully as bb

    board = bb.Board()
    uid_before = board.uid()

    assert board.play(1)  # Make a move in column 1.

    uid_after = board.uid()
    assert uid_after != uid_before
    ```

Example:
    Copies share the same UID while they are identical:
    ```python
    import bitbully as bb

    board = bb.Board("0123")

    # Create an independent copy of the same position.
    board_copy = board.copy()

    assert board is not board_copy  # Different objects
    assert board == board_copy  # Same position
    assert board.uid() == board_copy.uid()  # Same UID

    # After modifying the copy, they diverge.
    assert board_copy.play(4)
    assert board != board_copy
    assert board.uid() != board_copy.uid()
    ```

Example:
    Different move sequences leading to the same position share the same UID:
    ```python
    import bitbully as bb

    board_1 = bb.Board("01234444")
    board_2 = bb.Board("44440123")

    assert board_1 is not board_2  # Different objects
    assert board_1 == board_2  # Same position
    assert board_1.uid() == board_2.uid()  # Same UID

    # After modifying the copy, they diverge.
    assert board_1.play(4)
    assert board_1 != board_2
    assert board_1.uid() != board_2.uid()
    ```

Definition at line 1288 of file board.py.

◆ winner()

int | None bitbully.board.Board.winner ( self)
Returns the winning player, if the game has been won.

This helper interprets the current board under the assumption that
[`bitbully.Board.has_win`][src.bitbully.Board.has_win] indicates **the last move** created a
winning configuration. In that case, the winner is the *previous* player:

- If it is currently Player 1's turn, then Player 2 must have just won.
- If it is currently Player 2's turn, then Player 1 must have just won.

If there is no winner (i.e. [`bitbully.Board.has_win`][src.bitbully.Board.has_win] is ``False``),
this method returns ``None``.

Returns:
    int | None:
        The winning player, or ``None`` if there is no winner.

        - ``1`` → Player 1 (yellow, ``X``)
        - ``2`` → Player 2 (red, ``O``)
        - ``None`` → No winner (game still ongoing or draw)

Example:
    Detecting a winner:
    ```python
    import bitbully as bb

    # Player 1 wins with a horizontal line at the bottom.
    board = bb.Board()
    assert board.play("1122334")

    assert board.has_win()
    assert board.is_game_over()

    # It is now Player 2's turn to move next...
    assert board.current_player == 2

    # ...which implies Player 1 must be the winner.
    assert board.winner() == 1
    ```

Example:
    No winner yet:
    ```python
    import bitbully as bb

    board = bb.Board()
    assert board.play("112233")  # no connect-four yet

    assert not board.has_win()
    assert not board.is_game_over()
    assert board.winner() is None
    ```

Definition at line 1498 of file board.py.

Member Data Documentation

◆ _board

bitbully.board.Board._board = bitbully_core.BoardCore()
protected

Definition at line 218 of file board.py.

◆ current_player

int bitbully.board.Board.current_player = 1 else 1

Definition at line 1554 of file board.py.


The documentation for this class was generated from the following file: