Parties enregistrées : IndexedDB, branches, export SGF relu par gnubg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
08cb89735f
commit
274fbf6252
+100
-7
@@ -5,36 +5,120 @@
|
||||
// Copyright (C) 2026 les auteurs d'Ustagammon.
|
||||
// Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||||
|
||||
import { switchToBranch } from "./domain/game/branches.js";
|
||||
import type { Level } from "./domain/game/engine.js";
|
||||
import { Game } from "./domain/game/game.js";
|
||||
import { exportSgf } from "./domain/game/sgf.js";
|
||||
import { createWasmEngine } from "./engine/wasm-engine.js";
|
||||
import { startEngineWorker } from "./engine/worker-run.js";
|
||||
import { createGameStore } from "./storage/game-store.js";
|
||||
import { newGameId } from "./storage/game-library.js";
|
||||
import { openGameLibrary } from "./storage/open-library.js";
|
||||
import { startApp } from "./ui/app.js";
|
||||
import { GAMES_TEXTS } from "./ui/games-texts.js";
|
||||
import { createGamesView } from "./ui/games-view.js";
|
||||
|
||||
// Ce fichier est servi sous app/ ; le Worker et le service worker sont à la
|
||||
// racine du site.
|
||||
const worker = startEngineWorker(new URL("../engine-worker.mjs", import.meta.url));
|
||||
const engine = createWasmEngine(worker.run);
|
||||
const store = createGameStore(window.localStorage);
|
||||
|
||||
// Les parties enregistrées (IndexedDB, ou localStorage en repli). Chaque
|
||||
// accès au stockage du navigateur peut lever une exception (stockage
|
||||
// interdit) : on fait alors sans.
|
||||
const { library } = await openGameLibrary({
|
||||
indexedDB: attempt(() => window.indexedDB),
|
||||
localStorage: attempt(() => window.localStorage),
|
||||
});
|
||||
/** Le niveau de l'ordinateur (celui de Game.start, STANDARD_PLY). */
|
||||
const LEVEL: Level = "2ply";
|
||||
const saved = await library.current().catch(() => null);
|
||||
// La partie à l'écran ; un nouvel identifiant à chaque nouvelle partie.
|
||||
let shownId = saved?.summary.id ?? newGameId();
|
||||
// Vrai une fois qu'on recharge la page pour reprendre une autre partie :
|
||||
// plus rien n'est enregistré sous l'identifiant de celle qu'on quitte.
|
||||
let leaving = false;
|
||||
|
||||
startApp({
|
||||
engineReady: worker.ready,
|
||||
newGame: () => Game.start(engine),
|
||||
newGame: () => {
|
||||
shownId = newGameId();
|
||||
return Game.start(engine);
|
||||
},
|
||||
savedGame: () => {
|
||||
const journal = store.load();
|
||||
if (journal === null) {
|
||||
if (saved === null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Game.resume(engine, journal);
|
||||
return Game.resume(engine, saved.journal);
|
||||
} catch {
|
||||
return null; // partie illisible : on en commence une nouvelle
|
||||
}
|
||||
},
|
||||
saveGame: (journal) => store.save(journal),
|
||||
saveGame: (journal) => {
|
||||
if (!leaving) {
|
||||
// Stockage plein ou interdit : la partie continue, elle ne
|
||||
// pourra seulement pas être reprise.
|
||||
library.save(shownId, journal, { level: LEVEL }).catch(() => {});
|
||||
}
|
||||
},
|
||||
confirm: (question) => window.confirm(question),
|
||||
});
|
||||
|
||||
// « Mes parties ». Point d'accroche provisoire : un bouton ajouté devant
|
||||
// celui de l'aide, en attendant le menu de l'application (révision UX).
|
||||
// Reprendre une partie la désigne comme partie en cours, puis recharge la
|
||||
// page, qui l'ouvre.
|
||||
const gamesView = createGamesView({
|
||||
list: () => library.list(),
|
||||
journal: async (id) => (await library.load(id))?.journal ?? null,
|
||||
shownId: () => shownId,
|
||||
resume: async (id, branchHead) => {
|
||||
const game = await library.load(id);
|
||||
if (game === null) {
|
||||
throw new Error("partie introuvable");
|
||||
}
|
||||
leaving = true;
|
||||
if (branchHead === undefined) {
|
||||
await library.setCurrent(id);
|
||||
} else {
|
||||
const journal = switchToBranch(game.journal, branchHead);
|
||||
await library.save(id, journal, { level: game.summary.level });
|
||||
}
|
||||
window.location.reload();
|
||||
},
|
||||
remove: (id) => library.remove(id),
|
||||
exportSgf: async (id) => {
|
||||
const game = await library.load(id);
|
||||
if (game === null) {
|
||||
throw new Error("partie introuvable");
|
||||
}
|
||||
const started = new Date(game.summary.startedAt);
|
||||
const date = [started.getFullYear(), started.getMonth() + 1, started.getDate()]
|
||||
.map((n) => String(n).padStart(2, "0"))
|
||||
.join("-");
|
||||
const time = [started.getHours(), started.getMinutes()]
|
||||
.map((n) => String(n).padStart(2, "0"))
|
||||
.join("");
|
||||
const text = await exportSgf(engine, game.journal, {
|
||||
playerName: "Vous",
|
||||
computerName: "Ustagammon",
|
||||
date,
|
||||
});
|
||||
return { fileName: `ustagammon-${date}-${time}.sgf`, text };
|
||||
},
|
||||
confirm: (question) => window.confirm(question),
|
||||
});
|
||||
const helpButton = document.getElementById("help-button");
|
||||
if (helpButton !== null) {
|
||||
const gamesButton = document.createElement("button");
|
||||
gamesButton.id = "games-button";
|
||||
gamesButton.type = "button";
|
||||
gamesButton.className = "secondary";
|
||||
gamesButton.textContent = GAMES_TEXTS.button;
|
||||
gamesButton.onclick = () => void gamesView.open();
|
||||
helpButton.before(gamesButton);
|
||||
}
|
||||
|
||||
// Hors ligne : le service worker met tout le site en cache. Le navigateur le
|
||||
// refuse hors https (sauf localhost) : l'application marche alors sans.
|
||||
// Il est enregistré une fois le moteur chargé : si le Worker et le service
|
||||
@@ -46,3 +130,12 @@ if ("serviceWorker" in navigator) {
|
||||
const sw = new URL("../sw.js", import.meta.url);
|
||||
worker.ready.then(() => navigator.serviceWorker.register(sw)).catch(() => {});
|
||||
}
|
||||
|
||||
/** La valeur, ou undefined si l'obtenir lève une exception. */
|
||||
function attempt<T>(get: () => T): T | undefined {
|
||||
try {
|
||||
return get();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user