@@ -16,23 +16,29 @@ class Player:
1616 number_of_wins : int
1717 number_of_draws : int
1818 number_of_defeats : int
19- tracking : Tracking
19+ current_game_id : UUID | None
2020
21- def lose (self ) -> None :
21+ def be_in_game (self , game_id : UUID ) -> None :
22+ self .current_game_id = game_id
23+
24+ def lose (self , tracking : Tracking ) -> None :
25+ self .current_game_id = None
2226 self .number_of_defeats += 1
23- self . tracking .register_mutated (self )
27+ tracking .register_mutated (self )
2428
25- def win (self ) -> None :
29+ def win (self , tracking : Tracking ) -> None :
30+ self .current_game_id = None
2631 self .number_of_wins += 1
27- self . tracking .register_mutated (self )
32+ tracking .register_mutated (self )
2833
29- def be_draw (self ) -> None :
34+ def be_draw (self , tracking : Tracking ) -> None :
35+ self .current_game_id = None
3036 self .number_of_draws += 1
31- self . tracking .register_mutated (self )
37+ tracking .register_mutated (self )
3238
3339
3440def create_player (id_ : int , tracking : Tracking ) -> Player :
35- player = Player (id_ , 0 , 0 , 0 , tracking )
41+ player = Player (id_ , 0 , 0 , 0 , None )
3642 tracking .register_new (player )
3743
3844 return player
@@ -47,19 +53,18 @@ class Cell:
4753 game_id : UUID
4854 board_position : Vector
4955 filler_id : int | None
50- tracking : Tracking
5156
5257 def is_filled (self ) -> bool :
5358 return self .filler_id is not None
5459
55- def fill (self , filler_id : int ) -> None :
60+ def fill (self , filler_id : int , tracking : Tracking ) -> None :
5661 """
5762 :raises ttt.entities.core.AlreadyFilledCellError:
5863 """
5964
6065 assert_ (not self .is_filled (), else_ = AlreadyFilledCellError )
6166 self .filler_id = filler_id
62- self . tracking .register_mutated (self )
67+ tracking .register_mutated (self )
6368
6469
6570class GameState (Enum ):
@@ -72,7 +77,7 @@ class GameState(Enum):
7277
7378
7479def is_board_standard (board : Board ) -> bool :
75- return board .column_size () == board .line_size () == 3 # noqa: PLR2004
80+ return board .width () == board .height () == 3 # noqa: PLR2004
7681
7782
7883class InvalidCellIDMatrixError (Exception ): ...
@@ -91,7 +96,7 @@ def create_empty_board(
9196
9297 board = Matrix ([
9398 [
94- Cell (cell_id_matrix [x , y ], game_id , (x , y ), None , tracking )
99+ Cell (cell_id_matrix [x , y ], game_id , Vector (x , y ), None )
95100 for x in range (3 )
96101 ]
97102 for y in range (3 )
@@ -105,6 +110,8 @@ def create_empty_board(
105110
106111@dataclass (frozen = True )
107112class GameResult :
113+ id : UUID
114+ game_id : UUID
108115 winner_id : int | None
109116
110117
@@ -120,7 +127,9 @@ class InvalidCellOrderError(Exception): ...
120127class InvalidNumberOfUnfilledCellsError (Exception ): ...
121128
122129
123- class CompletedGameError (Exception ): ...
130+ @dataclass (frozen = True )
131+ class CompletedGameError (Exception ):
132+ game_result : GameResult
124133
125134
126135class NoCellError (Exception ): ...
@@ -152,16 +161,15 @@ class Game:
152161 number_of_unfilled_cells : int
153162 result : GameResult | None
154163 state : GameState
155- tracking : Tracking
156164
157165 def __post_init__ (self ) -> None :
158166 assert_ (self .player1 .id != self .player2 .id , else_ = OnePlayerError )
159167 assert_ (is_board_standard (self .board ), else_ = NotStandardBoardError )
160168
161169 is_cell_order_ok = all (
162170 self .board [x , y ].board_position == (x , y )
163- for x in range (self .board .line_size ())
164- for y in range (self .board .column_size ())
171+ for x in range (self .board .width ())
172+ for y in range (self .board .height ())
165173 )
166174 assert_ (is_cell_order_ok , else_ = InvalidCellOrderError )
167175
@@ -172,7 +180,11 @@ def __post_init__(self) -> None:
172180 )
173181
174182 def make_move (
175- self , player_id : int , cell_position : Vector ,
183+ self ,
184+ player_id : int ,
185+ cell_position : Vector ,
186+ game_result_id : UUID ,
187+ tracking : Tracking ,
176188 ) -> GameResult | None :
177189 """
178190 :raises ttt.entities.core.CompletedGameError:
@@ -183,37 +195,39 @@ def make_move(
183195 """
184196
185197 current_player = self ._current_player ()
186- current_player = not_none (current_player , else_ = CompletedGameError )
198+
199+ if current_player is None :
200+ raise CompletedGameError (not_none (self .result ))
187201
188202 assert_ (
189203 player_id in {self .player1 .id , self .player2 .id },
190204 else_ = NotPlayerError (),
191205 )
192206 assert_ (current_player .id == player_id , else_ = NotCurrentPlayerError ())
193207
194- self ._fill_cell (cell_position , player_id )
208+ self ._fill_cell (cell_position , player_id , tracking )
195209
196210 if self ._is_player_winner (current_player , cell_position ):
197211 not_current_player = not_none (self ._not_current_player ())
198212
199- current_player .win ()
200- not_current_player .lose ()
213+ current_player .win (tracking )
214+ not_current_player .lose (tracking )
201215
202- return self ._complete (current_player )
216+ return self ._complete (game_result_id , current_player , tracking )
203217
204218 if not self ._can_continue ():
205219 not_current_player = not_none (self ._not_current_player ())
206220
207- current_player .be_draw ()
208- not_current_player .be_draw ()
221+ current_player .be_draw (tracking )
222+ not_current_player .be_draw (tracking )
209223
210- return self ._complete (None )
224+ return self ._complete (game_result_id , None , tracking )
211225
212- self ._wait_next_move ()
226+ self ._wait_next_move (tracking )
213227 return None
214228
215229 def _fill_cell (
216- self , cell_position : Vector , player_id : int ,
230+ self , cell_position : Vector , player_id : int , tracking : Tracking ,
217231 ) -> GameResult | None :
218232 """
219233 :raises ttt.entities.core.NoCellError:
@@ -225,9 +239,9 @@ def _fill_cell(
225239 except IndexError as error :
226240 raise NoCellError from error
227241
228- cell .fill (player_id )
242+ cell .fill (player_id , tracking )
229243 self .number_of_unfilled_cells -= 1
230- self . tracking .register_mutated (self )
244+ tracking .register_mutated (self )
231245
232246 def _can_continue (self ) -> bool :
233247 return not self ._is_board_filled ()
@@ -240,11 +254,11 @@ def _is_player_winner(self, player: Player, cell_position: Vector) -> bool:
240254
241255 is_winner = all (
242256 self .board [cell_x , y ].filler_id == player .id
243- for y in range (self .board .column_size ())
257+ for y in range (self .board .height ())
244258 )
245259 is_winner |= all (
246260 int (self .board [x , cell_y ].filler_id == player .id )
247- for x in range (self .board .line_size ())
261+ for x in range (self .board .width ())
248262 )
249263
250264 is_winner |= {player .id } == {
@@ -278,7 +292,7 @@ def _not_current_player(self) -> Player | None:
278292 case GameState .completed :
279293 return None
280294
281- def _wait_next_move (self ) -> None :
295+ def _wait_next_move (self , tracking : Tracking ) -> None :
282296 match self .state :
283297 case GameState .wait_player1 :
284298 self .state = GameState .wait_player2
@@ -287,16 +301,25 @@ def _wait_next_move(self) -> None:
287301 case GameState .completed :
288302 raise ValueError
289303
290- self . tracking .register_mutated (self )
304+ tracking .register_mutated (self )
291305
292- def _complete (self , winner : Player | None ) -> GameResult :
293- self .result = GameResult (None if winner is None else winner .id )
306+ def _complete (
307+ self , game_result_id : UUID , winner : Player | None , tracking : Tracking ,
308+ ) -> GameResult :
309+ self .result = GameResult (
310+ game_result_id , self .id , None if winner is None else winner .id ,
311+ )
294312 self .state = GameState .completed
295- self . tracking .register_mutated (self )
313+ tracking .register_mutated (self )
296314
297315 return self .result
298316
299317
318+ @dataclass (frozen = True )
319+ class PlayerAlreadyInGameError (Exception ):
320+ players : tuple [Player , ...]
321+
322+
300323def start_game (
301324 cell_id_matrix : Matrix [UUID ],
302325 game_id : UUID ,
@@ -305,9 +328,21 @@ def start_game(
305328 tracking : Tracking ,
306329) -> Game :
307330 """
331+ :raises ttt.entities.core.PlayerAlreadyInGameError:
308332 :raises ttt.entities.core.InvalidCellIDMatrixError:
333+ :raises ttt.entities.core.OnePlayerError:
309334 """
310335
336+ players_in_game = tuple (
337+ player
338+ for player in (player1 , player2 )
339+ if player .current_game_id is not None
340+ )
341+ assert_ (
342+ not players_in_game ,
343+ else_ = PlayerAlreadyInGameError (players_in_game ),
344+ )
345+
311346 board = create_empty_board (cell_id_matrix , game_id , tracking )
312347
313348 game = Game (
@@ -318,8 +353,10 @@ def start_game(
318353 number_of_unfilled_cells (board ),
319354 None ,
320355 GameState .wait_player1 ,
321- tracking ,
322356 )
323357 tracking .register_new (game )
324358
359+ player1 .be_in_game (game_id )
360+ player2 .be_in_game (game_id )
361+
325362 return game
0 commit comments