Skip to content

Commit bb4221d

Browse files
Add docstrings for all functions
1 parent c21a7de commit bb4221d

2 files changed

Lines changed: 116 additions & 13 deletions

File tree

tictactoe/__init__.py

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@
1111

1212

1313
class 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

101164
class 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

tictactoe/egtb.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import operator
88
import re
99
import hashlib
10-
from typing import Tuple, List, Dict, Optional
10+
from typing import Iterable, List, Dict, Optional
1111
import logging
1212

1313
logger = logging.getLogger("tictactoe")
@@ -17,8 +17,14 @@
1717

1818

1919
class Generator:
20-
def __init__(self, dimensions: Tuple[int, ...] = (3, 3), x_in_a_row: int = 3, pieces: int = 9) -> None:
21-
self.dimensions = dimensions
20+
def __init__(self, dimensions: Iterable[int] = (3, 3), x_in_a_row: int = 3, pieces: int = 9) -> None:
21+
"""
22+
Generate EGTB.
23+
:param dimensions: The dimensions of the board.
24+
:param x_in_a_row: How many marks in a row are needed to win.
25+
:param pieces: How many pieces are placed in the board.
26+
"""
27+
self.dimensions = tuple(dimensions)
2228
self.x_in_a_row = x_in_a_row
2329
self.pieces = pieces
2430
self.results: List[str] = []
@@ -27,6 +33,10 @@ def __init__(self, dimensions: Tuple[int, ...] = (3, 3), x_in_a_row: int = 3, pi
2733
self.correct_hash = self.save_results()
2834

2935
def save_results(self) -> str:
36+
"""
37+
Save the EGTB.
38+
:return: The sha256 checksum of the contents of the EGTB file.
39+
"""
3040
logger.debug(f"Saving the EGTB.")
3141
name = f"{'_'.join(map(str, self.dimensions))}-{self.x_in_a_row}-{self.pieces}.ttb"
3242
with open(name, "wb") as file:
@@ -40,10 +50,17 @@ def save_results(self) -> str:
4050
return sha256_hash
4151

4252
def open_previous_egtb(self) -> Reader:
53+
"""
54+
Open the EGTB with one more move played.
55+
:return: A `tictactoe.egtb.Reader` object to index the EGTB.
56+
"""
4357
logger.debug(f"Opening previous EGTB (pieces = {self.pieces + 1}).")
4458
return Reader(self.dimensions, self.x_in_a_row, self.pieces + 1)
4559

4660
def get_all_board(self) -> None:
61+
"""
62+
Generate the EGTB.
63+
"""
4764
logger.debug(f"Generating the EGTB.")
4865
total_squares = functools.reduce(operator.mul, self.dimensions)
4966
empty_squares = total_squares - self.pieces
@@ -91,15 +108,25 @@ def get_all_board(self) -> None:
91108

92109

93110
class Reader:
94-
def __init__(self, dimensions: Tuple[int, ...] = (3, 3), x_in_a_row: int = 3, pieces: int = 9, verification_hash: Optional[str] = None) -> None:
95-
self.dimensions = dimensions
111+
def __init__(self, dimensions: Iterable[int] = (3, 3), x_in_a_row: int = 3, pieces: int = 9, verification_hash: Optional[str] = None) -> None:
112+
"""
113+
Read an EGTB file.
114+
:param dimensions: The dimensions of the board.
115+
:param x_in_a_row: How many marks in a row are needed to win.
116+
:param pieces: How many pieces are placed in the board.
117+
:param verification_hash: The sha256 checksum of the file to verify that the file is not corrupted.
118+
"""
119+
self.dimensions = tuple(dimensions)
96120
self.x_in_a_row = x_in_a_row
97121
self.pieces = pieces
98122
self.verification_hash = verification_hash
99123
self.ttb: Dict[str, int] = {}
100124
self.read()
101125

102126
def read(self) -> None:
127+
"""
128+
Read the EGTB file.
129+
"""
103130
total_squares = functools.reduce(operator.mul, self.dimensions)
104131
number_of_bits = total_squares * 2 + 1
105132
if self.pieces > total_squares or self.pieces < 0:
@@ -125,6 +152,9 @@ def read(self) -> None:
125152
logger.debug(f"Completed reading {name}.")
126153

127154
def verify(self) -> None:
155+
"""
156+
Verify that the EGTB is not corrupt, by checking the sha256 checksum.
157+
"""
128158
if not self.verification_hash:
129159
return
130160
logger.debug("Verifying EGTB.")
@@ -138,6 +168,11 @@ def verify(self) -> None:
138168
logger.debug("EGTB has no errors.")
139169

140170
def index(self, board: tictactoe.Board) -> int:
171+
"""
172+
Get the result of a position.
173+
:param board: A `tictactoe.Board` object to get the result of the position.
174+
:return: The result of the position.
175+
"""
141176
flattened_board = board.board.flatten()
142177
conversion_dict = {0: "00", 1: "01", 2: "10"}
143178
fen = "".join(map(lambda piece: conversion_dict[piece], flattened_board))

0 commit comments

Comments
 (0)