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