separate gamemodel from gamesate

fix next-round detection
This commit is contained in:
beo3000 2025-12-07 14:03:02 +01:00
parent 50824ba62d
commit 5696086022
17 changed files with 1433 additions and 196 deletions

View File

@ -23,7 +23,7 @@ namespace KoogleApp.Games
// NextThrowState is used to store the state for the next throw, that is modified by game logic or a user interaction. It overrides the default behaviour of resetting pins etc. // NextThrowState is used to store the state for the next throw, that is modified by game logic or a user interaction. It overrides the default behaviour of resetting pins etc.
public ThrowPanelState? NextThrowState { get; set; } = NextThrowState; public ThrowPanelState? NextThrowState { get; set; } = NextThrowState;
public object NextPlayer(Func<ParticipantsState, ParticipantsMode, int[]> getNextPlayer) public SelectedNextPlayerIds NextPlayer(Func<ParticipantsState, ParticipantsMode, int[]> getNextPlayer)
{ {
var newPlayersList = getNextPlayer(AfterParticipantsState, StartParams.ParticipantsMode); var newPlayersList = getNextPlayer(AfterParticipantsState, StartParams.ParticipantsMode);
@ -42,13 +42,25 @@ namespace KoogleApp.Games
public static bool IsNextRound(this GameProgress progress) public static bool IsNextRound(this GameProgress progress)
{ {
if (progress.BeforeThrowState.ThrowCounter + 1 >= progress.AfterThrowState.ThrowsPerRound) if (progress.AfterThrowState.ThrowCounterPerRound + 1 > progress.NextThrowState.ThrowsPerRound)
{ {
return true; return true;
} }
return false; return false;
} }
public static bool NextRound(this GameProgress progress)
{
var res = progress.IsNextRound();
if (res)
{
}
return res;
}
public static bool IsCircle(this GameProgress progress) public static bool IsCircle(this GameProgress progress)
{ {
var res = 0; var res = 0;

View File

@ -9,11 +9,18 @@ using System.Text.Json.Serialization;
namespace KoogleApp.Games namespace KoogleApp.Games
{ {
[JsonDerivedType(typeof(TrainingState), typeDiscriminator: "TrainingState")]
[JsonDerivedType(typeof(ShitState), typeDiscriminator: "ShitState")] public interface IGameState
{
}
[JsonDerivedType(typeof(TrainingGameModel), typeDiscriminator: "TrainingGameModel")]
[JsonDerivedType(typeof(ShitGameModel), typeDiscriminator: "ShitGameModel")]
public interface IGameModel public interface IGameModel
{ {
IGameModel DeepCopy(); IGameState ConvertToState();
//IGameModel DeepCopy();
} }
[JsonDerivedType(typeof(TrainingSetupState), typeDiscriminator: "TrainingSetupState")] [JsonDerivedType(typeof(TrainingSetupState), typeDiscriminator: "TrainingSetupState")]
@ -51,5 +58,7 @@ namespace KoogleApp.Games
IGameModel FinalizeGameModel(); IGameModel FinalizeGameModel();
GameProgress HandleGameButton(string actionCallback, GameProgress progress, IDispatcher dispatcher); GameProgress HandleGameButton(string actionCallback, GameProgress progress, IDispatcher dispatcher);
//void DispatchGameStateChanged();
} }
} }

View File

@ -7,9 +7,9 @@ namespace KoogleApp.Games.Shit
public static class Reducers public static class Reducers
{ {
[ReducerMethod] [ReducerMethod]
public static ShitState? OnGameModelChanged(ShitState state, GameModelChanged action) public static ShitGameState? OnGameModelChanged(ShitGameState gameModel, GameModelChangedAction action)
{ {
return action.Model as ShitState; return action.Model as ShitGameState;
} }
} }
} }

View File

@ -3,7 +3,7 @@
@using KoogleApp.Store.Player @using KoogleApp.Store.Player
@using MudBlazor @using MudBlazor
@inject IState<ShitState> ShitState @inject IState<ShitGameState> ShitState
@inject IState<PlayersState> PlayersState @inject IState<PlayersState> PlayersState
<MudItem> <MudItem>
gesammelt: @ShitState.Value.CollectedPoints gesammelt: @ShitState.Value.CollectedPoints

View File

@ -9,7 +9,7 @@ namespace KoogleApp.Games.Shit
public Type SetupComponentType => typeof(Setup); public Type SetupComponentType => typeof(Setup);
public Type SetupModelType => typeof(ShitSetupState); public Type SetupModelType => typeof(ShitSetupState);
public Type GameModelType => typeof(ShitState); public Type GameModelType => typeof(ShitGameModel);
public Type GameServiceType => typeof(ShitGameService); public Type GameServiceType => typeof(ShitGameService);
public Type BoardComponentType => typeof(ShitBoard); public Type BoardComponentType => typeof(ShitBoard);

