22from dataclasses import dataclass
33from enum import Enum , auto
44from itertools import chain
5+ from typing import cast
56from uuid import UUID
67
78from ttt .entities .core .game .board import (
@@ -30,12 +31,22 @@ class GameState(Enum):
3031
3132
3233@dataclass (frozen = True )
33- class GameResult :
34+ class GameCompletionResult :
3435 id : UUID
3536 game_id : UUID
3637 win : Win | None
3738
3839
40+ @dataclass (frozen = True )
41+ class GameCancellationResult :
42+ id : UUID
43+ game_id : UUID
44+ canceler_id : int
45+
46+
47+ type GameResult = GameCompletionResult | GameCancellationResult
48+
49+
3950class OnePlayerError (Exception ): ...
4051
4152
@@ -110,6 +121,29 @@ def __post_init__(self) -> None:
110121 else_ = InvalidNumberOfUnfilledCellsError ,
111122 )
112123
124+ def cancel (
125+ self , player_id : int , game_result_id : UUID , tracking : Tracking ,
126+ ) -> None :
127+ """
128+ :raises ttt.entities.core.game.game.AlreadyCompletedGameError:
129+ :raises ttt.entities.core.game.game.NotPlayerError:
130+ """
131+
132+ if self .result is not None :
133+ raise AlreadyCompletedGameError (self .result )
134+
135+ canceler = not_none (self ._player (player_id ), else_ = NotPlayerError )
136+
137+ self .player1 .leave_game (tracking )
138+ self .player2 .leave_game (tracking )
139+
140+ self .result = GameCancellationResult (
141+ game_result_id , self .id , canceler .id ,
142+ )
143+ tracking .register_new (self .result )
144+ self .state = GameState .completed
145+ tracking .register_mutated (self )
146+
113147 def make_move (
114148 self ,
115149 player_id : int ,
@@ -119,7 +153,7 @@ def make_move(
119153 tracking : Tracking ,
120154 ) -> GameResult | None :
121155 """
122- :raises ttt.entities.core.game.game.CompletedGameError :
156+ :raises ttt.entities.core.game.game.AlreadyCompletedGameError :
123157 :raises ttt.entities.core.game.game.NotPlayerError:
124158 :raises ttt.entities.core.game.game.NotCurrentPlayerError:
125159 :raises ttt.entities.core.game.game.NoCellError:
@@ -129,7 +163,9 @@ def make_move(
129163 current_player = self ._current_player ()
130164
131165 if current_player is None :
132- raise AlreadyCompletedGameError (not_none (self .result ))
166+ raise AlreadyCompletedGameError (
167+ cast (GameResult , not_none (self .result )),
168+ )
133169
134170 assert_ (
135171 player_id in {self .player1 .id , self .player2 .id },
@@ -220,6 +256,15 @@ def _current_player(self) -> Player | None:
220256 case GameState .completed :
221257 return None
222258
259+ def _player (self , player_id : int ) -> Player | None :
260+ match player_id :
261+ case self .player1 .id :
262+ return self .player1
263+ case self .player2 .id :
264+ return self .player2
265+ case _:
266+ return None
267+
223268 def _not_current_player (self ) -> Player | None :
224269 match self .state :
225270 case GameState .wait_player1 :
@@ -243,7 +288,8 @@ def _wait_next_move(self, tracking: Tracking) -> None:
243288 def _complete (
244289 self , win : Win | None , game_result_id : UUID , tracking : Tracking ,
245290 ) -> None :
246- self .result = GameResult (game_result_id , self .id , win )
291+ self .result = GameCompletionResult (game_result_id , self .id , win )
292+ tracking .register_new (self .result )
247293 self .state = GameState .completed
248294 tracking .register_mutated (self )
249295
0 commit comments