using System.Collections.Immutable;
using Fluxor;
using Koogle.Application.Games;
using Koogle.Domain.Enums;
namespace Koogle.Web.Store.GameState;
///
/// Fluxor state for game management.
///
[FeatureState]
public record GameState
{
///
/// Indicates whether a game is currently active.
///
public bool IsGameActive { get; init; }
///
/// Name of the active game type (e.g., "Training", "Shit").
///
public string? GameTypeName { get; init; }
///
/// ID of the day the game belongs to.
///
public Guid? DayId { get; init; }
///
/// ID of the currently active game.
///
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 ThrowPanelAfter { get; init; } = ThrowPanelState.Initial;
///
/// Current state of game participants.
///
public ParticipantsState Participants { get; init; } = ParticipantsState.Initial;
///
/// Game-specific model data (serialized to JSON).
///
public object? GameModel { get; init; }
///
/// Game setup configuration (persisted for recovery).
///
public IGameSetupModel? Setup { get; init; }
///
/// Stack of game snapshots for undo functionality (unlimited).
///
public ImmutableList UndoStack { get; init; } = [];
///
/// Stack of game snapshots for redo functionality (cleared on new throw).
///
public ImmutableList RedoStack { get; init; } = [];
///
/// List of completed games for the current day.
///
public IReadOnlyList CompletedGames { get; init; } = [];
///
/// Indicates whether a game operation is in progress.
///
public bool IsLoading { get; init; }
///
/// Indicates whether game is being saved.
///
public bool IsSaving { get; init; }
///
/// Error message if operation failed.
///
public string? Error { get; init; }
///
/// Indicates a concurrency conflict occurred (RowVersion mismatch).
///
public bool IsConcurrencyConflict { get; init; }
///
/// Private constructor for Fluxor initialization.
///
private GameState() { }
///
/// Creates the initial state.
///
public static GameState Initial => new()
{
IsGameActive = false,
GameTypeName = null,
DayId = null,
ActiveGameId = null,
ThrowPanelAfter = ThrowPanelState.Initial,
Participants = ParticipantsState.Initial,
GameModel = null,
Setup = null,
UndoStack = [],
RedoStack = [],
CompletedGames = [],
IsLoading = false,
IsSaving = false,
Error = null,
IsConcurrencyConflict = false
};
}
///
/// Represents the state of the throw panel with pin status and throw counters.
///
public record ThrowPanelState
{
///
/// Indicates whether the throw panel has been started/initialized.
///
public bool IsStarted { get; init; }
///
/// Status of pin 1 (top).
///
public PinStatus Pin1 { get; init; }
///
/// Status of pin 2 (second row left).
///
public PinStatus Pin2 { get; init; }
///
/// Status of pin 3 (second row right).
///
public PinStatus Pin3 { get; init; }
///
/// Status of pin 4 (third row left).
///
public PinStatus Pin4 { get; init; }
///
/// Status of pin 5 (third row center).
///
public PinStatus Pin5 { get; init; }
///
/// Status of pin 6 (third row right).
///
public PinStatus Pin6 { get; init; }
///
/// Status of pin 7 (fourth row left).
///
public PinStatus Pin7 { get; init; }
///
/// Status of pin 8 (fourth row right).
///
public PinStatus Pin8 { get; init; }
///
/// Status of pin 9 (bottom).
///
public PinStatus Pin9 { get; init; }
///
/// Number of throws allowed per round.
///
public int ThrowsPerRound { get; init; }
///
/// Current throw counter within the round.
///
public int ThrowCounterPerRound { get; init; }
///
/// Current throw mode (Reposition or Decrease).
///
public ThrowMode ThrowMode { get; init; }
///
/// Total throw counter across all rounds.
///
public int TotalThrowCounter { get; init; }
///
/// Current bell value (for certain game modes).
///
public bool BellValue { get; init; }
///
/// Initial state with all pins standing.
///
public static ThrowPanelState Initial => new()
{
IsStarted = false,
Pin1 = PinStatus.Standing,
Pin2 = PinStatus.Standing,
Pin3 = PinStatus.Standing,
Pin4 = PinStatus.Standing,
Pin5 = PinStatus.Standing,
Pin6 = PinStatus.Standing,
Pin7 = PinStatus.Standing,
Pin8 = PinStatus.Standing,
Pin9 = PinStatus.Standing,
ThrowsPerRound = 3,
ThrowCounterPerRound = 0,
ThrowMode = ThrowMode.Reposition,
TotalThrowCounter = 0,
BellValue = false
};
///
/// Gets all pin statuses as an array.
///
public PinStatus[] GetPins() => [Pin1, Pin2, Pin3, Pin4, Pin5, Pin6, Pin7, Pin8, Pin9];
///
/// Counts the number of pins knocked down (Fallen status).
///
public int CountFallenPins() => GetPins().Count(p => p == PinStatus.Fallen);
///
/// Counts the number of pins still standing.
///
public int CountStandingPins() => GetPins().Count(p => p == PinStatus.Standing);
///
/// Creates a new state with all pins reset to standing.
///
public ThrowPanelState ResetPins() => this with
{
Pin1 = PinStatus.Standing,
Pin2 = PinStatus.Standing,
Pin3 = PinStatus.Standing,
Pin4 = PinStatus.Standing,
Pin5 = PinStatus.Standing,
Pin6 = PinStatus.Standing,
Pin7 = PinStatus.Standing,
Pin8 = PinStatus.Standing,
Pin9 = PinStatus.Standing
};
///
/// Sets a specific pin status by index (1-9).
///
public ThrowPanelState SetPin(int pinNumber, PinStatus status) => pinNumber switch
{
1 => this with { Pin1 = status },
2 => this with { Pin2 = status },
3 => this with { Pin3 = status },
4 => this with { Pin4 = status },
5 => this with { Pin5 = status },
6 => this with { Pin6 = status },
7 => this with { Pin7 = status },
8 => this with { Pin8 = status },
9 => this with { Pin9 = status },
_ => this
};
///
/// Marks all fallen pins as disabled (for Decrease mode).
///
public ThrowPanelState MarkFallenAsDisabled() => this with
{
Pin1 = Pin1 == PinStatus.Fallen ? PinStatus.Disabled : Pin1,
Pin2 = Pin2 == PinStatus.Fallen ? PinStatus.Disabled : Pin2,
Pin3 = Pin3 == PinStatus.Fallen ? PinStatus.Disabled : Pin3,
Pin4 = Pin4 == PinStatus.Fallen ? PinStatus.Disabled : Pin4,
Pin5 = Pin5 == PinStatus.Fallen ? PinStatus.Disabled : Pin5,
Pin6 = Pin6 == PinStatus.Fallen ? PinStatus.Disabled : Pin6,
Pin7 = Pin7 == PinStatus.Fallen ? PinStatus.Disabled : Pin7,
Pin8 = Pin8 == PinStatus.Fallen ? PinStatus.Disabled : Pin8,
Pin9 = Pin9 == PinStatus.Fallen ? PinStatus.Disabled : Pin9
};
///
/// Checks if all pins are knocked down (fallen or disabled).
///
public bool AllPinsDown() => GetPins().All(p => p != PinStatus.Standing);
}
///
/// Represents the state of game participants.
///
public record ParticipantsState
{
///
/// IDs of players in the game (in turn order).
///
public Guid[] PlayerIds { get; init; } = [];
///
/// Index of the current player in the PlayerIds array.
///
public int CurrentPlayerIndex { get; init; }
///
/// Mode for managing player turns.
///
public ParticipantsMode Mode { get; init; }
///
/// Initial state with no players.
///
public static ParticipantsState Initial => new()
{
PlayerIds = [],
CurrentPlayerIndex = 0,
Mode = ParticipantsMode.GameLogic
};
///
/// Gets the current player ID, or null if no players.
///
public Guid? CurrentPlayerId =>
PlayerIds.Length > 0 && CurrentPlayerIndex < PlayerIds.Length
? PlayerIds[CurrentPlayerIndex]
: null;
///
/// Advances to the next player (wraps around).
///
public ParticipantsState NextPlayer() =>
PlayerIds.Length == 0
? this
: this with { CurrentPlayerIndex = (CurrentPlayerIndex + 1) % PlayerIds.Length };
///
/// Sets the current player by ID.
///
public ParticipantsState SetCurrentPlayer(Guid playerId)
{
var index = Array.IndexOf(PlayerIds, playerId);
return index >= 0 ? this with { CurrentPlayerIndex = index } : this;
}
}
///
/// Represents a snapshot of the game state for undo functionality.
///
public record GameSnapshot
{
///
/// Snapshot of the throw panel state.
///
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.
///
public ParticipantsState Participants { get; init; } = ParticipantsState.Initial;
///
/// Snapshot of the game-specific model.
///
public object? GameModel { get; init; }
}
///
/// Summary DTO for a completed game.
///
public record GameSummaryDto
{
///
/// Game ID.
///
public Guid Id { get; init; }
///
/// Game type name.
///
public string GameTypeName { get; init; } = string.Empty;
///
/// Game status.
///
public GameStatus Status { get; init; }
///
/// When the game was started.
///
public DateTime? StartedAt { get; init; }
///
/// When the game was completed.
///
public DateTime? CompletedAt { get; init; }
///
/// Number of participants.
///
public int ParticipantCount { get; init; }
}