@page "/application" @page "/application.html" @using System.Net @using System.Net.Http.Json @using System.Security.Claims @using ChessCubing.App.Models.Users @implements IDisposable @inject BrowserBridge Browser @inject MatchStore Store @inject NavigationManager Navigation @inject AuthenticationStateProvider AuthenticationStateProvider @inject HttpClient Http ChessCubing Arena | Application
Icone ChessCubing

Application officielle de match

ChessCubing Arena

Nouvelle rencontre

Configuration

Les reglages ci-dessous preparent les pages chrono et cube.

@if (Form.CompetitionMode) { }
Mode officiel
Cadence du match
Temps personnalises
@if (!UsesMoveLimit) { } @if (UsesMoveLimit) { }
@if (Form.CompetitionMode) { }
@CurrentMode.Label @CurrentPreset.Description @TimingText @TimeImpact @QuotaText
Consulter le reglement
@code { private SetupFormModel Form { get; set; } = new(); private bool _ready; private string? ConnectedPlayerName; private MatchState? CurrentMatch => Store.Current; private string SetupBodyClass => UsesMoveLimit ? string.Empty : "time-setup-mode"; private bool CanUseConnectedPlayerName => !string.IsNullOrWhiteSpace(ConnectedPlayerName); private bool UsesMoveLimit => MatchEngine.UsesMoveLimit(Form.Mode); private MatchPresetInfo CurrentPreset => MatchEngine.Presets.TryGetValue(Form.Preset, out var preset) ? preset : MatchEngine.Presets[MatchEngine.PresetFast]; private MatchModeInfo CurrentMode => MatchEngine.Modes.TryGetValue(Form.Mode, out var mode) ? mode : MatchEngine.Modes[MatchEngine.ModeTwice]; private string TimingText => UsesMoveLimit ? $"Temps configures : Block {MatchEngine.FormatClock(Form.BlockMinutes * 60_000L)}, coup {MatchEngine.FormatClock(Form.MoveSeconds * 1_000L)}." : $"Temps configures : Block {MatchEngine.FormatClock(Form.BlockMinutes * 60_000L)}, temps de chaque joueur {MatchEngine.FormatClock(Form.TimeInitialMinutes * 60_000L)}."; private string TimeImpact => Form.Mode == MatchEngine.ModeTime ? $"Chronos cumules de {MatchEngine.FormatClock(Form.TimeInitialMinutes * 60_000L)} par joueur, ajustes apres chaque phase cube avec plafond de 120 s pris en compte. Aucun temps par coup en mode Time." : "Le gagnant du cube commence le Block suivant, avec double coup V2 possible."; private string QuotaText => UsesMoveLimit ? $"Quota actif : {CurrentPreset.Quota} coups par joueur." : $"Quota actif : {CurrentPreset.Quota} coups par joueur et par Block."; protected override async Task OnInitializedAsync() { AuthenticationStateProvider.AuthenticationStateChanged += HandleAuthenticationStateChanged; await LoadConnectedPlayerAsync(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return; } await Store.EnsureLoadedAsync(); _ready = true; StateHasChanged(); } private void HandleAuthenticationStateChanged(Task authenticationStateTask) => _ = InvokeAsync(LoadConnectedPlayerAsync); private async Task LoadConnectedPlayerAsync() { string? fallbackName = null; try { var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated != true) { ConnectedPlayerName = null; await InvokeAsync(StateHasChanged); return; } fallbackName = BuildConnectedPlayerFallback(user); var response = await Http.GetAsync("api/users/me"); if (!response.IsSuccessStatusCode) { ConnectedPlayerName = response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden ? null : fallbackName; await InvokeAsync(StateHasChanged); return; } var profile = await response.Content.ReadFromJsonAsync(); ConnectedPlayerName = !string.IsNullOrWhiteSpace(profile?.DisplayName) ? profile.DisplayName : fallbackName; } catch { ConnectedPlayerName = fallbackName; } await InvokeAsync(StateHasChanged); } private async Task HandleSubmit() { await Store.EnsureLoadedAsync(); var match = MatchEngine.CreateMatch(Form.ToMatchConfig()); Store.SetCurrent(match); try { await Store.SaveAsync(); } catch { // La navigation vers la phase chrono doit rester possible meme si la persistence // du navigateur echoue ponctuellement. } Navigation.NavigateTo("/chrono.html"); } private void LoadDemo() => Form = SetupFormModel.CreateDemo(); private void FillWhiteWithConnectedPlayer() { if (!CanUseConnectedPlayerName) { return; } Form.WhiteName = ConnectedPlayerName!; } private void FillBlackWithConnectedPlayer() { if (!CanUseConnectedPlayerName) { return; } Form.BlackName = ConnectedPlayerName!; } private void SetMode(string mode) => Form.Mode = mode; private void SetPreset(string preset) => Form.Preset = preset; private void ResumeMatch() { if (CurrentMatch is null) { return; } Navigation.NavigateTo(MatchEngine.RouteForMatch(CurrentMatch)); } private async Task ClearMatchAsync() { await Store.ClearAsync(); StateHasChanged(); } private Task ForceRefreshAsync() => Browser.ForceRefreshAsync("/application.html").AsTask(); private static string ResumeMatchLabel(MatchState match) => string.IsNullOrWhiteSpace(match.Config.MatchLabel) ? "Rencontre ChessCubing" : match.Config.MatchLabel; private static string CurrentMatchMode(MatchState match) => MatchEngine.Modes.TryGetValue(match.Config.Mode, out var mode) ? mode.Label : "ChessCubing Twice"; private static string ResumePhaseLabel(MatchState match) { if (!string.IsNullOrEmpty(match.Result)) { return MatchEngine.ResultText(match); } return match.Phase == MatchEngine.PhaseCube ? "Page cube prete" : "Page chrono prete"; } private string BuildPrefillTitle(string color) => $"Utiliser mon nom cote {color}"; private static string? BuildConnectedPlayerFallback(ClaimsPrincipal user) => FirstNonEmpty( user.FindFirst("name")?.Value, user.FindFirst(ClaimTypes.Name)?.Value, user.FindFirst("preferred_username")?.Value, user.FindFirst(ClaimTypes.Email)?.Value); private static string? FirstNonEmpty(params string?[] candidates) => candidates.FirstOrDefault(candidate => !string.IsNullOrWhiteSpace(candidate)); public void Dispose() => AuthenticationStateProvider.AuthenticationStateChanged -= HandleAuthenticationStateChanged; }