View File

@ -0,0 +1,83 @@
using Fluxor;
using KoogleApp.Games.Shit;
using KoogleApp.Games.Training;
using KoogleApp.Store.Game.Menus;
using KoogleApp.Store.Game.ThrowPanel;
using System.Reflection.Metadata;
namespace KoogleApp.Games.Shit
{
public record PointStatus
{
public int PlayerId { get; set; }
//public int[] PointHistory { get; set; }
public int Points { get; set; }
//public string PlayerName { get; set; }
}
public record ShitGameState(
Dictionary<int, PointStatus> Points,
int CollectedPoints,
int PreviewPointsOk,
int PreviewPointsShit) : IGameState
{
public ShitGameState() : this([], 0, 0, 0)
{
}
}
public class ShitGameModel(
Dictionary<int, PointStatus> Points,
int CollectedPoints,
int PreviewPointsOk,
int PreviewPointsShit) : IGameModel
{
public Dictionary<int, PointStatus> Points { get; set; } = Points;
public int CollectedPoints { get; set; } = CollectedPoints;
public int PreviewPointsOk { get; set; } = PreviewPointsOk;
public int PreviewPointsShit { get; set; } = PreviewPointsShit;
public ShitGameModel() : this([],0,0,0)
{
}
//public IGameModel DeepCopy()
//{
// //// Array und alle Elemente kopieren
// //var itemsCopy = Throws.Select(item => item with { }).ToArray();
// //return this with { Throws = itemsCopy };
// // Dictionary und alle Elemente kopieren
// // Dictionary und alle Values kopieren
// var itemsCopy = Points.ToDictionary(
// kvp => kvp.Key, // Key kopieren (int ist value type)
// kvp => kvp.Value with { } // Value als Record kopieren
// );
// this.Points = itemsCopy;
// return this;
//}
public IGameState ConvertToState()
{
return new ShitGameState(Points,CollectedPoints,PreviewPointsOk,PreviewPointsShit);
}
}
[FeatureState]
public record ShitSetupState: GameSetupModelBase
{
public int ShitNumber { get; set; } = 5;
}
}

View File

