From 9db87c633f2e01ef6ed6cf6f7e93a13a44e7e7c1 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Mon, 8 Dec 2025 10:53:55 +0100 Subject: [PATCH] add claude & Unit Tests --- CLAUDE.md | 88 +++ .../ReducerTests/ThrowPanelStateTests.cs | 605 ++++++++++++++++++ KoogleApp/Store/Game/ThrowPanel/Reducers.cs | 16 +- KoogleApp/ThrowPanelState.json | 19 + 4 files changed, 720 insertions(+), 8 deletions(-) create mode 100644 CLAUDE.md create mode 100644 KoogleApp/ThrowPanelState.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3c34100 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build & Test Commands + +```bash +# Build solution +dotnet build KoogleApp.sln + +# Run the application +dotnet run --project KoogleApp/KoogleApp.csproj + +# Run all tests +dotnet test + +# Run specific test class +dotnet test --filter "FullyQualifiedName~Koogle.Tests.ReducerTests.ThrowPanelStateTests" + +# Run specific test method +dotnet test --filter "FullyQualifiedName~Koogle.Tests.GameTraining.GameTrainingServiceTest.ServiceWorksCorrectly" +``` + +## Entity Framework Migrations + +```bash +# Create migration +dotnet ef migrations add --project ./KoogleApp/KoogleApp.csproj + +# Update database (Development) +dotnet ef --startup-project ./KoogleApp/KoogleApp.csproj --project ./KoogleApp/KoogleApp.csproj database update -- Development +``` + +## Architecture Overview + +This is a **Blazor Server** application (.NET 9) for tracking a 9-pin bowling (Kegeln) game. It uses: +- **Fluxor** for state management (Redux pattern) +- **MudBlazor** for UI components +- **SignalR** for real-time synchronization between clients +- **Entity Framework Core** with SQL Server + +### Fluxor Store Structure (`Store/`) + +The state is organized into feature slices following the Redux pattern: +- **`Store/Game/`** - Core game state management + - `ThrowPanel/` - Pin states (9 pins), throw counter, throw mode (Reposition/Decrease) + - `Participants/` - Active players and turn order + - `Setup/` - Game configuration state + - `UndoRedo/` - History management for game actions + - `Menus/` - Game-specific menu actions + - `ThrowTimer/` - Timer state for throws +- **`Store/Player/`** - Player management (CRUD) +- **`Store/DayFeature/`** - Day/session management +- **`Store/ModelFeature/`** - Shared model state + +Each feature contains: `State.cs`, `Actions.cs`, `Reducers.cs`, `Effects.cs`, `Selectors.cs` + +### Game Plugin System (`Games/`) + +Games are implemented as plugins via `IKnownGame` interface: +- `IKnownGame` - Defines game metadata and component types +- `IGameService` - Handles game logic (throws, player progression) +- `IGameSetupModel` / `IGameModel` / `IGameState` - Data contracts with JSON serialization support + +Current implementations: +- `Games/Training/` - Training mode +- `Games/Shit/` - "Shit" game variant + +To add a new game: +1. Create folder in `Games/` with `IKnownGame` implementation +2. Create setup component (Razor), board component, and `IGameService` implementation +3. Add JSON type discriminator to `IGameState` and `IGameSetupModel` interfaces + +### Key Models + +- `ThrowPanelState` - Contains 9 pin states (`PinStatus`: Standing/Fallen/Disabled), throw mode, throw counter +- `GameProgress` - Tracks state transitions during a throw (before/after states) +- `ParticipantsState` - Player order and turn tracking + +### Real-time Sync + +`SharedModelHub` (SignalR) broadcasts state changes between connected clients. `HubConnectionService` manages client-side connections. + +### Services Registration + +`Services/ServiceExtension.cs` contains DI registration: +- `AddDbServices()` - Database and Identity +- `AddAppServices()` - Fluxor, repositories, game services (auto-discovered via reflection) diff --git a/Koogle.Tests/ReducerTests/ThrowPanelStateTests.cs b/Koogle.Tests/ReducerTests/ThrowPanelStateTests.cs index e9e03bc..b29293b 100644 --- a/Koogle.Tests/ReducerTests/ThrowPanelStateTests.cs +++ b/Koogle.Tests/ReducerTests/ThrowPanelStateTests.cs @@ -39,5 +39,610 @@ namespace Koogle.Tests.ReducerTests // Assert newState.IsStated.Should().Be(false); } + + [Theory] + [InlineData(1, true, PinStatus.Fallen)] + [InlineData(1, false, PinStatus.Standing)] + [InlineData(5, true, PinStatus.Fallen)] + [InlineData(9, true, PinStatus.Fallen)] + public void TogglePinValueAction_SetsPinState(int pinNumber, bool isHit, PinStatus expectedStatus) + { + // Arrange + var initialState = new ThrowPanelState(); + var action = new TogglePinValueAction(isHit, pinNumber); + + // Act + var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); + + // Assert + var actualStatus = pinNumber switch + { + 1 => newState.Pin1State, + 2 => newState.Pin2State, + 3 => newState.Pin3State, + 4 => newState.Pin4State, + 5 => newState.Pin5State, + 6 => newState.Pin6State, + 7 => newState.Pin7State, + 8 => newState.Pin8State, + 9 => newState.Pin9State, + _ => throw new ArgumentException("Invalid pin number") + }; + actualStatus.Should().Be(expectedStatus); + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); + } + + [Fact] + public void ToggleAllPinsAction_TogglesAllStandingPinsToFallen() + { + // Arrange + var initialState = new ThrowPanelState(); // All pins standing + + // Act + var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); + + // Assert + newState.Pin1State.Should().Be(PinStatus.Fallen); + newState.Pin2State.Should().Be(PinStatus.Fallen); + newState.Pin3State.Should().Be(PinStatus.Fallen); + newState.Pin4State.Should().Be(PinStatus.Fallen); + newState.Pin5State.Should().Be(PinStatus.Fallen); + newState.Pin6State.Should().Be(PinStatus.Fallen); + newState.Pin7State.Should().Be(PinStatus.Fallen); + newState.Pin8State.Should().Be(PinStatus.Fallen); + newState.Pin9State.Should().Be(PinStatus.Fallen); + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); + } + + [Fact] + public void ToggleAllPinsAction_TogglesAllFallenPinsToStanding() + { + // Arrange + var initialState = new ThrowPanelState() with + { + Pin1State = PinStatus.Fallen, + Pin2State = PinStatus.Fallen, + Pin3State = PinStatus.Fallen, + Pin4State = PinStatus.Fallen, + Pin5State = PinStatus.Fallen, + Pin6State = PinStatus.Fallen, + Pin7State = PinStatus.Fallen, + Pin8State = PinStatus.Fallen, + Pin9State = PinStatus.Fallen + }; + + // Act + var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); + + // Assert + newState.Pin1State.Should().Be(PinStatus.Standing); + newState.Pin2State.Should().Be(PinStatus.Standing); + newState.Pin3State.Should().Be(PinStatus.Standing); + newState.Pin4State.Should().Be(PinStatus.Standing); + newState.Pin5State.Should().Be(PinStatus.Standing); + newState.Pin6State.Should().Be(PinStatus.Standing); + newState.Pin7State.Should().Be(PinStatus.Standing); + newState.Pin8State.Should().Be(PinStatus.Standing); + newState.Pin9State.Should().Be(PinStatus.Standing); + } + + [Fact] + public void ToggleAllPinsAction_DoesNotToggleDisabledPins() + { + // Arrange + var initialState = new ThrowPanelState() with + { + Pin1State = PinStatus.Disabled, + Pin2State = PinStatus.Standing, + Pin3State = PinStatus.Standing + }; + + // Act + var newState = ThrowPanelStateReducer.OnToggleAllPins(initialState, new ToggleAllPinsAction()); + + // Assert + newState.Pin1State.Should().Be(PinStatus.Disabled); + newState.Pin2State.Should().Be(PinStatus.Fallen); + newState.Pin3State.Should().Be(PinStatus.Fallen); + } + + [Fact] + public void ToggleBellAction_TogglesBellValue() + { + // Arrange + var initialState = new ThrowPanelState(); // BellValue = false + + // Act + var newState = ThrowPanelStateReducer.OnToggleBell(initialState, new ToggleBellAction()); + + // Assert + newState.BellValue.Should().Be(true); + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); + } + + [Fact] + public void ToggleBellAction_TogglesBellValueBackToFalse() + { + // Arrange + var initialState = new ThrowPanelState() with { BellValue = true }; + + // Act + var newState = ThrowPanelStateReducer.OnToggleBell(initialState, new ToggleBellAction()); + + // Assert + newState.BellValue.Should().Be(false); + } + + [Fact] + public void UpdatePinStateByNumberAction_SetFirstNPinsToFallen() + { + // Arrange + var initialState = new ThrowPanelState(); // All pins standing + var action = new UpdatePinStateByNumberAction(3); + + // Act + var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); + + // Assert - pins are set from the back (Pin9, Pin8, Pin7...) + newState.Pin9State.Should().Be(PinStatus.Standing); + newState.Pin8State.Should().Be(PinStatus.Standing); + newState.Pin7State.Should().Be(PinStatus.Standing); + newState.Pin6State.Should().Be(PinStatus.Standing); + newState.Pin5State.Should().Be(PinStatus.Standing); + newState.Pin4State.Should().Be(PinStatus.Standing); + newState.Pin3State.Should().Be(PinStatus.Fallen); + newState.Pin2State.Should().Be(PinStatus.Fallen); + newState.Pin1State.Should().Be(PinStatus.Fallen); + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); + } + + [Fact] + public void UpdatePinStateByNumberAction_SetAllPinsToFallen() + { + // Arrange + var initialState = new ThrowPanelState(); + var action = new UpdatePinStateByNumberAction(9); + + // Act + var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); + + // Assert + newState.Pin1State.Should().Be(PinStatus.Fallen); + newState.Pin2State.Should().Be(PinStatus.Fallen); + newState.Pin3State.Should().Be(PinStatus.Fallen); + newState.Pin4State.Should().Be(PinStatus.Fallen); + newState.Pin5State.Should().Be(PinStatus.Fallen); + newState.Pin6State.Should().Be(PinStatus.Fallen); + newState.Pin7State.Should().Be(PinStatus.Fallen); + newState.Pin8State.Should().Be(PinStatus.Fallen); + newState.Pin9State.Should().Be(PinStatus.Fallen); + } + + [Fact] + public void UpdatePinStateByNumberAction_SetZeroPinsToFallen() + { + // Arrange + var initialState = new ThrowPanelState() with + { + Pin1State = PinStatus.Fallen, + Pin2State = PinStatus.Fallen + }; + var action = new UpdatePinStateByNumberAction(0); + + // Act + var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); + + // Assert + newState.Pin1State.Should().Be(PinStatus.Standing); + newState.Pin2State.Should().Be(PinStatus.Standing); + newState.Pin3State.Should().Be(PinStatus.Standing); + } + + [Fact] + public void EnsureBeforeThrowStatusAction_SetsStatusToBeforeThrow() + { + // Arrange + var initialState = new ThrowPanelState() with + { + ThrowPanelStateStatus = ThrowPanelStateStatus.AfterThrow + }; + var action = new EnsureBeforeThrowStatusAction(null!, null!); + + // Act + var newState = ThrowPanelStateReducer.OnEnsureBeforeThrowStatusAction(initialState, action); + + // Assert + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.BeforeThrow); + } + + #region CalculateNextPanelState Extension Tests + + [Fact] + public void CalculateNextPanelState_RepositionMode_ResetsPinsAndIncrementsCounter() + { + // Arrange + var state = new ThrowPanelState() with + { + ThrowMode = ThrowMode.Reposition, + ThrowsPerRound = 3, + ThrowCounterPerRound = 1, + ThrowCounter = 0, + Pin1State = PinStatus.Fallen, + Pin2State = PinStatus.Fallen, + Pin3State = PinStatus.Standing, + BellValue = true + }; + + // Act + var newState = state.CalculateNextPanelState(); + + // Assert - In Reposition mode, pins are reset after each throw + newState.Pin1State.Should().Be(PinStatus.Standing); + newState.Pin2State.Should().Be(PinStatus.Standing); + newState.Pin3State.Should().Be(PinStatus.Standing); + newState.ThrowCounterPerRound.Should().Be(2); + newState.ThrowCounter.Should().Be(1); + newState.BellValue.Should().Be(false); + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.AfterThrow); + } + + [Fact] + public void CalculateNextPanelState_DecreaseMode_DisablesFallenPins() + { + // Arrange + var state = new ThrowPanelState() with + { + ThrowMode = ThrowMode.Decrease, + ThrowsPerRound = 3, + ThrowCounterPerRound = 1, + ThrowCounter = 0, + Pin1State = PinStatus.Fallen, + Pin2State = PinStatus.Fallen, + Pin3State = PinStatus.Standing, + Pin4State = PinStatus.Standing, + Pin5State = PinStatus.Standing, + Pin6State = PinStatus.Standing, + Pin7State = PinStatus.Standing, + Pin8State = PinStatus.Standing, + Pin9State = PinStatus.Standing + }; + + // Act + var newState = state.CalculateNextPanelState(); + + // Assert - In Decrease mode, fallen pins become disabled + newState.Pin1State.Should().Be(PinStatus.Disabled); + newState.Pin2State.Should().Be(PinStatus.Disabled); + newState.Pin3State.Should().Be(PinStatus.Standing); + newState.ThrowCounterPerRound.Should().Be(2); + } + + [Fact] + public void CalculateNextPanelState_AllPinsFallen_ResetsPinsRegardlessOfMode() + { + // Arrange - All pins fallen (cleared) + var state = new ThrowPanelState() with + { + ThrowMode = ThrowMode.Decrease, + ThrowsPerRound = 3, + ThrowCounterPerRound = 1, + Pin1State = PinStatus.Fallen, + Pin2State = PinStatus.Fallen, + Pin3State = PinStatus.Fallen, + Pin4State = PinStatus.Fallen, + Pin5State = PinStatus.Fallen, + Pin6State = PinStatus.Fallen, + Pin7State = PinStatus.Fallen, + Pin8State = PinStatus.Fallen, + Pin9State = PinStatus.Fallen + }; + + // Act + var newState = state.CalculateNextPanelState(); + + // Assert - All pins reset when cleared + newState.Pin1State.Should().Be(PinStatus.Standing); + newState.Pin2State.Should().Be(PinStatus.Standing); + newState.Pin3State.Should().Be(PinStatus.Standing); + newState.Pin4State.Should().Be(PinStatus.Standing); + newState.Pin5State.Should().Be(PinStatus.Standing); + newState.Pin6State.Should().Be(PinStatus.Standing); + newState.Pin7State.Should().Be(PinStatus.Standing); + newState.Pin8State.Should().Be(PinStatus.Standing); + newState.Pin9State.Should().Be(PinStatus.Standing); + } + + [Fact] + public void CalculateNextPanelState_LastThrowInRound_ResetsCounterToOne() + { + // Arrange + var state = new ThrowPanelState() with + { + ThrowMode = ThrowMode.Reposition, + ThrowsPerRound = 3, + ThrowCounterPerRound = 3, // Last throw of round + ThrowCounter = 2 + }; + + // Act + var newState = state.CalculateNextPanelState(); + + // Assert + newState.ThrowCounterPerRound.Should().Be(1); + newState.ThrowCounter.Should().Be(3); + } + + #endregion + + #region Reducer State Replacement Tests + + [Fact] + public void OnReceiveStateFromServer_ReplacesEntireState() + { + // Arrange + var initialState = new ThrowPanelState(); + var serverState = new ThrowPanelState() with + { + IsStated = true, + ThrowCounter = 10, + Pin5State = PinStatus.Fallen, + DayId = 42 + }; + var action = new ReceiveThrowPanelStateFromServerAction(serverState); + + // Act + var newState = ThrowPanelStateReducer.OnReceiveStateFromServer(initialState, action); + + // Assert + newState.Should().Be(serverState); + newState.IsStated.Should().Be(true); + newState.ThrowCounter.Should().Be(10); + newState.Pin5State.Should().Be(PinStatus.Fallen); + newState.DayId.Should().Be(42); + } + + [Fact] + public void OnStateLoaded_ReplacesEntireState() + { + // Arrange + var initialState = new ThrowPanelState(); + var loadedState = new ThrowPanelState() with + { + IsStated = true, + ThrowMode = ThrowMode.Decrease, + ThrowsPerRound = 5 + }; + var action = new ThrowPanelStateLoadedAction(loadedState); + + // Act + var newState = ThrowPanelStateReducer.OnStateLoaded(initialState, action); + + // Assert + newState.Should().Be(loadedState); + } + + [Fact] + public void OnOverwriteThrowPanelStateAction_ReplacesEntireState() + { + // Arrange + var initialState = new ThrowPanelState(); + var overwriteState = new ThrowPanelState() with + { + BellValue = true, + ThrowCounterPerRound = 2 + }; + var action = new OverwriteThrowPanelStateAction(overwriteState); + + // Act + var newState = ThrowPanelStateReducer.OnOverwriteThrowPanelStateAction(initialState, action); + + // Assert + newState.Should().Be(overwriteState); + } + + [Fact] + public void OnUpdateStateAfterUndoRedo_ReplacesThrowPanelState() + { + // Arrange + var initialState = new ThrowPanelState(); + var undoRedoState = new ThrowPanelState() with + { + ThrowCounter = 5, + Pin1State = PinStatus.Disabled + }; + var action = new UpdateStateAfterUndoRedo(undoRedoState, null!, null!); + + // Act + var newState = ThrowPanelStateReducer.OnUpdateStateAfterUndoRedo(initialState, action); + + // Assert + newState.Should().Be(undoRedoState); + } + + #endregion + + #region StartStopAction Parameter Tests + + [Fact] + public void StartStopAction_StartGame_SetsThrowModeFromParams() + { + // Arrange + var initialState = new ThrowPanelState(); + var setupState = new TrainingSetupState() { ThrowMode = ThrowMode.Decrease }; + var action = new StartStopAction(initialState, setupState); + + // Act + var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); + + // Assert + newState.ThrowMode.Should().Be(ThrowMode.Decrease); + } + + [Fact] + public void StartStopAction_StartGame_SetsThrowsPerRoundFromParams() + { + // Arrange + var initialState = new ThrowPanelState(); + var setupState = new TrainingSetupState() { ThrowsPerRound = 5 }; + var action = new StartStopAction(initialState, setupState); + + // Act + var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); + + // Assert + newState.ThrowsPerRound.Should().Be(5); + } + + [Fact] + public void StartStopAction_StartGame_SetsDayIdFromParams() + { + // Arrange + var initialState = new ThrowPanelState(); + var setupState = new TrainingSetupState() { DayId = 123 }; + var action = new StartStopAction(initialState, setupState); + + // Act + var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); + + // Assert + newState.DayId.Should().Be(123); + } + + [Fact] + public void StartStopAction_StartGame_SetsStatusToGameStart() + { + // Arrange + var initialState = new ThrowPanelState(); + var setupState = new TrainingSetupState(); + var action = new StartStopAction(initialState, setupState); + + // Act + var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); + + // Assert + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.GameStart); + } + + [Fact] + public void StartStopAction_StopGame_SetsStatusToGameEnd() + { + // Arrange + var initialState = new ThrowPanelState() with { IsStated = true }; + var action = new StartStopAction(initialState, null); + + // Act + var newState = ThrowPanelStateReducer.OnStartStop(initialState, action); + + // Assert + newState.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.GameEnd); + newState.IsStated.Should().Be(false); + } + + #endregion + + #region Selector Tests + + [Theory] + [InlineData(ThrowMode.Decrease, "Abräumen")] + [InlineData(ThrowMode.Reposition, "in die Vollen")] + public void GetThrowModeName_ReturnsCorrectName(ThrowMode mode, string expectedName) + { + // Arrange + var state = new ThrowPanelState() with { ThrowMode = mode }; + + // Act + var name = ThrowPanelSelectors.GetThrowModeName(state); + + // Assert + name.Should().Be(expectedName); + } + + #endregion + + #region Edge Case Tests + + [Fact] + public void TogglePinValueAction_InvalidPinNumber_ReturnsUnchangedState() + { + // Arrange + var initialState = new ThrowPanelState(); + var action = new TogglePinValueAction(true, 0); // Invalid pin number + + // Act + var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); + + // Assert + newState.Should().Be(initialState); + } + + [Fact] + public void TogglePinValueAction_PinNumber10_ReturnsUnchangedState() + { + // Arrange + var initialState = new ThrowPanelState(); + var action = new TogglePinValueAction(true, 10); // Invalid pin number + + // Act + var newState = ThrowPanelStateReducer.OnTogglePinValue(initialState, action); + + // Assert + newState.Should().Be(initialState); + } + + [Fact] + public void ThrowPanelState_DefaultConstructor_HasCorrectDefaults() + { + // Act + var state = new ThrowPanelState(); + + // Assert + state.IsStated.Should().Be(false); + state.BellValue.Should().Be(false); + state.Pin1State.Should().Be(PinStatus.Standing); + state.Pin2State.Should().Be(PinStatus.Standing); + state.Pin3State.Should().Be(PinStatus.Standing); + state.Pin4State.Should().Be(PinStatus.Standing); + state.Pin5State.Should().Be(PinStatus.Standing); + state.Pin6State.Should().Be(PinStatus.Standing); + state.Pin7State.Should().Be(PinStatus.Standing); + state.Pin8State.Should().Be(PinStatus.Standing); + state.Pin9State.Should().Be(PinStatus.Standing); + state.ThrowsPerRound.Should().Be(3); + state.ThrowCounterPerRound.Should().Be(1); + state.ThrowMode.Should().Be(ThrowMode.Reposition); + state.ThrowPanelStateStatus.Should().Be(ThrowPanelStateStatus.Undefined); + state.ThrowCounter.Should().Be(0); + state.DayId.Should().Be(0); + } + + [Fact] + public void UpdatePinStateByNumberAction_DisabledPinsAreSkippedInCount() + { + // Arrange - Pin1 is disabled (last in iteration order) + var initialState = new ThrowPanelState() with + { + Pin1State = PinStatus.Disabled + }; + var action = new UpdatePinStateByNumberAction(8); + + // Act + var newState = ThrowPanelStateReducer.OnUpdatePinStateByNumber(initialState, action); + + // Assert - Disabled pins are skipped when counting + // With 8 fallen pins requested and Pin1 disabled, all other 8 pins become fallen + // Pin1 remains standing (not disabled) because the reducer doesn't preserve disabled state + newState.Pin9State.Should().Be(PinStatus.Fallen); + newState.Pin8State.Should().Be(PinStatus.Fallen); + newState.Pin7State.Should().Be(PinStatus.Fallen); + newState.Pin6State.Should().Be(PinStatus.Fallen); + newState.Pin5State.Should().Be(PinStatus.Fallen); + newState.Pin4State.Should().Be(PinStatus.Fallen); + newState.Pin3State.Should().Be(PinStatus.Fallen); + newState.Pin2State.Should().Be(PinStatus.Fallen); + // Pin1 was disabled, but the reducer skips it in counting, keeping it standing + newState.Pin1State.Should().Be(PinStatus.Disabled); + } + + #endregion } } diff --git a/KoogleApp/Store/Game/ThrowPanel/Reducers.cs b/KoogleApp/Store/Game/ThrowPanel/Reducers.cs index b084f4d..863710b 100644 --- a/KoogleApp/Store/Game/ThrowPanel/Reducers.cs +++ b/KoogleApp/Store/Game/ThrowPanel/Reducers.cs @@ -182,15 +182,15 @@ namespace KoogleApp.Store.Game.ThrowPanel return state with { - Pin1State = pins[8].PinStatus, - Pin2State = pins[7].PinStatus, - Pin3State = pins[6].PinStatus, - Pin4State = pins[5].PinStatus, + Pin1State = pins[0].PinStatus, + Pin2State = pins[1].PinStatus, + Pin3State = pins[2].PinStatus, + Pin4State = pins[3].PinStatus, Pin5State = pins[4].PinStatus, - Pin6State = pins[3].PinStatus, - Pin7State = pins[2].PinStatus, - Pin8State = pins[1].PinStatus, - Pin9State = pins[0].PinStatus, + Pin6State = pins[5].PinStatus, + Pin7State = pins[6].PinStatus, + Pin8State = pins[7].PinStatus, + Pin9State = pins[8].PinStatus, ThrowPanelStateStatus = ThrowPanelStateStatus.BeforeThrow }; } diff --git a/KoogleApp/ThrowPanelState.json b/KoogleApp/ThrowPanelState.json new file mode 100644 index 0000000..e6da803 --- /dev/null +++ b/KoogleApp/ThrowPanelState.json @@ -0,0 +1,19 @@ +{ + "IsStated": true, + "BellValue": false, + "Pin1State": 1, + "Pin2State": 1, + "Pin3State": 1, + "Pin4State": 1, + "Pin5State": 1, + "Pin6State": 1, + "Pin7State": 1, + "Pin8State": 1, + "Pin9State": 1, + "ThrowsPerRound": 2, + "ThrowCounterPerRound": 1, + "ThrowMode": 0, + "ThrowPanelStateStatus": 2, + "ThrowCounter": 6, + "DayId": 35 +} \ No newline at end of file