KoogleApp/src/Koogle.Web/Components/Game/DeathBox/DeathBoxBoard.razor

365 lines
13 KiB
Plaintext

@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> GameState
@inject IState<DayState> DayState
@inject IClubTerminologyService Terms
<MudPaper Class="pa-4">
@if (_model == null)
{
<MudAlert Severity="Severity.Info">
Spiel noch nicht gestartet.
</MudAlert>
}
else
{
@* Game info header *@
<MudPaper Class="pa-3 mb-4" Elevation="0" Style="background-color: var(--mud-palette-background-grey);">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">
<strong>Sarggröße:</strong>
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">
@_model.CoffinSize Striche
</MudChip>
</MudText>
<MudText Typo="Typo.body1">
<strong>Spieler übrig:</strong>
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
@_activePlayerCount
</MudChip>
</MudText>
</MudStack>
</MudPaper>
@* Last throw info *@
@if (_model.LastThrow != null)
{
<MudAlert Severity="@GetLastThrowSeverity()" Class="mb-4" Dense="true">
@(_ = GetLastThrowMessage().Result)
</MudAlert>
}
@* Winner announcement *@
@if (_model.IsGameOver && _model.WinnerId.HasValue)
{
var winnerName = GetPlayerName(_model.WinnerId.Value);
<MudAlert Severity="Severity.Success" Class="mb-4">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.EmojiEvents" />
<MudText Typo="Typo.h6">@winnerName hat überlebt!</MudText>
</MudStack>
</MudAlert>
}
@* Players table *@
<MudTable Items="@_playerStats"
Dense="true"
Hover="true"
Striped="true"
Bordered="true"
Class="mb-4">
<HeaderContent>
<MudTh>Spieler</MudTh>
<MudTh>Sarg</MudTh>
<MudTh Style="text-align: center">Xs</MudTh>
<MudTh Style="text-align: center">Eier</MudTh>
<MudTh Style="text-align: center">Status</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>
@if (context.IsCurrentPlayer && !_model.IsGameOver)
{
<MudBadge Color="Color.Primary" Dot="true" Overlap="false"
Icon=@Icons.Material.Filled.ArrowCircleDown
Origin="Origin.TopLeft">
<MudText Typo="Typo.body1" Style="font-weight: 600">
@context.PlayerName
</MudText>
</MudBadge>
}
else
{
<MudText Typo="Typo.body1" Style="@(context.IsEliminated ? "text-decoration: line-through; color: var(--mud-palette-text-disabled);" : "")">
@context.PlayerName
</MudText>
}
</MudTd>
<MudTd>
@if (!context.IsEliminated)
{
<MudStack Spacing="0">
<MudProgressLinear Value="@context.MarkPercent" Color="Color.Error" Size="Size.Medium" Rounded="true" />
<MudText Typo="Typo.caption" Color="Color.Secondary">
@context.Marks / @_model.CoffinSize
</MudText>
</MudStack>
}
else
{
<MudText Color="Color.Error" Typo="Typo.caption">VOLL</MudText>
}
</MudTd>
<MudTd Style="text-align: center">
@if (!context.IsEliminated && context.XCount > 0)
{
<MudText Style="color: var(--mud-palette-error); font-weight: bold;">
@(string.Join("", Enumerable.Repeat("✗", context.XCount)))
</MudText>
}
else
{
<MudText Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd Style="text-align: center">
@if (!context.IsEliminated && context.EggCount > 0)
{
<span>@(string.Join("", Enumerable.Repeat("🥚", context.EggCount)))</span>
}
else
{
<MudText Color="Color.Secondary">-</MudText>
}
</MudTd>
<MudTd Style="text-align: center">
@if (context.IsWinner)
{
<MudChip T="string" Size="Size.Small" Color="Color.Success" Variant="Variant.Filled"
Icon="@Icons.Material.Filled.EmojiEvents">
SIEGER
</MudChip>
}
else if (context.IsEliminated)
{
<MudChip T="string" Size="Size.Small" Color="Color.Error" Variant="Variant.Outlined">
☠️ Platz @context.EliminationOrder
</MudChip>
}
else if (context.IsCurrentPlayer)
{
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Variant="Variant.Outlined">
Am Zug
</MudChip>
}
</MudTd>
</RowTemplate>
</MudTable>
@* Info footer *@
@if (!_model.IsGameOver)
{
<MudPaper Class="pa-3" Elevation="0" Style="background-color: var(--mud-palette-background-grey);">
<MudText Typo="Typo.body2" Color="Color.Secondary">
Überlebe als letzter Spieler!
</MudText>
</MudPaper>
}
}
</MudPaper>
@code {
private DeathBoxGameModel? _model;
private List<PlayerStatsRow> _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<DeathBoxGameModel>(
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<string> GetLastThrowMessage()
{
if (_model?.LastThrow == null) return "";
var lt = _model.LastThrow;
var playerName = GetPlayerName(lt.PlayerId);
var messages = new List<string>();
// 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; }
}
}