fix undo/redo handling
This commit is contained in:
parent
0a7fb1f0af
commit
191fb3db3f
|
|
@ -163,6 +163,7 @@
|
|||
private int? _selectedNumber;
|
||||
private bool _hasModifiedPins;
|
||||
private ThrowPanelState? _beforeThrowState;
|
||||
private int _lastKnownThrowCounter;
|
||||
|
||||
private bool IsInteractive => GameState.Value.IsGameActive && !GameState.Value.IsLoading;
|
||||
|
||||
|
|
@ -172,15 +173,30 @@
|
|||
GameState.StateChanged += OnGameStateChanged;
|
||||
// Store initial state for before/after comparison
|
||||
CaptureBeforeThrowState();
|
||||
_lastKnownThrowCounter = GameState.Value.ThrowPanel.TotalThrowCounter;
|
||||
}
|
||||
|
||||
private void OnGameStateChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// After throw is processed and pins are reset, capture new before state
|
||||
if (!_hasModifiedPins)
|
||||
var currentCounter = GameState.Value.ThrowPanel.TotalThrowCounter;
|
||||
|
||||
// Detect external state changes (Undo/Redo/SignalR) by comparing throw counter
|
||||
// If counter changed externally, reset local state and capture new before state
|
||||
if (currentCounter != _lastKnownThrowCounter)
|
||||
{
|
||||
_hasModifiedPins = false;
|
||||
_selectedNumber = null;
|
||||
CaptureBeforeThrowState();
|
||||
_lastKnownThrowCounter = currentCounter;
|
||||
}
|
||||
else if (!_hasModifiedPins)
|
||||
{
|
||||
// Normal state change (e.g., after throw processed), capture new before state
|
||||
CaptureBeforeThrowState();
|
||||
}
|
||||
|
||||
// Force re-render to update child components (ThrowPanel, PinPanel, etc.)
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void CaptureBeforeThrowState()
|
||||
|
|
@ -201,7 +217,7 @@
|
|||
var currentStatus = GetPinStatus(pinNumber);
|
||||
|
||||
// In Decrease mode, fallen pins cannot be reset to standing
|
||||
if (GameState.Value.ThrowPanel.ThrowMode == ThrowMode.Decrease && currentStatus == PinStatus.Fallen)
|
||||
if (GameState.Value.ThrowPanel.ThrowMode == ThrowMode.Decrease && currentStatus == PinStatus.Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,6 +467,14 @@ public class GameEffects
|
|||
[EffectMethod]
|
||||
public Task HandleUndoThrowSuccess(UndoThrowSuccessAction action, IDispatcher dispatcher)
|
||||
{
|
||||
//var stateAfterReducer = _gameState.Value;
|
||||
//_logger.LogInformation(
|
||||
// "UNDO SUCCESS: State after reducer - pins fallen={Fallen}, total={Total}, UndoStack={UndoCount}, RedoStack={RedoCount}",
|
||||
// stateAfterReducer.ThrowPanel.CountFallenPins(),
|
||||
// stateAfterReducer.ThrowPanel.TotalThrowCounter,
|
||||
// stateAfterReducer.UndoStack.Count,
|
||||
// stateAfterReducer.RedoStack.Count);
|
||||
|
||||
// Save after undo as well
|
||||
_saveDebounceTimer?.Dispose();
|
||||
_saveDebounceTimer = new Timer(
|
||||
|
|
@ -495,9 +503,12 @@ public class GameEffects
|
|||
|
||||
var lastSnapshot = state.UndoStack[^1];
|
||||
|
||||
_logger.LogDebug(
|
||||
"Undoing throw. Stack size before: {StackSize}, Restoring to throw counter: {ThrowCounter}",
|
||||
_logger.LogInformation(
|
||||
"UNDO: Stack={StackSize}, Current pins fallen={CurrentFallen}, Snapshot pins fallen={SnapshotFallen}, Current total={CurrentTotal}, Snapshot total={SnapshotTotal}",
|
||||
state.UndoStack.Count,
|
||||
state.ThrowPanel.CountFallenPins(),
|
||||
lastSnapshot.ThrowPanel.CountFallenPins(),
|
||||
state.ThrowPanel.TotalThrowCounter,
|
||||
lastSnapshot.ThrowPanel.TotalThrowCounter);
|
||||
|
||||
dispatcher.Dispatch(new UndoThrowSuccessAction(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using Fluxor;
|
||||
using Koogle.Application.Games;
|
||||
using Koogle.Domain.Enums;
|
||||
|
||||
namespace Koogle.Web.Store.GameState;
|
||||
|
|
@ -191,11 +193,14 @@ public static class GameReducers
|
|||
public static GameState OnRecordThrow(GameState state, RecordThrowAction action)
|
||||
{
|
||||
// Create snapshot of current state before throw is processed
|
||||
// Deep clone GameModel to avoid reference sharing issues
|
||||
object? clonedGameModel = CloneGameModel(state.GameModel);
|
||||
|
||||
var snapshot = new GameSnapshot
|
||||
{
|
||||
ThrowPanel = state.ThrowPanel,
|
||||
Participants = state.Participants,
|
||||
GameModel = state.GameModel
|
||||
GameModel = clonedGameModel
|
||||
};
|
||||
|
||||
return state with
|
||||
|
|
@ -322,12 +327,12 @@ public static class GameReducers
|
|||
[ReducerMethod]
|
||||
public static GameState OnUndoThrowSuccess(GameState state, UndoThrowSuccessAction action)
|
||||
{
|
||||
// Create snapshot of current state for redo
|
||||
// Create snapshot of current state for redo (deep clone GameModel)
|
||||
var currentSnapshot = new GameSnapshot
|
||||
{
|
||||
ThrowPanel = state.ThrowPanel,
|
||||
Participants = state.Participants,
|
||||
GameModel = state.GameModel
|
||||
GameModel = CloneGameModel(state.GameModel)
|
||||
};
|
||||
|
||||
// Remove the last snapshot from undo stack
|
||||
|
|
@ -364,12 +369,12 @@ public static class GameReducers
|
|||
[ReducerMethod]
|
||||
public static GameState OnRedoThrowSuccess(GameState state, RedoThrowSuccessAction action)
|
||||
{
|
||||
// Create snapshot of current state for undo
|
||||
// Create snapshot of current state for undo (deep clone GameModel)
|
||||
var currentSnapshot = new GameSnapshot
|
||||
{
|
||||
ThrowPanel = state.ThrowPanel,
|
||||
Participants = state.Participants,
|
||||
GameModel = state.GameModel
|
||||
GameModel = CloneGameModel(state.GameModel)
|
||||
};
|
||||
|
||||
// Remove the last snapshot from redo stack
|
||||
|
|
@ -508,4 +513,18 @@ public static class GameReducers
|
|||
{
|
||||
GameModel = action.GameModel
|
||||
};
|
||||
|
||||
// Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Deep clones a GameModel using JSON serialization.
|
||||
/// </summary>
|
||||
private static object? CloneGameModel(object? gameModel)
|
||||
{
|
||||
if (gameModel == null)
|
||||
return null;
|
||||
|
||||
var json = JsonSerializer.Serialize(gameModel, gameModel.GetType(), GameModelFactory.JsonSerializerOptions);
|
||||
return JsonSerializer.Deserialize(json, gameModel.GetType(), GameModelFactory.JsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue