diff --git a/src/Koogle.Web/Store/GameState/GameEffects.cs b/src/Koogle.Web/Store/GameState/GameEffects.cs index f587445..4fe1a58 100644 --- a/src/Koogle.Web/Store/GameState/GameEffects.cs +++ b/src/Koogle.Web/Store/GameState/GameEffects.cs @@ -468,9 +468,10 @@ public class GameEffects afterThrowState.PinsKnocked); } - // If game is over, skip save (user must confirm end via UI) + // If game is over, don't start debounce timer - the ProcessThrowResultAction effect handles saving if (isGameOver) { + _saveDebounceTimer?.Dispose(); return; } @@ -483,6 +484,30 @@ public class GameEffects Timeout.Infinite); } + /// + /// Handles ProcessThrowResultAction - saves game state immediately when game is over. + /// This effect runs after the reducer, so the state is already updated. + /// + [EffectMethod] + public async Task HandleProcessThrowResult(ProcessThrowResultAction action, IDispatcher dispatcher) + { + // Only save immediately if game is over + if (!action.IsGameOver) return; + + var state = _gameState.Value; + if (!state.ActiveGameId.HasValue) return; + + var success = await SaveCurrentStateAsync(state.ActiveGameId.Value); + if (success) + { + _logger.LogInformation("Game over state saved for game {GameId}", state.ActiveGameId.Value); + } + else + { + _logger.LogWarning("Failed to save game over state for game {GameId}", state.ActiveGameId.Value); + } + } + /// /// Handles ExecuteGameActionAction - executes game-specific action via IGameLogicService. /// @@ -538,7 +563,7 @@ public class GameEffects dispatcher); } - // If game is over, skip save (user must confirm end via UI) + // If game is over, skip debounce - the ExecuteGameActionSuccessAction effect handles saving if (result.IsGameOver) { return; @@ -564,6 +589,30 @@ public class GameEffects } } + /// + /// Handles ExecuteGameActionSuccessAction - saves game state immediately when game is over. + /// This effect runs after the reducer, so the state is already updated. + /// + [EffectMethod] + public async Task HandleExecuteGameActionSuccess(ExecuteGameActionSuccessAction action, IDispatcher dispatcher) + { + // Only save immediately if game is over + if (!action.IsGameOver) return; + + var state = _gameState.Value; + if (!state.ActiveGameId.HasValue) return; + + var success = await SaveCurrentStateAsync(state.ActiveGameId.Value); + if (success) + { + _logger.LogInformation("Game over state saved (from action) for game {GameId}", state.ActiveGameId.Value); + } + else + { + _logger.LogWarning("Failed to save game over state (from action) for game {GameId}", state.ActiveGameId.Value); + } + } + private static ThrowPanelSnapshot CreateThrowPanelSnapshot(ThrowPanelState state) { return ThrowPanelSnapshot.FromPins(