diff --git a/app/src/domain/analysis/progression.ts b/app/src/domain/analysis/progression.ts index 13940d2..60a2a90 100644 --- a/app/src/domain/analysis/progression.ts +++ b/app/src/domain/analysis/progression.ts @@ -12,8 +12,8 @@ // n'a pas encore de taux ; l'application l'analyse en arrière-plan. import type { Level } from "../game/engine.js"; -import type { GameJournal } from "../game/events.js"; -import type { GameResult } from "../game/state.js"; +import { currentLine, type GameJournal } from "../game/events.js"; +import { type GameResult, replay } from "../game/state.js"; import { type ErrorRate, readyForSummary, summarizeAnalysis } from "./game-analysis.js"; /** Les parties prises en compte au plus : les plus récentes. Au-delà, la @@ -43,6 +43,8 @@ export interface ProgressionEntry { readonly startedAt: number; readonly level: Level; readonly result: GameResult | null; + /** La valeur du videau à la fin de la partie (1 : jamais doublé). */ + readonly cube: number; readonly errorRate: ErrorRate | null; } @@ -56,6 +58,7 @@ export function progressionEntries(games: readonly FinishedGame[]): ProgressionE startedAt: game.startedAt, level: game.level, result: game.result, + cube: replay(currentLine(game.journal)).cube.value, errorRate: readyForSummary(game.journal) ? summarizeAnalysis(game.journal).player.errorRate : null, diff --git a/app/src/ui/home-texts.ts b/app/src/ui/home-texts.ts index 6ad7bd1..b792fcf 100644 --- a/app/src/ui/home-texts.ts +++ b/app/src/ui/home-texts.ts @@ -43,7 +43,7 @@ export const HOME_TEXTS = { progress: (played: number, won: number) => played === 0 ? "aucune partie jouée" - : `${plural(played, "partie jouée", "parties jouées")} · ${plural(won, "gagnée", "gagnées")}`, + : `${plural(played, "partie jouée", "parties jouées")} · ${won === 0 ? "aucune gagnée" : plural(won, "gagnée", "gagnées")}`, /** La ligne de progression, lien vers l'écran de progression (1h). */ progressLink: (line: string) => `Votre progression : ${line}`, footer: "Né à l'Atelier du Huit, à Cugnaux. Logiciel libre, sans publicité ni mise.", diff --git a/app/src/ui/progression-texts.ts b/app/src/ui/progression-texts.ts index 0d2f6c3..249a92f 100644 --- a/app/src/ui/progression-texts.ts +++ b/app/src/ui/progression-texts.ts @@ -31,15 +31,24 @@ export const PROGRESSION_TEXTS = { `Le maître analyse ${plural(n, "partie", "parties")} : ${n > 1 ? "elles apparaîtront" : "elle apparaîtra"} ici une fois ${n > 1 ? "analysées" : "analysée"}.`, exercises: "Demander des exercices à une IA", listTitle: "Vos parties", - /** Le résultat court : « Gagnée, 1 point », « Perdue, gammon, 2 points » - * (jamais « partie simple »). */ - result: (result: GameResult | null) => { + /** Le résultat court, qui dit d'où viennent les points : « Gagnée, + * 1 point », « Perdue, gammon, 2 points », « Perdue, videau à 2, + * 2 points », « Perdue, gammon, videau à 2, 4 points » (jamais + * « partie simple »). cube : la valeur du videau à la fin de la + * partie. */ + result: (result: GameResult | null, cube: number) => { if (result === null) { return "Partie terminée"; } - const who = result.winner === "player" ? "Gagnée" : "Perdue"; - const kind = result.kind === "single" ? "" : `${RESULT_KIND[result.kind]}, `; - return `${who}, ${kind}${plural(result.points, "point", "points")}`; + const parts = [result.winner === "player" ? "Gagnée" : "Perdue"]; + if (result.kind !== "single") { + parts.push(RESULT_KIND[result.kind]); + } + if (cube > 1) { + parts.push(`videau à ${cube}`); + } + parts.push(plural(result.points, "point", "points")); + return parts.join(", "); }, level: (name: string) => `contre ${name}`, rateLabel: "taux d'erreur", diff --git a/app/src/ui/progression-view.ts b/app/src/ui/progression-view.ts index 60ada65..a1bfaca 100644 --- a/app/src/ui/progression-view.ts +++ b/app/src/ui/progression-view.ts @@ -125,6 +125,7 @@ export function createProgressionView(options: ProgressionViewOptions): Progress entries: data.entries.map((e) => [ e.id, e.result, + e.cube, e.level, e.errorRate?.perDecision ?? null, ]), @@ -222,7 +223,7 @@ export function createProgressionView(options: ProgressionViewOptions): Progress li.className = "progression-item"; li.dataset.id = entry.id; const date = PROGRESSION_TEXTS.date(entry.startedAt); - const result = PROGRESSION_TEXTS.result(entry.result); + const result = PROGRESSION_TEXTS.result(entry.result, entry.cube); // Une ligne, un seul lien : le bilan (l'analyse s'ouvre depuis // lui), chevron à droite. const game = element(li, "a", { className: "progression-game" }); diff --git a/app/tests/progression.test.ts b/app/tests/progression.test.ts index 54a8f09..baab703 100644 --- a/app/tests/progression.test.ts +++ b/app/tests/progression.test.ts @@ -38,6 +38,8 @@ import { mistakeThemes, } from "../src/domain/prompts/prompts.js"; import { PROMPT_FILES, parsePromptTemplate } from "../src/domain/prompts/templates.js"; +import { HOME_TEXTS } from "../src/ui/home-texts.js"; +import { PROGRESSION_TEXTS } from "../src/ui/progression-texts.js"; import { parseRoute, routeHash } from "../src/ui/router.js"; import { loadPhrases } from "./support/advice.js"; import { loadNodeEngine } from "./support/node-engine.js"; @@ -86,6 +88,25 @@ test("l'adresse #/progression et celle de « Créer des exercices »", () => { assert.equal(routeHash({ screen: "progression", ai: true }), "#/progression/ia"); }); +test("le résultat d'une partie dit d'où viennent les points : gammon, videau", () => { + const result = PROGRESSION_TEXTS.result; + const lost = (kind: "single" | "gammon" | "backgammon", points: number) => + ({ winner: "computer", kind, points }) as const; + assert.equal(result({ winner: "player", kind: "single", points: 1 }, 1), "Gagnée, 1 point"); + assert.equal(result(lost("single", 1), 1), "Perdue, 1 point"); + assert.equal(result(lost("gammon", 2), 1), "Perdue, gammon, 2 points"); + assert.equal(result(lost("single", 2), 2), "Perdue, videau à 2, 2 points"); + assert.equal(result(lost("gammon", 4), 2), "Perdue, gammon, videau à 2, 4 points"); + assert.equal(result(lost("backgammon", 12), 4), "Perdue, backgammon, videau à 4, 12 points"); + assert.equal(result(null, 1), "Partie terminée"); +}); + +test("la ligne de l'accueil : « aucune gagnée », comme « aucune leçon terminée »", () => { + assert.equal(HOME_TEXTS.progress(3, 0), "3 parties jouées · aucune gagnée"); + assert.equal(HOME_TEXTS.progress(3, 1), "3 parties jouées · 1 gagnée"); + assert.equal(HOME_TEXTS.progress(0, 0), "aucune partie jouée"); +}); + test("le taux d'erreur s'écrit en millièmes, comme les mEMG de GNU Backgammon", () => { assert.equal(errorRateMillis(0.0142), 14); assert.equal(errorRateMillis(0.0075), 8); diff --git a/tests/browser/progression.mjs b/tests/browser/progression.mjs index 7a9a074..31f8517 100644 --- a/tests/browser/progression.mjs +++ b/tests/browser/progression.mjs @@ -308,6 +308,7 @@ const shown = await page.evaluate(() => ({ id: li.dataset.id, summary: li.querySelector(".progression-game").getAttribute("href"), links: li.querySelectorAll("a, button").length, + result: li.querySelector(".progression-text strong").textContent, chevron: li.querySelector(".progression-game > svg.icon") !== null, rate: li.querySelector(".progression-rate")?.dataset.rate, })), @@ -348,7 +349,7 @@ check( `exercices : ${shown.exercises} « ${shown.exercisesText} »`, ); await capture("2-trois-parties"); -log(`courbe : ${shown.dots.map((d) => d.rate).join(", ")} ; « ${shown.summary} »`); +log(`courbe : ${shown.dots.map((d) => d.rate).join(", ")} ; « ${shown.summary} » ; résultats : ${shown.items.map((i) => i.result).join(" | ")}`); // Une ligne ouvre le bilan ; le fermer ramène à la progression. await page.click(`#progression-list .progression-item[data-id="${games[0].id}"] .progression-game`);