From dbb59ed54f32efa31a38c4d54c884c62f6e022b9 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sun, 28 Dec 2025 14:09:21 +0100 Subject: [PATCH] fix endOfGame: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zusammenfassung der Änderungen: 1. GameState.cs - Neue Properties hinzugefügt: - IsGameOver - zeigt an, dass das Spiel beendet ist - WinnerId - ID des Gewinners 2. GameReducers.cs: - OnProcessThrowResult setzt jetzt IsGameOver und WinnerId - OnExecuteGameActionSuccess setzt jetzt IsGameOver und WinnerId - OnStartGameSuccess setzt IsGameOver=false - OnEndGameSuccess setzt IsGameOver=false 3. GameEffects.cs: - HandleRecordThrow blockiert Eingaben wenn IsGameOver=true - HandleExecuteGameAction blockiert Eingaben wenn IsGameOver=true - EndGameAction wird nicht automatisch dispatcht - der Benutzer muss das Spiel explizit über die UI beenden Verhalten jetzt: - Wenn ProcessThrow oder ExecuteAction IsGameOver=true zurückgibt, wird der State auf IsGameOver=true gesetzt - Die Tafel bleibt sichtbar mit dem Endergebnis - Weitere Würfe/Actions werden blockiert - Der Benutzer muss EndGameAction explizit über einen Button in der UI auslösen Die UI muss jetzt GameState.IsGameOver und GameState.WinnerId nutzen, um: 1. Eingaben zu deaktivieren 2. Eine "Spiel beenden"-Schaltfläche anzuzeigen --- src/Koogle.Web/Store/GameState/GameEffects.cs | 34 +++++++++++-- .../Store/GameState/GameReducers.cs | 49 ++++++++++++------- src/Koogle.Web/Store/GameState/GameState.cs | 14 +++++- 3 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/Koogle.Web/Store/GameState/GameEffects.cs b/src/Koogle.Web/Store/GameState/GameEffects.cs index eb8effb..6c9dbd8 100644 --- a/src/Koogle.Web/Store/GameState/GameEffects.cs +++ b/src/Koogle.Web/Store/GameState/GameEffects.cs @@ -303,6 +303,13 @@ public class GameEffects var gameTypeName = state.GameTypeName; var currentPlayerId = state.Participants.CurrentPlayerId; + // Block input if game is already over + if (state.IsGameOver) + { + _logger.LogWarning("Cannot process throw: game is already over"); + return Task.CompletedTask; + } + if (string.IsNullOrEmpty(gameTypeName) || !currentPlayerId.HasValue) { _logger.LogWarning("Cannot process throw: missing game type or player"); @@ -407,6 +414,12 @@ public class GameEffects isGameOver, winnerId)); + // If game is over, skip save (user must confirm end via UI) + if (isGameOver) + { + return Task.CompletedTask; + } + // Debounce save operations to avoid excessive DB writes _saveDebounceTimer?.Dispose(); _saveDebounceTimer = new Timer( @@ -428,6 +441,13 @@ public class GameEffects var gameTypeName = state.GameTypeName; var currentPlayerId = state.Participants.CurrentPlayerId; + // Block input if game is already over + if (state.IsGameOver) + { + dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: game is already over")); + return Task.CompletedTask; + } + if (string.IsNullOrEmpty(gameTypeName) || !currentPlayerId.HasValue || state.GameModel == null) { dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: missing game state")); @@ -451,6 +471,16 @@ public class GameEffects result.IsGameOver, result.WinnerId)); + _logger.LogDebug( + "Game action executed: {ActionId}, rotate={Rotate}, gameOver={GameOver}", + action.ActionId, result.ShouldRotatePlayer, result.IsGameOver); + + // If game is over, skip save (user must confirm end via UI) + if (result.IsGameOver) + { + return Task.CompletedTask; + } + // Debounce save operations _saveDebounceTimer?.Dispose(); _saveDebounceTimer = new Timer( @@ -458,10 +488,6 @@ public class GameEffects null, SaveDebounceMs, Timeout.Infinite); - - _logger.LogDebug( - "Game action executed: {ActionId}, rotate={Rotate}, gameOver={GameOver}", - action.ActionId, result.ShouldRotatePlayer, result.IsGameOver); } else { diff --git a/src/Koogle.Web/Store/GameState/GameReducers.cs b/src/Koogle.Web/Store/GameState/GameReducers.cs index dc2ee48..b2a35c3 100644 --- a/src/Koogle.Web/Store/GameState/GameReducers.cs +++ b/src/Koogle.Web/Store/GameState/GameReducers.cs @@ -42,7 +42,9 @@ public static class GameReducers UndoStack = [], RedoStack = [], IsLoading = false, - Error = null + Error = null, + IsGameOver = false, + WinnerId = null }; /// @@ -84,7 +86,9 @@ public static class GameReducers UndoStack = [], RedoStack = [], CompletedGames = [.. state.CompletedGames, action.Summary], - IsLoading = false + IsLoading = false, + IsGameOver = false, + WinnerId = null }; /// @@ -235,25 +239,30 @@ public static class GameReducers ThrowPanelAfter = action.NewThrowPanelState, GameModel = action.UpdatedGameModel, IsLoading = false, - IsSaving = true + IsSaving = !action.IsGameOver, // Don't save if game is over (EndGame handles it) + IsGameOver = action.IsGameOver, + WinnerId = action.WinnerId }; - // Apply player rotation or override - if (action.NextPlayerId.HasValue) + // Apply player rotation or override (only if game not over) + if (!action.IsGameOver) { - // Game logic specifies exact next player - newState = newState with + if (action.NextPlayerId.HasValue) { - Participants = newState.Participants.SetCurrentPlayer(action.NextPlayerId.Value) - }; - } - else if (action.ShouldRotatePlayer) - { - // Standard rotation to next player - newState = newState with + // Game logic specifies exact next player + newState = newState with + { + Participants = newState.Participants.SetCurrentPlayer(action.NextPlayerId.Value) + }; + } + else if (action.ShouldRotatePlayer) { - Participants = newState.Participants.NextPlayer() - }; + // Standard rotation to next player + newState = newState with + { + Participants = newState.Participants.NextPlayer() + }; + } } return newState; @@ -569,11 +578,13 @@ public static class GameReducers { GameModel = action.UpdatedGameModel, IsLoading = false, - IsSaving = true + IsSaving = !action.IsGameOver, // Don't save if game is over (EndGame handles it) + IsGameOver = action.IsGameOver, + WinnerId = action.WinnerId }; - // Apply player rotation if requested - if (action.ShouldRotatePlayer) + // Apply player rotation if requested (only if game not over) + if (!action.IsGameOver && action.ShouldRotatePlayer) { newState = newState with { diff --git a/src/Koogle.Web/Store/GameState/GameState.cs b/src/Koogle.Web/Store/GameState/GameState.cs index 99458af..67c25a4 100644 --- a/src/Koogle.Web/Store/GameState/GameState.cs +++ b/src/Koogle.Web/Store/GameState/GameState.cs @@ -91,6 +91,16 @@ public record GameState /// public bool IsConcurrencyConflict { get; init; } + /// + /// Indicates the game has ended (show results, block input). + /// + public bool IsGameOver { get; init; } + + /// + /// Winner ID if game is over. + /// + public Guid? WinnerId { get; init; } + /// /// Private constructor for Fluxor initialization. /// @@ -115,7 +125,9 @@ public record GameState IsLoading = false, IsSaving = false, Error = null, - IsConcurrencyConflict = false + IsConcurrencyConflict = false, + IsGameOver = false, + WinnerId = null }; }