@ -15,23 +15,28 @@ namespace KoogleApp.Games.Shit
public GameProgress HandleThrow(GameProgress progress, IDispatcher dispatcher) public GameProgress HandleThrow(GameProgress progress, IDispatcher dispatcher)
{ {
if (progress.GameModel is not ShitState) if (progress.GameModel is not ShitGameModel)
{ {
throw new InvalidOperationException("game service does not match game model"); throw new InvalidOperationException("game service does not match game model");
} }
//if (_isShit) var pinCount = progress.PinCount();
//{ var playerId = progress.GetCurrentPlayerId();
var gameModel = progress.GameModel as ShitGameModel;
var isShit = progress.PinCount() == ((ShitSetupState)progress.StartParams).ShitNumber;
if (isShit)
{
// status.Points += GameModel.CollectedPoints; // status.Points += GameModel.CollectedPoints;
// GameModel.CollectedPoints = 0; gameModel.Points[playerId].Points += gameModel.CollectedPoints;
//} gameModel.CollectedPoints = 0;
//else }
//{ else
// GameModel.CollectedPoints += Game.StateAfter.PinCount; {
//} gameModel.CollectedPoints += pinCount;
}
//GameModel.PreviewPointsOk -= Game.StateAfter.PinCount; gameModel.PreviewPointsOk -= pinCount;
//GameModel.PreviewPointsShit += Game.StateAfter.PinCount; gameModel.PreviewPointsShit += pinCount;
//if (GameModel.PreviewPointsOk <= 0) //if (GameModel.PreviewPointsOk <= 0)
//{ //{
@ -65,7 +70,7 @@ namespace KoogleApp.Games.Shit
dict.Add(participant, tm); dict.Add(participant, tm);
} }
var newModel = new ShitState(dict,0,0,0); var newModel = new ShitGameModel(dict,0,0,0);
return newModel; return newModel;
} }
@ -85,15 +90,16 @@ namespace KoogleApp.Games.Shit
var actions = new List<object>(); var actions = new List<object>();
var playerId = progress.GetCurrentPlayerId();
if (progress.AfterThrowState.ThrowCounterPerRound < 4) // three throws are at least required if (progress.AfterThrowState.ThrowCounterPerRound < 4) // three throws are at least required
{ {
return progress; return progress;
} }
// neue Runde starten: nächster Spieler und ThrowCounterPerRound zurücksetzen
//var playerId = progress.GetCurrentPlayerId();
// neue Runde starten: nächster Spieler und ThrowCounterPerRound zurücksetzen
var nextPlayerAction = progress.NextPlayer(NextPlayerHandler.GetNextPlayerIndex); var nextPlayerAction = progress.NextPlayer(NextPlayerHandler.GetNextPlayerIndex);
if (nextPlayerAction != null) if (nextPlayerAction != null)
{ {

View File

@ -1,60 +0,0 @@
using Fluxor;
using KoogleApp.Games.Shit;
using KoogleApp.Games.Training;
using KoogleApp.Store.Game.Menus;
using KoogleApp.Store.Game.ThrowPanel;
using System.Reflection.Metadata;
namespace KoogleApp.Games.Shit
{
public record PointStatus
{
public int PlayerId { get; set; }
//public int[] PointHistory { get; set; }
public int Points { get; set; }
//public string PlayerName { get; set; }
}
[FeatureState]
public record ShitState(
Dictionary<int, PointStatus> Points,
int CollectedPoints,
int PreviewPointsOk,
int PreviewPointsShit) : IGameModel
{
public ShitState() : this([],0,0,0)
{
}
public IGameModel DeepCopy()
{
//// Array und alle Elemente kopieren
//var itemsCopy = Throws.Select(item => item with { }).ToArray();
//return this with { Throws = itemsCopy };
// Dictionary und alle Elemente kopieren
// Dictionary und alle Values kopieren
var itemsCopy = Points.ToDictionary(
kvp => kvp.Key, // Key kopieren (int ist value type)
kvp => kvp.Value with { } // Value als Record kopieren
);
return this with { Points = itemsCopy };
}
}
[FeatureState]
public record ShitSetupState: GameSetupModelBase
{
public int ShitNumber { get; set; } = 5;
}
}

View File

@ -8,7 +8,7 @@
@inherits FluxorComponent @inherits FluxorComponent
@inject IState<TrainingState> TrainingState @inject IState<TrainingGameState> TrainingState
@inject IState<ParticipantsState> ParticipantsState @inject IState<ParticipantsState> ParticipantsState
@inject IState<PlayersState> PlayersState @inject IState<PlayersState> PlayersState

View File

@ -14,7 +14,7 @@ namespace KoogleApp.Games.Training
public Type SetupModelType => typeof(TrainingSetupState); public Type SetupModelType => typeof(TrainingSetupState);
public Type GameModelType => typeof(TrainingState); public Type GameModelType => typeof(TrainingGameModel);
public MenuAction[] MenuActions => [ ]; public MenuAction[] MenuActions => [ ];
} }

View File

