Compare commits
2 Commits
65df43ed23
...
f1880f08a8
| Author | SHA1 | Date |
|---|---|---|
|
|
f1880f08a8 | |
|
|
c3beda405c |
|
|
@ -10,7 +10,8 @@
|
||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"WebSearch",
|
"WebSearch",
|
||||||
"Bash(git merge:*)",
|
"Bash(git merge:*)",
|
||||||
"Bash(findstr:*)"
|
"Bash(findstr:*)",
|
||||||
|
"Bash(find:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ public record GameStateSerializationDto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonElement? GameModel { get; init; }
|
public JsonElement? GameModel { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game setup configuration. Persisted for game recovery.
|
||||||
|
/// </summary>
|
||||||
|
public JsonElement? Setup { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stack of game snapshots for undo functionality.
|
/// Stack of game snapshots for undo functionality.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for game setup configuration.
|
||||||
|
/// Supports JSON polymorphism for persistence.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
|
||||||
|
[JsonDerivedType(typeof(Shit.ShitGameSetup), "Shit")]
|
||||||
|
[JsonDerivedType(typeof(Training.TrainingGameSetup), "Training")]
|
||||||
|
public interface IGameSetupModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Throw mode for the game (Reposition or Decrease).
|
||||||
|
/// </summary>
|
||||||
|
ThrowMode ThrowMode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws allowed per round.
|
||||||
|
/// </summary>
|
||||||
|
int ThrowsPerRound { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mode for participant rotation.
|
||||||
|
/// </summary>
|
||||||
|
ParticipantsMode ParticipantsMode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game type identifier.
|
||||||
|
/// </summary>
|
||||||
|
string GameType { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base record implementing common setup properties.
|
||||||
|
/// </summary>
|
||||||
|
public abstract record GameSetupModelBase : IGameSetupModel
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ThrowMode ThrowMode { get; init; } = ThrowMode.Reposition;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int ThrowsPerRound { get; init; } = 3;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ParticipantsMode ParticipantsMode { get; init; } = ParticipantsMode.GameLogic;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract string GameType { get; }
|
||||||
|
}
|
||||||
|
|
@ -230,33 +230,33 @@ public class ShitGameLogicService : IGameLogicService
|
||||||
: GameSetupValidationResult.Valid();
|
: GameSetupValidationResult.Valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ShitSetupOptions ParseSetupOptions(object? setupOptions)
|
private static ShitGameSetup ParseSetupOptions(object? setupOptions)
|
||||||
{
|
{
|
||||||
if (setupOptions is null)
|
if (setupOptions is null)
|
||||||
{
|
{
|
||||||
return new ShitSetupOptions();
|
return new ShitGameSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setupOptions is ShitSetupOptions typedOptions)
|
if (setupOptions is ShitGameSetup typedSetup)
|
||||||
{
|
{
|
||||||
return typedOptions;
|
return typedSetup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setupOptions is JsonElement jsonElement)
|
if (setupOptions is JsonElement jsonElement)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<ShitSetupOptions>(
|
return JsonSerializer.Deserialize<ShitGameSetup>(
|
||||||
jsonElement.GetRawText(),
|
jsonElement.GetRawText(),
|
||||||
GameModelFactory.JsonSerializerOptions) ?? new ShitSetupOptions();
|
GameModelFactory.JsonSerializerOptions) ?? new ShitGameSetup();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return new ShitSetupOptions();
|
return new ShitGameSetup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ShitSetupOptions();
|
return new ShitGameSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ShitGameModel CastModel(object gameModel)
|
private static ShitGameModel CastModel(object gameModel)
|
||||||
|
|
|
||||||
|
|
@ -55,25 +55,3 @@ public record ShitGameModel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LastThrowWasGutter { get; set; }
|
public bool LastThrowWasGutter { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup options for Scheiss-Spiel (Shit Game).
|
|
||||||
/// </summary>
|
|
||||||
public record ShitSetupOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The "Scheiss-Zahl" (1-9, default 5). When a player knocks down exactly this many pins,
|
|
||||||
/// they must take the collected points.
|
|
||||||
/// </summary>
|
|
||||||
public int ShitNumber { get; init; } = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starting points for each player (10-1000, default 50).
|
|
||||||
/// </summary>
|
|
||||||
public int StartNumber { get; init; } = 50;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mode for player rotation.
|
|
||||||
/// </summary>
|
|
||||||
public ParticipantsMode ParticipantsMode { get; init; } = ParticipantsMode.GameLogic;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games.Shit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup model for Scheiss-Spiel (Shit Game).
|
||||||
|
/// </summary>
|
||||||
|
public record ShitGameSetup : GameSetupModelBase
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GameType => "Shit";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "Scheiss-Zahl" (1-9, default 5).
|
||||||
|
/// When a player knocks down exactly this many pins, they must take the collected points.
|
||||||
|
/// </summary>
|
||||||
|
public int ShitNumber { get; init; } = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starting points for each player (10-1000, default 50).
|
||||||
|
/// </summary>
|
||||||
|
public int StartNumber { get; init; } = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default ShitGameSetup with specified base parameters.
|
||||||
|
/// </summary>
|
||||||
|
public static ShitGameSetup Create(
|
||||||
|
ThrowMode throwMode,
|
||||||
|
int throwsPerRound,
|
||||||
|
ParticipantsMode participantsMode,
|
||||||
|
int shitNumber = 5,
|
||||||
|
int startNumber = 50) => new()
|
||||||
|
{
|
||||||
|
ThrowMode = throwMode,
|
||||||
|
ThrowsPerRound = throwsPerRound,
|
||||||
|
ParticipantsMode = participantsMode,
|
||||||
|
ShitNumber = shitNumber,
|
||||||
|
StartNumber = startNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -110,18 +110,18 @@ public class TrainingGameLogicService : IGameLogicService
|
||||||
return GameSetupValidationResult.Valid();
|
return GameSetupValidationResult.Valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
TrainingSetupOptions options;
|
TrainingGameSetup? setup;
|
||||||
if (setupOptions is TrainingSetupOptions typedOptions)
|
if (setupOptions is TrainingGameSetup typedSetup)
|
||||||
{
|
{
|
||||||
options = typedOptions;
|
setup = typedSetup;
|
||||||
}
|
}
|
||||||
else if (setupOptions is JsonElement jsonElement)
|
else if (setupOptions is JsonElement jsonElement)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
options = JsonSerializer.Deserialize<TrainingSetupOptions>(
|
setup = JsonSerializer.Deserialize<TrainingGameSetup>(
|
||||||
jsonElement.GetRawText(),
|
jsonElement.GetRawText(),
|
||||||
GameModelFactory.JsonSerializerOptions)!;
|
GameModelFactory.JsonSerializerOptions);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -133,9 +133,14 @@ public class TrainingGameLogicService : IGameLogicService
|
||||||
return GameSetupValidationResult.Invalid("Ungültige Setup-Optionen.");
|
return GameSetupValidationResult.Invalid("Ungültige Setup-Optionen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (setup is null)
|
||||||
|
{
|
||||||
|
return GameSetupValidationResult.Valid();
|
||||||
|
}
|
||||||
|
|
||||||
var errors = new List<string>();
|
var errors = new List<string>();
|
||||||
|
|
||||||
if (options.ThrowsPerRound < 1 || options.ThrowsPerRound > 5)
|
if (setup.ThrowsPerRound < 1 || setup.ThrowsPerRound > 5)
|
||||||
{
|
{
|
||||||
errors.Add("Würfe pro Runde muss zwischen 1 und 5 liegen.");
|
errors.Add("Würfe pro Runde muss zwischen 1 und 5 liegen.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using Koogle.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Koogle.Application.Games.Training;
|
namespace Koogle.Application.Games.Training;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -48,24 +46,3 @@ public record TrainingPlayerStats
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Average => ThrowCount > 0 ? (double)PinCount / ThrowCount : 0;
|
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,25 @@
|
||||||
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Koogle.Application.Games.Training;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setup model for Training game.
|
||||||
|
/// </summary>
|
||||||
|
public record TrainingGameSetup : GameSetupModelBase
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GameType => "Training";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default TrainingGameSetup with specified base parameters.
|
||||||
|
/// </summary>
|
||||||
|
public static TrainingGameSetup Create(
|
||||||
|
ThrowMode throwMode,
|
||||||
|
int throwsPerRound,
|
||||||
|
ParticipantsMode participantsMode) => new()
|
||||||
|
{
|
||||||
|
ThrowMode = throwMode,
|
||||||
|
ThrowsPerRound = throwsPerRound,
|
||||||
|
ParticipantsMode = participantsMode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
@using Fluxor
|
@using Fluxor
|
||||||
@using Koogle.Application.DTOs
|
@using Koogle.Application.DTOs
|
||||||
@using Koogle.Application.Games
|
@using Koogle.Application.Games
|
||||||
|
@using Koogle.Application.Games.Shit
|
||||||
|
@using Koogle.Application.Games.Training
|
||||||
@using Koogle.Domain.Enums
|
@using Koogle.Domain.Enums
|
||||||
@using Koogle.Domain.Interfaces
|
@using Koogle.Domain.Interfaces
|
||||||
@using Koogle.Web.Store.DayState
|
@using Koogle.Web.Store.DayState
|
||||||
|
|
@ -201,9 +203,13 @@
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initial game model
|
// Create initial game model and setup
|
||||||
var playerIds = _selectedParticipantIds.ToArray();
|
var playerIds = _selectedParticipantIds.ToArray();
|
||||||
var initialModel = logicService.CreateInitialModel(playerIds, _gameSpecificSetupOptions);
|
|
||||||
|
// Create typed setup model based on game type
|
||||||
|
IGameSetupModel? setup = CreateSetupModel(_selectedDefinition.Name);
|
||||||
|
|
||||||
|
var initialModel = logicService.CreateInitialModel(playerIds, setup ?? _gameSpecificSetupOptions);
|
||||||
|
|
||||||
// Dispatch start game action
|
// Dispatch start game action
|
||||||
var action = new StartGameAction(
|
var action = new StartGameAction(
|
||||||
|
|
@ -213,7 +219,8 @@
|
||||||
ThrowMode: _throwMode,
|
ThrowMode: _throwMode,
|
||||||
ThrowsPerRound: _throwsPerRound,
|
ThrowsPerRound: _throwsPerRound,
|
||||||
ParticipantsMode: _participantsMode,
|
ParticipantsMode: _participantsMode,
|
||||||
InitialGameModel: initialModel);
|
InitialGameModel: initialModel,
|
||||||
|
Setup: setup);
|
||||||
|
|
||||||
Dispatcher.Dispatch(action);
|
Dispatcher.Dispatch(action);
|
||||||
|
|
||||||
|
|
@ -243,4 +250,42 @@
|
||||||
ThrowMode.Decrease => "Abräumen",
|
ThrowMode.Decrease => "Abräumen",
|
||||||
_ => mode.ToString()
|
_ => mode.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private IGameSetupModel? CreateSetupModel(string gameTypeName)
|
||||||
|
{
|
||||||
|
return gameTypeName switch
|
||||||
|
{
|
||||||
|
"Shit" => CreateShitSetup(),
|
||||||
|
"Training" => CreateTrainingSetup(),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShitGameSetup CreateShitSetup()
|
||||||
|
{
|
||||||
|
// Extract game-specific options if available
|
||||||
|
var shitNumber = 5;
|
||||||
|
var startNumber = 50;
|
||||||
|
|
||||||
|
if (_gameSpecificSetupOptions is ShitGameSetup existingSetup)
|
||||||
|
{
|
||||||
|
shitNumber = existingSetup.ShitNumber;
|
||||||
|
startNumber = existingSetup.StartNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShitGameSetup.Create(
|
||||||
|
throwMode: _throwMode,
|
||||||
|
throwsPerRound: _throwsPerRound,
|
||||||
|
participantsMode: _participantsMode,
|
||||||
|
shitNumber: shitNumber,
|
||||||
|
startNumber: startNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrainingGameSetup CreateTrainingSetup()
|
||||||
|
{
|
||||||
|
return TrainingGameSetup.Create(
|
||||||
|
throwMode: _throwMode,
|
||||||
|
throwsPerRound: _throwsPerRound,
|
||||||
|
participantsMode: _participantsMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,13 @@
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (InitialOptions is ShitSetupOptions options)
|
if (InitialOptions is ShitGameSetup setup)
|
||||||
{
|
{
|
||||||
_options = new ShitSetupOptionsInternal
|
_options = new ShitSetupOptionsInternal
|
||||||
{
|
{
|
||||||
ShitNumber = options.ShitNumber,
|
ShitNumber = setup.ShitNumber,
|
||||||
StartNumber = options.StartNumber,
|
StartNumber = setup.StartNumber,
|
||||||
ParticipantsMode = options.ParticipantsMode
|
ParticipantsMode = setup.ParticipantsMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,19 +104,19 @@
|
||||||
|
|
||||||
private async Task NotifyOptionsChanged()
|
private async Task NotifyOptionsChanged()
|
||||||
{
|
{
|
||||||
var options = new ShitSetupOptions
|
var setup = new ShitGameSetup
|
||||||
{
|
{
|
||||||
ShitNumber = _options.ShitNumber,
|
ShitNumber = _options.ShitNumber,
|
||||||
StartNumber = _options.StartNumber,
|
StartNumber = _options.StartNumber,
|
||||||
ParticipantsMode = _options.ParticipantsMode
|
ParticipantsMode = _options.ParticipantsMode
|
||||||
};
|
};
|
||||||
await OnOptionsChanged.InvokeAsync(options);
|
await OnOptionsChanged.InvokeAsync(setup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current setup options.
|
/// Gets the current setup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ShitSetupOptions GetOptions() => new()
|
public ShitGameSetup GetSetup() => new()
|
||||||
{
|
{
|
||||||
ShitNumber = _options.ShitNumber,
|
ShitNumber = _options.ShitNumber,
|
||||||
StartNumber = _options.StartNumber,
|
StartNumber = _options.StartNumber,
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,13 @@
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
if (InitialOptions is TrainingSetupOptions options)
|
if (InitialOptions is TrainingGameSetup setup)
|
||||||
{
|
{
|
||||||
_options = new TrainingSetupOptionsInternal
|
_options = new TrainingSetupOptionsInternal
|
||||||
{
|
{
|
||||||
ThrowMode = options.ThrowMode,
|
ThrowMode = setup.ThrowMode,
|
||||||
ThrowsPerRound = options.ThrowsPerRound,
|
ThrowsPerRound = setup.ThrowsPerRound,
|
||||||
ParticipantsMode = options.ParticipantsMode
|
ParticipantsMode = setup.ParticipantsMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,19 +79,19 @@
|
||||||
|
|
||||||
private async Task NotifyOptionsChanged()
|
private async Task NotifyOptionsChanged()
|
||||||
{
|
{
|
||||||
var options = new TrainingSetupOptions
|
var setup = new TrainingGameSetup
|
||||||
{
|
{
|
||||||
ThrowMode = _options.ThrowMode,
|
ThrowMode = _options.ThrowMode,
|
||||||
ThrowsPerRound = _options.ThrowsPerRound,
|
ThrowsPerRound = _options.ThrowsPerRound,
|
||||||
ParticipantsMode = _options.ParticipantsMode
|
ParticipantsMode = _options.ParticipantsMode
|
||||||
};
|
};
|
||||||
await OnOptionsChanged.InvokeAsync(options);
|
await OnOptionsChanged.InvokeAsync(setup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current setup options.
|
/// Gets the current setup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TrainingSetupOptions GetOptions() => new()
|
public TrainingGameSetup GetSetup() => new()
|
||||||
{
|
{
|
||||||
ThrowMode = _options.ThrowMode,
|
ThrowMode = _options.ThrowMode,
|
||||||
ThrowsPerRound = _options.ThrowsPerRound,
|
ThrowsPerRound = _options.ThrowsPerRound,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Koogle.Application.Games;
|
||||||
using Koogle.Domain.Enums;
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
namespace Koogle.Web.Store.GameState;
|
namespace Koogle.Web.Store.GameState;
|
||||||
|
|
@ -14,7 +15,8 @@ public record StartGameAction(
|
||||||
ThrowMode ThrowMode,
|
ThrowMode ThrowMode,
|
||||||
int ThrowsPerRound,
|
int ThrowsPerRound,
|
||||||
ParticipantsMode ParticipantsMode,
|
ParticipantsMode ParticipantsMode,
|
||||||
object? InitialGameModel);
|
object? InitialGameModel,
|
||||||
|
IGameSetupModel? Setup);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action dispatched when game is started successfully.
|
/// Action dispatched when game is started successfully.
|
||||||
|
|
@ -24,7 +26,8 @@ public record StartGameSuccessAction(
|
||||||
string GameTypeName,
|
string GameTypeName,
|
||||||
ThrowPanelState ThrowPanel,
|
ThrowPanelState ThrowPanel,
|
||||||
ParticipantsState Participants,
|
ParticipantsState Participants,
|
||||||
object? GameModel);
|
object? GameModel,
|
||||||
|
IGameSetupModel? Setup);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action dispatched when starting game fails.
|
/// Action dispatched when starting game fails.
|
||||||
|
|
@ -60,7 +63,8 @@ public record LoadActiveGameSuccessAction(
|
||||||
ThrowPanelState ThrowPanel,
|
ThrowPanelState ThrowPanel,
|
||||||
ParticipantsState Participants,
|
ParticipantsState Participants,
|
||||||
object? GameModel,
|
object? GameModel,
|
||||||
IReadOnlyList<GameSnapshot> UndoStack);
|
IReadOnlyList<GameSnapshot> UndoStack,
|
||||||
|
IGameSetupModel? Setup);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action dispatched when no active game exists.
|
/// Action dispatched when no active game exists.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Fluxor;
|
using Fluxor;
|
||||||
using Koogle.Application.DTOs;
|
using Koogle.Application.DTOs;
|
||||||
|
using Koogle.Application.Games;
|
||||||
using Koogle.Application.Interfaces;
|
using Koogle.Application.Interfaces;
|
||||||
using Koogle.Domain.Enums;
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
|
|
@ -61,6 +62,9 @@ public class GameEffects
|
||||||
GameModel = action.InitialGameModel != null
|
GameModel = action.InitialGameModel != null
|
||||||
? JsonSerializer.SerializeToElement(action.InitialGameModel, GameStateSerializationDto.JsonOptions)
|
? JsonSerializer.SerializeToElement(action.InitialGameModel, GameStateSerializationDto.JsonOptions)
|
||||||
: null,
|
: null,
|
||||||
|
Setup = action.Setup != null
|
||||||
|
? JsonSerializer.SerializeToElement(action.Setup, GameStateSerializationDto.JsonOptions)
|
||||||
|
: null,
|
||||||
UndoStack = []
|
UndoStack = []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -94,7 +98,8 @@ public class GameEffects
|
||||||
action.GameTypeName,
|
action.GameTypeName,
|
||||||
throwPanel,
|
throwPanel,
|
||||||
participants,
|
participants,
|
||||||
action.InitialGameModel));
|
action.InitialGameModel,
|
||||||
|
action.Setup));
|
||||||
|
|
||||||
_logger.LogInformation("Game started: {GameId}, Type: {GameType}", gameId, action.GameTypeName);
|
_logger.LogInformation("Game started: {GameId}, Type: {GameType}", gameId, action.GameTypeName);
|
||||||
}
|
}
|
||||||
|
|
@ -183,13 +188,23 @@ public class GameEffects
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// Deserialize setup if present (may be null for legacy games)
|
||||||
|
IGameSetupModel? setup = null;
|
||||||
|
if (stateDto.Setup.HasValue)
|
||||||
|
{
|
||||||
|
setup = JsonSerializer.Deserialize<IGameSetupModel>(
|
||||||
|
stateDto.Setup.Value.GetRawText(),
|
||||||
|
GameStateSerializationDto.JsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
dispatcher.Dispatch(new LoadActiveGameSuccessAction(
|
dispatcher.Dispatch(new LoadActiveGameSuccessAction(
|
||||||
activeGame.Id,
|
activeGame.Id,
|
||||||
activeGame.GameType,
|
activeGame.GameType,
|
||||||
throwPanel,
|
throwPanel,
|
||||||
participants,
|
participants,
|
||||||
stateDto.GameModel.HasValue ? (object?)stateDto.GameModel.Value : null,
|
stateDto.GameModel.HasValue ? (object?)stateDto.GameModel.Value : null,
|
||||||
undoStack));
|
undoStack,
|
||||||
|
setup));
|
||||||
|
|
||||||
_logger.LogInformation("Active game loaded: {GameId}, Type: {GameType}", activeGame.Id, activeGame.GameType);
|
_logger.LogInformation("Active game loaded: {GameId}, Type: {GameType}", activeGame.Id, activeGame.GameType);
|
||||||
}
|
}
|
||||||
|
|
@ -353,6 +368,9 @@ public class GameEffects
|
||||||
GameModel = state.GameModel != null
|
GameModel = state.GameModel != null
|
||||||
? JsonSerializer.SerializeToElement(state.GameModel, GameStateSerializationDto.JsonOptions)
|
? JsonSerializer.SerializeToElement(state.GameModel, GameStateSerializationDto.JsonOptions)
|
||||||
: null,
|
: null,
|
||||||
|
Setup = state.Setup != null
|
||||||
|
? JsonSerializer.SerializeToElement(state.Setup, GameStateSerializationDto.JsonOptions)
|
||||||
|
: null,
|
||||||
UndoStack = state.UndoStack.Select(s => new GameSnapshotDto
|
UndoStack = state.UndoStack.Select(s => new GameSnapshotDto
|
||||||
{
|
{
|
||||||
ThrowPanel = MapThrowPanelToDto(s.ThrowPanel),
|
ThrowPanel = MapThrowPanelToDto(s.ThrowPanel),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public static class GameReducers
|
||||||
ThrowPanel = action.ThrowPanel,
|
ThrowPanel = action.ThrowPanel,
|
||||||
Participants = action.Participants,
|
Participants = action.Participants,
|
||||||
GameModel = action.GameModel,
|
GameModel = action.GameModel,
|
||||||
|
Setup = action.Setup,
|
||||||
UndoStack = [],
|
UndoStack = [],
|
||||||
IsLoading = false,
|
IsLoading = false,
|
||||||
Error = null
|
Error = null
|
||||||
|
|
@ -75,6 +76,7 @@ public static class GameReducers
|
||||||
ThrowPanel = ThrowPanelState.Initial,
|
ThrowPanel = ThrowPanelState.Initial,
|
||||||
Participants = ParticipantsState.Initial,
|
Participants = ParticipantsState.Initial,
|
||||||
GameModel = null,
|
GameModel = null,
|
||||||
|
Setup = null,
|
||||||
UndoStack = [],
|
UndoStack = [],
|
||||||
CompletedGames = [.. state.CompletedGames, action.Summary],
|
CompletedGames = [.. state.CompletedGames, action.Summary],
|
||||||
IsLoading = false
|
IsLoading = false
|
||||||
|
|
@ -116,6 +118,7 @@ public static class GameReducers
|
||||||
ThrowPanel = action.ThrowPanel,
|
ThrowPanel = action.ThrowPanel,
|
||||||
Participants = action.Participants,
|
Participants = action.Participants,
|
||||||
GameModel = action.GameModel,
|
GameModel = action.GameModel,
|
||||||
|
Setup = action.Setup,
|
||||||
UndoStack = action.UndoStack.ToImmutableList(),
|
UndoStack = action.UndoStack.ToImmutableList(),
|
||||||
IsLoading = false
|
IsLoading = false
|
||||||
};
|
};
|
||||||
|
|
@ -387,6 +390,7 @@ public static class GameReducers
|
||||||
ThrowPanel = ThrowPanelState.Initial,
|
ThrowPanel = ThrowPanelState.Initial,
|
||||||
Participants = ParticipantsState.Initial,
|
Participants = ParticipantsState.Initial,
|
||||||
GameModel = null,
|
GameModel = null,
|
||||||
|
Setup = null,
|
||||||
UndoStack = [],
|
UndoStack = [],
|
||||||
CompletedGames = [.. state.CompletedGames, action.Summary]
|
CompletedGames = [.. state.CompletedGames, action.Summary]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Fluxor;
|
using Fluxor;
|
||||||
|
using Koogle.Application.Games;
|
||||||
using Koogle.Domain.Enums;
|
using Koogle.Domain.Enums;
|
||||||
|
|
||||||
namespace Koogle.Web.Store.GameState;
|
namespace Koogle.Web.Store.GameState;
|
||||||
|
|
@ -45,6 +46,11 @@ public record GameState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object? GameModel { get; init; }
|
public object? GameModel { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game setup configuration (persisted for recovery).
|
||||||
|
/// </summary>
|
||||||
|
public IGameSetupModel? Setup { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stack of game snapshots for undo functionality (unlimited).
|
/// Stack of game snapshots for undo functionality (unlimited).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -92,6 +98,7 @@ public record GameState
|
||||||
ThrowPanel = ThrowPanelState.Initial,
|
ThrowPanel = ThrowPanelState.Initial,
|
||||||
Participants = ParticipantsState.Initial,
|
Participants = ParticipantsState.Initial,
|
||||||
GameModel = null,
|
GameModel = null,
|
||||||
|
Setup = null,
|
||||||
UndoStack = [],
|
UndoStack = [],
|
||||||
CompletedGames = [],
|
CompletedGames = [],
|
||||||
IsLoading = false,
|
IsLoading = false,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue