fix endOfGame:

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
This commit is contained in:
beo3000 2025-12-28 14:09:21 +01:00
parent 5c088345b3
commit dbb59ed54f
3 changed files with 73 additions and 24 deletions

View File

@ -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
{

View File

@ -42,7 +42,9 @@ public static class GameReducers
UndoStack = [],
RedoStack = [],
IsLoading = false,
Error = null
Error = null,
IsGameOver = false,
WinnerId = null
};
/// <summary>
@ -84,7 +86,9 @@ public static class GameReducers
UndoStack = [],
RedoStack = [],
CompletedGames = [.. state.CompletedGames, action.Summary],
IsLoading = false
IsLoading = false,
IsGameOver = false,
WinnerId = null
};
/// <summary>
@ -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
{

View File

@ -91,6 +91,16 @@ public record GameState
/// </summary>
public bool IsConcurrencyConflict { get; init; }
/// <summary>
/// Indicates the game has ended (show results, block input).
/// </summary>
public bool IsGameOver { get; init; }
/// <summary>
/// Winner ID if game is over.
/// </summary>
public Guid? WinnerId { get; init; }
/// <summary>
/// Private constructor for Fluxor initialization.
/// </summary>
@ -115,7 +125,9 @@ public record GameState
IsLoading = false,
IsSaving = false,
Error = null,
IsConcurrencyConflict = false
IsConcurrencyConflict = false,
IsGameOver = false,
WinnerId = null
};
}