@ -15,7 +15,7 @@ namespace KoogleApp.Games.Training
public IGameModel InitGameModel(IGameSetupModel startParams, IDispatcher dispatcher) public IGameModel InitGameModel(IGameSetupModel startParams, IDispatcher dispatcher)
{ {
var _gameModel = new TrainingState(); var _gameModel = new TrainingGameModel();
var dict = new Dictionary<int, ThrowModel>(); var dict = new Dictionary<int, ThrowModel>();
@ -25,7 +25,7 @@ namespace KoogleApp.Games.Training
dict.Add(participant, tm); dict.Add(participant, tm);
} }
_gameModel = _gameModel with { Throws = dict }; _gameModel.Throws = dict;
return _gameModel; return _gameModel;
} }
@ -41,7 +41,7 @@ namespace KoogleApp.Games.Training
public GameProgress HandleThrow(GameProgress progress, IDispatcher dispatcher) public GameProgress HandleThrow(GameProgress progress, IDispatcher dispatcher)
{ {
if (progress.GameModel is not TrainingState) if (progress.GameModel is not TrainingGameModel)
{ {
throw new InvalidOperationException("game service does not match game model"); throw new InvalidOperationException("game service does not match game model");
} }
@ -49,10 +49,12 @@ namespace KoogleApp.Games.Training
var actions = new List<object>(); var actions = new List<object>();
var afterParticipantsState = progress.AfterParticipantsState; var afterParticipantsState = progress.AfterParticipantsState;
if (progress.IsNextRound()) if (progress.NextRound())
{ {
var action = progress.NextPlayer(NextPlayerHandler.GetNextPlayerIndex); var action = progress.NextPlayer(NextPlayerHandler.GetNextPlayerIndex);
actions.Add(new SelectedNextPlayerIds(afterParticipantsState)); actions.Add(action);
afterParticipantsState = action.ParticipantsState;
//var newPlayersList = NextPlayerHandler.GetNextPlayerIndex(progress.AfterParticipantsState, progress.StartParams.ParticipantsMode); //var newPlayersList = NextPlayerHandler.GetNextPlayerIndex(progress.AfterParticipantsState, progress.StartParams.ParticipantsMode);
@ -64,29 +66,30 @@ namespace KoogleApp.Games.Training
var playerId = progress.GetCurrentPlayerId(); var playerId = progress.GetCurrentPlayerId();
var gm = progress.GameModel as TrainingState; var gm = progress.GameModel as TrainingGameModel;
var newModel = (gm.DeepCopy()) as TrainingState; //var newModel = (gm.DeepCopy()) as TrainingGameModel;
var oldThrowStatistic = newModel.Throws[playerId]; //var oldThrowStatistic = newModel.Throws[playerId];
newModel.Throws[playerId] = oldThrowStatistic with gm.Throws[playerId].ThrowCount += 1;
{ gm.Throws[playerId].PinCount += progress.PinCount();
ThrowCount = oldThrowStatistic.ThrowCount + 1, gm.Throws[playerId].CircleCount += Convert.ToInt32(progress.IsCircle());
PinCount = oldThrowStatistic.PinCount + progress.PinCount(), //{
CircleCount = oldThrowStatistic.CircleCount + Convert.ToInt32(progress.IsCircle()) // ThrowCount = oldThrowStatistic.ThrowCount + 1,
}; // PinCount = oldThrowStatistic.PinCount + progress.PinCount(),
// CircleCount = oldThrowStatistic.CircleCount + Convert.ToInt32(progress.IsCircle())
//};
//var newModel = ((TrainingState)(progress.GameModel)) with { Throws = ts.Throws }; //var newModel = ((TrainingGameModel)(progress.GameModel)) with { Throws = ts.Throws };
//res.Add(new UpdateTrainingStateAction(newModel)); //res.Add(new UpdateTrainingStateAction(newModel));
actions.Add(new GameModelChanged(newModel));
foreach (var action in actions) foreach (var action in actions)
{ {
dispatcher.Dispatch(action); dispatcher.Dispatch(action);
} }
progress.GameModel = newModel; progress.GameModel = gm;
progress.AfterParticipantsState = afterParticipantsState; progress.AfterParticipantsState = afterParticipantsState;
return progress; return progress;

View File

@ -7,9 +7,9 @@ namespace KoogleApp.Games.Training
public static class Reducers public static class Reducers
{ {
[ReducerMethod] [ReducerMethod]
public static TrainingState? OnGameModelChanged(TrainingState state, GameModelChanged action) public static TrainingGameState? OnGameModelChangedAction(TrainingGameState gameModel, GameModelChangedAction action)
{ {
return action.Model as TrainingState; return action.Model as TrainingGameState;
} }
} }
} }

View File

@ -29,20 +29,34 @@ namespace KoogleApp.Games.Training
} }
[FeatureState] [FeatureState]
public record TrainingState(Dictionary<int, ThrowModel> Throws) : IGameModel public record TrainingGameState(Dictionary<int, ThrowModel> Throws) : IGameState
{ {
public TrainingState() : this([]) public TrainingGameState() : this([])
{
}
}
public class TrainingGameModel(Dictionary<int, ThrowModel> Throws) : IGameModel
{
public Dictionary<int, ThrowModel> Throws { get; set; } = Throws;
public TrainingGameModel() : this([])
{ {
} }
public IGameModel DeepCopy() //public IGameModel DeepCopy()
//{
// var itemsCopy = Throws.ToDictionary(
// kvp => kvp.Key, // Key kopieren (int ist value type)
// kvp => kvp.Value with { } // Value als Record kopieren
// );
// return this with { Throws = itemsCopy };
//}
public IGameState ConvertToState()
{ {
var itemsCopy = Throws.ToDictionary( return new TrainingGameState(Throws);
kvp => kvp.Key, // Key kopieren (int ist value type)
kvp => kvp.Value with { } // Value als Record kopieren
);
return this with { Throws = itemsCopy };
} }
} }

View File

@ -2,7 +2,7 @@
namespace KoogleApp.Store.Game namespace KoogleApp.Store.Game
{ {
public record GameModelChanged(IGameModel Model); public record GameModelChangedAction(IGameState Model);
public record LoadGameStatesFromDataServiceAction(); public record LoadGameStatesFromDataServiceAction();
} }

View File

