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.
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);
@ -42,13 +42,25 @@ namespace KoogleApp.Games
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 false;
}
public static bool NextRound(this GameProgress progress)
{
var res = progress.IsNextRound();
if (res)
{
}
return res;
}
public static bool IsCircle(this GameProgress progress)
{
var res = 0;

View File

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

View File

@ -7,9 +7,9 @@ namespace KoogleApp.Games.Shit
public static class Reducers
{
[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 MudBlazor
@inject IState<ShitState> ShitState
@inject IState<ShitGameState> ShitState
@inject IState<PlayersState> PlayersState
<MudItem>
gesammelt: @ShitState.Value.CollectedPoints

View File

@ -9,7 +9,7 @@ namespace KoogleApp.Games.Shit
public Type SetupComponentType => typeof(Setup);
public Type SetupModelType => typeof(ShitSetupState);
public Type GameModelType => typeof(ShitState);
public Type GameModelType => typeof(ShitGameModel);
public Type GameServiceType => typeof(ShitGameService);
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)
{
if (progress.GameModel is not ShitState)
if (progress.GameModel is not ShitGameModel)
{
throw new InvalidOperationException("game service does not match game model");
}
//if (_isShit)
//{
// status.Points += GameModel.CollectedPoints;
// GameModel.CollectedPoints = 0;
//}
//else
//{
// GameModel.CollectedPoints += Game.StateAfter.PinCount;
//}
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;
gameModel.Points[playerId].Points += gameModel.CollectedPoints;
gameModel.CollectedPoints = 0;
}
else
{
gameModel.CollectedPoints += pinCount;
}
//GameModel.PreviewPointsOk -= Game.StateAfter.PinCount;
//GameModel.PreviewPointsShit += Game.StateAfter.PinCount;
gameModel.PreviewPointsOk -= pinCount;
gameModel.PreviewPointsShit += pinCount;
//if (GameModel.PreviewPointsOk <= 0)
//{
@ -65,7 +70,7 @@ namespace KoogleApp.Games.Shit
dict.Add(participant, tm);
}
var newModel = new ShitState(dict,0,0,0);
var newModel = new ShitGameModel(dict,0,0,0);
return newModel;
}
@ -85,15 +90,16 @@ namespace KoogleApp.Games.Shit
var actions = new List<object>();
var playerId = progress.GetCurrentPlayerId();
if (progress.AfterThrowState.ThrowCounterPerRound < 4) // three throws are at least required
{
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);
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
@inject IState<TrainingState> TrainingState
@inject IState<TrainingGameState> TrainingState
@inject IState<ParticipantsState> ParticipantsState
@inject IState<PlayersState> PlayersState

View File

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

View File

@ -15,7 +15,7 @@ namespace KoogleApp.Games.Training
public IGameModel InitGameModel(IGameSetupModel startParams, IDispatcher dispatcher)
{
var _gameModel = new TrainingState();
var _gameModel = new TrainingGameModel();
var dict = new Dictionary<int, ThrowModel>();
@ -25,7 +25,7 @@ namespace KoogleApp.Games.Training
dict.Add(participant, tm);
}
_gameModel = _gameModel with { Throws = dict };
_gameModel.Throws = dict;
return _gameModel;
}
@ -41,7 +41,7 @@ namespace KoogleApp.Games.Training
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");
}
@ -49,44 +49,47 @@ namespace KoogleApp.Games.Training
var actions = new List<object>();
var afterParticipantsState = progress.AfterParticipantsState;
if (progress.IsNextRound())
if (progress.NextRound())
{
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);
//afterParticipantsState = progress.AfterParticipantsState with { PlayerIds = newPlayersList };
//progress.AfterParticipantsState = afterParticipantsState;
//actions.Add(new SelectedNextPlayerIds(afterParticipantsState));
}
var playerId = progress.GetCurrentPlayerId();
var gm = progress.GameModel as TrainingState;
var newModel = (gm.DeepCopy()) as TrainingState;
var gm = progress.GameModel as TrainingGameModel;
//var newModel = (gm.DeepCopy()) as TrainingGameModel;
var oldThrowStatistic = newModel.Throws[playerId];
//var oldThrowStatistic = newModel.Throws[playerId];
newModel.Throws[playerId] = oldThrowStatistic with
{
ThrowCount = oldThrowStatistic.ThrowCount + 1,
PinCount = oldThrowStatistic.PinCount + progress.PinCount(),
CircleCount = oldThrowStatistic.CircleCount + Convert.ToInt32(progress.IsCircle())
};
gm.Throws[playerId].ThrowCount += 1;
gm.Throws[playerId].PinCount += progress.PinCount();
gm.Throws[playerId].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));
actions.Add(new GameModelChanged(newModel));
foreach (var action in actions)
{
dispatcher.Dispatch(action);
}
progress.GameModel = newModel;
progress.GameModel = gm;
progress.AfterParticipantsState = afterParticipantsState;
return progress;

