Make CPU turn chain fire-and-forget so end game is instant

The CPU turn chain was awaited inline inside game_lock, blocking the
WebSocket message loop. The end_game message couldn't be processed
until all CPU turns finished. Now check_and_run_cpu_turn launches a
background task and returns immediately, keeping the message loop
responsive. The end_game and leave handlers cancel the task on demand.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-02-22 17:26:58 -05:00
parent de3495635b
commit 3261e6ee26
2 changed files with 47 additions and 40 deletions

View File

@@ -229,7 +229,7 @@ async def handle_start_game(data: dict, ctx: ConnectionContext, *, broadcast_gam
"game_state": game_state,
})
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_flip_initial(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -240,7 +240,7 @@ async def handle_flip_initial(data: dict, ctx: ConnectionContext, *, broadcast_g
async with ctx.current_room.game_lock:
if ctx.current_room.game.flip_initial_cards(ctx.player_id, positions):
await broadcast_game_state(ctx.current_room)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
# ---------------------------------------------------------------------------
@@ -297,7 +297,7 @@ async def handle_swap(data: dict, ctx: ConnectionContext, *, broadcast_game_stat
await broadcast_game_state(ctx.current_room)
await asyncio.sleep(1.0)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_discard(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -329,12 +329,12 @@ async def handle_discard(data: dict, ctx: ConnectionContext, *, broadcast_game_s
})
else:
await asyncio.sleep(0.5)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
else:
logger.debug("Player discarded, waiting 0.5s before CPU turn")
await asyncio.sleep(0.5)
logger.debug("Post-discard delay complete, checking for CPU turn")
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_cancel_draw(data: dict, ctx: ConnectionContext, *, broadcast_game_state, **kw) -> None:
@@ -364,7 +364,7 @@ async def handle_flip_card(data: dict, ctx: ConnectionContext, *, broadcast_game
)
await broadcast_game_state(ctx.current_room)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_skip_flip(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -380,7 +380,7 @@ async def handle_skip_flip(data: dict, ctx: ConnectionContext, *, broadcast_game
)
await broadcast_game_state(ctx.current_room)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_flip_as_action(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -400,7 +400,7 @@ async def handle_flip_as_action(data: dict, ctx: ConnectionContext, *, broadcast
)
await broadcast_game_state(ctx.current_room)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_knock_early(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -418,7 +418,7 @@ async def handle_knock_early(data: dict, ctx: ConnectionContext, *, broadcast_ga
)
await broadcast_game_state(ctx.current_room)
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
async def handle_next_round(data: dict, ctx: ConnectionContext, *, broadcast_game_state, check_and_run_cpu_turn, **kw) -> None:
@@ -443,7 +443,7 @@ async def handle_next_round(data: dict, ctx: ConnectionContext, *, broadcast_gam
"game_state": game_state,
})
await check_and_run_cpu_turn(ctx.current_room)
check_and_run_cpu_turn(ctx.current_room)
else:
await broadcast_game_state(ctx.current_room)