@ -13,14 +13,14 @@ namespace KoogleApp.Store.Game
if (current.Status != null) if (current.Status != null)
{ {
var gameModel = current.Status.GameModel; var gameModel = current.Status.GameModel;
dispatcher.Dispatch(new GameModelChanged(gameModel)); dispatcher.Dispatch(new GameModelChangedAction(gameModel.ConvertToState()));
} }
} }
[EffectMethod] [EffectMethod]
public async Task HandleUpdateStateAfterUndoRedo(UpdateStateAfterUndoRedo action, IDispatcher dispatcher) public async Task HandleUpdateStateAfterUndoRedo(UpdateStateAfterUndoRedo action, IDispatcher dispatcher)
{ {
dispatcher.Dispatch(new GameModelChanged(action.GameModel)); dispatcher.Dispatch(new GameModelChangedAction(action.GameModel.ConvertToState()));
} }
} }
} }

View File

@ -1,5 +1,6 @@
using Fluxor; using Fluxor;
using KoogleApp.Games; using KoogleApp.Games;
using KoogleApp.Games.Training;
using KoogleApp.Hub; using KoogleApp.Hub;
using KoogleApp.Model; using KoogleApp.Model;
using KoogleApp.Model.EventMessages; using KoogleApp.Model.EventMessages;
@ -10,12 +11,12 @@ using KoogleApp.Store.Game.Setup;
using KoogleApp.Store.Game.ThrowTimer; using KoogleApp.Store.Game.ThrowTimer;
using KoogleApp.Store.Game.UndoRedo; using KoogleApp.Store.Game.UndoRedo;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using KoogleApp.Games.Training;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace KoogleApp.Store.Game.ThrowPanel namespace KoogleApp.Store.Game.ThrowPanel
{ {
@ -54,7 +55,8 @@ namespace KoogleApp.Store.Game.ThrowPanel
var gameService = gameServiceFactory.GetService(startStopAction.StartParams.KnownGameType); var gameService = gameServiceFactory.GetService(startStopAction.StartParams.KnownGameType);
var gameModel = gameService.InitGameModel(startStopAction.StartParams, dispatcher); var gameModel = gameService.InitGameModel(startStopAction.StartParams, dispatcher);
dispatcher.Dispatch(new GameModelChanged(gameModel));
dispatcher.Dispatch(new GameModelChangedAction(gameModel.ConvertToState()));
var username = await GetUsername(); var username = await GetUsername();
@ -65,6 +67,8 @@ namespace KoogleApp.Store.Game.ThrowPanel
SetupModel = startStopAction.StartParams, SetupModel = startStopAction.StartParams,
GameModel = gameModel GameModel = gameModel
}, username); }, username);
} }
else // stop action else // stop action
{ {
@ -161,8 +165,9 @@ namespace KoogleApp.Store.Game.ThrowPanel
// perform throw - we will get the new state after resetting all pins for the next throw. // perform throw - we will get the new state after resetting all pins for the next throw.
// For the calculation of the game-progress in HandleThrow nextState if normally not useful // For the calculation of the game-progress in HandleThrow nextState if normally not useful
var nextState = throwPanelState.Value.CalculateNextPanelState(); //var nextState = throwPanelState.Value.CalculateNextPanelState();
dispatcher.Dispatch(new ThrowUpdateAction(nextState)); var nextState = beforeStates.Status.ThrowPanelState.CalculateNextPanelState();
//dispatcher.Dispatch(new ThrowUpdateAction(nextState));
var generalGameProgress = new GameProgress( var generalGameProgress = new GameProgress(
BeforeThrowState: beforeStates.Status?.ThrowPanelState, BeforeThrowState: beforeStates.Status?.ThrowPanelState,
@ -177,7 +182,7 @@ namespace KoogleApp.Store.Game.ThrowPanel
//var participants = throwAction.ParticipantsState; //var participants = throwAction.ParticipantsState;
//var gameSpecificProgress = generalGameProgress;
var gameSpecificProgress = GameService.HandleThrow(generalGameProgress, dispatcher); var gameSpecificProgress = GameService.HandleThrow(generalGameProgress, dispatcher);
//foreach (var action in actions) //foreach (var action in actions)
@ -188,6 +193,7 @@ namespace KoogleApp.Store.Game.ThrowPanel
// } // }
// dispatcher.Dispatch(action); // dispatcher.Dispatch(action);
//} //}
dispatcher.Dispatch(new GameModelChangedAction(gameSpecificProgress.GameModel.ConvertToState()));
await ShowBoardForSeconds(dispatcher); await ShowBoardForSeconds(dispatcher);

File diff suppressed because it is too large Load Diff