Add Training Game (Phase H4)
- TrainingGameDefinition with game type metadata - TrainingGameModel + TrainingPlayerStats for stats tracking - TrainingGameLogicService implementing game logic - TrainingSetup.razor for config (ThrowMode, ThrowsPerRound, ParticipantsMode) - TrainingBoard.razor showing player stats table with totals/averages - Game type registration in Program.cs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f4cc740a1
commit
8824162cd9
|
|
@ -0,0 +1,29 @@
|
|||
using Koogle.Domain.Interfaces;
|
||||
|
||||
namespace Koogle.Application.Games.Training;
|
||||
|
||||
/// <summary>
|
||||
/// Game definition for Kegel-Training game type.
|
||||
/// </summary>
|
||||
public class TrainingGameDefinition : IGameDefinition
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "Training";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Kegel-Training";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type SetupComponentType => Type.GetType(
|
||||
"Koogle.Web.Components.Game.Training.TrainingSetup, Koogle.Web")!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type BoardComponentType => Type.GetType(
|
||||
"Koogle.Web.Components.Game.Training.TrainingBoard, Koogle.Web")!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GameLogicServiceType => typeof(TrainingGameLogicService);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GameModelType => typeof(TrainingGameModel);
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace Koogle.Application.Games.Training;
|
||||
|
||||
/// <summary>
|
||||
/// Game logic service for Training game type.
|
||||
/// Training is a practice mode where players track their throw statistics.
|
||||
/// </summary>
|
||||
public class TrainingGameLogicService : IGameLogicService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object CreateInitialModel(Guid[] playerIds, object? setupOptions)
|
||||
{
|
||||
var model = new TrainingGameModel
|
||||
{
|
||||
PlayerStatistics = playerIds.ToDictionary(
|
||||
id => id,
|
||||
_ => new TrainingPlayerStats())
|
||||
};
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public (object UpdatedModel, ThrowResult Result) ProcessThrow(object gameModel, AfterThrowState afterThrow)
|
||||
{
|
||||
var model = CastModel(gameModel);
|
||||
var playerId = afterThrow.CurrentPlayerId;
|
||||
|
||||
if (!model.PlayerStatistics.TryGetValue(playerId, out var stats))
|
||||
{
|
||||
stats = new TrainingPlayerStats();
|
||||
model = model with
|
||||
{
|
||||
PlayerStatistics = new Dictionary<Guid, TrainingPlayerStats>(model.PlayerStatistics)
|
||||
{
|
||||
[playerId] = stats
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
stats.ThrowCount++;
|
||||
stats.PinCount += afterThrow.PinsKnocked;
|
||||
|
||||
if (afterThrow.IsCircle)
|
||||
{
|
||||
stats.CircleCount++;
|
||||
}
|
||||
|
||||
if (afterThrow.IsStrike)
|
||||
{
|
||||
stats.StrikeCount++;
|
||||
}
|
||||
|
||||
if (afterThrow.IsGutter)
|
||||
{
|
||||
stats.GutterCount++;
|
||||
}
|
||||
|
||||
// Determine if player should rotate (round complete)
|
||||
var shouldRotate = afterThrow.ThrowPanel.IsRoundComplete();
|
||||
|
||||
var result = new ThrowResult
|
||||
{
|
||||
PointsScored = afterThrow.PinsKnocked,
|
||||
ShouldRotatePlayer = shouldRotate,
|
||||
IsGameOver = false, // Training never auto-ends
|
||||
WinnerId = null,
|
||||
Triggers = []
|
||||
};
|
||||
|
||||
return (model, result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public (bool IsGameOver, Guid? WinnerId) CheckGameEnd(object gameModel)
|
||||
{
|
||||
// Training mode never auto-ends - must be manually ended
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<Guid, PlayerStatsSummary> GetPlayerStats(object gameModel)
|
||||
{
|
||||
var model = CastModel(gameModel);
|
||||
|
||||
return model.PlayerStatistics.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => new PlayerStatsSummary
|
||||
{
|
||||
ThrowCount = kvp.Value.ThrowCount,
|
||||
PinCount = kvp.Value.PinCount,
|
||||
CircleCount = kvp.Value.CircleCount,
|
||||
StrikeCount = kvp.Value.StrikeCount,
|
||||
CurrentPoints = kvp.Value.PinCount, // Total pins = "points" in training
|
||||
CustomStats = new Dictionary<string, object>
|
||||
{
|
||||
["GutterCount"] = kvp.Value.GutterCount
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameSetupValidationResult ValidateSetup(object? setupOptions)
|
||||
{
|
||||
if (setupOptions is null)
|
||||
{
|
||||
// Default options are valid
|
||||
return GameSetupValidationResult.Valid();
|
||||
}
|
||||
|
||||
TrainingSetupOptions options;
|
||||
if (setupOptions is TrainingSetupOptions typedOptions)
|
||||
{
|
||||
options = typedOptions;
|
||||
}
|
||||
else if (setupOptions is JsonElement jsonElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
options = JsonSerializer.Deserialize<TrainingSetupOptions>(
|
||||
jsonElement.GetRawText(),
|
||||
GameModelFactory.JsonSerializerOptions)!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return GameSetupValidationResult.Invalid("Ungültige Setup-Optionen.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return GameSetupValidationResult.Invalid("Ungültige Setup-Optionen.");
|
||||
}
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
if (options.ThrowsPerRound < 1 || options.ThrowsPerRound > 5)
|
||||
{
|
||||
errors.Add("Würfe pro Runde muss zwischen 1 und 5 liegen.");
|
||||
}
|
||||
|
||||
return errors.Count > 0
|
||||
? GameSetupValidationResult.Invalid(errors.ToArray())
|
||||
: GameSetupValidationResult.Valid();
|
||||
}
|
||||
|
||||
private static TrainingGameModel CastModel(object gameModel)
|
||||
{
|
||||
if (gameModel is TrainingGameModel model)
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
if (gameModel is JsonElement jsonElement)
|
||||
{
|
||||
return JsonSerializer.Deserialize<TrainingGameModel>(
|
||||
jsonElement.GetRawText(),
|
||||
GameModelFactory.JsonSerializerOptions)!;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Expected TrainingGameModel but got {gameModel.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
using Koogle.Domain.Enums;
|
||||
|
||||
namespace Koogle.Application.Games.Training;
|
||||
|
||||
/// <summary>
|
||||
/// Game model for Training game type. Stores player statistics.
|
||||
/// </summary>
|
||||
public record TrainingGameModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Statistics for each player in the game.
|
||||
/// </summary>
|
||||
public Dictionary<Guid, TrainingPlayerStats> PlayerStatistics { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for a single player in a training game.
|
||||
/// </summary>
|
||||
public record TrainingPlayerStats
|
||||
{
|
||||
/// <summary>
|
||||
/// Total number of throws made.
|
||||
/// </summary>
|
||||
public int ThrowCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of pins knocked down.
|
||||
/// </summary>
|
||||
public int PinCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of circles (Kranz) scored - 8 outer pins down, center standing.
|
||||
/// </summary>
|
||||
public int CircleCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of strikes scored - all 9 pins knocked down.
|
||||
/// </summary>
|
||||
public int StrikeCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of gutters (Rinne) - no pins knocked down.
|
||||
/// </summary>
|
||||
public int GutterCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the average pins per throw.
|
||||
/// </summary>
|
||||
public double Average => ThrowCount > 0 ? (double)PinCount / ThrowCount : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup options for Training game.
|
||||
/// </summary>
|
||||
public record TrainingSetupOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Throw mode: Reposition (in die Vollen) or Decrease (Abräumen).
|
||||
/// </summary>
|
||||
public ThrowMode ThrowMode { get; init; } = ThrowMode.Reposition;
|
||||
|
||||
/// <summary>
|
||||
/// Number of throws per round (1-5, default 3).
|
||||
/// </summary>
|
||||
public int ThrowsPerRound { get; init; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Mode for player rotation.
|
||||
/// </summary>
|
||||
public ParticipantsMode ParticipantsMode { get; init; } = ParticipantsMode.GameLogic;
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
@using Fluxor
|
||||
@using Koogle.Application.DTOs
|
||||
@using Koogle.Application.Games
|
||||
@using Koogle.Application.Games.Training
|
||||
@using Koogle.Web.Store.GameState
|
||||
@using Koogle.Web.Store.PersonState
|
||||
@using MudBlazor
|
||||
|
||||
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||
|
||||
@implements IDisposable
|
||||
@inject IState<GameState> GameState
|
||||
@inject IState<PersonState> PersonState
|
||||
|
||||
<MudPaper Class="pa-4">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TableChart" Class="mr-2" />
|
||||
Training - Tafel
|
||||
</MudText>
|
||||
|
||||
@if (_playerStats.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">
|
||||
Noch keine Statistiken vorhanden. Starte mit dem Werfen!
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@_playerStats"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Striped="true"
|
||||
Bordered="true"
|
||||
Class="mb-4">
|
||||
<HeaderContent>
|
||||
<MudTh>Spieler</MudTh>
|
||||
<MudTh Style="text-align: right">Würfe</MudTh>
|
||||
<MudTh Style="text-align: right">Kegel</MudTh>
|
||||
<MudTh Style="text-align: right">Kränze</MudTh>
|
||||
<MudTh Style="text-align: right">Strikes</MudTh>
|
||||
<MudTh Style="text-align: right">Rinnen</MudTh>
|
||||
<MudTh Style="text-align: right">⌀</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
@if (context.IsCurrentPlayer)
|
||||
{
|
||||
<MudBadge Color="Color.Primary" Dot="true" Overlap="true">
|
||||
<MudText Typo="Typo.body1" Style="font-weight: 600">
|
||||
@context.PlayerName
|
||||
</MudText>
|
||||
</MudBadge>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1">@context.PlayerName</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: right">@context.ThrowCount</MudTd>
|
||||
<MudTd Style="text-align: right">@context.PinCount</MudTd>
|
||||
<MudTd Style="text-align: right">
|
||||
@if (context.CircleCount > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">
|
||||
@context.CircleCount
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>0</span>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: right">
|
||||
@if (context.StrikeCount > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Warning" Variant="Variant.Filled">
|
||||
@context.StrikeCount
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>0</span>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: right">
|
||||
@if (context.GutterCount > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Error" Variant="Variant.Filled">
|
||||
@context.GutterCount
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>0</span>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: right; font-weight: 600">
|
||||
@context.Average.ToString("F1")
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@* Summary row *@
|
||||
<MudPaper Class="pa-3 mt-4" Elevation="0" Style="background-color: var(--mud-palette-background-grey);">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">
|
||||
<strong>Gesamt:</strong> @_totalThrows Würfe, @_totalPins Kegel
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
<strong>Team-⌀:</strong> @_teamAverage.ToString("F2")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private List<PlayerStatsRow> _playerStats = [];
|
||||
private int _totalThrows;
|
||||
private int _totalPins;
|
||||
private double _teamAverage;
|
||||
|
||||
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();
|
||||
_totalThrows = 0;
|
||||
_totalPins = 0;
|
||||
|
||||
var gameState = GameState.Value;
|
||||
if (gameState.GameModel is not TrainingGameModel model)
|
||||
{
|
||||
// Try to deserialize if it's a JsonElement
|
||||
if (gameState.GameModel is System.Text.Json.JsonElement jsonElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
model = System.Text.Json.JsonSerializer.Deserialize<TrainingGameModel>(
|
||||
jsonElement.GetRawText(),
|
||||
GameModelFactory.JsonSerializerOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (model?.PlayerStatistics == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentPlayerId = gameState.Participants.CurrentPlayerId;
|
||||
var persons = PersonState.Value.Persons;
|
||||
|
||||
foreach (var (playerId, stats) in model.PlayerStatistics)
|
||||
{
|
||||
var person = persons.FirstOrDefault(p => p.Id == playerId);
|
||||
var playerName = person?.Name ?? "Unbekannt";
|
||||
|
||||
_playerStats.Add(new PlayerStatsRow
|
||||
{
|
||||
PlayerId = playerId,
|
||||
PlayerName = playerName,
|
||||
ThrowCount = stats.ThrowCount,
|
||||
PinCount = stats.PinCount,
|
||||
CircleCount = stats.CircleCount,
|
||||
StrikeCount = stats.StrikeCount,
|
||||
GutterCount = stats.GutterCount,
|
||||
Average = stats.Average,
|
||||
IsCurrentPlayer = playerId == currentPlayerId
|
||||
});
|
||||
|
||||
_totalThrows += stats.ThrowCount;
|
||||
_totalPins += stats.PinCount;
|
||||
}
|
||||
|
||||
// Sort by pin count descending (best player first)
|
||||
_playerStats = _playerStats.OrderByDescending(p => p.PinCount).ToList();
|
||||
|
||||
_teamAverage = _totalThrows > 0 ? (double)_totalPins / _totalThrows : 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameState.StateChanged -= OnGameStateChanged;
|
||||
}
|
||||
|
||||
private record PlayerStatsRow
|
||||
{
|
||||
public Guid PlayerId { get; init; }
|
||||
public string PlayerName { get; init; } = "";
|
||||
public int ThrowCount { get; init; }
|
||||
public int PinCount { get; init; }
|
||||
public int CircleCount { get; init; }
|
||||
public int StrikeCount { get; init; }
|
||||
public int GutterCount { get; init; }
|
||||
public double Average { get; init; }
|
||||
public bool IsCurrentPlayer { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
@using Koogle.Application.Games.Training
|
||||
@using Koogle.Domain.Enums
|
||||
@using MudBlazor
|
||||
|
||||
<MudPaper Class="pa-4">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">Training-Einstellungen</MudText>
|
||||
|
||||
<MudStack Spacing="4">
|
||||
<MudSelect T="ThrowMode"
|
||||
@bind-Value="_options.ThrowMode"
|
||||
Label="Wurf-Modus"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="In die Vollen: Kegel werden nach jedem Wurf aufgestellt. Abräumen: Kegel bleiben liegen.">
|
||||
<MudSelectItem Value="ThrowMode.Reposition">In die Vollen</MudSelectItem>
|
||||
<MudSelectItem Value="ThrowMode.Decrease">Abräumen</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudNumericField T="int"
|
||||
@bind-Value="_options.ThrowsPerRound"
|
||||
Label="Würfe pro Runde"
|
||||
Variant="Variant.Outlined"
|
||||
Min="1"
|
||||
Max="5"
|
||||
HelperText="Anzahl Würfe bevor der nächste Spieler an der Reihe ist (1-5)" />
|
||||
|
||||
<MudSelect T="ParticipantsMode"
|
||||
@bind-Value="_options.ParticipantsMode"
|
||||
Label="Spieler-Rotation"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="Automatisch: Spieler wechseln nach der Runde. Frei wählbar: Jeder kann werfen.">
|
||||
<MudSelectItem Value="ParticipantsMode.GameLogic">Automatisch</MudSelectItem>
|
||||
<MudSelectItem Value="ParticipantsMode.FreeToChoose">Frei wählbar</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
Training ist ein freies Übungsspiel. Es gibt keinen Gewinner - nur Statistiken.
|
||||
Das Spiel muss manuell beendet werden.
|
||||
</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 TrainingSetupOptionsInternal _options = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (InitialOptions is TrainingSetupOptions options)
|
||||
{
|
||||
_options = new TrainingSetupOptionsInternal
|
||||
{
|
||||
ThrowMode = options.ThrowMode,
|
||||
ThrowsPerRound = options.ThrowsPerRound,
|
||||
ParticipantsMode = options.ParticipantsMode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await NotifyOptionsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyOptionsChanged()
|
||||
{
|
||||
var options = new TrainingSetupOptions
|
||||
{
|
||||
ThrowMode = _options.ThrowMode,
|
||||
ThrowsPerRound = _options.ThrowsPerRound,
|
||||
ParticipantsMode = _options.ParticipantsMode
|
||||
};
|
||||
await OnOptionsChanged.InvokeAsync(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current setup options.
|
||||
/// </summary>
|
||||
public TrainingSetupOptions GetOptions() => new()
|
||||
{
|
||||
ThrowMode = _options.ThrowMode,
|
||||
ThrowsPerRound = _options.ThrowsPerRound,
|
||||
ParticipantsMode = _options.ParticipantsMode
|
||||
};
|
||||
|
||||
// Internal mutable class for two-way binding
|
||||
private class TrainingSetupOptionsInternal
|
||||
{
|
||||
private ThrowMode _throwMode = ThrowMode.Reposition;
|
||||
private int _throwsPerRound = 3;
|
||||
private ParticipantsMode _participantsMode = ParticipantsMode.GameLogic;
|
||||
|
||||
public ThrowMode ThrowMode
|
||||
{
|
||||
get => _throwMode;
|
||||
set => _throwMode = value;
|
||||
}
|
||||
|
||||
public int ThrowsPerRound
|
||||
{
|
||||
get => _throwsPerRound;
|
||||
set => _throwsPerRound = value;
|
||||
}
|
||||
|
||||
public ParticipantsMode ParticipantsMode
|
||||
{
|
||||
get => _participantsMode;
|
||||
set => _participantsMode = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ using System.Reflection;
|
|||
using Fluxor;
|
||||
using Fluxor.Blazor.Web.ReduxDevTools;
|
||||
using Koogle.Application;
|
||||
using Koogle.Application.Games;
|
||||
using Koogle.Application.Games.Training;
|
||||
using Koogle.Infrastructure;
|
||||
using Koogle.Infrastructure.Security;
|
||||
using Koogle.Web.Components;
|
||||
|
|
@ -19,6 +21,9 @@ builder.Services.AddInfrastructure(builder.Configuration);
|
|||
// Application Layer (Services, AutoMapper, Validators)
|
||||
builder.Services.AddApplication();
|
||||
|
||||
// Game Types
|
||||
builder.Services.AddGameType<TrainingGameDefinition, TrainingGameLogicService>();
|
||||
|
||||
|
||||
// FluentValidation
|
||||
//builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
|
|
@ -80,6 +85,14 @@ app.UseAuthorization();
|
|||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
await IdentityRoleSeeder.SeedAsync(scope.ServiceProvider);
|
||||
|
||||
// Initialize game types
|
||||
var registry = scope.ServiceProvider.GetRequiredService<GameDefinitionRegistry>();
|
||||
var initializers = scope.ServiceProvider.GetServices<IGameTypeInitializer>();
|
||||
foreach (var initializer in initializers)
|
||||
{
|
||||
initializer.Initialize(registry);
|
||||
}
|
||||
}
|
||||
await BootstrapSeeder.SeedAsync(app.Services, app.Configuration, app.Environment);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue