Coup de l'ordinateur : animation, traces et bouton Revoir
Le domaine donne le dernier coup demi-coup par demi-coup (plateaux intermédiaires du moteur, pions frappés lus sur la barre) ; l'interface fait glisser les pions puis laisse flèches, pions entourés et fantômes jusqu'à l'action suivante. Scénario « Voir le coup de l'ordinateur », tests unitaires et test dans le navigateur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9c241de5c9
commit
7a9b376417
@@ -130,6 +130,77 @@ async function waitForPlayer() {
|
||||
throw new Error(`rien à faire pour le joueur depuis 60 s : ${screen}`);
|
||||
}
|
||||
|
||||
/** Le dernier coup de l'ordinateur, s'il est la dernière chose jouée (fin de
|
||||
* partie mise à part) et qu'il a déplacé des pions ; sinon null. */
|
||||
function computerMoveToShow(journal) {
|
||||
const last = journal.events.filter((e) => e.type !== "GameEnded").at(-1);
|
||||
return last?.type === "MovePlayed" && last.side === "computer" && last.halfMoves.length > 0
|
||||
? last
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Ce que montre le plateau du coup de l'ordinateur. */
|
||||
async function shownTraces() {
|
||||
return page.evaluate(() => ({
|
||||
arrows: document.querySelectorAll(".trace-arrow").length,
|
||||
landed: document.querySelectorAll(".trace-landed").length,
|
||||
ghosts: document.querySelectorAll(".trace-ghost").length,
|
||||
hits: document.querySelectorAll(".trace-hit").length,
|
||||
replay: !document.getElementById("replay").hidden,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Après un tour de l'ordinateur : ses traces sont là si son coup est la
|
||||
* dernière chose jouée, et rien sinon. */
|
||||
async function checkTraces() {
|
||||
const move = computerMoveToShow(await savedJournal());
|
||||
const traces = await shownTraces();
|
||||
if (move === null) {
|
||||
check(traces.arrows + traces.landed + traces.ghosts === 0 && !traces.replay,
|
||||
`traces sans coup de l'ordinateur à montrer : ${JSON.stringify(traces)}`);
|
||||
return null;
|
||||
}
|
||||
check(traces.arrows === move.halfMoves.length && traces.landed > 0 && traces.ghosts > 0 &&
|
||||
traces.replay, `coup de l'ordinateur mal montré : ${JSON.stringify(traces)}`);
|
||||
return traces;
|
||||
}
|
||||
|
||||
/** Observe la page pendant ms millisecondes : le plus grand nombre
|
||||
* d'animations en cours à la fois, et le plus petit nombre de flèches. */
|
||||
async function watchReplay(ms) {
|
||||
let animations = 0;
|
||||
let arrows = Infinity;
|
||||
for (const end = Date.now() + ms; Date.now() < end; ) {
|
||||
const seen = await page.evaluate(() => ({
|
||||
animations: document.getAnimations().length,
|
||||
arrows: document.querySelectorAll(".trace-arrow").length,
|
||||
}));
|
||||
animations = Math.max(animations, seen.animations);
|
||||
arrows = Math.min(arrows, seen.arrows);
|
||||
await new Promise((resolve) => setTimeout(resolve, 40));
|
||||
}
|
||||
return { animations, arrows };
|
||||
}
|
||||
|
||||
/** « Revoir » : rejoue le coup (sans animation en mouvement réduit), puis
|
||||
* remet les mêmes traces. */
|
||||
async function checkReplay(traces, reducedMotion) {
|
||||
await page.emulateMedia({ reducedMotion: reducedMotion ? "reduce" : "no-preference" });
|
||||
await page.click("#replay");
|
||||
const seen = await watchReplay(300);
|
||||
if (reducedMotion) {
|
||||
check(seen.animations === 0, "Revoir en mouvement réduit : des pions glissent quand même");
|
||||
} else {
|
||||
check(seen.animations > 0 && seen.arrows === 0,
|
||||
`Revoir : pas d'animation sans traces (${JSON.stringify(seen)})`);
|
||||
}
|
||||
await waitForPlayer();
|
||||
const after = await shownTraces();
|
||||
check(JSON.stringify(after) === JSON.stringify(traces),
|
||||
`traces différentes après Revoir : ${JSON.stringify(after)}`);
|
||||
await page.emulateMedia({ reducedMotion: "no-preference" });
|
||||
}
|
||||
|
||||
// Garde-fou : une étape bloquée plus de deux minutes arrête tout, avec une
|
||||
// capture et l'état de l'écran.
|
||||
let lastStep = Date.now();
|
||||
@@ -157,10 +228,28 @@ let doubled = false;
|
||||
let selectionShot = false;
|
||||
let reloaded = false;
|
||||
let computerMs = [];
|
||||
let tracesSeen = 0;
|
||||
let replays = 0;
|
||||
for (let actions = 0; ; actions++) {
|
||||
check(actions < 800, "la partie ne finit pas");
|
||||
const todo = await waitForPlayer();
|
||||
step(`partie, action ${actions}`);
|
||||
// Le coup de l'ordinateur est montré jusqu'à l'action suivante du
|
||||
// joueur ; deux fois, on le revoit (la seconde en mouvement réduit).
|
||||
const traces = await checkTraces();
|
||||
if (traces !== null) {
|
||||
tracesSeen++;
|
||||
if (replays < 2) {
|
||||
replays++;
|
||||
await checkReplay(traces, replays === 2);
|
||||
if (replays === 1) {
|
||||
await shot("coup-adverse");
|
||||
}
|
||||
log(`coup de l'ordinateur revu${replays === 2 ? " en mouvement réduit" : ""} : ` +
|
||||
`${traces.arrows} flèches, ${traces.landed} pions entourés, ` +
|
||||
`${traces.ghosts} fantômes, ${traces.hits} pions frappés signalés`);
|
||||
}
|
||||
}
|
||||
if (process.env.TRACE) {
|
||||
log(`${todo} : ${await page.textContent("#info")} / ${await page.textContent("#prompt")}`);
|
||||
}
|
||||
@@ -223,6 +312,8 @@ log(
|
||||
);
|
||||
check(countMoves(journal, "player") >= 3 && countMoves(journal, "computer") >= 3, "trop peu de coups");
|
||||
check(reloaded, "la reprise après rechargement n'a pas été essayée");
|
||||
check(replays === 2, "le coup de l'ordinateur n'a pas été revu");
|
||||
log(`coup de l'ordinateur montré ${tracesSeen} fois, et effacé à chaque action suivante`);
|
||||
if (computerMs.length > 0) {
|
||||
log(`temps affichés des tours de l'ordinateur : max ${Math.max(...computerMs)} ms, ` +
|
||||
`${computerMs.length} tours`);
|
||||
|
||||
Reference in New Issue
Block a user