diff --git a/app.js b/app.js index 2daf59f..b15cbd4 100644 --- a/app.js +++ b/app.js @@ -13,17 +13,17 @@ const PRESETS = { fast: { label: "FAST", quota: 6, - description: "6 coups par joueur et par partie.", + description: "6 coups par joueur.", }, freeze: { label: "FREEZE", quota: 8, - description: "8 coups par joueur et par partie.", + description: "8 coups par joueur.", }, masters: { label: "MASTERS", quota: 10, - description: "10 coups par joueur et par partie.", + description: "10 coups par joueur.", }, }; @@ -114,6 +114,8 @@ function initSetupPage() { const summary = document.querySelector("#setupSummary"); const loadDemoButton = document.querySelector("#loadDemoButton"); const resumeCard = document.querySelector("#resumeCard"); + const moveSecondsField = document.querySelector("#moveSecondsField"); + const moveSecondsInput = form?.querySelector('[name="moveSeconds"]'); if (!form || !summary || !loadDemoButton || !resumeCard) { return; @@ -125,17 +127,34 @@ function initSetupPage() { const quota = PRESETS[preset].quota; const blockDurationMs = getDurationInputMs(form, "blockSeconds", DEFAULT_BLOCK_DURATION_MS); const moveLimitMs = getDurationInputMs(form, "moveSeconds", DEFAULT_MOVE_LIMIT_MS); + const moveLimitActive = usesMoveLimit(mode); const timeImpact = mode === "time" - ? "Chronos cumules de 10 minutes, ajustes apres chaque phase cube avec plafond de 120 s pris en compte." + ? "Chronos cumules de 10 minutes 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 la partie suivante, avec double coup V2 possible."; + const timingText = moveLimitActive + ? `Temps configures : partie ${formatClock(blockDurationMs)}, coup ${formatClock(moveLimitMs)}.` + : `Temps configure : Block ${formatClock(blockDurationMs)}.`; + const quotaText = moveLimitActive + ? `Quota actif : ${quota} coups par joueur.` + : `Quota actif : ${quota} coups par joueur et par Block.`; + + if (moveSecondsField instanceof HTMLElement) { + moveSecondsField.hidden = !moveLimitActive; + } + + if (moveSecondsInput instanceof HTMLInputElement) { + moveSecondsInput.disabled = !moveLimitActive; + } + + document.body.classList.toggle("time-setup-mode", !moveLimitActive); summary.innerHTML = ` ${MODES[mode].label} ${PRESETS[preset].description} - Temps configures : partie ${formatClock(blockDurationMs)}, coup ${formatClock(moveLimitMs)}. + ${timingText} ${timeImpact} - Quota actif : ${quota} coups par joueur. + ${quotaText} `; }; @@ -228,7 +247,9 @@ function initChronoPage() { title: document.querySelector("#chronoTitle"), subtitle: document.querySelector("#chronoSubtitle"), blockTimer: document.querySelector("#blockTimer"), + blockTimerLabel: document.querySelector("#blockTimerLabel"), moveTimer: document.querySelector("#moveTimer"), + moveTimerCard: document.querySelector("#moveTimerCard"), centerLabel: document.querySelector("#chronoCenterLabel"), centerValue: document.querySelector("#chronoCenterValue"), spineLabel: document.querySelector("#spineLabel"), @@ -299,7 +320,7 @@ function initChronoPage() { return; } - requestBlockClosure(match, "Cloture manuelle de la partie demandee par l'arbitre."); + requestBlockClosure(match, `Cloture manuelle ${getBlockGenitivePhrase(match)} demandee par l'arbitre.`); dirty = true; if (!match.result && match.phase === "cube") { goToCubePage(); @@ -310,7 +331,7 @@ function initChronoPage() { refs.arbiterTimeoutButton?.addEventListener("click", () => { syncRunningState(match); - if (match.result || match.phase !== "block") { + if (match.result || match.phase !== "block" || !usesMoveLimit(match)) { return; } @@ -326,7 +347,9 @@ function initChronoPage() { } match.currentTurn = opponentOf(match.currentTurn); - match.moveRemainingMs = getMoveLimitMs(match); + if (usesMoveLimit(match)) { + match.moveRemainingMs = getMoveLimitMs(match); + } logEvent(match, "Trait corrige manuellement par l'arbitre."); dirty = true; render(); @@ -416,17 +439,20 @@ function initChronoPage() { const clock = isWhite ? refs.whiteClock : refs.blackClock; const hint = isWhite ? refs.whiteHint : refs.blackHint; const zone = isWhite ? refs.whiteZone : refs.blackZone; - const actorName = playerName(match, color); const active = match.currentTurn === color; + const unitLabel = getBlockLabel(match); + const unitPhrase = getBlockPhrase(match); - name.textContent = actorName; + name.textContent = playerName(match, color); moves.textContent = `${match.moves[color]} / ${match.quota}`; - clock.textContent = match.clocks - ? `Chrono ${formatSignedClock(match.clocks[color])}` - : `Dernier cube ${renderLastCube(match, color)}`; + clock.textContent = match.clocks ? formatSignedClock(match.clocks[color]) : `Dernier cube ${renderLastCube(match, color)}`; button.classList.toggle("active-turn", active && !match.result); zone.classList.toggle("active-zone", active && !match.result); + zone.classList.toggle("has-player-clock", Boolean(match.clocks)); + clock.classList.toggle("player-clock", Boolean(match.clocks)); + clock.classList.toggle("negative-clock", Boolean(match.clocks) && match.clocks[color] < 0); + clock.classList.toggle("active-clock", Boolean(match.clocks) && active && !match.result); if (match.result) { button.textContent = resultText(match); @@ -436,10 +462,10 @@ function initChronoPage() { } if (!match.running) { - button.textContent = "Partie en pause"; + button.textContent = `${unitLabel} en pause`; button.disabled = true; hint.textContent = active - ? "La partie n'a pas encore demarre ou a ete mise en pause." + ? `${unitPhrase} n'a pas encore demarre ou a ete mis en pause.` : `${playerName(match, match.currentTurn)} reprendra au demarrage.`; return; } @@ -480,11 +506,21 @@ function initChronoPage() { return; } + const timeMode = isTimeMode(match); + const blockHeading = formatBlockHeading(match, match.blockNumber); + + document.body.classList.toggle("time-mode", timeMode); refs.title.textContent = match.config.matchLabel; - refs.subtitle.textContent = `Partie ${match.blockNumber} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`; + refs.subtitle.textContent = `${blockHeading} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`; + refs.blockTimerLabel.textContent = timeMode ? "Temps Block" : "Temps partie"; refs.blockTimer.textContent = formatClock(match.blockRemainingMs); - refs.moveTimer.textContent = formatClock(match.moveRemainingMs); - refs.arbiterTimeoutButton.textContent = `Depassement ${formatClock(getMoveLimitMs(match))}`; + refs.moveTimer.textContent = usesMoveLimit(match) ? formatClock(match.moveRemainingMs) : "--:--"; + refs.moveTimerCard.hidden = timeMode; + refs.arbiterTimeoutButton.hidden = timeMode; + refs.arbiterTimeoutButton.disabled = timeMode; + if (usesMoveLimit(match)) { + refs.arbiterTimeoutButton.textContent = `Depassement ${formatClock(getMoveLimitMs(match))}`; + } if (match.result) { refs.centerLabel.textContent = "Resultat"; @@ -498,20 +534,20 @@ function initChronoPage() { refs.centerLabel.textContent = "Trait"; refs.centerValue.textContent = playerName(match, match.currentTurn); refs.spineLabel.textContent = "Chrono en cours"; - refs.spineHeadline.textContent = `Partie ${match.blockNumber} active`; + refs.spineHeadline.textContent = `${blockHeading} actif`; refs.spineText.textContent = "Chaque joueur tape sur sa grande zone quand son coup est termine. La page cube s'ouvrira automatiquement a la fin de la phase chess."; refs.primaryButton.textContent = "Pause arbitre"; - refs.arbiterStatus.textContent = `Partie en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`; + refs.arbiterStatus.textContent = `${blockHeading} en cours. Joueur au trait : ${playerName(match, match.currentTurn)}.`; } else { refs.centerLabel.textContent = "Trait"; refs.centerValue.textContent = playerName(match, match.currentTurn); - refs.spineLabel.textContent = "Pret"; - refs.spineHeadline.textContent = `Partie ${match.blockNumber}`; + refs.spineLabel.textContent = timeMode ? "Etat du Block" : "Pret"; + refs.spineHeadline.textContent = blockHeading; refs.spineText.textContent = "Demarrez la partie, puis laissez uniquement les deux grandes zones aux joueurs. La page cube prendra automatiquement le relais."; - refs.primaryButton.textContent = "Demarrer la partie"; - refs.arbiterStatus.textContent = `Partie prete. ${playerName(match, match.currentTurn)} commencera.`; + refs.primaryButton.textContent = timeMode ? "Demarrer le Block" : "Demarrer la partie"; + refs.arbiterStatus.textContent = `${blockHeading} pret. ${playerName(match, match.currentTurn)} commencera.`; } refs.arbiterCloseBlockButton.textContent = "Passer au cube"; @@ -555,6 +591,7 @@ function initCubePage() { const refs = { title: document.querySelector("#cubeTitle"), subtitle: document.querySelector("#cubeSubtitle"), + blockLabelText: document.querySelector("#cubeBlockLabelText"), blockLabel: document.querySelector("#cubeBlockLabel"), elapsed: document.querySelector("#cubeElapsed"), centerLabel: document.querySelector("#cubeCenterLabel"), @@ -892,8 +929,11 @@ function initCubePage() { } function render() { + const blockHeading = formatBlockHeading(match, match.blockNumber); + refs.title.textContent = match.cube.number ? `Cube n${match.cube.number}` : "Phase cube"; - refs.subtitle.textContent = `Partie ${match.blockNumber} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`; + refs.subtitle.textContent = `${blockHeading} - ${MODES[match.config.mode].label} - ${renderModeContext(match)}`; + refs.blockLabelText.textContent = getBlockLabel(match); refs.blockLabel.textContent = `${match.blockNumber}`; refs.elapsed.textContent = renderCubeElapsed(match); @@ -923,7 +963,9 @@ function initCubePage() { refs.centerValue.textContent = "Phase cube complete"; refs.spineLabel.textContent = "Suite"; refs.spineHeadline.textContent = "Ouvrir la page chrono"; - refs.spineText.textContent = "Appliquer le resultat du cube pour preparer la partie suivante."; + refs.spineText.textContent = isTimeMode(match) + ? "Appliquer le resultat du cube pour preparer le Block suivant." + : "Appliquer le resultat du cube pour preparer la partie suivante."; refs.primaryButton.textContent = "Appliquer et ouvrir la page chrono"; refs.helpStatus.textContent = refs.spineText.textContent; } else if (match.cube.running) { @@ -1033,9 +1075,11 @@ function createMatch(config) { logEvent( newMatch, - `Match cree en mode ${MODES[config.mode].label}, cadence ${PRESETS[config.preset].label}, partie ${formatClock(config.blockDurationMs)} et coup ${formatClock(config.moveLimitMs)}.`, + usesMoveLimit(config.mode) + ? `Match cree en mode ${MODES[config.mode].label}, cadence ${PRESETS[config.preset].label}, partie ${formatClock(config.blockDurationMs)} et coup ${formatClock(config.moveLimitMs)}.` + : `Match cree en mode ${MODES[config.mode].label}, cadence ${PRESETS[config.preset].label}, Block ${formatClock(config.blockDurationMs)} sans temps par coup.`, ); - logEvent(newMatch, "Les Blancs commencent la partie 1."); + logEvent(newMatch, `Les Blancs commencent ${formatBlockHeading(config, 1)}.`); return newMatch; } @@ -1163,7 +1207,9 @@ function syncRunningState(storedMatch) { storedMatch.lastTickAt = now; storedMatch.blockRemainingMs = Math.max(0, storedMatch.blockRemainingMs - delta); - storedMatch.moveRemainingMs = Math.max(0, storedMatch.moveRemainingMs - delta); + if (usesMoveLimit(storedMatch)) { + storedMatch.moveRemainingMs = Math.max(0, storedMatch.moveRemainingMs - delta); + } if (storedMatch.clocks) { storedMatch.clocks[storedMatch.currentTurn] -= delta; @@ -1172,9 +1218,9 @@ function syncRunningState(storedMatch) { if (storedMatch.blockRemainingMs === 0) { requestBlockClosure( storedMatch, - `Le temps de partie ${formatClock(getBlockDurationMs(storedMatch))} est ecoule.`, + `Le temps ${getBlockGenitivePhrase(storedMatch)} ${formatClock(getBlockDurationMs(storedMatch))} est ecoule.`, ); - } else if (storedMatch.moveRemainingMs === 0) { + } else if (usesMoveLimit(storedMatch) && storedMatch.moveRemainingMs === 0) { registerMoveTimeout(storedMatch, true); } @@ -1193,8 +1239,8 @@ function startBlock(storedMatch) { logEvent( storedMatch, storedMatch.blockNumber === 1 && storedMatch.moves.white === 0 && storedMatch.moves.black === 0 - ? "Partie 1 demarre." - : `Partie ${storedMatch.blockNumber} relance.`, + ? `${formatBlockHeading(storedMatch, 1)} demarre.` + : `${formatBlockHeading(storedMatch, storedMatch.blockNumber)} relance.`, ); } @@ -1205,7 +1251,7 @@ function pauseBlock(storedMatch) { storedMatch.running = false; storedMatch.lastTickAt = null; - logEvent(storedMatch, `Partie ${storedMatch.blockNumber} mise en pause.`); + logEvent(storedMatch, `${formatBlockHeading(storedMatch, storedMatch.blockNumber)} passe en pause.`); } function requestBlockClosure(storedMatch, reason) { @@ -1388,7 +1434,7 @@ function prepareNextTimeBlock(storedMatch) { storedMatch.cube.number = null; logEvent( storedMatch, - `Partie ${storedMatch.blockNumber} prete. Le trait est conserve : ${playerName( + `${formatBlockHeading(storedMatch, storedMatch.blockNumber)} pret. Le trait est conserve : ${playerName( storedMatch, storedMatch.currentTurn, )} reprend.`, @@ -1489,7 +1535,7 @@ function registerFreeDoubleMove(storedMatch) { } function registerMoveTimeout(storedMatch, automatic) { - if (!storedMatch || storedMatch.phase !== "block") { + if (!storedMatch || storedMatch.phase !== "block" || !usesMoveLimit(storedMatch)) { return; } @@ -1718,6 +1764,34 @@ function getDurationInputMs(form, name, fallbackMs) { return seconds * 1000; } +function isTimeMode(matchOrConfig) { + const mode = + typeof matchOrConfig === "string" + ? matchOrConfig + : matchOrConfig?.config?.mode ?? matchOrConfig?.mode; + return mode === "time"; +} + +function usesMoveLimit(matchOrConfig) { + return !isTimeMode(matchOrConfig); +} + +function getBlockLabel(matchOrConfig) { + return isTimeMode(matchOrConfig) ? "Block" : "Partie"; +} + +function getBlockPhrase(matchOrConfig) { + return isTimeMode(matchOrConfig) ? "Le Block" : "La partie"; +} + +function getBlockGenitivePhrase(matchOrConfig) { + return isTimeMode(matchOrConfig) ? "du Block" : "de la partie"; +} + +function formatBlockHeading(matchOrConfig, blockNumber) { + return `${getBlockLabel(matchOrConfig)} ${blockNumber}`; +} + function playerName(storedMatch, color) { return color === "white" ? storedMatch.config.whiteName : storedMatch.config.blackName; } diff --git a/application.html b/application.html index 9593bf0..a44c38f 100644 --- a/application.html +++ b/application.html @@ -46,7 +46,7 @@