Recette 1e : titre du bilan sur l'issue, notation du moteur partout, boutons de 48 px en paysage, filtre en accent-soft, longue partie ; tests navigateur adaptés au bilan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-10 14:37:44 +01:00
co-authored by Claude Opus 5.5
parent ced004edb0
commit 663ef35b51
11 changed files with 263 additions and 61 deletions
+20 -5
View File
@@ -446,7 +446,9 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
? REVIEW_TEXTS.computerCube
: turn.move.halfMoves.length === 0
? REVIEW_TEXTS.computerCannotMove
: REVIEW_TEXTS.computerPlayed(formatHalfMoves(turn.move.halfMoves));
: REVIEW_TEXTS.computerPlayed(
theirs?.played.notation ?? formatHalfMoves(turn.move.halfMoves),
);
} else if (turn.move !== null && turn.move.halfMoves.length === 0) {
phrase.textContent = REVIEW_TEXTS.playerCannotMove;
}
@@ -456,6 +458,10 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
const side = turn.side;
let mine: MoveSteps | null = null;
let best: MoveSteps | null = null;
// Les coups s'écrivent dans la notation du moteur, comme dans le
// bilan et la feuille du maître (« 6/off », pas « 6/5 5/off ») ;
// à défaut d'analyse, à partir des demi-coups.
let names = { mine: "", best: "" };
const moveAdvice: Advice | null = side === "player" ? main : theirs;
try {
if (moveAdvice?.kind === "move") {
@@ -464,6 +470,10 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
side,
moveAdvice.played.halfMoves,
);
names = {
mine: moveAdvice.played.notation,
best: moveAdvice.best.notation,
};
if (moveAdvice.rank > 1) {
best = await current.game.stepsBefore(
moveAdvice.eventId,
@@ -475,6 +485,7 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
mine = await current.game.stepsBefore(moveAdvice.eventId, "player", []);
} else if (turn.move !== null) {
mine = await current.game.stepsBefore(turn.move.id, side, turn.move.halfMoves);
names = { mine: formatHalfMoves(turn.move.halfMoves), best: "" };
} else {
const first = turn.events[0];
mine =
@@ -488,15 +499,19 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
if (id !== request || mine === null) {
return;
}
showMoves(mine, best, side);
showMoves(mine, best, side, names);
})();
}
function showMoves(mine: MoveSteps, best: MoveSteps | null, side: "player" | "computer"): void {
function showMoves(
mine: MoveSteps,
best: MoveSteps | null,
side: "player" | "computer",
names: { readonly mine: string; readonly best: string },
): void {
const who = side === "player" ? REVIEW_TEXTS.yourMoveSegment : REVIEW_TEXTS.theirMove;
segments.mine.textContent = who;
choice.hidden = best === null;
const notation = (steps: MoveSteps) => formatHalfMoves(steps.halfMoves);
const select = (shown: Shown) => {
const steps = shown === "best" && best !== null ? best : mine;
for (const key of ["mine", "best"] as const) {
@@ -511,7 +526,7 @@ export function createAnalysisView(options: AnalysisViewOptions): AnalysisView {
}
const text = REVIEW_TEXTS.legendMove(
shown === "best" ? REVIEW_TEXTS.masterMove : who,
notation(steps),
shown === "best" ? names.best : names.mine,
);
legend.textContent = text;
board.setAttribute("aria-label", text);
+6 -5
View File
@@ -1419,7 +1419,7 @@ export function startApp(options: AppOptions): void {
) {
queueMicrotask(() => {
if (navigation.route.screen === "game" && !panelDismissed) {
navigation.replace(routeHash({ screen: "summary", game: id }));
navigation.go(routeHash({ screen: "summary", game: id }));
}
});
}
@@ -2527,6 +2527,7 @@ function endParts(game: Game): { title: string; detail: string } {
}
const { winner, kind, points } = result;
const dropped = game.events.some((e) => e.type === "CubeDropped");
const how = dropped ? TEXTS.droppedDouble(winner) : "";
const { match, matchAfter } = game.state;
if (match !== null && matchAfter !== null) {
const { player, computer } = matchAfter.score;
@@ -2541,13 +2542,13 @@ function endParts(game: Game): { title: string; detail: string } {
return {
title: matchAfter.over
? MATCH_TEXTS.matchWonTitle(winner)
: TEXTS.endTitle(winner, kind, dropped),
detail: matchAfter.crawford ? `${detail} ${MATCH_TEXTS.crawfordNext}` : detail,
: TEXTS.endTitle(winner, kind),
detail: `${how}${matchAfter.crawford ? `${detail} ${MATCH_TEXTS.crawfordNext}` : detail}`,
};
}
return {
title: TEXTS.endTitle(winner, kind, dropped),
detail: TEXTS.endDetail(winner, points, game.state.cube.value),
title: TEXTS.endTitle(winner, kind),
detail: `${how}${TEXTS.endDetail(winner, points, game.state.cube.value)}`,
};
}
+5 -2
View File
@@ -290,7 +290,10 @@ export function createSummaryView(options: SummaryViewOptions): SummaryView {
}
function actions(data: SummaryData): void {
const bar = element(panel, "div", { className: "panel-actions summary-actions" });
// Les actions et le lien des réglages, ensemble : en paysage, en bas
// de la colonne de droite.
const end = element(panel, "div", { className: "summary-end" });
const bar = element(end, "div", { className: "panel-actions summary-actions" });
const analysis = element(bar, "a", {
id: "summary-analysis",
className: "button secondary",
@@ -304,7 +307,7 @@ export function createSummaryView(options: SummaryViewOptions): SummaryView {
});
again.type = "button";
again.onclick = () => options.again();
const settings = element(panel, "a", {
const settings = element(end, "a", {
id: "summary-settings",
className: "panel-link",
text: MATCH_TEXTS.changeSettings,
+11 -8
View File
@@ -136,15 +136,18 @@ export const TEXTS = {
// Fin de partie
gameOver: "Partie finie",
endOverline: "Fin de partie",
/** Titre du panneau de fin : qui gagne, et comment. */
endTitle: (winner: "player" | "computer", kind: ResultKind, dropped: boolean) => {
if (dropped) {
return winner === "player" ? "L'ordinateur passe" : "Vous passez";
}
return winner === "player"
/** Titre du panneau de fin et du bilan : toujours l'issue, qui gagne
* et comment (un double passé se dit dans le détail). */
endTitle: (winner: "player" | "computer", kind: ResultKind) =>
winner === "player"
? `Vous gagnez ${RESULT_KIND[kind]}`
: `L'ordinateur gagne ${RESULT_KIND[kind]}`;
},
: `L'ordinateur gagne ${RESULT_KIND[kind]}`,
/** Avant le détail des points, quand la partie finit sur un double
* passé : « Vous avez passé son double : 1 point pour lui. ». */
droppedDouble: (winner: "player" | "computer") =>
winner === "player"
? "L'ordinateur a passé votre double : "
: "Vous avez passé son double : ",
/** « 8 points pour lui, videau à 4. » */
endDetail: (winner: "player" | "computer", n: number, cube: number) =>
`${points(n)} pour ${winner === "player" ? "vous" : "lui"}${