Ajuste le mode Time et met en valeur les chronos

This commit is contained in:
2026-04-12 19:51:41 +02:00
parent a6e007762d
commit 7649ba2fb9
6 changed files with 199 additions and 56 deletions

148
app.js
View File

@@ -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 = `
<strong>${MODES[mode].label}</strong>
<span>${PRESETS[preset].description}</span>
<span>Temps configures : partie ${formatClock(blockDurationMs)}, coup ${formatClock(moveLimitMs)}.</span>
<span>${timingText}</span>
<span>${timeImpact}</span>
<span>Quota actif : ${quota} coups par joueur.</span>
<span>${quotaText}</span>
`;
};
@@ -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;
}