diff --git a/KoogleApp/Components/Controls/BoardPanel.razor b/KoogleApp/Components/Controls/BoardPanel.razor index 380d804..d049e0b 100644 --- a/KoogleApp/Components/Controls/BoardPanel.razor +++ b/KoogleApp/Components/Controls/BoardPanel.razor @@ -1,8 +1,15 @@ @using KoogleApp.Model +@using KoogleApp.Store.Game.ThrowTimer + +@inherits FluxorComponent + + + + Tafel diff --git a/KoogleApp/Components/Controls/DrawerPanel.razor b/KoogleApp/Components/Controls/DrawerPanel.razor index 04b4148..6a81485 100644 --- a/KoogleApp/Components/Controls/DrawerPanel.razor +++ b/KoogleApp/Components/Controls/DrawerPanel.razor @@ -2,7 +2,7 @@ Spieler - + @code { diff --git a/KoogleApp/Components/Controls/PlayersPanel.razor b/KoogleApp/Components/Controls/PlayersPanel.razor index 9e12f46..7ae0d05 100644 --- a/KoogleApp/Components/Controls/PlayersPanel.razor +++ b/KoogleApp/Components/Controls/PlayersPanel.razor @@ -21,14 +21,18 @@ @inject IDialogService DialogService @* @inject SharedDataService _dataService *@ - - - - - + +@if (ShowMenu) +{ + + + + + +} @if (PlayersState.Value != null) { @@ -50,6 +54,8 @@ @code { + [Parameter] public bool ShowMenu { get; set; } = false; + private PlayerState _selectedValue; public PlayerState? SelectedValue diff --git a/KoogleApp/Components/Controls/ThrowPanel.razor b/KoogleApp/Components/Controls/ThrowPanel.razor index 73a9677..b281801 100644 --- a/KoogleApp/Components/Controls/ThrowPanel.razor +++ b/KoogleApp/Components/Controls/ThrowPanel.razor @@ -1,6 +1,7 @@ @using KoogleApp.Model @using KoogleApp.Store.Game @using KoogleApp.Store.Game.ThrowPanel +@using KoogleApp.Store.Game.ThrowTimer @using KoogleApp.Store.Game.UndoRedo @using Microsoft.AspNetCore.Mvc.TagHelpers @@ -129,6 +130,7 @@ { _correcting = false; Dispatcher.Dispatch(new ThrowAction(LeftSink: leftSink, RightSink: rightSink)); + } private async Task UnlockClick(MouseEventArgs obj) diff --git a/KoogleApp/Components/Controls/ThrowPanelMenu.razor b/KoogleApp/Components/Controls/ThrowPanelMenu.razor index ba6d43a..d407746 100644 --- a/KoogleApp/Components/Controls/ThrowPanelMenu.razor +++ b/KoogleApp/Components/Controls/ThrowPanelMenu.razor @@ -56,7 +56,6 @@ }; - var dialog = await DialogService.ShowAsync("Spiel starten", parameters, options); var result = await dialog.Result; var startParams = result.Data as StartParams; @@ -69,7 +68,7 @@ else { var result = await DialogService.ShowMessageBox("Spiel beenden?", "Spiel wirklich abbrechen?", "JA", "NEIN", cancelText: "Abbrechen"); - if (result.Value) + if (result.HasValue && result.Value) { var action = new StartStopAction(ThrowPanelState.Value, null); Dispatcher.Dispatch(action); diff --git a/KoogleApp/Components/Controls/ThrowTimer.razor b/KoogleApp/Components/Controls/ThrowTimer.razor new file mode 100644 index 0000000..cc183b6 --- /dev/null +++ b/KoogleApp/Components/Controls/ThrowTimer.razor @@ -0,0 +1,21 @@ +@using KoogleApp.Store.Game.ThrowTimer + +@inherits FluxorComponent + +@inject IState TimerState +@inject IDispatcher Dispatcher + +@if (TimerState.Value.IsRunning) +{ + + @TimerState.Value.RemainingSeconds +} + +@code { + + private void AbortTimer(MouseEventArgs obj) + { + Dispatcher.Dispatch(new StopTimerAction()); + } + +} diff --git a/KoogleApp/Components/Dialogs/StartGameDialog.razor b/KoogleApp/Components/Dialogs/StartGameDialog.razor index 1923f28..0dba39e 100644 --- a/KoogleApp/Components/Dialogs/StartGameDialog.razor +++ b/KoogleApp/Components/Dialogs/StartGameDialog.razor @@ -1,11 +1,13 @@ @using KoogleApp.Games @using KoogleApp.Model @using KoogleApp.Services +@using KoogleApp.Store.DayFeature @using KoogleApp.Store.Game.Setup @using KoogleApp.Store.Game.ThrowPanel @inject GameTypeService GameTypeService @inject IState SetupState +@inject IState DayState @@ -74,7 +76,7 @@ // if (!string.IsNullOrEmpty(Description)) { // Snackbar.Add("Favorite added", Severity.Success); - MudDialog.Close(DialogResult.Ok(new StartParams(SetupState.Value.ThrowMode, SetupState.Value.ThrowsPerRound))); + MudDialog.Close(DialogResult.Ok(new StartParams(DayState.Value.Id, SetupState.Value.ThrowMode, SetupState.Value.ThrowsPerRound))); } } } diff --git a/KoogleApp/Components/Pages/Game.razor b/KoogleApp/Components/Pages/Game.razor index 2d005d5..3fdfcfc 100644 --- a/KoogleApp/Components/Pages/Game.razor +++ b/KoogleApp/Components/Pages/Game.razor @@ -11,6 +11,7 @@ @using KoogleApp.Store.DayFeature @using KoogleApp.Store.Game @using KoogleApp.Store.Game.ThrowPanel +@using KoogleApp.Store.Game.ThrowTimer @using KoogleApp.Store.Game.UndoRedo @inherits FluxorComponent @@ -25,6 +26,7 @@ @inject IMyEventAggregator EventAggregator @inject IState ThrowPanelState @inject IState DayState + @inject IDispatcher Dispatcher @inject IDialogService DialogService @@ -48,7 +50,7 @@ break; case GameView.Board: - + break; case GameView.Players: @@ -79,7 +81,7 @@ break; case GameView.Players: - + break; case GameView.Player: @@ -227,6 +229,11 @@ public async Task HandleAsync(GameViewChangedMessage message) { + if (message is { GameView: GameView.Throw, AutoReturnFromTimer: false }) + { + Dispatcher.Dispatch(new StopTimerAction()); + } + _gameView = message.GameView; await InvokeAsync(StateHasChanged); } diff --git a/KoogleApp/Model/EventMessages/GameViewChangedMessage.cs b/KoogleApp/Model/EventMessages/GameViewChangedMessage.cs index c7b0ad7..5911f22 100644 --- a/KoogleApp/Model/EventMessages/GameViewChangedMessage.cs +++ b/KoogleApp/Model/EventMessages/GameViewChangedMessage.cs @@ -10,8 +10,10 @@ namespace KoogleApp.Model.EventMessages Player } - public class GameViewChangedMessage(GameView gameView) : ScopedEventBase + public class GameViewChangedMessage(GameView gameView, bool autoReturnFromTimer=false) : ScopedEventBase { public GameView GameView { get; private set; } = gameView; + + public bool AutoReturnFromTimer { get; private set; } = autoReturnFromTimer; } } diff --git a/KoogleApp/Model/StartParams.cs b/KoogleApp/Model/StartParams.cs index 5de2d33..1ccfa3f 100644 --- a/KoogleApp/Model/StartParams.cs +++ b/KoogleApp/Model/StartParams.cs @@ -2,9 +2,9 @@ namespace KoogleApp.Model { - public record StartParams(ThrowMode ThrowMode, int ThrowsPerRound) + public record StartParams(int DayId, ThrowMode ThrowMode, int ThrowsPerRound) { - public StartParams() : this(ThrowMode: ThrowMode.Reposition, ThrowsPerRound:3) + public StartParams() : this(DayId:0, ThrowMode: ThrowMode.Reposition, ThrowsPerRound:3) {} } } diff --git a/KoogleApp/Store/Game/ThrowPanel/Effects.cs b/KoogleApp/Store/Game/ThrowPanel/Effects.cs index 250831c..d7d71b6 100644 --- a/KoogleApp/Store/Game/ThrowPanel/Effects.cs +++ b/KoogleApp/Store/Game/ThrowPanel/Effects.cs @@ -1,7 +1,10 @@ using Fluxor; using KoogleApp.Hub; using KoogleApp.Model; +using KoogleApp.Model.EventMessages; +using KoogleApp.Model.Framework; using KoogleApp.Services; +using KoogleApp.Store.Game.ThrowTimer; using KoogleApp.Store.Game.UndoRedo; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Win32.SafeHandles; @@ -20,8 +23,10 @@ namespace KoogleApp.Store.Game.ThrowPanel private readonly AuthenticationStateProvider _authenticationStateProvider; private readonly IGameStatusDataService _dataService; private readonly Lock _lock = new Lock(); + private readonly IMyEventAggregator _eventAggregator; public ThrowPanelEffects( + IMyEventAggregator eventAggregator, ILogger logger, HubConnectionService sharedHubService, IState state, @@ -35,6 +40,7 @@ namespace KoogleApp.Store.Game.ThrowPanel _sessionStorage = sessionStorage; _dataService = dataService; _authenticationStateProvider = authenticationStateProvider; + _eventAggregator = eventAggregator; } public void Dispose() @@ -101,6 +107,9 @@ namespace KoogleApp.Store.Game.ThrowPanel [EffectMethod] public async Task HandleThrowAction(ThrowAction throwAction, IDispatcher dispatcher) { + await ShowBoardForSeconds(dispatcher); + + // 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()); @@ -111,6 +120,12 @@ namespace KoogleApp.Store.Game.ThrowPanel dispatcher.Dispatch(new TriggerNewThrowPanelStateChangedAction(true)); // this will trigger ThrowPanelStateChangedAction with the new state (not current _state.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) diff --git a/KoogleApp/Store/Game/ThrowPanel/Reducers.cs b/KoogleApp/Store/Game/ThrowPanel/Reducers.cs index 8fe662d..03d8aaf 100644 --- a/KoogleApp/Store/Game/ThrowPanel/Reducers.cs +++ b/KoogleApp/Store/Game/ThrowPanel/Reducers.cs @@ -93,7 +93,8 @@ namespace KoogleApp.Store.Game.ThrowPanel IsStated = !state.IsStated, ThrowMode = startStopAction.StartParams.ThrowMode, ThrowsPerRound = startStopAction.StartParams.ThrowsPerRound, - ThrowPanelStateStatus = ThrowPanelStateStatus.GameStart + ThrowPanelStateStatus = ThrowPanelStateStatus.GameStart, + DayId = startStopAction.StartParams.DayId }; return state; diff --git a/KoogleApp/Store/Game/ThrowPanel/State.cs b/KoogleApp/Store/Game/ThrowPanel/State.cs index f6aa3e5..3672622 100644 --- a/KoogleApp/Store/Game/ThrowPanel/State.cs +++ b/KoogleApp/Store/Game/ThrowPanel/State.cs @@ -32,12 +32,14 @@ namespace KoogleApp.Store.Game.ThrowPanel [FeatureState] public record ThrowPanelState(bool IsStated, bool BellValue, PinStatus Pin1State, PinStatus Pin2State, PinStatus Pin3State, PinStatus Pin4State, PinStatus Pin5State, PinStatus Pin6State, PinStatus Pin7State, PinStatus Pin8State, PinStatus Pin9State, - int ThrowsPerRound, int ThrowCounterPerRound, ThrowMode ThrowMode, ThrowPanelStateStatus ThrowPanelStateStatus, int ThrowCounter) + int ThrowsPerRound, int ThrowCounterPerRound, ThrowMode ThrowMode, ThrowPanelStateStatus ThrowPanelStateStatus, int ThrowCounter, + int DayId) { // Required for creating initial state public ThrowPanelState() : this(BellValue:false, IsStated:false, Pin1State : PinStatus.Standing, Pin2State: PinStatus.Standing, Pin3State: PinStatus.Standing, Pin4State: PinStatus.Standing, Pin5State: PinStatus.Standing, Pin6State: PinStatus.Standing, Pin7State: PinStatus.Standing, Pin8State: PinStatus.Standing, Pin9State: PinStatus.Standing, - ThrowsPerRound : 3, ThrowCounterPerRound : 1, ThrowMode : ThrowMode.Reposition, ThrowPanelStateStatus: ThrowPanelStateStatus.Undefined, ThrowCounter:0 + ThrowsPerRound : 3, ThrowCounterPerRound : 1, ThrowMode : ThrowMode.Reposition, ThrowPanelStateStatus: ThrowPanelStateStatus.Undefined, ThrowCounter:0, + DayId:0 ) { } } diff --git a/KoogleApp/Store/Game/ThrowTimer/Actions.cs b/KoogleApp/Store/Game/ThrowTimer/Actions.cs new file mode 100644 index 0000000..dc4ca37 --- /dev/null +++ b/KoogleApp/Store/Game/ThrowTimer/Actions.cs @@ -0,0 +1,13 @@ +namespace KoogleApp.Store.Game.ThrowTimer +{ + public class StartTimerAction(int seconds) + { + public int Seconds { get; } = seconds; + } + + public class TimerCompletedAction { } + + public class StopTimerAction { } + + public class TickAction { } +} diff --git a/KoogleApp/Store/Game/ThrowTimer/Effects.cs b/KoogleApp/Store/Game/ThrowTimer/Effects.cs new file mode 100644 index 0000000..22f2316 --- /dev/null +++ b/KoogleApp/Store/Game/ThrowTimer/Effects.cs @@ -0,0 +1,70 @@ +using Fluxor; +using KoogleApp.Model.EventMessages; +using KoogleApp.Model.Framework; +using KoogleApp.Services; + +namespace KoogleApp.Store.Game.ThrowTimer +{ + public class TimerEffects + { + private readonly ILogger _logger; + private readonly IMyEventAggregator _eventAggregator; + private CancellationTokenSource? _timerCancellation; + + public TimerEffects(ILogger logger, IMyEventAggregator eventAggregator) + { + _logger = logger; + _eventAggregator = eventAggregator; + } + + [EffectMethod] + public async Task HandleStartTimer(StartTimerAction action, IDispatcher dispatcher) + { + _logger.LogInformation("Timer gestartet für {Seconds} Sekunden", action.Seconds); + + // Vorherigen Timer stoppen falls vorhanden + _timerCancellation?.Cancel(); + _timerCancellation = new CancellationTokenSource(); + + try + { + for (int i = 0; i < action.Seconds; i++) + { + await Task.Delay(1000, _timerCancellation.Token); + dispatcher.Dispatch(new TickAction()); + } + + // Timer abgelaufen + dispatcher.Dispatch(new TimerCompletedAction()); + _logger.LogInformation("Timer abgelaufen"); + } + catch (TaskCanceledException) + { + _logger.LogInformation("Timer wurde abgebrochen"); + } + } + + [EffectMethod] + public async Task HandleStopTimer(StopTimerAction action, IDispatcher dispatcher) + { + _logger.LogInformation("Timer wird gestoppt"); + _timerCancellation?.Cancel(); + _timerCancellation = null; + + await ReturnToThrowView(); + } + + private async Task ReturnToThrowView() + { + await _eventAggregator.PublishAsync(new GameViewChangedMessage(GameView.Throw, true) { Scope = EventScope.Circuit }); + } + + [EffectMethod] + public async Task HandleTimerCompleted(TimerCompletedAction action, IDispatcher dispatcher) + { + _logger.LogInformation("Timer-Completed Action wurde verarbeitet"); + + await ReturnToThrowView(); + } + } +} diff --git a/KoogleApp/Store/Game/ThrowTimer/Reducers.cs b/KoogleApp/Store/Game/ThrowTimer/Reducers.cs new file mode 100644 index 0000000..679b36f --- /dev/null +++ b/KoogleApp/Store/Game/ThrowTimer/Reducers.cs @@ -0,0 +1,50 @@ +using Fluxor; + +namespace KoogleApp.Store.Game.ThrowTimer +{ + public static class Reducers + { + [ReducerMethod] + public static TimerState OnStartTimer(TimerState state, StartTimerAction action) + { + return state with + { + RemainingSeconds = action.Seconds, + IsRunning = true + }; + } + + [ReducerMethod] + public static TimerState OnTick(TimerState state, TickAction action) + { + if (!state.IsRunning || state.RemainingSeconds <= 0) + return state; + + return state with + { + RemainingSeconds = state.RemainingSeconds - 1, + IsRunning = state.RemainingSeconds - 1 > 0 + }; + } + + [ReducerMethod] + public static TimerState OnTimerCompleted(TimerState state, TimerCompletedAction action) + { + return state with + { + IsRunning = false, + RemainingSeconds = 0 + }; + } + + [ReducerMethod] + public static TimerState OnStopTimer(TimerState state, StopTimerAction action) + { + return state with + { + IsRunning = false, + RemainingSeconds = 0 + }; + } + } +} diff --git a/KoogleApp/Store/Game/ThrowTimer/State.cs b/KoogleApp/Store/Game/ThrowTimer/State.cs new file mode 100644 index 0000000..9dc4931 --- /dev/null +++ b/KoogleApp/Store/Game/ThrowTimer/State.cs @@ -0,0 +1,11 @@ +using Fluxor; + +namespace KoogleApp.Store.Game.ThrowTimer +{ + [FeatureState] + public record TimerState(int RemainingSeconds, bool IsRunning) + { + public TimerState():this(0, false) + {} + } +} diff --git a/KoogleApp/ThrowPanelState.json b/KoogleApp/ThrowPanelState.json index d03461c..6e3b3f4 100644 --- a/KoogleApp/ThrowPanelState.json +++ b/KoogleApp/ThrowPanelState.json @@ -14,5 +14,6 @@ "ThrowCounterPerRound": 1, "ThrowMode": 0, "ThrowPanelStateStatus": 4, - "ThrowCounter": 0 + "ThrowCounter": 0, + "DayId": 0 } \ No newline at end of file