View File

@ -7,9 +7,9 @@ namespace KoogleApp.Games.Training
public static class Reducers
{
[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]
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(
kvp => kvp.Key, // Key kopieren (int ist value type)
kvp => kvp.Value with { } // Value als Record kopieren
);
return this with { Throws = itemsCopy };
return new TrainingGameState(Throws);
}
}

View File

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

View File

@ -13,14 +13,14 @@ namespace KoogleApp.Store.Game
if (current.Status != null)
{
var gameModel = current.Status.GameModel;
dispatcher.Dispatch(new GameModelChanged(gameModel));
dispatcher.Dispatch(new GameModelChangedAction(gameModel.ConvertToState()));
}
}
[EffectMethod]
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 KoogleApp.Games;
using KoogleApp.Games.Training;
using KoogleApp.Hub;
using KoogleApp.Model;
using KoogleApp.Model.EventMessages;
@ -10,12 +11,12 @@ using KoogleApp.Store.Game.Setup;
using KoogleApp.Store.Game.ThrowTimer;
using KoogleApp.Store.Game.UndoRedo;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Win32.SafeHandles;
using System;
using System.Diagnostics;
using System.Text.Json;
using KoogleApp.Games.Training;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace KoogleApp.Store.Game.ThrowPanel
{
@ -54,9 +55,10 @@ namespace KoogleApp.Store.Game.ThrowPanel
var gameService = gameServiceFactory.GetService(startStopAction.StartParams.KnownGameType);
var gameModel = gameService.InitGameModel(startStopAction.StartParams, dispatcher);
dispatcher.Dispatch(new GameModelChanged(gameModel));
dispatcher.Dispatch(new GameModelChangedAction(gameModel.ConvertToState()));
var username = await GetUsername();
dataService.UpdateData(new GameStatus()
{
@ -65,6 +67,8 @@ namespace KoogleApp.Store.Game.ThrowPanel
SetupModel = startStopAction.StartParams,
GameModel = gameModel
}, username);
}
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.
// For the calculation of the game-progress in HandleThrow nextState if normally not useful
var nextState = throwPanelState.Value.CalculateNextPanelState();
dispatcher.Dispatch(new ThrowUpdateAction(nextState));
//var nextState = throwPanelState.Value.CalculateNextPanelState();
var nextState = beforeStates.Status.ThrowPanelState.CalculateNextPanelState();
//dispatcher.Dispatch(new ThrowUpdateAction(nextState));
var generalGameProgress = new GameProgress(
BeforeThrowState: beforeStates.Status?.ThrowPanelState,
@ -177,7 +182,7 @@ namespace KoogleApp.Store.Game.ThrowPanel
//var participants = throwAction.ParticipantsState;
//var gameSpecificProgress = generalGameProgress;
var gameSpecificProgress = GameService.HandleThrow(generalGameProgress, dispatcher);
//foreach (var action in actions)
@ -188,6 +193,7 @@ namespace KoogleApp.Store.Game.ThrowPanel
// }
// dispatcher.Dispatch(action);
//}
dispatcher.Dispatch(new GameModelChangedAction(gameSpecificProgress.GameModel.ConvertToState()));
await ShowBoardForSeconds(dispatcher);

File diff suppressed because it is too large Load Diff