fix undo/redo handling

This commit is contained in:
beo3000 2025-12-27 21:46:20 +01:00
parent 191fb3db3f
commit 03364291aa
7 changed files with 138 additions and 69 deletions

View File

@ -9,10 +9,15 @@ namespace Koogle.Application.DTOs;
/// </summary> /// </summary>
public record GameStateSerializationDto public record GameStateSerializationDto
{ {
/// <summary>
/// State of the throw panel before current throw.
/// </summary>
public ThrowPanelStateDto ThrowPanelBefore { get; init; } = new();
/// <summary> /// <summary>
/// Current state of the throw panel. /// Current state of the throw panel.
/// </summary> /// </summary>
public ThrowPanelStateDto ThrowPanel { get; init; } = new(); public ThrowPanelStateDto ThrowPanelAfter { get; init; } = new();
/// <summary> /// <summary>
/// Current state of game participants. /// Current state of game participants.
@ -120,7 +125,12 @@ public record GameSnapshotDto
/// <summary> /// <summary>
/// Snapshot of the throw panel state. /// Snapshot of the throw panel state.
/// </summary> /// </summary>
public ThrowPanelStateDto ThrowPanel { get; init; } = new(); public ThrowPanelStateDto ThrowPanelBefore { get; init; } = new();
/// <summary>
/// Snapshot of the throw panel state.
/// </summary>
public ThrowPanelStateDto ThrowPanelAfter { get; init; } = new();
/// <summary> /// <summary>
/// Snapshot of the participants state. /// Snapshot of the participants state.

View File

@ -7,6 +7,12 @@
@inject IState<GameState> GameState @inject IState<GameState> GameState
@inject IDispatcher Dispatcher @inject IDispatcher Dispatcher
@* @GameState.Value.ThrowPanelAfter
<hr/>
@GameState.Value.ThrowPanelBefore *@
<div class="game-input-panel"> <div class="game-input-panel">
@* Current player display *@ @* Current player display *@
<div class="current-player-section"> <div class="current-player-section">
@ -28,13 +34,13 @@
@* Pin input area *@ @* Pin input area *@
<div class="pin-input-section"> <div class="pin-input-section">
<PinPanel ThrowPanelState="@GameState.Value.ThrowPanel" <PinPanel ThrowPanelState="@GameState.Value.ThrowPanelAfter"
IsInteractive="@IsInteractive" IsInteractive="@IsInteractive"
OnPinClicked="HandlePinClick" /> OnPinClicked="HandlePinClick" />
</div> </div>
@if (@GameState.Value.ThrowPanel.ThrowMode == ThrowMode.Reposition) @if (@GameState.Value.ThrowPanelAfter.ThrowMode == ThrowMode.Reposition)
{ {
@* Number quick-entry *@ @* Number quick-entry *@
<div class="number-input-section"> <div class="number-input-section">
@ -52,7 +58,7 @@
@* Throw info and controls *@ @* Throw info and controls *@
<div class="throw-control-section"> <div class="throw-control-section">
<ThrowPanel ThrowPanelState="@GameState.Value.ThrowPanel" <ThrowPanel ThrowPanelState="@GameState.Value.ThrowPanelAfter"
IsInteractive="@IsInteractive" IsInteractive="@IsInteractive"
OnGutterClicked="HandleGutterClick" /> OnGutterClicked="HandleGutterClick" />
</div> </div>
@ -162,7 +168,7 @@
private int? _selectedNumber; private int? _selectedNumber;
private bool _hasModifiedPins; private bool _hasModifiedPins;
private ThrowPanelState? _beforeThrowState; // private ThrowPanelState? _beforeThrowState;
private int _lastKnownThrowCounter; private int _lastKnownThrowCounter;
private bool IsInteractive => GameState.Value.IsGameActive && !GameState.Value.IsLoading; private bool IsInteractive => GameState.Value.IsGameActive && !GameState.Value.IsLoading;
@ -173,35 +179,46 @@
GameState.StateChanged += OnGameStateChanged; GameState.StateChanged += OnGameStateChanged;
// Store initial state for before/after comparison // Store initial state for before/after comparison
CaptureBeforeThrowState(); CaptureBeforeThrowState();
_lastKnownThrowCounter = GameState.Value.ThrowPanel.TotalThrowCounter; _lastKnownThrowCounter = GameState.Value.ThrowPanelAfter.TotalThrowCounter;
} }
private void OnGameStateChanged(object? sender, EventArgs e) private void OnGameStateChanged(object? sender, EventArgs e)
{ {
var currentCounter = GameState.Value.ThrowPanel.TotalThrowCounter; var tp = GameState.Value.ThrowPanelAfter;
var currentCounter = tp.TotalThrowCounter;
// Detect external state changes (Undo/Redo/SignalR) by comparing throw counter // Detect external state changes (Undo/Redo/SignalR) by comparing throw counter
// If counter changed externally, reset local state and capture new before state // If counter changed externally (went backwards = Undo, or different = external change)
if (currentCounter != _lastKnownThrowCounter) if (currentCounter != _lastKnownThrowCounter)
{ {
_hasModifiedPins = false; _hasModifiedPins = false;
_selectedNumber = null; _selectedNumber = null;
CaptureBeforeThrowState();
// // In Reposition mode after Undo, reset pins for clean re-entry
// // Counter going backwards indicates Undo
// if (currentCounter < _lastKnownThrowCounter &&
// tp.ThrowMode == ThrowMode.Reposition)
// {
// // Reset pins to standing for clean re-entry
// Dispatcher.Dispatch(new ResetPinsAction());
// }
_lastKnownThrowCounter = currentCounter; _lastKnownThrowCounter = currentCounter;
} }
else if (!_hasModifiedPins)
// Always capture before state when not actively modifying pins
if (!_hasModifiedPins)
{ {
// Normal state change (e.g., after throw processed), capture new before state
CaptureBeforeThrowState(); CaptureBeforeThrowState();
} }
// Force re-render to update child components (ThrowPanel, PinPanel, etc.) // Force re-render to update child components (ThrowPanelAfter, PinPanel, etc.)
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
private void CaptureBeforeThrowState() private void CaptureBeforeThrowState()
{ {
_beforeThrowState = GameState.Value.ThrowPanel; // _beforeThrowState = GameState.Value.ThrowPanelAfter;
} }
public void Dispose() public void Dispose()
@ -217,7 +234,7 @@
var currentStatus = GetPinStatus(pinNumber); var currentStatus = GetPinStatus(pinNumber);
// In Decrease mode, fallen pins cannot be reset to standing // In Decrease mode, fallen pins cannot be reset to standing
if (GameState.Value.ThrowPanel.ThrowMode == ThrowMode.Decrease && currentStatus == PinStatus.Disabled) if (GameState.Value.ThrowPanelAfter.ThrowMode == ThrowMode.Decrease && currentStatus == PinStatus.Disabled)
{ {
return; return;
} }
@ -232,7 +249,7 @@
private void HandleNumberClick(int number) private void HandleNumberClick(int number)
{ {
// Quick entry: set pins 1-N as fallen, reset as standing // Quick entry: set pins 1-N as fallen, reset as standing
var newState = GameState.Value.ThrowPanel.ResetPins(); var newState = GameState.Value.ThrowPanelAfter.ResetPins();
for (int i = 1; i <= 9; i++) for (int i = 1; i <= 9; i++)
{ {
@ -254,7 +271,7 @@
private void HandleBellClick() private void HandleBellClick()
{ {
Dispatcher.Dispatch(new SetBellValueAction(!GameState.Value.ThrowPanel.BellValue)); Dispatcher.Dispatch(new SetBellValueAction(!GameState.Value.ThrowPanelAfter.BellValue));
} }
private async Task HandleGutterClick(bool isLeft) private async Task HandleGutterClick(bool isLeft)
@ -273,7 +290,7 @@
private async Task ConfirmThrow(bool isGutter, bool isLeftGutter) private async Task ConfirmThrow(bool isGutter, bool isLeftGutter)
{ {
var currentThrowPanel = GameState.Value.ThrowPanel; var currentThrowPanel = GameState.Value.ThrowPanelAfter;
var fallenPins = currentThrowPanel.CountFallenPins(); var fallenPins = currentThrowPanel.CountFallenPins();
var bellValue = currentThrowPanel.BellValue; var bellValue = currentThrowPanel.BellValue;
@ -288,7 +305,8 @@
}; };
// Get before state (captured at start of input session) // Get before state (captured at start of input session)
var beforeState = _beforeThrowState ?? currentThrowPanel; // var beforeState = _beforeThrowState ?? currentThrowPanel;
var beforeState = GameState.Value.ThrowPanelBefore;
// Dispatch record throw action - game logic will handle pin reset and player rotation // Dispatch record throw action - game logic will handle pin reset and player rotation
Dispatcher.Dispatch(new RecordThrowAction(beforeState, currentThrowPanel, isGutter, isLeftGutter)); Dispatcher.Dispatch(new RecordThrowAction(beforeState, currentThrowPanel, isGutter, isLeftGutter));
@ -310,15 +328,15 @@
private PinStatus GetPinStatus(int pinNumber) => pinNumber switch private PinStatus GetPinStatus(int pinNumber) => pinNumber switch
{ {
1 => GameState.Value.ThrowPanel.Pin1, 1 => GameState.Value.ThrowPanelAfter.Pin1,
2 => GameState.Value.ThrowPanel.Pin2, 2 => GameState.Value.ThrowPanelAfter.Pin2,
3 => GameState.Value.ThrowPanel.Pin3, 3 => GameState.Value.ThrowPanelAfter.Pin3,
4 => GameState.Value.ThrowPanel.Pin4, 4 => GameState.Value.ThrowPanelAfter.Pin4,
5 => GameState.Value.ThrowPanel.Pin5, 5 => GameState.Value.ThrowPanelAfter.Pin5,
6 => GameState.Value.ThrowPanel.Pin6, 6 => GameState.Value.ThrowPanelAfter.Pin6,
7 => GameState.Value.ThrowPanel.Pin7, 7 => GameState.Value.ThrowPanelAfter.Pin7,
8 => GameState.Value.ThrowPanel.Pin8, 8 => GameState.Value.ThrowPanelAfter.Pin8,
9 => GameState.Value.ThrowPanel.Pin9, 9 => GameState.Value.ThrowPanelAfter.Pin9,
_ => PinStatus.Standing _ => PinStatus.Standing
}; };

View File

@ -68,7 +68,8 @@ public record LoadActiveGameAction(Guid DayId);
public record LoadActiveGameSuccessAction( public record LoadActiveGameSuccessAction(
Guid GameId, Guid GameId,
string GameTypeName, string GameTypeName,
ThrowPanelState ThrowPanel, ThrowPanelState ThrowPanelBefore,
ThrowPanelState ThrowPanelAfter,
ParticipantsState Participants, ParticipantsState Participants,
object? GameModel, object? GameModel,
IReadOnlyList<GameSnapshot> UndoStack, IReadOnlyList<GameSnapshot> UndoStack,

View File

@ -55,7 +55,13 @@ public class GameEffects
{ {
var initialState = new GameStateSerializationDto var initialState = new GameStateSerializationDto
{ {
ThrowPanel = MapThrowPanelToDto(ThrowPanelState.Initial with ThrowPanelAfter = MapThrowPanelToDto(ThrowPanelState.Initial with
{
IsStarted = true,
ThrowsPerRound = action.ThrowsPerRound,
ThrowMode = action.ThrowMode
}),
ThrowPanelBefore = MapThrowPanelToDto(ThrowPanelState.Initial with
{ {
IsStarted = true, IsStarted = true,
ThrowsPerRound = action.ThrowsPerRound, ThrowsPerRound = action.ThrowsPerRound,
@ -201,12 +207,14 @@ public class GameEffects
return; return;
} }
var throwPanel = MapDtoToThrowPanel(stateDto.ThrowPanel); var throwPanelBefore = MapDtoToThrowPanel(stateDto.ThrowPanelBefore);
var throwPanelAfter = MapDtoToThrowPanel(stateDto.ThrowPanelAfter);
var participants = MapDtoToParticipants(stateDto.Participants); var participants = MapDtoToParticipants(stateDto.Participants);
var undoStack = stateDto.UndoStack var undoStack = stateDto.UndoStack
.Select(s => new GameSnapshot .Select(s => new GameSnapshot
{ {
ThrowPanel = MapDtoToThrowPanel(s.ThrowPanel), ThrowPanelBefore = MapDtoToThrowPanel(s.ThrowPanelBefore),
ThrowPanelAfter = MapDtoToThrowPanel(s.ThrowPanelAfter),
Participants = MapDtoToParticipants(s.Participants), Participants = MapDtoToParticipants(s.Participants),
GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null
}) })
@ -214,7 +222,8 @@ public class GameEffects
var redoStack = stateDto.RedoStack var redoStack = stateDto.RedoStack
.Select(s => new GameSnapshot .Select(s => new GameSnapshot
{ {
ThrowPanel = MapDtoToThrowPanel(s.ThrowPanel), ThrowPanelBefore = MapDtoToThrowPanel(s.ThrowPanelBefore),
ThrowPanelAfter = MapDtoToThrowPanel(s.ThrowPanelAfter),
Participants = MapDtoToParticipants(s.Participants), Participants = MapDtoToParticipants(s.Participants),
GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null
}) })
@ -232,7 +241,8 @@ public class GameEffects
dispatcher.Dispatch(new LoadActiveGameSuccessAction( dispatcher.Dispatch(new LoadActiveGameSuccessAction(
activeGame.Id, activeGame.Id,
activeGame.GameType, activeGame.GameType,
throwPanel, throwPanelBefore,
throwPanelAfter,
participants, participants,
stateDto.GameModel.HasValue ? (object?)stateDto.GameModel.Value : null, stateDto.GameModel.HasValue ? (object?)stateDto.GameModel.Value : null,
undoStack, undoStack,
@ -467,13 +477,13 @@ public class GameEffects
[EffectMethod] [EffectMethod]
public Task HandleUndoThrowSuccess(UndoThrowSuccessAction action, IDispatcher dispatcher) public Task HandleUndoThrowSuccess(UndoThrowSuccessAction action, IDispatcher dispatcher)
{ {
//var stateAfterReducer = _gameState.Value; var stateAfterReducer = _gameState.Value;
//_logger.LogInformation( _logger.LogInformation(
// "UNDO SUCCESS: State after reducer - pins fallen={Fallen}, total={Total}, UndoStack={UndoCount}, RedoStack={RedoCount}", "UNDO SUCCESS: State after reducer - pins fallen={Fallen}, total={Total}, UndoStack={UndoCount}, RedoStack={RedoCount}",
// stateAfterReducer.ThrowPanel.CountFallenPins(), stateAfterReducer.ThrowPanelAfter.CountFallenPins(),
// stateAfterReducer.ThrowPanel.TotalThrowCounter, stateAfterReducer.ThrowPanelAfter.TotalThrowCounter,
// stateAfterReducer.UndoStack.Count, stateAfterReducer.UndoStack.Count,
// stateAfterReducer.RedoStack.Count); stateAfterReducer.RedoStack.Count);
// Save after undo as well // Save after undo as well
_saveDebounceTimer?.Dispose(); _saveDebounceTimer?.Dispose();
@ -506,13 +516,13 @@ public class GameEffects
_logger.LogInformation( _logger.LogInformation(
"UNDO: Stack={StackSize}, Current pins fallen={CurrentFallen}, Snapshot pins fallen={SnapshotFallen}, Current total={CurrentTotal}, Snapshot total={SnapshotTotal}", "UNDO: Stack={StackSize}, Current pins fallen={CurrentFallen}, Snapshot pins fallen={SnapshotFallen}, Current total={CurrentTotal}, Snapshot total={SnapshotTotal}",
state.UndoStack.Count, state.UndoStack.Count,
state.ThrowPanel.CountFallenPins(), state.ThrowPanelAfter.CountFallenPins(),
lastSnapshot.ThrowPanel.CountFallenPins(), lastSnapshot.ThrowPanelAfter.CountFallenPins(),
state.ThrowPanel.TotalThrowCounter, state.ThrowPanelAfter.TotalThrowCounter,
lastSnapshot.ThrowPanel.TotalThrowCounter); lastSnapshot.ThrowPanelAfter.TotalThrowCounter);
dispatcher.Dispatch(new UndoThrowSuccessAction( dispatcher.Dispatch(new UndoThrowSuccessAction(
lastSnapshot.ThrowPanel, lastSnapshot.ThrowPanelAfter,
lastSnapshot.Participants, lastSnapshot.Participants,
lastSnapshot.GameModel)); lastSnapshot.GameModel));
@ -539,10 +549,10 @@ public class GameEffects
_logger.LogDebug( _logger.LogDebug(
"Redoing throw. Stack size before: {StackSize}, Restoring to throw counter: {ThrowCounter}", "Redoing throw. Stack size before: {StackSize}, Restoring to throw counter: {ThrowCounter}",
state.RedoStack.Count, state.RedoStack.Count,
lastSnapshot.ThrowPanel.TotalThrowCounter); lastSnapshot.ThrowPanelAfter.TotalThrowCounter);
dispatcher.Dispatch(new RedoThrowSuccessAction( dispatcher.Dispatch(new RedoThrowSuccessAction(
lastSnapshot.ThrowPanel, lastSnapshot.ThrowPanelAfter,
lastSnapshot.Participants, lastSnapshot.Participants,
lastSnapshot.GameModel)); lastSnapshot.GameModel));
@ -617,7 +627,8 @@ public class GameEffects
{ {
return new GameStateSerializationDto return new GameStateSerializationDto
{ {
ThrowPanel = MapThrowPanelToDto(state.ThrowPanel), ThrowPanelBefore = MapThrowPanelToDto(state.ThrowPanelBefore),
ThrowPanelAfter = MapThrowPanelToDto(state.ThrowPanelAfter),
Participants = new ParticipantsStateDto Participants = new ParticipantsStateDto
{ {
PlayerIds = state.Participants.PlayerIds, PlayerIds = state.Participants.PlayerIds,
@ -632,7 +643,7 @@ public class GameEffects
: null, : null,
UndoStack = state.UndoStack.Select(s => new GameSnapshotDto UndoStack = state.UndoStack.Select(s => new GameSnapshotDto
{ {
ThrowPanel = MapThrowPanelToDto(s.ThrowPanel), ThrowPanelAfter = MapThrowPanelToDto(s.ThrowPanelAfter),
Participants = new ParticipantsStateDto Participants = new ParticipantsStateDto
{ {
PlayerIds = s.Participants.PlayerIds, PlayerIds = s.Participants.PlayerIds,
@ -645,7 +656,7 @@ public class GameEffects
}).ToList(), }).ToList(),
RedoStack = state.RedoStack.Select(s => new GameSnapshotDto RedoStack = state.RedoStack.Select(s => new GameSnapshotDto
{ {
ThrowPanel = MapThrowPanelToDto(s.ThrowPanel), ThrowPanelAfter = MapThrowPanelToDto(s.ThrowPanelAfter),
Participants = new ParticipantsStateDto Participants = new ParticipantsStateDto
{ {
PlayerIds = s.Participants.PlayerIds, PlayerIds = s.Participants.PlayerIds,

View File

@ -3,6 +3,7 @@ using System.Text.Json;
using Fluxor; using Fluxor;
using Koogle.Application.Games; using Koogle.Application.Games;
using Koogle.Domain.Enums; using Koogle.Domain.Enums;
using Microsoft.Extensions.Logging;
namespace Koogle.Web.Store.GameState; namespace Koogle.Web.Store.GameState;
@ -34,7 +35,7 @@ public static class GameReducers
IsGameActive = true, IsGameActive = true,
ActiveGameId = action.GameId, ActiveGameId = action.GameId,
GameTypeName = action.GameTypeName, GameTypeName = action.GameTypeName,
ThrowPanel = action.ThrowPanel, ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants, Participants = action.Participants,
GameModel = action.GameModel, GameModel = action.GameModel,
Setup = action.Setup, Setup = action.Setup,
@ -76,7 +77,7 @@ public static class GameReducers
IsGameActive = false, IsGameActive = false,
ActiveGameId = null, ActiveGameId = null,
GameTypeName = null, GameTypeName = null,
ThrowPanel = ThrowPanelState.Initial, ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial, Participants = ParticipantsState.Initial,
GameModel = null, GameModel = null,
Setup = null, Setup = null,
@ -119,7 +120,7 @@ public static class GameReducers
IsGameActive = true, IsGameActive = true,
ActiveGameId = action.GameId, ActiveGameId = action.GameId,
GameTypeName = action.GameTypeName, GameTypeName = action.GameTypeName,
ThrowPanel = action.ThrowPanel, ThrowPanelAfter = action.ThrowPanelAfter,
Participants = action.Participants, Participants = action.Participants,
GameModel = action.GameModel, GameModel = action.GameModel,
Setup = action.Setup, Setup = action.Setup,
@ -198,11 +199,17 @@ public static class GameReducers
var snapshot = new GameSnapshot var snapshot = new GameSnapshot
{ {
ThrowPanel = state.ThrowPanel, ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants, Participants = state.Participants,
GameModel = clonedGameModel GameModel = clonedGameModel
}; };
Console.WriteLine($"[OnRecordThrow] Creating snapshot - state.ThrowPanelAfter pins: " +
$"[{(int)state.ThrowPanelAfter.Pin1},{(int)state.ThrowPanelAfter.Pin2},{(int)state.ThrowPanelAfter.Pin3}," +
$"{(int)state.ThrowPanelAfter.Pin4},{(int)state.ThrowPanelAfter.Pin5},{(int)state.ThrowPanelAfter.Pin6}," +
$"{(int)state.ThrowPanelAfter.Pin7},{(int)state.ThrowPanelAfter.Pin8},{(int)state.ThrowPanelAfter.Pin9}] " +
$"TotalThrows={state.ThrowPanelAfter.TotalThrowCounter}");
return state with return state with
{ {
UndoStack = state.UndoStack.Add(snapshot), UndoStack = state.UndoStack.Add(snapshot),
@ -217,9 +224,15 @@ public static class GameReducers
[ReducerMethod] [ReducerMethod]
public static GameState OnProcessThrowResult(GameState state, ProcessThrowResultAction action) public static GameState OnProcessThrowResult(GameState state, ProcessThrowResultAction action)
{ {
Console.WriteLine($"[OnProcessThrowResult] Setting new ThrowPanelAfter pins: " +
$"[{(int)action.NewThrowPanelState.Pin1},{(int)action.NewThrowPanelState.Pin2},{(int)action.NewThrowPanelState.Pin3}," +
$"{(int)action.NewThrowPanelState.Pin4},{(int)action.NewThrowPanelState.Pin5},{(int)action.NewThrowPanelState.Pin6}," +
$"{(int)action.NewThrowPanelState.Pin7},{(int)action.NewThrowPanelState.Pin8},{(int)action.NewThrowPanelState.Pin9}] " +
$"TotalThrows={action.NewThrowPanelState.TotalThrowCounter}");
var newState = state with var newState = state with
{ {
ThrowPanel = action.NewThrowPanelState, ThrowPanelAfter = action.NewThrowPanelState,
GameModel = action.UpdatedGameModel, GameModel = action.UpdatedGameModel,
IsLoading = false, IsLoading = false,
IsSaving = true IsSaving = true
@ -274,7 +287,7 @@ public static class GameReducers
public static GameState OnSetPinStatus(GameState state, SetPinStatusAction action) public static GameState OnSetPinStatus(GameState state, SetPinStatusAction action)
=> state with => state with
{ {
ThrowPanel = state.ThrowPanel.SetPin(action.PinNumber, action.Status) ThrowPanelAfter = state.ThrowPanelAfter.SetPin(action.PinNumber, action.Status)
}; };
/// <summary> /// <summary>
@ -284,7 +297,7 @@ public static class GameReducers
public static GameState OnResetPins(GameState state) public static GameState OnResetPins(GameState state)
=> state with => state with
{ {
ThrowPanel = state.ThrowPanel.ResetPins() ThrowPanelAfter = state.ThrowPanelAfter.ResetPins()
}; };
/// <summary> /// <summary>
@ -294,7 +307,7 @@ public static class GameReducers
public static GameState OnSetBellValue(GameState state, SetBellValueAction action) public static GameState OnSetBellValue(GameState state, SetBellValueAction action)
=> state with => state with
{ {
ThrowPanel = state.ThrowPanel with { BellValue = action.Value } ThrowPanelAfter = state.ThrowPanelAfter with { BellValue = action.Value }
}; };
// Player Reducers // Player Reducers
@ -327,10 +340,16 @@ public static class GameReducers
[ReducerMethod] [ReducerMethod]
public static GameState OnUndoThrowSuccess(GameState state, UndoThrowSuccessAction action) public static GameState OnUndoThrowSuccess(GameState state, UndoThrowSuccessAction action)
{ {
Console.WriteLine($"[OnUndoThrowSuccess] Restoring - action.ThrowPanelAfter pins: " +
$"[{(int)action.ThrowPanel.Pin1},{(int)action.ThrowPanel.Pin2},{(int)action.ThrowPanel.Pin3}," +
$"{(int)action.ThrowPanel.Pin4},{(int)action.ThrowPanel.Pin5},{(int)action.ThrowPanel.Pin6}," +
$"{(int)action.ThrowPanel.Pin7},{(int)action.ThrowPanel.Pin8},{(int)action.ThrowPanel.Pin9}] " +
$"TotalThrows={action.ThrowPanel.TotalThrowCounter}");
// Create snapshot of current state for redo (deep clone GameModel) // Create snapshot of current state for redo (deep clone GameModel)
var currentSnapshot = new GameSnapshot var currentSnapshot = new GameSnapshot
{ {
ThrowPanel = state.ThrowPanel, ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants, Participants = state.Participants,
GameModel = CloneGameModel(state.GameModel) GameModel = CloneGameModel(state.GameModel)
}; };
@ -342,7 +361,7 @@ public static class GameReducers
return state with return state with
{ {
ThrowPanel = action.ThrowPanel, ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants, Participants = action.Participants,
GameModel = action.GameModel, GameModel = action.GameModel,
UndoStack = newUndoStack, UndoStack = newUndoStack,
@ -372,7 +391,7 @@ public static class GameReducers
// Create snapshot of current state for undo (deep clone GameModel) // Create snapshot of current state for undo (deep clone GameModel)
var currentSnapshot = new GameSnapshot var currentSnapshot = new GameSnapshot
{ {
ThrowPanel = state.ThrowPanel, ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants, Participants = state.Participants,
GameModel = CloneGameModel(state.GameModel) GameModel = CloneGameModel(state.GameModel)
}; };
@ -384,7 +403,7 @@ public static class GameReducers
return state with return state with
{ {
ThrowPanel = action.ThrowPanel, ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants, Participants = action.Participants,
GameModel = action.GameModel, GameModel = action.GameModel,
UndoStack = state.UndoStack.Add(currentSnapshot), UndoStack = state.UndoStack.Add(currentSnapshot),
@ -466,7 +485,7 @@ public static class GameReducers
public static GameState OnGameStateUpdatedFromHub(GameState state, GameStateUpdatedFromHubAction action) public static GameState OnGameStateUpdatedFromHub(GameState state, GameStateUpdatedFromHubAction action)
=> state with => state with
{ {
ThrowPanel = action.ThrowPanel, ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants, Participants = action.Participants,
GameModel = action.GameModel GameModel = action.GameModel
}; };
@ -481,7 +500,7 @@ public static class GameReducers
IsGameActive = false, IsGameActive = false,
ActiveGameId = null, ActiveGameId = null,
GameTypeName = null, GameTypeName = null,
ThrowPanel = ThrowPanelState.Initial, ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial, Participants = ParticipantsState.Initial,
GameModel = null, GameModel = null,
Setup = null, Setup = null,

View File

@ -31,10 +31,15 @@ public record GameState
/// </summary> /// </summary>
public Guid? ActiveGameId { get; init; } public Guid? ActiveGameId { get; init; }
/// <summary>
/// State of the throw panel before the current throw.
/// </summary>
public ThrowPanelState ThrowPanelBefore { get; init; } = ThrowPanelState.Initial;
/// <summary> /// <summary>
/// Current state of the throw panel. /// Current state of the throw panel.
/// </summary> /// </summary>
public ThrowPanelState ThrowPanel { get; init; } = ThrowPanelState.Initial; public ThrowPanelState ThrowPanelAfter { get; init; } = ThrowPanelState.Initial;
/// <summary> /// <summary>
/// Current state of game participants. /// Current state of game participants.
@ -100,7 +105,7 @@ public record GameState
GameTypeName = null, GameTypeName = null,
DayId = null, DayId = null,
ActiveGameId = null, ActiveGameId = null,
ThrowPanel = ThrowPanelState.Initial, ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial, Participants = ParticipantsState.Initial,
GameModel = null, GameModel = null,
Setup = null, Setup = null,
@ -350,7 +355,12 @@ public record GameSnapshot
/// <summary> /// <summary>
/// Snapshot of the throw panel state. /// Snapshot of the throw panel state.
/// </summary> /// </summary>
public ThrowPanelState ThrowPanel { get; init; } = ThrowPanelState.Initial; public ThrowPanelState ThrowPanelBefore { get; init; } = ThrowPanelState.Initial;
/// <summary>
/// Snapshot of the throw panel state.
/// </summary>
public ThrowPanelState ThrowPanelAfter { get; init; } = ThrowPanelState.Initial;
/// <summary> /// <summary>
/// Snapshot of the participants state. /// Snapshot of the participants state.

View File

@ -41,7 +41,7 @@
@inject IGameStatusDataService DataService; @inject IGameStatusDataService DataService;
@* @ThrowPanelState.Value *@ @* @ThrowPanelAfterAfterState.Value *@
@switch (_gameView) @switch (_gameView)
{ {