fox hunt part1
This commit is contained in:
parent
9356dab062
commit
29fa9c5e84
|
|
@ -0,0 +1,32 @@
|
||||||
|
using Koogle.Application.Games.Shit;
|
||||||
|
using Koogle.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games.FoxHunt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Game definition for Fuchsjagd-Spiel (Fox Hunt) game type.
|
||||||
|
/// One Player starts as fox, and all others try to hunt him.
|
||||||
|
/// </summary>
|
||||||
|
public class FoxHuntGameDefinition : IGameDefinition
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => "FoxHunt";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Fuchsjagd";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Type SetupComponentType => Type.GetType(
|
||||||
|
"Koogle.Web.Components.Game.FoxHunt.FoxSetup, Koogle.Web")!;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Type BoardComponentType => Type.GetType(
|
||||||
|
"Koogle.Web.Components.Game.FoxHunt.FoxBoard, Koogle.Web")!;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Type GameLogicServiceType => typeof(FoxHuntGameLogicService);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Type GameModelType => typeof(FoxHuntGameModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
using Koogle.Application.Games.DeathBox;
|
||||||
|
using Koogle.Application.Interfaces;
|
||||||
|
using Koogle.Domain.Enums;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Org.BouncyCastle.Cms;
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games.FoxHunt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Game logic service for Fuchsjagd-Spiel (Fox Hunt) game type.
|
||||||
|
/// One Player starts as fox, and all others try to hunt him.
|
||||||
|
/// </summary>
|
||||||
|
public class FoxHuntGameLogicService : IGameLogicService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
public FoxHuntGameLogicService(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object CreateInitialModel(Guid[] playerIds, object? setupOptions)
|
||||||
|
{
|
||||||
|
var options = ParseSetupOptions(setupOptions);
|
||||||
|
|
||||||
|
var playerOrder = playerIds.ToArray();
|
||||||
|
|
||||||
|
var playerStates = playerIds.ToDictionary(
|
||||||
|
id => id,
|
||||||
|
_ => new FoxHuntPlayerState());
|
||||||
|
|
||||||
|
return new FoxHuntGameModel
|
||||||
|
{
|
||||||
|
FoxIndex = 0, // aktueller Fuchs (Index in PlayerOrder)
|
||||||
|
NonFoxIndex = 0, // Index für Nicht-Fuchs-Spieler
|
||||||
|
FoxTurnsRemaining = 2, // erste 2 Fuchs-Züge
|
||||||
|
FoxTurn = false,
|
||||||
|
|
||||||
|
LeadingThrows = options.LeadingThrows,
|
||||||
|
PlayerStates = playerStates,
|
||||||
|
PlayerOrder = playerOrder,
|
||||||
|
//CurrentPlayerIndex = 0,
|
||||||
|
//FoxLeadingThrowCount = 0,
|
||||||
|
//CurrentFoxIndex = 0,
|
||||||
|
WinnerId = null,
|
||||||
|
IsGameOver = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public (object UpdatedModel, ThrowResult Result) ProcessThrow(object gameModel, AfterThrowState afterThrow)
|
||||||
|
{
|
||||||
|
var model = CastModel(gameModel);
|
||||||
|
var playerId = afterThrow.CurrentPlayerId;
|
||||||
|
var pinsKnocked = afterThrow.PinsKnocked;
|
||||||
|
|
||||||
|
if (model.IsGameOver)
|
||||||
|
{
|
||||||
|
return (model, new ThrowResult
|
||||||
|
{
|
||||||
|
PointsScored = 0,
|
||||||
|
ShouldRotatePlayer = false,
|
||||||
|
IsGameOver = true,
|
||||||
|
WinnerId = model.WinnerId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//var actualName = GetPlayerName(playerId).Result;
|
||||||
|
var playerStates = new Dictionary<Guid, FoxHuntPlayerState>(model.PlayerStates);
|
||||||
|
//var eliminatedPlayers = new List<Guid>(model.EliminatedPlayers);
|
||||||
|
var triggers = new List<TriggerEvent>();
|
||||||
|
|
||||||
|
var foxId = model.PlayerOrder[model.FoxIndex];
|
||||||
|
var isFoxThrow = playerId == foxId;
|
||||||
|
|
||||||
|
|
||||||
|
// 5. Check GAME END
|
||||||
|
bool isGameOver = false;
|
||||||
|
Guid? winnerId = null;
|
||||||
|
|
||||||
|
|
||||||
|
var nextPlayerId = GetNextId(model);
|
||||||
|
//var nextName = GetPlayerName(nextPlayerId).Result;
|
||||||
|
|
||||||
|
// Update model
|
||||||
|
model = model with
|
||||||
|
{
|
||||||
|
FoxIndex = model.FoxIndex,
|
||||||
|
FoxTurn = model.FoxTurn,
|
||||||
|
FoxTurnsRemaining = model.FoxTurnsRemaining,
|
||||||
|
NonFoxIndex = model.NonFoxIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new ThrowResult
|
||||||
|
{
|
||||||
|
PointsScored = pinsKnocked,
|
||||||
|
ShouldRotatePlayer = true,
|
||||||
|
IsGameOver = isGameOver,
|
||||||
|
WinnerId = winnerId,
|
||||||
|
Triggers = triggers,
|
||||||
|
Overrides = isGameOver ? null : new GameLogicOverrides
|
||||||
|
{
|
||||||
|
NextPlayerId = nextPlayerId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (model, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// algorithm:
|
||||||
|
/// A, A, B, A, C, A, D,
|
||||||
|
/// B, B, A, B, C, B, D,
|
||||||
|
/// C, C, A, C, B, C, D,
|
||||||
|
/// D, D, A, D, B, D, C
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
|
private Guid GetNextId(FoxHuntGameModel model)
|
||||||
|
{
|
||||||
|
// Abbruch: alle waren einmal Fuchs
|
||||||
|
if (model.FoxIndex >= model.PlayerOrder.Length)
|
||||||
|
throw new InvalidOperationException("Alle Spieler waren bereits Fuchs.");
|
||||||
|
|
||||||
|
Guid foxId = model.PlayerOrder[model.FoxIndex];
|
||||||
|
|
||||||
|
// 1️⃣ Fuchs ist 2x hintereinander dran
|
||||||
|
if (model.FoxTurnsRemaining > 0)
|
||||||
|
{
|
||||||
|
model.FoxTurnsRemaining--;
|
||||||
|
model.FoxTurn = false; // danach abwechselnd
|
||||||
|
return foxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Abwechselnd Nicht-Fuchs → Fuchs
|
||||||
|
if (!model.FoxTurn)
|
||||||
|
{
|
||||||
|
// nächsten Nicht-Fuchs suchen
|
||||||
|
while (model.NonFoxIndex == model.FoxIndex)
|
||||||
|
model.NonFoxIndex++;
|
||||||
|
|
||||||
|
if (model.NonFoxIndex < model.PlayerOrder.Length)
|
||||||
|
{
|
||||||
|
Guid next = model.PlayerOrder[model.NonFoxIndex];
|
||||||
|
model.NonFoxIndex++;
|
||||||
|
model.FoxTurn = true;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Alle Nicht-Füchse durch → neuer Fuchs
|
||||||
|
model.FoxIndex++;
|
||||||
|
model.NonFoxIndex = 0;
|
||||||
|
model.FoxTurnsRemaining = 2;
|
||||||
|
model.FoxTurn = true;
|
||||||
|
return GetNextId(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Fuchs-Zug im Wechsel
|
||||||
|
model.FoxTurn = false;
|
||||||
|
return foxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetNextActivePlayerIndex(
|
||||||
|
Guid[] playerOrder,
|
||||||
|
int currentIndex,
|
||||||
|
Dictionary<Guid, FoxHuntPlayerState> playerStates)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
private async Task<string> GetPlayerName(Guid playerId)
|
||||||
|
{
|
||||||
|
using (var scope = _serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var _personService = scope.ServiceProvider.GetRequiredService<IPersonService>();
|
||||||
|
var persons = await _personService.GetAllAsync();
|
||||||
|
var person = persons.FirstOrDefault(p => p.Id == playerId);
|
||||||
|
return person?.Name ?? "Unbekannt";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public (bool IsGameOver, Guid? WinnerId) CheckGameEnd(object gameModel)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<Guid, PlayerStatsSummary> GetPlayerStats(object gameModel)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameSetupValidationResult ValidateSetup(object? setupOptions)
|
||||||
|
{
|
||||||
|
if (setupOptions is null)
|
||||||
|
{
|
||||||
|
return GameSetupValidationResult.Valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = ParseSetupOptions(setupOptions);
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
if (options.LeadingThrows < 2 || options.LeadingThrows > 4)
|
||||||
|
{
|
||||||
|
errors.Add("Vorsprung muss zwischen 6 und 12 liegen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: validate player-count, at lease 2 players needed
|
||||||
|
|
||||||
|
return errors.Count > 0
|
||||||
|
? GameSetupValidationResult.Invalid(errors.ToArray())
|
||||||
|
: GameSetupValidationResult.Valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<GameActionDescriptor> GetAvailableActions(object gameModel, Guid currentPlayerId)
|
||||||
|
{
|
||||||
|
// FoxHunt has no custom actions
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameActionResult ExecuteAction(object gameModel, string actionId, Guid currentPlayerId,
|
||||||
|
IReadOnlyDictionary<string, object>? parameters = null)
|
||||||
|
{
|
||||||
|
return GameActionResult.Failure($"Unknown action: {actionId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FoxHuntGameSetup ParseSetupOptions(object? setupOptions)
|
||||||
|
{
|
||||||
|
if (setupOptions is null)
|
||||||
|
{
|
||||||
|
return new FoxHuntGameSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setupOptions is FoxHuntGameSetup typedSetup)
|
||||||
|
{
|
||||||
|
return typedSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setupOptions is JsonElement jsonElement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<FoxHuntGameSetup>(
|
||||||
|
jsonElement.GetRawText(),
|
||||||
|
GameModelFactory.JsonSerializerOptions) ?? new FoxHuntGameSetup();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new FoxHuntGameSetup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FoxHuntGameSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FoxHuntGameModel CastModel(object gameModel)
|
||||||
|
{
|
||||||
|
if (gameModel is FoxHuntGameModel model)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameModel is JsonElement jsonElement)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<FoxHuntGameModel>(
|
||||||
|
jsonElement.GetRawText(),
|
||||||
|
GameModelFactory.JsonSerializerOptions)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Expected FoxHuntGameModel but got {gameModel.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
namespace Koogle.Application.Games.FoxHunt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Game model for Fuchsjagd-Spiel (Fox Hunt) game type.
|
||||||
|
/// One Player starts as fox, and all others try to hunt him.
|
||||||
|
/// </summary>
|
||||||
|
public record FoxHuntGameModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the winner (first player to reach 0 points).
|
||||||
|
/// </summary>
|
||||||
|
public Guid? WinnerId { get; set; }
|
||||||
|
|
||||||
|
public bool IsGameOver { get; set; }
|
||||||
|
//public int LeadingThrows { get; set; }
|
||||||
|
//public int FoxLeadingThrowCount { get; set; }
|
||||||
|
public Dictionary<Guid, FoxHuntPlayerState> PlayerStates { get; set; }
|
||||||
|
public Guid[] PlayerOrder { get; set; }
|
||||||
|
|
||||||
|
public int LeadingThrows { get; set; } // copy from setup model
|
||||||
|
//public int CurrentPlayerIndex { get; set; }
|
||||||
|
//public int CurrentFoxIndex { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int FoxIndex = 0; // aktueller Fuchs (Index in PlayerOrder)
|
||||||
|
public int NonFoxIndex = 0; // Index für Nicht-Fuchs-Spieler
|
||||||
|
public int FoxTurnsRemaining = 2; // erste 2 Fuchs-Züge
|
||||||
|
public bool FoxTurn = true; // wer ist dran
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FoxHuntPlayerState
|
||||||
|
{
|
||||||
|
|
||||||
|
public int PinCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games.FoxHunt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Setup model for Fuchsjagd-Spiel (Fox Hunt) game type.
|
||||||
|
/// One Player starts as fox, and all others try to hunt him.
|
||||||
|
/// </summary>
|
||||||
|
public record FoxHuntGameSetup : GameSetupModelBase
|
||||||
|
{
|
||||||
|
public override string GameType => "FoxHunt";
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Leading throw the fox do, before the hunt begins
|
||||||
|
/// </summary>
|
||||||
|
public int LeadingThrows { get; init; } = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default ShitGameSetup with specified base parameters.
|
||||||
|
/// </summary>
|
||||||
|
public static FoxHuntGameSetup Create(
|
||||||
|
ThrowMode throwMode,
|
||||||
|
int throwsPerRound,
|
||||||
|
ParticipantsMode participantsMode,
|
||||||
|
int leadingThrows = 2) => new()
|
||||||
|
{
|
||||||
|
ThrowMode = throwMode,
|
||||||
|
ThrowsPerRound = throwsPerRound,
|
||||||
|
ParticipantsMode = participantsMode,
|
||||||
|
LeadingThrows = leadingThrows
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Koogle.Application.Games;
|
||||||
[JsonDerivedType(typeof(Shit.ShitGameSetup), "Shit")]
|
[JsonDerivedType(typeof(Shit.ShitGameSetup), "Shit")]
|
||||||
[JsonDerivedType(typeof(Training.TrainingGameSetup), "Training")]
|
[JsonDerivedType(typeof(Training.TrainingGameSetup), "Training")]
|
||||||
[JsonDerivedType(typeof(DeathBox.DeathBoxGameSetup), "DeathBox")]
|
[JsonDerivedType(typeof(DeathBox.DeathBoxGameSetup), "DeathBox")]
|
||||||
|
[JsonDerivedType(typeof(FoxHunt.FoxHuntGameSetup), "FoxHunt")]
|
||||||
public interface IGameSetupModel
|
public interface IGameSetupModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@using System.Text.Json
|
||||||
|
@using Koogle.Application.Games.FoxHunt
|
||||||
|
@using Koogle.Web.Store.DayState
|
||||||
|
@using Koogle.Web.Store.GameState
|
||||||
|
@using Koogle.Application.Games;
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
@inject IState<GameState> GameState
|
||||||
|
@inject IState<DayState> DayState
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
@_debug
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<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>Vorsprung für den Fuchs:</strong>
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">
|
||||||
|
@_model.LeadingThrows Vorsprung
|
||||||
|
</MudChip>
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
<strong>Füchse übrig:</strong>
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
|
||||||
|
0
|
||||||
|
</MudChip>
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@* Last throw info *@
|
||||||
|
@* @if (_model.LastThrow != null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="@GetLastThrowSeverity()" Class="mb-4" Dense="true">
|
||||||
|
@GetLastThrowMessage()
|
||||||
|
</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>Status</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 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.FoxSurvivalOrder
|
||||||
|
</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 als Fuchs, mit dem größten Vorsprung!
|
||||||
|
</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private FoxHuntGameModel? _model;
|
||||||
|
private List<PlayerStatsRow> _playerStats = [];
|
||||||
|
private string _debug;
|
||||||
|
|
||||||
|
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 FoxHuntGameModel model)
|
||||||
|
{
|
||||||
|
_model = model;
|
||||||
|
}
|
||||||
|
else if (gameState.GameModel is JsonElement jsonElement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_model = JsonSerializer.Deserialize<FoxHuntGameModel>(
|
||||||
|
jsonElement.GetRawText(),
|
||||||
|
GameModelFactory.JsonSerializerOptions);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_debug = JsonSerializer.Serialize(_model, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
|
||||||
|
if (_model?.PlayerStates == null || _model.PlayerOrder == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GameState.StateChanged -= OnGameStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlayerName(Guid playerId)
|
||||||
|
{
|
||||||
|
var person = DayState.Value.AvailablePersons.FirstOrDefault(p => p.Id == playerId);
|
||||||
|
return person?.Name ?? "Unbekannt";
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PlayerStatsRow
|
||||||
|
{
|
||||||
|
public Guid PlayerId { get; init; }
|
||||||
|
public string PlayerName { get; init; } = "";
|
||||||
|
public string Status { get; init; } = "";
|
||||||
|
public bool IsWinner { get; init; }
|
||||||
|
public bool IsCurrentPlayer { get; init; }
|
||||||
|
public bool IsEliminated { get; init; }
|
||||||
|
public int FoxSurvivalOrder { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
@inject ITriggerService TriggerService
|
||||||
|
@using Koogle.Application.Games
|
||||||
|
@using Koogle.Application.Games.FoxHunt
|
||||||
|
@using Koogle.Application.Interfaces
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
@implements Koogle.Application.Interfaces.IGameSetupControl
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-4">Fuchsjagd Einstellungen</MudText>
|
||||||
|
|
||||||
|
<MudStack Spacing="4">
|
||||||
|
<MudNumericField T="int"
|
||||||
|
@bind-Value="_options.LeadingThrows"
|
||||||
|
Label="Würfe-Vorsprung"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="2"
|
||||||
|
Max="4"
|
||||||
|
HelperText="Anzahl Würfe, die der Fuchs an Vorspring bekommt (2-4)" />
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
|
||||||
|
@if (!_hasExpensePointTrigger)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Class="mt-4">
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
<strong>Hinweis:</strong> Es ist kein "Strafpunkt"-Trigger konfiguriert.
|
||||||
|
Die Verlierer-Strafen werden nicht automatisch berechnet.
|
||||||
|
Gehe zu Stammdaten → Trigger, um einen ExpensePoint-Trigger einzurichten.
|
||||||
|
</MudText>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||||
|
<strong>Spielregeln:</strong>
|
||||||
|
<ul style="margin: 8px 0 0 16px; padding: 0;">
|
||||||
|
<li>Jeder Spieler reihum ist einmal der Fuchs</li>
|
||||||
|
<li>Der Fuchs startet mit 'Anzahl-Würfe-Vorsprung' in die Vollen</li>
|
||||||
|
<li>Er sammelt möglichst viele Kegel und erarbeitet sich sich einen Vorsprung</li>
|
||||||
|
<li>Anschließend hat jeder weitere Spieler einen Wurf, abwechselnd mit dem Fuchs</li>
|
||||||
|
<li>Ziel der anderen Spieler ist es, den Fuchs zu fangen!</li>
|
||||||
|
<li>Ziel des Fuchses ist es, am Ende der Runde mindestens einen Punkt Vorsprung zu haben</li>
|
||||||
|
<li>Es gewinnt der Fuchs mit dem größten Vorsprung</li>
|
||||||
|
</ul>
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
</MudPaper>
|
||||||
|
@code {
|
||||||
|
/// <summary>
|
||||||
|
/// Callback when setup options change.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<object> OnOptionsChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initial setup options.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public object? InitialOptions { get; set; }
|
||||||
|
|
||||||
|
private bool _hasExpensePointTrigger = true;
|
||||||
|
|
||||||
|
private FoxSetupOptionsInternal _options = new();
|
||||||
|
|
||||||
|
// Internal mutable class for two-way binding
|
||||||
|
private class FoxSetupOptionsInternal
|
||||||
|
{
|
||||||
|
public int LeadingThrows { get; set; } = 2;
|
||||||
|
|
||||||
|
public ParticipantsMode ParticipantsMode { get; set; } = ParticipantsMode.GameLogic;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (InitialOptions is FoxHuntGameSetup setup)
|
||||||
|
{
|
||||||
|
_options = new FoxSetupOptionsInternal
|
||||||
|
{
|
||||||
|
LeadingThrows = setup.LeadingThrows,
|
||||||
|
ParticipantsMode = setup.ParticipantsMode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ExpensePoint trigger is configured
|
||||||
|
_hasExpensePointTrigger = await TriggerService.HasExpensesForTriggerAsync(ExpenseTriggerType.ExpensePoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await NotifyOptionsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NotifyOptionsChanged()
|
||||||
|
{
|
||||||
|
var setup = new FoxHuntGameSetup
|
||||||
|
{
|
||||||
|
LeadingThrows = _options.LeadingThrows,
|
||||||
|
ParticipantsMode = _options.ParticipantsMode
|
||||||
|
};
|
||||||
|
await OnOptionsChanged.InvokeAsync(setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGameSetupModel GameSetupModel => CreateFoxSetup();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default FoxHuntSetup with specified base parameters.
|
||||||
|
/// </summary>
|
||||||
|
private FoxHuntGameSetup CreateFoxSetup()
|
||||||
|
{
|
||||||
|
return FoxHuntGameSetup.Create(
|
||||||
|
throwMode: ThrowMode.Reposition,
|
||||||
|
throwsPerRound: 1,
|
||||||
|
participantsMode: ParticipantsMode.GameLogic,
|
||||||
|
leadingThrows: _options.LeadingThrows);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -92,6 +92,8 @@
|
||||||
{
|
{
|
||||||
"Training" => Icons.Material.Filled.FitnessCenter,
|
"Training" => Icons.Material.Filled.FitnessCenter,
|
||||||
"Shit" => Icons.Material.Filled.Casino,
|
"Shit" => Icons.Material.Filled.Casino,
|
||||||
|
"DeathBox" => Icons.Material.Filled.Church,
|
||||||
|
"FoxHunt" => Icons.Material.Filled.DirectionsRun,
|
||||||
_ => Icons.Material.Filled.SportsScore
|
_ => Icons.Material.Filled.SportsScore
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.AspNetCore.Components.Server;
|
using Microsoft.AspNetCore.Components.Server;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Koogle.Application.Games.FoxHunt;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
@ -32,6 +33,7 @@ builder.Services.AddApplication();
|
||||||
builder.Services.AddGameType<TrainingGameDefinition, TrainingGameLogicService>();
|
builder.Services.AddGameType<TrainingGameDefinition, TrainingGameLogicService>();
|
||||||
builder.Services.AddGameType<ShitGameDefinition, ShitGameLogicService>();
|
builder.Services.AddGameType<ShitGameDefinition, ShitGameLogicService>();
|
||||||
builder.Services.AddGameType<DeathBoxGameDefinition, DeathBoxGameLogicService>();
|
builder.Services.AddGameType<DeathBoxGameDefinition, DeathBoxGameLogicService>();
|
||||||
|
builder.Services.AddGameType<FoxHuntGameDefinition, FoxHuntGameLogicService>();
|
||||||
|
|
||||||
// SignalR for real-time game updates
|
// SignalR for real-time game updates
|
||||||
builder.Services.AddSignalR();
|
builder.Services.AddSignalR();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue