BitBully 0.0.56-a6
Loading...
Searching...
No Matches
board.py
1"""This module defines the Board class for managing the state of a Connect Four game."""
2
3from __future__ import annotations # for forward references in type hints (Python 3.7+)
4
5from collections.abc import Sequence
6
7from bitbully import bitbully_core
8
9
10class Board:
11 """Represents the state of a Connect Four board. Mostly a thin wrapper around BoardCore."""
12
13 def __init__(self, board: Sequence[Sequence[int]] | Sequence[int] | str | None = None) -> None:
14 """Initializes a Board instance.
15
16 Args:
17 board (Sequence[Sequence[int]] | Sequence[int] | str | None):
18 Optional initial board state. Accepts:
19 - 2D array (list, tuple, numpy-array) with shape 7x6 or 6x7
20 - 1D sequence of ints: a move sequence of columns (e.g., [0, 0, 2, 2, 3, 3])
21 - String: A move sequence of columns as string (e.g., "002233...")
22 - None for an empty board
23
24 Example:
25 Here we have an example:
26 ``` py
27 board = Board() # Empty board
28
29 board = Board([[0] * 6 for _ in range(7)]) # empty board as 2D list
30 ```
31
32 It is possible to write some text inbetween the examples.
33
34 Example:
35 And here we have another example:
36 ``` py
37 board = Board("002233...") # String
38 ```
39 """
40 # TODO: The constructor does still not accept all types here. Fix that.
41 self._board = bitbully_core.BoardCore()
42
43 def __eq__(self, value: object) -> bool:
44 """Checks equality between two Board instances.
45
46 Args:
47 value (object): The other Board instance to compare against.
48
49 Returns:
50 bool: True if both boards are equal, False otherwise.
51
52 Raises:
53 NotImplementedError: If the other value is not a Board instance.
54 """
55 if not isinstance(value, Board):
56 raise NotImplementedError("Can only compare with another Board instance.")
57 return bool(self._board == value._board)
58
59 def __ne__(self, value: object) -> bool:
60 """Checks inequality between two Board instances.
61
62 Args:
63 value (object): The other Board instance to compare against.
64
65 Returns:
66 bool: True if both boards are not equal, False otherwise.
67 """
68 return not self.__eq__(value)
69
70 def __repr__(self) -> str:
71 """Returns a string representation of the Board instance."""
72 return f"Board({self._board})"
73
74 def all_positions(self, up_to_n_ply: int, exactly_n: bool) -> list[Board]:
75 """Finds all positions on the board up to a certain ply.
76
77 Args:
78 up_to_n_ply (int): The maximum ply depth to search.
79 exactly_n (bool): If True, only returns positions at exactly N ply.
80
81 Returns:
82 list[Board]: A list of Board instances representing all positions.
83 """
84 # TODO: Implement this method properly. Need to convert BoardCore instances to Board.
85 # return self._board.all_positions(up_to_n_ply, exactly_n)
86 return [Board()]
87
88 def can_win_next(self, move: int | None = None) -> bool:
89 """Checks if the current player can win in the next move.
90
91 Args:
92 move (int | None): Optional column to check for an immediate win. If None, checks all columns.
93
94 Returns:
95 bool: True if the current player can win next, False otherwise.
96 """
97 if move is None:
98 return self._board.canWin()
99 return bool(self._board.canWin(move))
100
101 def copy(self) -> Board:
102 """Creates a copy of the current Board instance.
103
104 Returns:
105 Board: A new Board instance that is a copy of the current one.
106 """
107 new_board = Board()
108 new_board._board = self._board.copy()
109 return new_board
110
111 def count_tokens(self) -> int:
112 """Counts the total number of tokens on the board.
113
114 Returns:
115 int: The total number of tokens.
116 """
117 return self._board.countTokens()
118
119 def get_legal_moves(self) -> list[int]:
120 """Returns a list of legal moves (columns) that can be played.
121
122 Returns:
123 list[int]: A list of column indices (0-6) where a move can be played.
124
125 Raises:
126 NotImplementedError: If the method is not implemented yet.
127 """
128 raise NotImplementedError("get_legal_moves is not implemented yet.")
129
130 def has_win(self) -> bool:
131 """Checks if the current player has a winning position.
132
133 Returns:
134 bool: True if the current player has a winning position (4-in-a-row), False otherwise.
135 """
136 return self._board.hasWin()
137
138 def __hash__(self) -> int:
139 """Returns a hash of the Board instance for use in hash-based collections.
140
141 Returns:
142 int: The hash value of the Board instance.
143 """
144 return self._board.hash()
145
146 def is_legal_move(self, move: int) -> bool:
147 """Checks if a move (column) is legal.
148
149 Args:
150 move (int): The column index (0-6) to check.
151
152 Returns:
153 bool: True if the move is legal, False otherwise.
154 """
155 return self._board.isLegalMove(move)
156
157 def mirror(self) -> Board:
158 """Returns a new Board instance that is the mirror image of the current board.
159
160 Returns:
161 Board: A new Board instance that is the mirror image.
162 """
163 new_board = Board()
164 new_board._board = self._board.mirror()
165 return new_board
166
167 def moves_left(self) -> int:
168 """Returns the number of moves left until the board is full.
169
170 Returns:
171 int: The number of moves left (0-42).
172 """
173 return self._board.movesLeft()
174
175 def play_move(self, move: int) -> bool:
176 """Plays a move (column) for the current player.
177
178 Args:
179 move (int): The column index (0-6) where the token should be placed.
180
181 Returns:
182 bool: True if the move was played successfully, False if the move was illegal.
183 """
184 return self._board.playMove(move)
185
186 def play_move_on_copy(self, move: int) -> Board | None:
187 """Plays a move on a copy of the current board and returns the new board.
188
189 Args:
190 move (int): The column index (0-6) where the token should be placed.
191
192 Returns:
193 Board | None: A new Board instance with the move played, or None if the move was illegal.
194 """
195 new_board = self.copy()
196 if new_board.play_move(move):
197 return new_board
198 return None
199
200 def set_board(self, board: list[list[int]] | list[int]) -> bool:
201 """Sets (overrides) the board to a specific state.
202
203 Args:
204 board (list[list[int]] | list[int]):
205 The new board state. Accepts:
206 - 2D array (list, tuple, numpy-array) with shape 7x6 or 6x7
207 - 1D sequence of ints: a move sequence of columns (e.g., [0, 0, 2, 2, 3, 3])
208 - String: A move sequence of columns as string (e.g., "002233...")
209
210 Returns:
211 bool: True if the board was set successfully, False otherwise.
212 """
213 # TODO: also allow other types for `board`, e.g., numpy arrays and convert to a list of lists
214 if isinstance(board, list):
215 return self._board.setBoard(board)
216 return False
217
218 def to_array(self) -> list[list[int]]:
219 """Returns the board state as a 2D array (list of lists).
220
221 Returns:
222 list[list[int]]: A 2D list representing the board state.
223 """
224 return self._board.toArray()
225
226 def to_string(self) -> str:
227 """Returns a string representation of the board to print on the command line.
228
229 Returns:
230 str: A string representing the board (e.g., "002233...").
231 """
232 return self._board.toString()
233
234 def uid(self) -> int:
235 """Returns a unique identifier for the current board state.
236
237 Returns:
238 int: A unique integer identifier for the board state.
239 """
240 return self._board.uid()
241
242 @staticmethod
243 def random_board(n_ply: int, forbid_direct_win: bool) -> None:
244 """Generates a random board state by playing a specified number of random moves.
245
246 Args:
247 n_ply (int): The number of random moves to play on the board.
248 forbid_direct_win (bool): If True, the board will have a state that would result in an immediate win.
249 """
250 bitbully_core.BoardCore.randomBoard(n_ply, forbid_direct_win)
251
252 def reset(self) -> None:
253 """Resets the board to an empty state."""
254 self._board = bitbully_core.BoardCore()
Board copy(self)
Definition board.py:101
bool __eq__(self, object value)
Definition board.py:43
int count_tokens(self)
Definition board.py:111
str to_string(self)
Definition board.py:226
list[Board] all_positions(self, int up_to_n_ply, bool exactly_n)
Definition board.py:74
str __repr__(self)
Definition board.py:70
Board|None play_move_on_copy(self, int move)
Definition board.py:186
bool can_win_next(self, int|None move=None)
Definition board.py:88
None reset(self)
Definition board.py:252
list[int] get_legal_moves(self)
Definition board.py:119
bool play_move(self, int move)
Definition board.py:175
int __hash__(self)
Definition board.py:138
Board mirror(self)
Definition board.py:157
None __init__(self, Sequence[Sequence[int]]|Sequence[int]|str|None board=None)
Definition board.py:13
int uid(self)
Definition board.py:234
bool set_board(self, list[list[int]]|list[int] board)
Definition board.py:200
list[list[int]] to_array(self)
Definition board.py:218
None random_board(int n_ply, bool forbid_direct_win)
Definition board.py:243
bool has_win(self)
Definition board.py:130
int moves_left(self)
Definition board.py:167
bool is_legal_move(self, int move)
Definition board.py:146
bool __ne__(self, object value)
Definition board.py:59