Add Game Definition Framework (Phase H3)
- IGameDefinition interface in Domain - GameProgress.cs with throw state records - IGameLogicService interface - GameDefinitionRegistry for polymorphic game types - GameModelFactory for JSON serialization - DI registration extensions 🤖 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
d59005f6df
commit
0207c5fe80
|
|
@ -1,4 +1,5 @@
|
|||
using Koogle.Application.Interfaces;
|
||||
using Koogle.Application.Games;
|
||||
using Koogle.Application.Interfaces;
|
||||
using Koogle.Application.Services;
|
||||
using Koogle.Domain.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
@ -16,6 +17,8 @@ namespace Koogle.Application
|
|||
{
|
||||
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||
{
|
||||
// Game Framework
|
||||
services.AddGameFramework();
|
||||
// AutoMapper
|
||||
services.AddAutoMapper(cfg => cfg.LicenseKey = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzk3MjkyODAwIiwiaWF0IjoiMTc2NTgyNzkzMyIsImFjY291bnRfaWQiOiIwMTliMjM4YjYxZGM3MWYwOTAyYmY5OGU0NzNmOTY5ZSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxa2NocnF3dHNkczM4cjJnOTBncmZ5ajA1Iiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ehnUm61bFyXv2s0RScHd3vV2wIRivFN-phslB65UxztWBsk1EAtqTgPT55ONQ6-k7zi7G1vpLUUz9NL4EfpMRwgl1obeCTrs1pzvIRkednzrSdcPKAOmil-xiCS1TlrvaLzLnBWCr0JroGrAmtMYjsfUpYayRx9x8BzjUGBPUvb6eUR6wSbEtPzZbycgsp4Oj4Wwi23o56UGWHdNz7R8ofKy9EyzrgiG1uYfJZUDB_B5uWtdWi5M2bfoYHcLj-7VbSJlVlW2RoETEYuylBjG0Bwg_ZtSoVsCuqi_qV_GuyBOKbPpjFK4NFcInYFkAxAyzV_uTaFQpCFukPpCMZpFtA",
|
||||
Assembly.GetExecutingAssembly());
|
||||
|
|
@ -33,6 +36,9 @@ namespace Koogle.Application
|
|||
services.AddScoped<IEmailService, StubEmailService>();
|
||||
services.AddScoped<ITriggerService, TriggerService>();
|
||||
|
||||
// Note: Game types are registered in Koogle.Web where Blazor components are defined
|
||||
// Use services.AddGameType<TDefinition, TLogicService>() to register game types
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
using Koogle.Domain.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Koogle.Application.Games;
|
||||
|
||||
/// <summary>
|
||||
/// Registry for game definitions. Manages available game types.
|
||||
/// </summary>
|
||||
public class GameDefinitionRegistry
|
||||
{
|
||||
private readonly Dictionary<string, IGameDefinition> _definitions = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new registry with service provider for resolving game logic services.
|
||||
/// </summary>
|
||||
public GameDefinitionRegistry(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a game definition.
|
||||
/// </summary>
|
||||
public void Register(IGameDefinition definition)
|
||||
{
|
||||
_definitions[definition.Name] = definition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all registered game definitions.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IGameDefinition> GetAll() => _definitions.Values.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a game definition by name.
|
||||
/// </summary>
|
||||
/// <param name="name">Game type name (e.g., "Training").</param>
|
||||
/// <returns>Game definition or null if not found.</returns>
|
||||
public IGameDefinition? Get(string name)
|
||||
=> _definitions.TryGetValue(name, out var def) ? def : null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a game definition by name, throwing if not found.
|
||||
/// </summary>
|
||||
public IGameDefinition GetRequired(string name)
|
||||
=> Get(name) ?? throw new InvalidOperationException($"Game type '{name}' is not registered.");
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the game logic service for a game type.
|
||||
/// </summary>
|
||||
/// <param name="name">Game type name.</param>
|
||||
/// <returns>Game logic service instance.</returns>
|
||||
public IGameLogicService GetLogicService(string name)
|
||||
{
|
||||
var definition = GetRequired(name);
|
||||
return (IGameLogicService)_serviceProvider.GetRequiredService(definition.GameLogicServiceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a game type is registered.
|
||||
/// </summary>
|
||||
public bool IsRegistered(string name) => _definitions.ContainsKey(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for deserializing polymorphic game models.
|
||||
/// </summary>
|
||||
public static class GameModelFactory
|
||||
{
|
||||
private static readonly Dictionary<string, Type> _modelTypes = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a game model type for deserialization.
|
||||
/// </summary>
|
||||
public static void RegisterModelType(string gameType, Type modelType)
|
||||
{
|
||||
_modelTypes[gameType] = modelType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a game model from JSON based on game type.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON string containing the game model.</param>
|
||||
/// <param name="gameType">Game type name for polymorphic deserialization.</param>
|
||||
/// <returns>Deserialized game model object.</returns>
|
||||
public static object Deserialize(string json, string gameType)
|
||||
{
|
||||
if (!_modelTypes.TryGetValue(gameType, out var modelType))
|
||||
{
|
||||
throw new NotSupportedException($"Unknown game type: {gameType}. Ensure the game model is registered.");
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize(json, modelType, JsonSerializerOptions)
|
||||
?? throw new InvalidOperationException($"Failed to deserialize game model for type: {gameType}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a game model to JSON.
|
||||
/// </summary>
|
||||
/// <param name="model">Game model object.</param>
|
||||
/// <returns>JSON string.</returns>
|
||||
public static string Serialize(object model)
|
||||
{
|
||||
return JsonSerializer.Serialize(model, model.GetType(), JsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON serializer options for game models.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering game definitions in DI.
|
||||
/// </summary>
|
||||
public static class GameRegistrationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds game framework services to DI.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddGameFramework(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<GameDefinitionRegistry>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a game definition and its logic service.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDefinition">Game definition type.</typeparam>
|
||||
/// <typeparam name="TLogicService">Game logic service type.</typeparam>
|
||||
public static IServiceCollection AddGameType<TDefinition, TLogicService>(this IServiceCollection services)
|
||||
where TDefinition : class, IGameDefinition, new()
|
||||
where TLogicService : class, IGameLogicService
|
||||
{
|
||||
// Register the logic service
|
||||
services.AddScoped<TLogicService>();
|
||||
services.AddScoped(typeof(TLogicService), typeof(TLogicService));
|
||||
|
||||
// Register initializer to add definition to registry
|
||||
services.AddTransient<IGameTypeInitializer>(sp =>
|
||||
new GameTypeInitializer<TDefinition>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for game type initialization.
|
||||
/// </summary>
|
||||
public interface IGameTypeInitializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the game type by registering it with the registry.
|
||||
/// </summary>
|
||||
void Initialize(GameDefinitionRegistry registry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializer for a specific game type.
|
||||
/// </summary>
|
||||
internal class GameTypeInitializer<TDefinition> : IGameTypeInitializer
|
||||
where TDefinition : class, IGameDefinition, new()
|
||||
{
|
||||
public void Initialize(GameDefinitionRegistry registry)
|
||||
{
|
||||
var definition = new TDefinition();
|
||||
registry.Register(definition);
|
||||
GameModelFactory.RegisterModelType(definition.Name, definition.GameModelType);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
using Koogle.Domain.Enums;
|
||||
|
||||
namespace Koogle.Application.Games;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the state before a throw is made.
|
||||
/// </summary>
|
||||
/// <param name="ThrowPanel">Current throw panel state with pin positions.</param>
|
||||
/// <param name="CurrentPlayerId">ID of the player about to throw.</param>
|
||||
public record BeforeThrowState(
|
||||
ThrowPanelSnapshot ThrowPanel,
|
||||
Guid CurrentPlayerId
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the state after a throw is made.
|
||||
/// </summary>
|
||||
/// <param name="ThrowPanel">Updated throw panel state after the throw.</param>
|
||||
/// <param name="CurrentPlayerId">ID of the player who just threw.</param>
|
||||
/// <param name="PinsKnocked">Number of pins knocked down in this throw.</param>
|
||||
/// <param name="IsCircle">Whether a circle (Kranz) was scored.</param>
|
||||
/// <param name="IsStrike">Whether all 9 pins were knocked down.</param>
|
||||
/// <param name="IsGutter">Whether the throw was a gutter (Rinne).</param>
|
||||
public record AfterThrowState(
|
||||
ThrowPanelSnapshot ThrowPanel,
|
||||
Guid CurrentPlayerId,
|
||||
int PinsKnocked,
|
||||
bool IsCircle,
|
||||
bool IsStrike,
|
||||
bool IsGutter
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of throw panel state for game progress tracking.
|
||||
/// </summary>
|
||||
public record ThrowPanelSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of all 9 pins (index 0 = pin 1, etc.).
|
||||
/// </summary>
|
||||
public PinStatus[] Pins { get; init; } = new PinStatus[9];
|
||||
|
||||
/// <summary>
|
||||
/// Number of throws allowed per round.
|
||||
/// </summary>
|
||||
public int ThrowsPerRound { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Current throw count within the round.
|
||||
/// </summary>
|
||||
public int ThrowCounterPerRound { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total throws made in the game.
|
||||
/// </summary>
|
||||
public int TotalThrowCounter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Current throw mode.
|
||||
/// </summary>
|
||||
public ThrowMode ThrowMode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Bell value for special scoring.
|
||||
/// </summary>
|
||||
public bool BellValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates snapshot from pin status array.
|
||||
/// </summary>
|
||||
public static ThrowPanelSnapshot FromPins(
|
||||
PinStatus[] pins,
|
||||
int throwsPerRound,
|
||||
int throwCounterPerRound,
|
||||
int totalThrowCounter,
|
||||
ThrowMode throwMode,
|
||||
bool bellValue) => new()
|
||||
{
|
||||
Pins = pins,
|
||||
ThrowsPerRound = throwsPerRound,
|
||||
ThrowCounterPerRound = throwCounterPerRound,
|
||||
TotalThrowCounter = totalThrowCounter,
|
||||
ThrowMode = throwMode,
|
||||
BellValue = bellValue
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for game progress calculations.
|
||||
/// </summary>
|
||||
public static class GameProgressExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Counts fallen pins in the snapshot.
|
||||
/// </summary>
|
||||
public static int PinCount(this ThrowPanelSnapshot snapshot)
|
||||
=> snapshot.Pins.Count(p => p == PinStatus.Fallen);
|
||||
|
||||
/// <summary>
|
||||
/// Counts standing pins in the snapshot.
|
||||
/// </summary>
|
||||
public static int StandingPinCount(this ThrowPanelSnapshot snapshot)
|
||||
=> snapshot.Pins.Count(p => p == PinStatus.Standing);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a circle (Kranz) was scored - 8 outer pins down, center standing.
|
||||
/// Pin layout: 1=top, 5=center, 9=bottom
|
||||
/// Circle: pins 1,2,3,4,6,7,8,9 down, pin 5 standing.
|
||||
/// </summary>
|
||||
public static bool IsCircle(this ThrowPanelSnapshot snapshot)
|
||||
{
|
||||
var pins = snapshot.Pins;
|
||||
if (pins.Length != 9) return false;
|
||||
|
||||
// Pin 5 (index 4) must be standing
|
||||
if (pins[4] != PinStatus.Standing) return false;
|
||||
|
||||
// All other pins must be fallen
|
||||
return pins[0] == PinStatus.Fallen &&
|
||||
pins[1] == PinStatus.Fallen &&
|
||||
pins[2] == PinStatus.Fallen &&
|
||||
pins[3] == PinStatus.Fallen &&
|
||||
pins[5] == PinStatus.Fallen &&
|
||||
pins[6] == PinStatus.Fallen &&
|
||||
pins[7] == PinStatus.Fallen &&
|
||||
pins[8] == PinStatus.Fallen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all 9 pins were knocked down (strike).
|
||||
/// </summary>
|
||||
public static bool IsStrike(this ThrowPanelSnapshot snapshot)
|
||||
=> snapshot.Pins.All(p => p == PinStatus.Fallen);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if no pins were knocked down (gutter/Rinne).
|
||||
/// </summary>
|
||||
public static bool IsGutter(this ThrowPanelSnapshot snapshot)
|
||||
=> snapshot.Pins.All(p => p == PinStatus.Standing);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if round is complete based on throw counter.
|
||||
/// </summary>
|
||||
public static bool IsRoundComplete(this ThrowPanelSnapshot snapshot)
|
||||
=> snapshot.ThrowCounterPerRound >= snapshot.ThrowsPerRound;
|
||||
|
||||
/// <summary>
|
||||
/// Creates after-throw state from before and after snapshots.
|
||||
/// </summary>
|
||||
public static AfterThrowState CreateAfterThrowState(
|
||||
this ThrowPanelSnapshot afterThrow,
|
||||
ThrowPanelSnapshot beforeThrow,
|
||||
Guid playerId)
|
||||
{
|
||||
var pinsKnockedBefore = beforeThrow.PinCount();
|
||||
var pinsKnockedAfter = afterThrow.PinCount();
|
||||
var pinsKnocked = pinsKnockedAfter - pinsKnockedBefore;
|
||||
|
||||
return new AfterThrowState(
|
||||
ThrowPanel: afterThrow,
|
||||
CurrentPlayerId: playerId,
|
||||
PinsKnocked: pinsKnocked,
|
||||
IsCircle: afterThrow.IsCircle(),
|
||||
IsStrike: afterThrow.IsStrike(),
|
||||
IsGutter: pinsKnocked == 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of processing a throw in the game.
|
||||
/// </summary>
|
||||
public record ThrowResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Points scored in this throw.
|
||||
/// </summary>
|
||||
public int PointsScored { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the round is complete and player should rotate.
|
||||
/// </summary>
|
||||
public bool ShouldRotatePlayer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game has ended.
|
||||
/// </summary>
|
||||
public bool IsGameOver { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional winner ID if game is over.
|
||||
/// </summary>
|
||||
public Guid? WinnerId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Trigger events that should fire (e.g., for penalties).
|
||||
/// </summary>
|
||||
public IReadOnlyList<TriggerEvent> Triggers { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a trigger event that should fire.
|
||||
/// </summary>
|
||||
public record TriggerEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of trigger to fire.
|
||||
/// </summary>
|
||||
public required string TriggerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Person receiving the penalty/reward.
|
||||
/// </summary>
|
||||
public required Guid PersonId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for the expense (e.g., remaining points in Shit game).
|
||||
/// </summary>
|
||||
public int Multiplier { get; init; } = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
namespace Koogle.Application.Games;
|
||||
|
||||
/// <summary>
|
||||
/// Defines game-specific logic for processing throws and managing game state.
|
||||
/// Each game type (Training, Shit, etc.) implements this interface.
|
||||
/// </summary>
|
||||
public interface IGameLogicService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates initial game model with setup configuration.
|
||||
/// </summary>
|
||||
/// <param name="playerIds">IDs of participating players.</param>
|
||||
/// <param name="setupOptions">Game-specific setup options (JSON or typed object).</param>
|
||||
/// <returns>Initial game model object.</returns>
|
||||
object CreateInitialModel(Guid[] playerIds, object? setupOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Processes a throw and updates the game model.
|
||||
/// </summary>
|
||||
/// <param name="gameModel">Current game model.</param>
|
||||
/// <param name="afterThrow">State after the throw was made.</param>
|
||||
/// <returns>Tuple of updated game model and throw result.</returns>
|
||||
(object UpdatedModel, ThrowResult Result) ProcessThrow(object gameModel, AfterThrowState afterThrow);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the game should end based on current state.
|
||||
/// </summary>
|
||||
/// <param name="gameModel">Current game model.</param>
|
||||
/// <returns>True if game should end, along with optional winner ID.</returns>
|
||||
(bool IsGameOver, Guid? WinnerId) CheckGameEnd(object gameModel);
|
||||
|
||||
/// <summary>
|
||||
/// Gets summary statistics for display on the board.
|
||||
/// </summary>
|
||||
/// <param name="gameModel">Current game model.</param>
|
||||
/// <returns>Dictionary of player ID to their stats.</returns>
|
||||
IReadOnlyDictionary<Guid, PlayerStatsSummary> GetPlayerStats(object gameModel);
|
||||
|
||||
/// <summary>
|
||||
/// Validates setup options before starting the game.
|
||||
/// </summary>
|
||||
/// <param name="setupOptions">Setup options to validate.</param>
|
||||
/// <returns>Validation result with errors if any.</returns>
|
||||
GameSetupValidationResult ValidateSetup(object? setupOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of a player's statistics for board display.
|
||||
/// </summary>
|
||||
public record PlayerStatsSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// Total throws made by the player.
|
||||
/// </summary>
|
||||
public int ThrowCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total pins knocked down.
|
||||
/// </summary>
|
||||
public int PinCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of circles (Kranz) scored.
|
||||
/// </summary>
|
||||
public int CircleCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of strikes (all 9 pins) scored.
|
||||
/// </summary>
|
||||
public int StrikeCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Current points/score (game-specific meaning).
|
||||
/// </summary>
|
||||
public int CurrentPoints { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Average pins per throw.
|
||||
/// </summary>
|
||||
public double Average => ThrowCount > 0 ? (double)PinCount / ThrowCount : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Custom display values for game-specific stats.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> CustomStats { get; init; } = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of game setup validation.
|
||||
/// </summary>
|
||||
public record GameSetupValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the setup is valid.
|
||||
/// </summary>
|
||||
public bool IsValid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Validation errors if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Errors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Warnings that don't prevent starting but should be shown.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Warnings { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a valid result.
|
||||
/// </summary>
|
||||
public static GameSetupValidationResult Valid() => new() { IsValid = true };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a valid result with warnings.
|
||||
/// </summary>
|
||||
public static GameSetupValidationResult ValidWithWarnings(params string[] warnings) => new()
|
||||
{
|
||||
IsValid = true,
|
||||
Warnings = warnings
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates an invalid result with errors.
|
||||
/// </summary>
|
||||
public static GameSetupValidationResult Invalid(params string[] errors) => new()
|
||||
{
|
||||
IsValid = false,
|
||||
Errors = errors
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
namespace Koogle.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a game type with its components and logic.
|
||||
/// </summary>
|
||||
public interface IGameDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal name for the game type (e.g., "Training", "Shit").
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name shown to users (e.g., "Kegel-Training", "Scheiss-Spiel").
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Blazor component type for game setup configuration.
|
||||
/// </summary>
|
||||
Type SetupComponentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Blazor component type for the game board/scoreboard display.
|
||||
/// </summary>
|
||||
Type BoardComponentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Service type that implements game-specific logic.
|
||||
/// </summary>
|
||||
Type GameLogicServiceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of the game-specific model class.
|
||||
/// </summary>
|
||||
Type GameModelType { get; }
|
||||
}
|
||||
Loading…
Reference in New Issue