add claude & Unit Tests

This commit is contained in:
beo3000 2025-12-08 10:53:55 +01:00
parent e6e059c9c2
commit 9db87c633f
4 changed files with 720 additions and 8 deletions

88
CLAUDE.md Normal file
View File

@ -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 <MigrationName> --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)

View File

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

View File

@ -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
};
}

View File

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