added Board-Timer Feature

This commit is contained in:
beo3000 2025-11-19 21:23:00 +01:00
parent dee21d8c08
commit 6090eb849f
18 changed files with 230 additions and 21 deletions

View File

@ -1,8 +1,15 @@
@using KoogleApp.Model
@using KoogleApp.Store.Game.ThrowTimer
@inherits FluxorComponent
<ThrowTimer/>
<MudGrid>
<MudItem xs="12">
<MudPaper Class="d-flex align-center justify-center mud-width-full py-0">
Tafel
</MudPaper>
</MudItem>

View File

@ -2,7 +2,7 @@
<MudText Typo="Typo.h6">Spieler</MudText>
</MudDrawerHeader>
<PlayersPanel/>
<PlayersPanel ShowMenu="false"/>
@code {

View File

@ -21,14 +21,18 @@
@inject IDialogService DialogService
@* @inject SharedDataService _dataService *@
<PanelToolbar>
<MudTooltip Text="Spieler anlegen"
Color="Color.Primary" Placement="Placement.Bottom" Arrow="true">
<MudIconButton Icon="@Icons.Material.Filled.Add"
Variant="Variant.Filled" Color="Color.Primary"
Class="mr-5" OnClick="CreatePlayerClick"/>
</MudTooltip>
</PanelToolbar>
@if (ShowMenu)
{
<PanelToolbar>
<MudTooltip Text="Spieler anlegen"
Color="Color.Primary" Placement="Placement.Bottom" Arrow="true">
<MudIconButton Icon="@Icons.Material.Filled.Add"
Variant="Variant.Filled" Color="Color.Primary"
Class="mr-5" OnClick="CreatePlayerClick"/>
</MudTooltip>
</PanelToolbar>
}
@if (PlayersState.Value != null)
{
@ -50,6 +54,8 @@
@code {
[Parameter] public bool ShowMenu { get; set; } = false;
private PlayerState _selectedValue;
public PlayerState? SelectedValue

View File

@ -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)

View File

@ -56,7 +56,6 @@
};
var dialog = await DialogService.ShowAsync<StartGameDialog>("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);

View File

@ -0,0 +1,21 @@
@using KoogleApp.Store.Game.ThrowTimer
@inherits FluxorComponent
@inject IState<TimerState> TimerState
@inject IDispatcher Dispatcher
@if (TimerState.Value.IsRunning)
{
<MudButton OnClick="AbortTimer">
@TimerState.Value.RemainingSeconds</MudButton>
}
@code {
private void AbortTimer(MouseEventArgs obj)
{
Dispatcher.Dispatch(new StopTimerAction());
}
}

View File

@ -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> SetupState
@inject IState<DayState> DayState
<MudDialog Style="height: 800px; width:600px">
<TitleContent>
@ -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)));
}
}
}

View File

@ -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> ThrowPanelState
@inject IState<DayState> DayState
@inject IDispatcher Dispatcher
@inject IDialogService DialogService
@ -48,7 +50,7 @@
</PanelToolbar>
break;
case GameView.Board:
break;
case GameView.Players:
@ -79,7 +81,7 @@
<BoardPanel/>
break;
case GameView.Players:
<PlayersPanel/>
<PlayersPanel ShowMenu="true"/>
break;
case GameView.Player:
<PlayerPanel/>
@ -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);
}

View File

@ -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;
}
}

View File

@ -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)
{}
}
}

View File

@ -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<ThrowPanelEffects> logger,
HubConnectionService sharedHubService,
IState<ThrowPanelState> 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)

View File

@ -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;

View File

@ -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
)
{ }
}

View File

@ -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 { }
}

View File

@ -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<TimerEffects> _logger;
private readonly IMyEventAggregator _eventAggregator;
private CancellationTokenSource? _timerCancellation;
public TimerEffects(ILogger<TimerEffects> 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();
}
}
}

View File

@ -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
};
}
}
}

View File

@ -0,0 +1,11 @@
using Fluxor;
namespace KoogleApp.Store.Game.ThrowTimer
{
[FeatureState]
public record TimerState(int RemainingSeconds, bool IsRunning)
{
public TimerState():this(0, false)
{}
}
}

View File

@ -14,5 +14,6 @@
"ThrowCounterPerRound": 1,
"ThrowMode": 0,
"ThrowPanelStateStatus": 4,
"ThrowCounter": 0
"ThrowCounter": 0,
"DayId": 0
}