- @if (@GameState.Value.ThrowPanel.ThrowMode == ThrowMode.Reposition)
+ @if (@GameState.Value.ThrowPanelAfter.ThrowMode == ThrowMode.Reposition)
{
@* Number quick-entry *@
@@ -52,7 +58,7 @@
@* Throw info and controls *@
-
@@ -162,7 +168,7 @@
private int? _selectedNumber;
private bool _hasModifiedPins;
- private ThrowPanelState? _beforeThrowState;
+ // private ThrowPanelState? _beforeThrowState;
private int _lastKnownThrowCounter;
private bool IsInteractive => GameState.Value.IsGameActive && !GameState.Value.IsLoading;
@@ -173,35 +179,46 @@
GameState.StateChanged += OnGameStateChanged;
// Store initial state for before/after comparison
CaptureBeforeThrowState();
- _lastKnownThrowCounter = GameState.Value.ThrowPanel.TotalThrowCounter;
+ _lastKnownThrowCounter = GameState.Value.ThrowPanelAfter.TotalThrowCounter;
}
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
- // 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)
{
_hasModifiedPins = false;
_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;
}
- 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();
}
- // Force re-render to update child components (ThrowPanel, PinPanel, etc.)
+ // Force re-render to update child components (ThrowPanelAfter, PinPanel, etc.)
InvokeAsync(StateHasChanged);
}
private void CaptureBeforeThrowState()
{
- _beforeThrowState = GameState.Value.ThrowPanel;
+ // _beforeThrowState = GameState.Value.ThrowPanelAfter;
}
public void Dispose()
@@ -217,7 +234,7 @@
var currentStatus = GetPinStatus(pinNumber);
// 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;
}
@@ -232,7 +249,7 @@
private void HandleNumberClick(int number)
{
// 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++)
{
@@ -254,7 +271,7 @@
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)
@@ -273,7 +290,7 @@
private async Task ConfirmThrow(bool isGutter, bool isLeftGutter)
{
- var currentThrowPanel = GameState.Value.ThrowPanel;
+ var currentThrowPanel = GameState.Value.ThrowPanelAfter;
var fallenPins = currentThrowPanel.CountFallenPins();
var bellValue = currentThrowPanel.BellValue;
@@ -288,7 +305,8 @@
};
// 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
Dispatcher.Dispatch(new RecordThrowAction(beforeState, currentThrowPanel, isGutter, isLeftGutter));
@@ -310,15 +328,15 @@
private PinStatus GetPinStatus(int pinNumber) => pinNumber switch
{
- 1 => GameState.Value.ThrowPanel.Pin1,
- 2 => GameState.Value.ThrowPanel.Pin2,
- 3 => GameState.Value.ThrowPanel.Pin3,
- 4 => GameState.Value.ThrowPanel.Pin4,
- 5 => GameState.Value.ThrowPanel.Pin5,
- 6 => GameState.Value.ThrowPanel.Pin6,
- 7 => GameState.Value.ThrowPanel.Pin7,
- 8 => GameState.Value.ThrowPanel.Pin8,
- 9 => GameState.Value.ThrowPanel.Pin9,
+ 1 => GameState.Value.ThrowPanelAfter.Pin1,
+ 2 => GameState.Value.ThrowPanelAfter.Pin2,
+ 3 => GameState.Value.ThrowPanelAfter.Pin3,
+ 4 => GameState.Value.ThrowPanelAfter.Pin4,
+ 5 => GameState.Value.ThrowPanelAfter.Pin5,
+ 6 => GameState.Value.ThrowPanelAfter.Pin6,
+ 7 => GameState.Value.ThrowPanelAfter.Pin7,
+ 8 => GameState.Value.ThrowPanelAfter.Pin8,
+ 9 => GameState.Value.ThrowPanelAfter.Pin9,
_ => PinStatus.Standing
};
diff --git a/src/Koogle.Web/Store/GameState/GameActions.cs b/src/Koogle.Web/Store/GameState/GameActions.cs
index f4e41c2..dda5588 100644
--- a/src/Koogle.Web/Store/GameState/GameActions.cs
+++ b/src/Koogle.Web/Store/GameState/GameActions.cs
@@ -68,7 +68,8 @@ public record LoadActiveGameAction(Guid DayId);
public record LoadActiveGameSuccessAction(
Guid GameId,
string GameTypeName,
- ThrowPanelState ThrowPanel,
+ ThrowPanelState ThrowPanelBefore,
+ ThrowPanelState ThrowPanelAfter,
ParticipantsState Participants,
object? GameModel,
IReadOnlyList
UndoStack,
diff --git a/src/Koogle.Web/Store/GameState/GameEffects.cs b/src/Koogle.Web/Store/GameState/GameEffects.cs
index 7ffebdc..90dcc7f 100644
--- a/src/Koogle.Web/Store/GameState/GameEffects.cs
+++ b/src/Koogle.Web/Store/GameState/GameEffects.cs
@@ -55,7 +55,13 @@ public class GameEffects
{
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,
ThrowsPerRound = action.ThrowsPerRound,
@@ -201,12 +207,14 @@ public class GameEffects
return;
}
- var throwPanel = MapDtoToThrowPanel(stateDto.ThrowPanel);
+ var throwPanelBefore = MapDtoToThrowPanel(stateDto.ThrowPanelBefore);
+ var throwPanelAfter = MapDtoToThrowPanel(stateDto.ThrowPanelAfter);
var participants = MapDtoToParticipants(stateDto.Participants);
var undoStack = stateDto.UndoStack
.Select(s => new GameSnapshot
{
- ThrowPanel = MapDtoToThrowPanel(s.ThrowPanel),
+ ThrowPanelBefore = MapDtoToThrowPanel(s.ThrowPanelBefore),
+ ThrowPanelAfter = MapDtoToThrowPanel(s.ThrowPanelAfter),
Participants = MapDtoToParticipants(s.Participants),
GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null
})
@@ -214,7 +222,8 @@ public class GameEffects
var redoStack = stateDto.RedoStack
.Select(s => new GameSnapshot
{
- ThrowPanel = MapDtoToThrowPanel(s.ThrowPanel),
+ ThrowPanelBefore = MapDtoToThrowPanel(s.ThrowPanelBefore),
+ ThrowPanelAfter = MapDtoToThrowPanel(s.ThrowPanelAfter),
Participants = MapDtoToParticipants(s.Participants),
GameModel = s.GameModel.HasValue ? (object?)s.GameModel.Value : null
})
@@ -232,7 +241,8 @@ public class GameEffects
dispatcher.Dispatch(new LoadActiveGameSuccessAction(
activeGame.Id,
activeGame.GameType,
- throwPanel,
+ throwPanelBefore,
+ throwPanelAfter,
participants,
stateDto.GameModel.HasValue ? (object?)stateDto.GameModel.Value : null,
undoStack,
@@ -467,13 +477,13 @@ 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);
+ var stateAfterReducer = _gameState.Value;
+ _logger.LogInformation(
+ "UNDO SUCCESS: State after reducer - pins fallen={Fallen}, total={Total}, UndoStack={UndoCount}, RedoStack={RedoCount}",
+ stateAfterReducer.ThrowPanelAfter.CountFallenPins(),
+ stateAfterReducer.ThrowPanelAfter.TotalThrowCounter,
+ stateAfterReducer.UndoStack.Count,
+ stateAfterReducer.RedoStack.Count);
// Save after undo as well
_saveDebounceTimer?.Dispose();
@@ -506,13 +516,13 @@ public class GameEffects
_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);
+ state.ThrowPanelAfter.CountFallenPins(),
+ lastSnapshot.ThrowPanelAfter.CountFallenPins(),
+ state.ThrowPanelAfter.TotalThrowCounter,
+ lastSnapshot.ThrowPanelAfter.TotalThrowCounter);
dispatcher.Dispatch(new UndoThrowSuccessAction(
- lastSnapshot.ThrowPanel,
+ lastSnapshot.ThrowPanelAfter,
lastSnapshot.Participants,
lastSnapshot.GameModel));
@@ -539,10 +549,10 @@ public class GameEffects
_logger.LogDebug(
"Redoing throw. Stack size before: {StackSize}, Restoring to throw counter: {ThrowCounter}",
state.RedoStack.Count,
- lastSnapshot.ThrowPanel.TotalThrowCounter);
+ lastSnapshot.ThrowPanelAfter.TotalThrowCounter);
dispatcher.Dispatch(new RedoThrowSuccessAction(
- lastSnapshot.ThrowPanel,
+ lastSnapshot.ThrowPanelAfter,
lastSnapshot.Participants,
lastSnapshot.GameModel));
@@ -617,7 +627,8 @@ public class GameEffects
{
return new GameStateSerializationDto
{
- ThrowPanel = MapThrowPanelToDto(state.ThrowPanel),
+ ThrowPanelBefore = MapThrowPanelToDto(state.ThrowPanelBefore),
+ ThrowPanelAfter = MapThrowPanelToDto(state.ThrowPanelAfter),
Participants = new ParticipantsStateDto
{
PlayerIds = state.Participants.PlayerIds,
@@ -632,7 +643,7 @@ public class GameEffects
: null,
UndoStack = state.UndoStack.Select(s => new GameSnapshotDto
{
- ThrowPanel = MapThrowPanelToDto(s.ThrowPanel),
+ ThrowPanelAfter = MapThrowPanelToDto(s.ThrowPanelAfter),
Participants = new ParticipantsStateDto
{
PlayerIds = s.Participants.PlayerIds,
@@ -645,7 +656,7 @@ public class GameEffects
}).ToList(),
RedoStack = state.RedoStack.Select(s => new GameSnapshotDto
{
- ThrowPanel = MapThrowPanelToDto(s.ThrowPanel),
+ ThrowPanelAfter = MapThrowPanelToDto(s.ThrowPanelAfter),
Participants = new ParticipantsStateDto
{
PlayerIds = s.Participants.PlayerIds,
diff --git a/src/Koogle.Web/Store/GameState/GameReducers.cs b/src/Koogle.Web/Store/GameState/GameReducers.cs
index d22e15f..5fad56d 100644
--- a/src/Koogle.Web/Store/GameState/GameReducers.cs
+++ b/src/Koogle.Web/Store/GameState/GameReducers.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
using Fluxor;
using Koogle.Application.Games;
using Koogle.Domain.Enums;
+using Microsoft.Extensions.Logging;
namespace Koogle.Web.Store.GameState;
@@ -34,7 +35,7 @@ public static class GameReducers
IsGameActive = true,
ActiveGameId = action.GameId,
GameTypeName = action.GameTypeName,
- ThrowPanel = action.ThrowPanel,
+ ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants,
GameModel = action.GameModel,
Setup = action.Setup,
@@ -76,7 +77,7 @@ public static class GameReducers
IsGameActive = false,
ActiveGameId = null,
GameTypeName = null,
- ThrowPanel = ThrowPanelState.Initial,
+ ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial,
GameModel = null,
Setup = null,
@@ -119,7 +120,7 @@ public static class GameReducers
IsGameActive = true,
ActiveGameId = action.GameId,
GameTypeName = action.GameTypeName,
- ThrowPanel = action.ThrowPanel,
+ ThrowPanelAfter = action.ThrowPanelAfter,
Participants = action.Participants,
GameModel = action.GameModel,
Setup = action.Setup,
@@ -198,11 +199,17 @@ public static class GameReducers
var snapshot = new GameSnapshot
{
- ThrowPanel = state.ThrowPanel,
+ ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants,
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
{
UndoStack = state.UndoStack.Add(snapshot),
@@ -217,9 +224,15 @@ public static class GameReducers
[ReducerMethod]
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
{
- ThrowPanel = action.NewThrowPanelState,
+ ThrowPanelAfter = action.NewThrowPanelState,
GameModel = action.UpdatedGameModel,
IsLoading = false,
IsSaving = true
@@ -274,7 +287,7 @@ public static class GameReducers
public static GameState OnSetPinStatus(GameState state, SetPinStatusAction action)
=> state with
{
- ThrowPanel = state.ThrowPanel.SetPin(action.PinNumber, action.Status)
+ ThrowPanelAfter = state.ThrowPanelAfter.SetPin(action.PinNumber, action.Status)
};
///
@@ -284,7 +297,7 @@ public static class GameReducers
public static GameState OnResetPins(GameState state)
=> state with
{
- ThrowPanel = state.ThrowPanel.ResetPins()
+ ThrowPanelAfter = state.ThrowPanelAfter.ResetPins()
};
///
@@ -294,7 +307,7 @@ public static class GameReducers
public static GameState OnSetBellValue(GameState state, SetBellValueAction action)
=> state with
{
- ThrowPanel = state.ThrowPanel with { BellValue = action.Value }
+ ThrowPanelAfter = state.ThrowPanelAfter with { BellValue = action.Value }
};
// Player Reducers
@@ -327,10 +340,16 @@ public static class GameReducers
[ReducerMethod]
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)
var currentSnapshot = new GameSnapshot
{
- ThrowPanel = state.ThrowPanel,
+ ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants,
GameModel = CloneGameModel(state.GameModel)
};
@@ -342,7 +361,7 @@ public static class GameReducers
return state with
{
- ThrowPanel = action.ThrowPanel,
+ ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants,
GameModel = action.GameModel,
UndoStack = newUndoStack,
@@ -372,7 +391,7 @@ public static class GameReducers
// Create snapshot of current state for undo (deep clone GameModel)
var currentSnapshot = new GameSnapshot
{
- ThrowPanel = state.ThrowPanel,
+ ThrowPanelAfter = state.ThrowPanelAfter,
Participants = state.Participants,
GameModel = CloneGameModel(state.GameModel)
};
@@ -384,7 +403,7 @@ public static class GameReducers
return state with
{
- ThrowPanel = action.ThrowPanel,
+ ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants,
GameModel = action.GameModel,
UndoStack = state.UndoStack.Add(currentSnapshot),
@@ -466,7 +485,7 @@ public static class GameReducers
public static GameState OnGameStateUpdatedFromHub(GameState state, GameStateUpdatedFromHubAction action)
=> state with
{
- ThrowPanel = action.ThrowPanel,
+ ThrowPanelAfter = action.ThrowPanel,
Participants = action.Participants,
GameModel = action.GameModel
};
@@ -481,7 +500,7 @@ public static class GameReducers
IsGameActive = false,
ActiveGameId = null,
GameTypeName = null,
- ThrowPanel = ThrowPanelState.Initial,
+ ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial,
GameModel = null,
Setup = null,
diff --git a/src/Koogle.Web/Store/GameState/GameState.cs b/src/Koogle.Web/Store/GameState/GameState.cs
index b0f89bd..99458af 100644
--- a/src/Koogle.Web/Store/GameState/GameState.cs
+++ b/src/Koogle.Web/Store/GameState/GameState.cs
@@ -31,10 +31,15 @@ public record GameState
///
public Guid? ActiveGameId { get; init; }
+ ///
+ /// State of the throw panel before the current throw.
+ ///
+ public ThrowPanelState ThrowPanelBefore { get; init; } = ThrowPanelState.Initial;
+
///
/// Current state of the throw panel.
///
- public ThrowPanelState ThrowPanel { get; init; } = ThrowPanelState.Initial;
+ public ThrowPanelState ThrowPanelAfter { get; init; } = ThrowPanelState.Initial;
///
/// Current state of game participants.
@@ -100,7 +105,7 @@ public record GameState
GameTypeName = null,
DayId = null,
ActiveGameId = null,
- ThrowPanel = ThrowPanelState.Initial,
+ ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial,
GameModel = null,
Setup = null,
@@ -350,7 +355,12 @@ public record GameSnapshot
///
/// Snapshot of the throw panel state.
///
- public ThrowPanelState ThrowPanel { get; init; } = ThrowPanelState.Initial;
+ public ThrowPanelState ThrowPanelBefore { get; init; } = ThrowPanelState.Initial;
+
+ ///
+ /// Snapshot of the throw panel state.
+ ///
+ public ThrowPanelState ThrowPanelAfter { get; init; } = ThrowPanelState.Initial;
///
/// Snapshot of the participants state.
diff --git a/src/KoogleApp/Components/Pages/Game.razor b/src/KoogleApp/Components/Pages/Game.razor
index dff96ec..08c02fd 100644
--- a/src/KoogleApp/Components/Pages/Game.razor
+++ b/src/KoogleApp/Components/Pages/Game.razor
@@ -41,7 +41,7 @@
@inject IGameStatusDataService DataService;
-@* @ThrowPanelState.Value *@
+@* @ThrowPanelAfterAfterState.Value *@
@switch (_gameView)
{