365 lines
13 KiB
Plaintext
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; }
|
|
}
|
|
}
|