KoogleApp/KoogleApp/Store/Game/ThrowPanel/Effects.cs

405 lines
16 KiB
C#

using Fluxor;
using KoogleApp.Games;
using KoogleApp.Games.Training;
using KoogleApp.Hub;
using KoogleApp.Model;
using KoogleApp.Model.EventMessages;
using KoogleApp.Model.Framework;
using KoogleApp.Services;
using KoogleApp.Store.Game.Participants;
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;
namespace KoogleApp.Store.Game.ThrowPanel
{
public class ThrowPanelEffects(
IMyEventAggregator eventAggregator,
ILogger<ThrowPanelEffects> logger,
HubConnectionService sharedHubService,
IState<ThrowPanelState> throwPanelState,
IState<SetupState> setupState,
AuthenticationStateProvider authenticationStateProvider,
IGameStatusDataService dataService,
IGameServiceFactory gameServiceFactory,
SessionStorage sessionStorage)
: IDisposable
{
private readonly string _dataFilePath = "ThrowPanelState.json";
private readonly Lock _lock = new Lock();
public void Dispose()
{
//_sharedHubService?.Dispose();
}
[EffectMethod]
public async Task HandleStartAction(StartStopAction startStopAction, IDispatcher dispatcher)
{
ParticipantsState? participantsState = null;
if (startStopAction.StartParams != null) // start action
{
dispatcher.Dispatch(new SetParticipatingPlayersAction(startStopAction.StartParams.Participants));
// here we just want to pass the participants with ThrowPanelStateChangedAction - therefore we create a copy of ParticipantsState - I think this is valid just for the startup of a new game
participantsState = new ParticipantsState(startStopAction.StartParams.Participants, []);
var gameService = gameServiceFactory.GetService(startStopAction.StartParams.KnownGameType);
var gameModel = gameService.InitGameModel(startStopAction.StartParams, dispatcher);
dispatcher.Dispatch(new GameModelChangedAction(gameModel.ConvertToState()));
var username = await GetUsername();
dataService.UpdateData(new GameStatus()
{
ThrowPanelState = throwPanelState.Value,
ParticipantsState = participantsState,
SetupModel = startStopAction.StartParams,
GameState = gameModel.ConvertToState(),
}, username);
}
else // stop action
{
var gameModel = GameService.FinalizeGameModel();
_gameService = null;
var username = await GetUsername();
await dataService.SaveToDatabaseAndReset(new GameStatus
{
ThrowPanelState = throwPanelState.Value with { ThrowPanelStateStatus = ThrowPanelStateStatus.GameEnd },
GameState = gameModel?.ConvertToState()
}, username);
dispatcher.Dispatch(new UpdateUndoRedoStateAction());
participantsState = new ParticipantsState();
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value, true,
ParticipantsState: participantsState, GameState: gameModel?.ConvertToState()));
dispatcher.Dispatch(new ResetSetupStateAction());
}
// reset settings and all relevant states for the next game
dispatcher.Dispatch(new ParticipantsStateChangedAction(participantsState));
//dispatcher.Dispatch(new ResetSetupStateAction()); // TODO: not reduced
}
[EffectMethod]
public Task HandleToggleAllPinsAction(ToggleAllPinsAction stopAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value,
false, null, null));
return Task.CompletedTask;
}
[EffectMethod]
public Task HandleTogglePinValueAction(TogglePinValueAction stopAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value,
false, null, null));
return Task.CompletedTask;
}
[EffectMethod]
public Task HandleUpdatePinStateByNumberAction(UpdatePinStateByNumberAction stopAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value,
false, null, null));
return Task.CompletedTask;
}
[EffectMethod]
public Task HandleToggleBellAction(ToggleBellAction stopAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value,
false, null, null));
return Task.CompletedTask;
}
//ThrowBeforeUpdateAction
//ThrowUpdateAction
//ThrowAfterUpdate
[EffectMethod]
public async Task HandleEnsureBeforeThrowStatusAction(EnsureBeforeThrowStatusAction throwAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value, true,
throwAction.ParticipantsState, throwAction.GameState));
}
private IGameService? _gameService = null;
private IGameService? GameService
{
get
{
if (_gameService == null)
{
_gameService = gameServiceFactory.GetService(dataService.StartParams.KnownGameType);
}
return _gameService;
}
}
[EffectMethod]
public async Task HandleThrowAction(ThrowAction throwAction, IDispatcher dispatcher)
{
var beforeStates = dataService.GetCurrentData();
// change ThrowStatus and save - here we need to save the old state, before it is changed by ThrowUpdateAction, so that undo/redo works correctly
dispatcher.Dispatch(new EnsureBeforeThrowStatusAction(throwAction.ParticipantsState, beforeStates.Status.GameState));
// 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 = beforeStates.Status.ThrowPanelState.CalculateNextPanelState();
//dispatcher.Dispatch(new ThrowUpdateAction(nextState));
var generalGameProgress = new GameProgress(
BeforeThrowState: beforeStates.Status?.ThrowPanelState,
BeforeParticipantsState: throwAction.ParticipantsState,
AfterThrowState: throwPanelState.Value,
AfterParticipantsState: throwAction.ParticipantsState,
StartParams: dataService.StartParams,
GameModel: beforeStates.Status.GameState.ConvertToModel(),
NextThrowState: nextState);
dispatcher.Dispatch(generalGameProgress); // currently unused
//var participants = throwAction.ParticipantsState;
//var gameSpecificProgress = generalGameProgress;
var gameSpecificProgress = GameService.HandleThrow(generalGameProgress, dispatcher);
//foreach (var action in actions)
//{
// if (action is SelectedNextPlayerIdsAction)
// {
// participants = ((SelectedNextPlayerIdsAction)action).ParticipantsState;
// }
// dispatcher.Dispatch(action);
//}
var gameModelState = gameSpecificProgress.GameModel.ConvertToState();
dispatcher.Dispatch(new GameModelChangedAction(gameModelState));
await ShowBoardForSeconds(dispatcher);
// save the new state after the throw
dispatcher.Dispatch(new TriggerNewThrowPanelStateChangedAction(true,
gameSpecificProgress.AfterParticipantsState, gameModelState,
gameSpecificProgress.NextThrowState)); // this will trigger ThrowPanelStateChangedAction with the new state (not current _throwPanelState.Value)
}
[EffectMethod]
public async Task HandleGameButtonAction(GameButtonAction action, IDispatcher dispatcher)
{
var beforeStates = dataService.GetCurrentData();
var beforeProgress = new GameProgress(
BeforeThrowState: beforeStates.Status?.ThrowPanelState,
BeforeParticipantsState: beforeStates.Status.ParticipantsState,
AfterThrowState: throwPanelState.Value,
AfterParticipantsState: beforeStates.Status.ParticipantsState,
StartParams: dataService.StartParams,
GameModel: beforeStates.Status.GameState.ConvertToModel(),
NextThrowState: null);
var afterProgress = GameService.HandleGameButton(action.CallbackName, beforeProgress, dispatcher);
dispatcher.Dispatch(new TriggerNewThrowPanelStateChangedAction(true,
afterProgress.AfterParticipantsState, afterProgress.GameModel.ConvertToState(),
afterProgress.NextThrowState)); // this will trigger ThrowPanelStateChangedAction with the new state (not current _throwPanelState.Value)
}
private async Task ShowBoardForSeconds(IDispatcher dispatcher)
{
dispatcher.Dispatch(new StartTimerAction(5));
await eventAggregator.PublishAsync(new GameViewChangedMessage(GameView.Board) { Scope = EventScope.Circuit });
}
[EffectMethod]
public Task HandleTriggerNewThrowPanelStateChangedAction(TriggerNewThrowPanelStateChangedAction action,
IDispatcher dispatcher)
{
if (action.NextModifiedThrowPanelState != null)
{
// Standardverhalten der Bahn wurde überschrieben, z.B. Bild gestellt oder sonstiges aus der game-spezifischen Logik
dispatcher.Dispatch(new OverwriteThrowPanelStateAction(action.NextModifiedThrowPanelState));
dispatcher.Dispatch(new ThrowPanelStateChangedAction(action.NextModifiedThrowPanelState, action.SaveToDb,
action.ParticipantsState, action.GameState));
}
else
{
dispatcher.Dispatch(new ThrowPanelStateChangedAction(throwPanelState.Value, action.SaveToDb,
action.ParticipantsState, action.GameState));
}
return Task.CompletedTask;
}
[EffectMethod]
public async Task HandleConnectToHubAction(ConnectToHubAction action, IDispatcher dispatcher)
{
try
{
await sharedHubService.ConnectToHub(dispatcher);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to connect to hub");
//dispatcher.Dispatch(new HubConnectionFailedAction(ex.Message));
}
}
[EffectMethod]
public async Task HandelThrowPanelStateChangedAction(ThrowPanelStateChangedAction action,
IDispatcher dispatcher)
{
dispatcher.Dispatch(new SaveThrowPanelStateAction(action.State));
dispatcher.Dispatch(new BroadcastThrowPanelStateAction(action.State));
if (action.SaveToDb)
{
var username = await GetUsername();
dataService.UpdateData(new GameStatus
{
ThrowPanelState = action.State,
ParticipantsState = action.ParticipantsState,
GameState = action.GameState
}, username);
// here we need to invalidate _dataFilePath and _sessionStorage, because with a new throw, state data must be provided by dataservice
DeleteAllSessionsFile(dispatcher);
dispatcher.Dispatch(new UpdateUndoRedoStateAction());
}
}
private static void DeleteAllSessionsFile(IDispatcher dispatcher)
{
dispatcher.Dispatch(new DeleteThrowPanelSessionAction());
dispatcher.Dispatch(new DeleteParticipantsSessionAction());
}
[EffectMethod]
public async Task HandleDeleteThrowPanelSessionAction(DeleteThrowPanelSessionAction allStatesFromSessionAction, IDispatcher dispatcher)
{
await sessionStorage.SetThrowPanelStateAsync(null);
if (File.Exists(_dataFilePath))
{
await Task.Delay(100);
try
{
File.Delete(_dataFilePath);
}
catch (Exception)
{ }
}
}
[EffectMethod]
public async Task HandelLoadAllStatesFromSessionAction(LoadAllStatesFromSessionAction allStatesFromSessionAction, IDispatcher dispatcher)
{
dispatcher.Dispatch(new LoadThrowPanelStatesFromSessionAction());
dispatcher.Dispatch(new LoadParticipantsStatesFromSessionAction());
dispatcher.Dispatch(new LoadSetupStatesFromSessionAction());
dispatcher.Dispatch(new LoadGameStatesFromDataServiceAction());
}
[EffectMethod]
public async Task HandelLoadThrowPanelStatesFromSessionAction(LoadThrowPanelStatesFromSessionAction allStatesFromSessionAction, IDispatcher dispatcher)
{
var state = await sessionStorage.GetThrowPanelStateAsync();
if (state == null)
{
try
{
if (File.Exists(_dataFilePath))
{
var json = await File.ReadAllTextAsync(_dataFilePath);
state = JsonSerializer.Deserialize<ThrowPanelState>(json);
}
}
catch (Exception e)
{
// TODO error handling
}
}
if (state == null)
{
var data = dataService.GetCurrentData();
if (data.Status != null)
{
state = data.Status.ThrowPanelState;
}
dispatcher.Dispatch(new UpdateUndoRedoStateAction());
}
if (state != null)
{
dispatcher.Dispatch(new ThrowPanelStateLoadedAction(state));
}
}
[EffectMethod]
public async Task HandelSaveThrowPanelStateAction(SaveThrowPanelStateAction action, IDispatcher dispatcher)
{
await sessionStorage.SetThrowPanelStateAsync(action.State);
try
{
var json = JsonSerializer.Serialize(action.State, new JsonSerializerOptions
{
WriteIndented = true
});
await File.WriteAllTextAsync(_dataFilePath, json);
}
catch (Exception ex)
{
// TODO error handling
}
}
[EffectMethod]
public async Task HandelBroadcastThrowPanelStateAction(BroadcastThrowPanelStateAction action, IDispatcher dispatcher)
{
await sharedHubService.BroadcastThrowPanelState(action.State, dispatcher);
}
private async Task<string> GetUsername()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
var isAuthenticated = user.Identity?.IsAuthenticated ?? false;
return isAuthenticated ? user.Identity?.Name : "Gast";
}
}
}