354 lines
16 KiB
Plaintext
354 lines
16 KiB
Plaintext
@page "/application"
|
|
@page "/application.html"
|
|
@inject BrowserBridge Browser
|
|
@inject MatchStore Store
|
|
@inject NavigationManager Navigation
|
|
|
|
<PageTitle>ChessCubing Arena | Application</PageTitle>
|
|
<PageBody Page="setup" BodyClass="@SetupBodyClass" />
|
|
|
|
<div class="ambient ambient-left"></div>
|
|
<div class="ambient ambient-right"></div>
|
|
|
|
<div class="setup-shell">
|
|
<header class="hero hero-setup">
|
|
<div class="hero-copy">
|
|
<a class="logo-lockup" href="index.html" aria-label="Accueil ChessCubing">
|
|
<img class="hero-logo-icon" src="logo.png" alt="Icone ChessCubing" />
|
|
<img class="hero-logo" src="transparent.png" alt="Logo ChessCubing" />
|
|
</a>
|
|
<p class="eyebrow">Application officielle de match</p>
|
|
<h1>ChessCubing Arena</h1>
|
|
<div class="hero-actions">
|
|
<a class="button ghost" href="index.html">Accueil du site</a>
|
|
<a class="button secondary" href="reglement.html">Consulter le reglement</a>
|
|
</div>
|
|
<UserAccessBar />
|
|
</div>
|
|
|
|
<aside class="hero-preview">
|
|
<div class="preview-card">
|
|
<p class="micro-label">Flux de match</p>
|
|
<ol class="phase-list">
|
|
<li>Configurer la rencontre</li>
|
|
<li>Passer a la page chrono</li>
|
|
<li>Basculer automatiquement sur la page cube</li>
|
|
<li>Revenir sur la page chrono pour le Block suivant</li>
|
|
</ol>
|
|
</div>
|
|
</aside>
|
|
</header>
|
|
|
|
<main class="setup-grid">
|
|
<section class="panel">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Nouvelle rencontre</p>
|
|
<h2>Configuration</h2>
|
|
</div>
|
|
<p class="section-copy">
|
|
Les reglages ci-dessous preparent les pages chrono et cube.
|
|
</p>
|
|
</div>
|
|
|
|
<form class="setup-form" @onsubmit="HandleSubmit">
|
|
<label class="option-card competition-option span-2 @(Form.CompetitionMode ? "is-selected" : string.Empty)">
|
|
<input @bind="Form.CompetitionMode" id="competitionMode" name="competitionMode" type="checkbox" />
|
|
<strong>Mode competition</strong>
|
|
<span>
|
|
Affiche le nom de la rencontre, l'arbitre, le club ou l'evenement
|
|
et les notes d'organisation.
|
|
</span>
|
|
</label>
|
|
|
|
@if (Form.CompetitionMode)
|
|
{
|
|
<label class="field span-2" id="matchLabelField">
|
|
<span>Nom de la rencontre</span>
|
|
<input @bind="Form.MatchLabel" @bind:event="oninput" name="matchLabel" type="text" maxlength="80" placeholder="Open ChessCubing de Paris" />
|
|
</label>
|
|
}
|
|
|
|
<fieldset class="field span-2">
|
|
<legend>Mode officiel</legend>
|
|
<div class="option-grid mode-grid">
|
|
<label class="option-card @(Form.Mode == MatchEngine.ModeTwice ? "is-selected" : string.Empty)">
|
|
<input type="radio" name="mode" checked="@(Form.Mode == MatchEngine.ModeTwice)" @onchange="() => SetMode(MatchEngine.ModeTwice)" />
|
|
<strong>ChessCubing Twice</strong>
|
|
<span>
|
|
Le gagnant du cube ouvre le Block suivant et peut obtenir un
|
|
double coup V2.
|
|
</span>
|
|
</label>
|
|
<label class="option-card @(Form.Mode == MatchEngine.ModeTime ? "is-selected" : string.Empty)">
|
|
<input type="radio" name="mode" checked="@(Form.Mode == MatchEngine.ModeTime)" @onchange="() => SetMode(MatchEngine.ModeTime)" />
|
|
<strong>ChessCubing Time</strong>
|
|
<span>
|
|
Meme structure de Blocks, avec chronos cumules et alternance
|
|
bloc - / bloc +.
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset class="field span-2">
|
|
<legend>Cadence du match</legend>
|
|
<div class="option-grid preset-grid">
|
|
<label class="option-card @(Form.Preset == MatchEngine.PresetFast ? "is-selected" : string.Empty)">
|
|
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetFast)" @onchange="() => SetPreset(MatchEngine.PresetFast)" />
|
|
<strong>FAST</strong>
|
|
<span>6 coups par joueur</span>
|
|
</label>
|
|
<label class="option-card @(Form.Preset == MatchEngine.PresetFreeze ? "is-selected" : string.Empty)">
|
|
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetFreeze)" @onchange="() => SetPreset(MatchEngine.PresetFreeze)" />
|
|
<strong>FREEZE</strong>
|
|
<span>8 coups par joueur</span>
|
|
</label>
|
|
<label class="option-card @(Form.Preset == MatchEngine.PresetMasters ? "is-selected" : string.Empty)">
|
|
<input type="radio" name="preset" checked="@(Form.Preset == MatchEngine.PresetMasters)" @onchange="() => SetPreset(MatchEngine.PresetMasters)" />
|
|
<strong>MASTERS</strong>
|
|
<span>10 coups par joueur</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset class="field span-2">
|
|
<legend>Temps personnalises</legend>
|
|
<div class="timing-grid">
|
|
<label class="field">
|
|
<span>Temps du Block (minutes)</span>
|
|
<input @bind="Form.BlockMinutes" name="blockMinutes" type="number" min="1" max="180" step="1" />
|
|
</label>
|
|
|
|
@if (!UsesMoveLimit)
|
|
{
|
|
<label class="field" id="timeInitialField">
|
|
<span>Temps de chaque joueur (minutes)</span>
|
|
<input @bind="Form.TimeInitialMinutes" name="timeInitialMinutes" type="number" min="1" max="180" step="1" />
|
|
</label>
|
|
}
|
|
|
|
@if (UsesMoveLimit)
|
|
{
|
|
<label class="field" id="moveSecondsField">
|
|
<span>Temps coup (secondes)</span>
|
|
<input @bind="Form.MoveSeconds" name="moveSeconds" type="number" min="5" max="300" step="1" />
|
|
</label>
|
|
}
|
|
</div>
|
|
</fieldset>
|
|
|
|
<label class="field">
|
|
<span>Joueur blanc</span>
|
|
<input @bind="Form.WhiteName" @bind:event="oninput" name="whiteName" type="text" maxlength="40" placeholder="Blanc" />
|
|
</label>
|
|
|
|
<label class="field">
|
|
<span>Joueur noir</span>
|
|
<input @bind="Form.BlackName" @bind:event="oninput" name="blackName" type="text" maxlength="40" placeholder="Noir" />
|
|
</label>
|
|
|
|
@if (Form.CompetitionMode)
|
|
{
|
|
<label class="field" id="arbiterField">
|
|
<span>Arbitre</span>
|
|
<input @bind="Form.ArbiterName" @bind:event="oninput" name="arbiterName" type="text" maxlength="40" placeholder="Arbitre principal" />
|
|
</label>
|
|
|
|
<label class="field" id="eventField">
|
|
<span>Club / evenement</span>
|
|
<input @bind="Form.EventName" @bind:event="oninput" name="eventName" type="text" maxlength="60" placeholder="Club, tournoi, demonstration" />
|
|
</label>
|
|
|
|
<label class="field span-2" id="notesField">
|
|
<span>Notes</span>
|
|
<textarea @bind="Form.Notes" @bind:event="oninput" name="notes" rows="3" placeholder="Tirage au sort effectue, 8 cubes verifies, variante prete..."></textarea>
|
|
</label>
|
|
}
|
|
|
|
<div id="setupSummary" class="setup-summary span-2">
|
|
<strong>@CurrentMode.Label</strong>
|
|
<span>@CurrentPreset.Description</span>
|
|
<span>@TimingText</span>
|
|
<span>@TimeImpact</span>
|
|
<span>@QuotaText</span>
|
|
</div>
|
|
|
|
<div class="setup-actions span-2">
|
|
<button class="button primary" type="submit">Ouvrir la page chrono</button>
|
|
<button class="button secondary" id="loadDemoButton" type="button" @onclick="LoadDemo">Charger une demo</button>
|
|
<a class="button ghost" href="reglement.html">Consulter le reglement</a>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<aside class="panel side-panel">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Match en memoire</p>
|
|
<h2>Reprise rapide</h2>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!_ready || CurrentMatch is null)
|
|
{
|
|
<div id="resumeCard" class="resume-card empty">
|
|
<p>Aucun match en cours pour l'instant.</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div id="resumeCard" class="resume-card">
|
|
<strong>@ResumeMatchLabel(CurrentMatch)</strong>
|
|
<p>@CurrentMatchMode(CurrentMatch)</p>
|
|
<p>@($"{CurrentMatch.Config.WhiteName} vs {CurrentMatch.Config.BlackName}")</p>
|
|
<p>@ResumePhaseLabel(CurrentMatch)</p>
|
|
<div class="resume-actions">
|
|
<button class="button primary" id="resumeMatchButton" type="button" @onclick="ResumeMatch">
|
|
@(string.IsNullOrEmpty(CurrentMatch.Result) ? "Reprendre la phase" : "Voir le match")
|
|
</button>
|
|
<button class="button ghost" id="clearMatchButton" type="button" @onclick="ClearMatchAsync">Effacer le match</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="rules-stack">
|
|
<article class="rule-card">
|
|
<span class="micro-label">Page chrono</span>
|
|
<strong>Gros boutons uniquement</strong>
|
|
<p>
|
|
Chaque joueur dispose d'une grande zone tactile pour signaler la
|
|
fin de son coup, puis l'app ouvre automatiquement la phase cube
|
|
quand le Block d'echecs est termine.
|
|
</p>
|
|
</article>
|
|
<article class="rule-card">
|
|
<span class="micro-label">Page cube</span>
|
|
<strong>Une page dediee</strong>
|
|
<p>
|
|
Les deux joueurs lancent et arretent leur propre chrono cube sur
|
|
un ecran separe, toujours en face-a-face sur mobile.
|
|
</p>
|
|
</article>
|
|
<article class="rule-card">
|
|
<span class="micro-label">Sources</span>
|
|
<strong>Reglements integres</strong>
|
|
<p>
|
|
<a href="reglement.html">Page reglement du site</a>
|
|
<br />
|
|
<a href="ChessCubing_Twice_Reglement_Officiel_V2-1.pdf" target="_blank">Reglement ChessCubing Twice</a>
|
|
<br />
|
|
<a href="ChessCubing_Time_Reglement_Officiel_V1-1.pdf" target="_blank">Reglement ChessCubing Time</a>
|
|
</p>
|
|
</article>
|
|
</div>
|
|
</aside>
|
|
</main>
|
|
|
|
<div class="setup-refresh-footer">
|
|
<button class="refresh-link-button" id="refreshAppButton" type="button" @onclick="ForceRefreshAsync">
|
|
Rafraichir l'app
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private SetupFormModel Form { get; set; } = new();
|
|
private bool _ready;
|
|
|
|
private MatchState? CurrentMatch => Store.Current;
|
|
|
|
private string SetupBodyClass => UsesMoveLimit ? string.Empty : "time-setup-mode";
|
|
|
|
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 OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (!firstRender)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await Store.EnsureLoadedAsync();
|
|
_ready = true;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task HandleSubmit()
|
|
{
|
|
var match = MatchEngine.CreateMatch(Form.ToMatchConfig());
|
|
Store.SetCurrent(match);
|
|
await Store.SaveAsync();
|
|
Navigation.NavigateTo("/chrono.html");
|
|
}
|
|
|
|
private void LoadDemo()
|
|
=> Form = SetupFormModel.CreateDemo();
|
|
|
|
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";
|
|
}
|
|
}
|