@@ -273,7 +273,7 @@ async def submit_move(
273273 board_after = board_to_state (board )
274274
275275 ply = await self .repository .next_ply (game_id )
276- stored = await self .repository .create_move (
276+ stored = await self .repository .create_move_uncommitted (
277277 GameMove (
278278 game_id = game_id ,
279279 ply = ply ,
@@ -289,8 +289,14 @@ async def submit_move(
289289 value = 0.0 ,
290290 )
291291 )
292-
293- await self ._update_game_terminal_state (game = game , board = board )
292+ should_apply_rated_result = await self ._update_game_terminal_state (
293+ game = game ,
294+ board = board ,
295+ commit = False ,
296+ )
297+ await self .repository .commit ()
298+ if should_apply_rated_result :
299+ await self ._apply_rated_result (game )
294300 return stored
295301
296302 async def advance_bot_turn (
@@ -368,7 +374,7 @@ async def advance_bot_turn(
368374 board_after = board_to_state (board )
369375 ply = await self .repository .next_ply (game_id )
370376
371- stored = await self .repository .create_move (
377+ stored = await self .repository .create_move_uncommitted (
372378 GameMove (
373379 game_id = game_id ,
374380 ply = ply ,
@@ -384,7 +390,14 @@ async def advance_bot_turn(
384390 value = value ,
385391 )
386392 )
387- await self ._update_game_terminal_state (game = game , board = board )
393+ should_apply_rated_result = await self ._update_game_terminal_state (
394+ game = game ,
395+ board = board ,
396+ commit = False ,
397+ )
398+ await self .repository .commit ()
399+ if should_apply_rated_result :
400+ await self ._apply_rated_result (game )
388401 return stored
389402
390403 @staticmethod
@@ -399,12 +412,21 @@ def _resolve_actor_side(game: Game, actor_user_id: UUID) -> PlayerSide:
399412 return PlayerSide .P2
400413 raise PermissionError ("Authenticated user is not a participant in this match." )
401414
402- async def _update_game_terminal_state (self , game : Game , board : AtaxxBoard ) -> None :
415+ async def _update_game_terminal_state (
416+ self ,
417+ game : Game ,
418+ board : AtaxxBoard ,
419+ * ,
420+ commit : bool = True ,
421+ ) -> bool :
403422 if not board .is_game_over ():
404423 if game .status != GameStatus .IN_PROGRESS :
405424 game .status = GameStatus .IN_PROGRESS
406- await self .repository .save_game (game )
407- return
425+ if commit :
426+ await self .repository .save_game (game )
427+ else :
428+ await self .repository .save_game_uncommitted (game )
429+ return False
408430
409431 result = board .get_result ()
410432 if result == 1 :
@@ -420,28 +442,39 @@ async def _update_game_terminal_state(self, game: Game, board: AtaxxBoard) -> No
420442 game .status = GameStatus .FINISHED
421443 game .termination_reason = TerminationReason .NORMAL
422444 game .ended_at = datetime .now (timezone .utc ).replace (tzinfo = None )
423- await self .repository .save_game (game )
445+ if commit :
446+ await self .repository .save_game (game )
447+ else :
448+ await self .repository .save_game_uncommitted (game )
449+ return self ._should_apply_rated_result (game )
424450
425- if (
451+ def _should_apply_rated_result (self , game : Game ) -> bool :
452+ return (
426453 game .rated
427454 and game .season_id is not None
428455 and game .player1_id is not None
429456 and game .player2_id is not None
430457 and game .winner_side is not None
431458 and self .ranking_service is not None
432- ):
433- await self .ranking_service .apply_rated_result (
434- game_id = game .id ,
435- season_id = game .season_id ,
436- player1_id = game .player1_id ,
437- player2_id = game .player2_id ,
438- winner_side = game .winner_side ,
439- )
440- # Keep public leaderboard in sync with the latest rated result.
441- await self .ranking_service .recompute_leaderboard (
442- season_id = game .season_id ,
443- limit = 500 ,
444- )
459+ )
460+
461+ async def _apply_rated_result (self , game : Game ) -> None :
462+ if self .ranking_service is None :
463+ raise RuntimeError ("Ranking service is required for rated result finalization." )
464+ if game .season_id is None or game .player1_id is None or game .player2_id is None or game .winner_side is None :
465+ raise RuntimeError ("Rated game is missing required identifiers for rating application." )
466+ await self .ranking_service .apply_rated_result (
467+ game_id = game .id ,
468+ season_id = game .season_id ,
469+ player1_id = game .player1_id ,
470+ player2_id = game .player2_id ,
471+ winner_side = game .winner_side ,
472+ )
473+ # Keep public leaderboard in sync with the latest rated result.
474+ await self .ranking_service .recompute_leaderboard (
475+ season_id = game .season_id ,
476+ limit = 500 ,
477+ )
445478
446479 @staticmethod
447480 def _to_side (player : int ) -> PlayerSide :
0 commit comments