@using Fluxor @using Koogle.Application.DTOs @using Koogle.Application.Games @using Koogle.Application.Games.DeathBox @using Koogle.Application.Interfaces @using Koogle.Domain.Enums @using Koogle.Web.Store.GameState @using Koogle.Web.Store.DayState @using MudBlazor @inherits Fluxor.Blazor.Web.Components.FluxorComponent @implements IDisposable @inject IState GameState @inject IState DayState @inject IClubTerminologyService Terms @if (_model == null) { Spiel noch nicht gestartet. } else { @* Game info header *@ Sarggröße: @_model.CoffinSize Striche Spieler übrig: @_activePlayerCount @* Last throw info *@ @if (_model.LastThrow != null) { @(_ = GetLastThrowMessage().Result) } @* Winner announcement *@ @if (_model.IsGameOver && _model.WinnerId.HasValue) { var winnerName = GetPlayerName(_model.WinnerId.Value); @winnerName hat überlebt! } @* Players table *@ Spieler Sarg Xs Eier Status @if (context.IsCurrentPlayer && !_model.IsGameOver) { @context.PlayerName } else { @context.PlayerName } @if (!context.IsEliminated) { @context.Marks / @_model.CoffinSize } else { VOLL } @if (!context.IsEliminated && context.XCount > 0) { @(string.Join("", Enumerable.Repeat("✗", context.XCount))) } else { - } @if (!context.IsEliminated && context.EggCount > 0) { @(string.Join("", Enumerable.Repeat("🥚", context.EggCount))) } else { - } @if (context.IsWinner) { SIEGER } else if (context.IsEliminated) { ☠️ Platz @context.EliminationOrder } else if (context.IsCurrentPlayer) { Am Zug } @* Info footer *@ @if (!_model.IsGameOver) { Überlebe als letzter Spieler! } } @code { private DeathBoxGameModel? _model; private List _playerStats = []; private int _activePlayerCount = 0; protected override void OnInitialized() { base.OnInitialized(); GameState.StateChanged += OnGameStateChanged; UpdateStats(); } private void OnGameStateChanged(object? sender, EventArgs e) { UpdateStats(); InvokeAsync(StateHasChanged); } private void UpdateStats() { _playerStats.Clear(); _model = null; _activePlayerCount = 0; var gameState = GameState.Value; if (gameState.GameModel is DeathBoxGameModel model) { _model = model; } else if (gameState.GameModel is System.Text.Json.JsonElement jsonElement) { try { _model = System.Text.Json.JsonSerializer.Deserialize( jsonElement.GetRawText(), GameModelFactory.JsonSerializerOptions); } catch { return; } } if (_model?.PlayerStates == null || _model.PlayerOrder == null) { return; } var currentPlayerId = gameState.Participants.CurrentPlayerId; var persons = DayState.Value.AvailablePersons; // Iterate in PlayerOrder to maintain fixed display order foreach (var playerId in _model.PlayerOrder) { if (!_model.PlayerStates.TryGetValue(playerId, out var state)) continue; var person = persons.FirstOrDefault(p => p.Id == playerId); var playerName = person?.Name ?? "Unbekannt"; var eliminationOrder = _model.EliminatedPlayers.IndexOf(playerId); _playerStats.Add(new PlayerStatsRow { PlayerId = playerId, PlayerName = playerName, Marks = state.Marks, MarkPercent = _model.CoffinSize > 0 ? (double)state.Marks / _model.CoffinSize * 100 : 0, XCount = state.XCount, EggCount = state.EggCount, IsEliminated = state.IsEliminated, EliminationOrder = eliminationOrder >= 0 ? _model.PlayerStates.Count - eliminationOrder : 0, IsWinner = _model.WinnerId == playerId, IsCurrentPlayer = playerId == currentPlayerId && !state.IsEliminated }); if (!state.IsEliminated) { _activePlayerCount++; } } // No sorting - keep fixed PlayerOrder for display } private string GetPlayerName(Guid playerId) { var person = DayState.Value.AvailablePersons.FirstOrDefault(p => p.Id == playerId); return person?.Name ?? "Unbekannt"; } private Severity GetLastThrowSeverity() { if (_model?.LastThrow == null) return Severity.Info; var lt = _model.LastThrow; if (lt.PlayerEliminated || lt.PreviousPlayerEliminated) return Severity.Error; if (lt.WasPenalty || lt.WasGutter || lt.WasNoWood || lt.PreviousPlayerGotMark || lt.ConvertedXsToMark) return Severity.Warning; if (lt.EarnedEgg || lt.ConvertedEggsToRemoveMark) return Severity.Success; if (lt.EarnedX) return Severity.Info; return Severity.Info; } private async Task GetLastThrowMessage() { if (_model?.LastThrow == null) return ""; var lt = _model.LastThrow; var playerName = GetPlayerName(lt.PlayerId); var messages = new List(); // Pin count messages.Add($"{playerName}: {lt.PinsKnocked} Pin(s)"); // Penalty for <3 pins if (lt.WasPenalty) messages.Add("Weniger als 3 Pins! +1 Strich"); // Gutter or no wood if (lt.WasGutter) messages.Add("Gosse! +1 Strich"); else if (lt.WasNoWood) { var term = await Terms.GetTermAsync(TermKey.NoWood); messages.Add($"{term}! +1 Strich"); } // Cleared if (lt.EarnedEgg) messages.Add("Abgeräumt! Ei gesammelt"); // Previous player penalty if (lt.PreviousPlayerGotMark && lt.PreviousPlayerPenalizedId.HasValue) { var prevName = GetPlayerName(lt.PreviousPlayerPenalizedId.Value); messages.Add($"{prevName} bekommt +1 Strich"); } // Conversions PreviousPlayer if (lt.ConvertedEggsToRemoveMark) messages.Add("3 Eier → -1 Strich!"); // X earned if (lt.EarnedX && lt.NextPlayerPenalizedId.HasValue) { var nextName = GetPlayerName(lt.NextPlayerPenalizedId.Value); messages.Add($"{nextName}: X gesammelt"); } // Conversions NextPlayer if (lt.ConvertedXsToMark && lt.NextPlayerPenalizedId.HasValue) { var nextName = GetPlayerName(lt.NextPlayerPenalizedId.Value); messages.Add($"{nextName} 3 Xe → +1 Strich"); } // Eliminations if (lt.PlayerEliminated) messages.Add($"{playerName} ist ausgeschieden!"); if (lt.PreviousPlayerEliminated && lt.PreviousPlayerPenalizedId.HasValue) { var prevName = GetPlayerName(lt.PreviousPlayerPenalizedId.Value); messages.Add($"{prevName} ist ausgeschieden!"); } var res = string.Join(" | ", messages); return res; } public void Dispose() { GameState.StateChanged -= OnGameStateChanged; } private record PlayerStatsRow { public Guid PlayerId { get; init; } public string PlayerName { get; init; } = ""; public int Marks { get; init; } public double MarkPercent { get; init; } public int XCount { get; init; } public int EggCount { get; init; } public bool IsEliminated { get; init; } public int EliminationOrder { get; init; } public bool IsWinner { get; init; } public bool IsCurrentPlayer { get; init; } } }