1111
1212
1313class Board :
14- def __init__ (self , dimensions : Tuple [int , ...] = (3 , 3 ), x_in_a_row : int = 3 ) -> None :
15- self .dimensions = dimensions
14+ def __init__ (self , dimensions : Iterable [int ] = (3 , 3 ), x_in_a_row : int = 3 ) -> None :
15+ """
16+ TicTacToe board.
17+ :param dimensions: The dimensions of the board.
18+ :param x_in_a_row: How many marks in a row are needed to win.
19+ """
20+ self .dimensions = tuple (dimensions )
1621 self .x_in_a_row = x_in_a_row
1722 self .board = self .create_board ()
1823 self ._directions = self .find_directions ()
@@ -23,29 +28,56 @@ def __init__(self, dimensions: Tuple[int, ...] = (3, 3), x_in_a_row: int = 3) ->
2328 self .turn = X
2429
2530 def create_board (self ) -> npt .NDArray [np .int8 ]:
31+ """
32+ Create the board state.
33+ :return: A `numpy.ndarray` filled with 0s.
34+ """
2635 return np .zeros (self .dimensions , dtype = np .int8 )
2736
2837 def copy (self ) -> Board :
38+ """
39+ Get a copy of the board.
40+ :return: A copy of the board.
41+ """
2942 board = Board (self .dimensions , self .x_in_a_row )
3043 board .turn = self .turn
3144 board .board = self .board .copy ()
3245 return board
3346
3447 def get_mark_at_position (self , position : Iterable [int ]) -> int :
48+ """
49+ Get the mark at a position.
50+ :param position: The position to check.
51+ :return: The player that has a mark at the position.
52+ """
3553 position = tuple (position )
3654 return int (self .board [position ])
3755
38- def set_mark (self , coordinates : Tuple [int , ...], player : int ) -> None :
39- self .board [coordinates ] = player
56+ def set_mark (self , coordinates : Iterable [int ], player : int ) -> None :
57+ """
58+ Set a mark at a position.
59+ :param coordinates: The position to add a mark at.
60+ :param player: The player that put the mark at the position.
61+ """
62+ self .board [tuple (coordinates )] = player
4063 if player == X :
4164 self .x .append (Move (coordinates ))
4265 else :
4366 self .o .append (Move (coordinates ))
4467
45- def is_empty (self , position : Tuple [int , ...]) -> bool :
68+ def is_empty (self , position : Iterable [int ]) -> bool :
69+ """
70+ Get if a position is empty.
71+ :param position: The position to check.
72+ :return: If the position is empty.
73+ """
4674 return self .get_mark_at_position (position ) == 0
4775
4876 def push (self , coordinates : Iterable [int ]) -> None :
77+ """
78+ Push a move.
79+ :param coordinates: The position to add a mark at.
80+ """
4981 coordinates = tuple (coordinates )
5082 if not self .is_empty (coordinates ):
5183 raise ValueError ("Position is not empty." )
@@ -56,6 +88,10 @@ def push(self, coordinates: Iterable[int]) -> None:
5688 self .move_count += 1
5789
5890 def find_directions (self ) -> List [Tuple [int , ...]]:
91+ """
92+ Get directions to be used when checking for a win.
93+ :return: The directions to check for a win.
94+ """
5995 directions = list (itertools .product ([1 , 0 , - 1 ], repeat = len (self .dimensions )))
6096 correct_directions = []
6197 for direction in directions :
@@ -68,15 +104,34 @@ def find_directions(self) -> List[Tuple[int, ...]]:
68104 return correct_directions
69105
70106 def possible_moves (self ) -> npt .NDArray [np .int64 ]:
107+ """
108+ Get all possible moves.
109+ :return: All the positions where there is no mark.
110+ """
71111 return np .argwhere (self .board == 0 )
72112
73113 def out_of_bounds (self , pos : npt .NDArray [_all_numpy_int_types ]) -> np .bool_ :
114+ """
115+ Get if a position is out of the board.
116+ :param pos: The position to check.
117+ :return: If the position is out of the board.
118+ """
74119 return (pos < 0 ).any () or (pos >= self .dimensions ).any ()
75120
76121 def in_bounds (self , pos : npt .NDArray [_all_numpy_int_types ]) -> bool :
122+ """
123+ Get if a position is inside the board.
124+ :param pos: The position to check.
125+ :return: If the position is inside the board.
126+ """
77127 return not self .out_of_bounds (pos )
78128
79129 def has_won (self , player : int ) -> bool :
130+ """
131+ Get if a player has won.
132+ :param player: The player to check.
133+ :return: If the player has won.
134+ """
80135 positions = np .argwhere (self .board == player )
81136 for position in positions :
82137 for direction in self ._directions :
@@ -89,17 +144,30 @@ def has_won(self, player: int) -> bool:
89144 return False
90145
91146 def result (self ) -> Optional [int ]:
92- if self .has_won (X ):
147+ """
148+ Get the result of the game.
149+ :return: The result of the board.
150+ """
151+ x_won = self .has_won (X )
152+ o_won = self .has_won (O )
153+ if x_won and o_won :
154+ raise Exception (f"Both X and O have { self .x_in_a_row } pieces in a row." )
155+ elif x_won :
93156 return X
94- elif self . has_won ( O ) :
157+ elif o_won :
95158 return O
96159 elif self .board .all ():
97160 return 0
98161 return None
99162
100163
101164class Move :
102- def __init__ (self , coordinate_move : Optional [Tuple [int , ...]] = None , str_move : Optional [str ] = None ) -> None :
165+ def __init__ (self , coordinate_move : Optional [Iterable [int ]] = None , str_move : Optional [str ] = None ) -> None :
166+ """
167+ Convert a move to other types.
168+ :param coordinate_move: A `tuple` with the position of the move.
169+ :param str_move: A move that is in a human-readable format.
170+ """
103171 assert coordinate_move or str_move
104172 self .coordinate_move = coordinate_move
105173 self .str_move = str_move
0 commit comments