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
@@ -0,0 +1,143 @@
|
||||
// Un coup demi-coup par demi-coup (Game.lastMoveSteps), sur le vrai moteur
|
||||
// WebAssembly : les plateaux intermédiaires que l'interface anime, et les
|
||||
// pions frappés qu'elle signale.
|
||||
//
|
||||
// Copyright (C) 2026 les auteurs d'Ustagammon.
|
||||
// Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import type { BoardSide } from "../src/domain/game/engine.js";
|
||||
import { Game } from "../src/domain/game/game.js";
|
||||
import {
|
||||
BAR,
|
||||
formatHalfMoves,
|
||||
type PositionId,
|
||||
parseHalfMoves,
|
||||
} from "../src/domain/game/position.js";
|
||||
import { fixedDice } from "../src/domain/game/roll.js";
|
||||
import { loadNodeEngine } from "./support/node-engine.js";
|
||||
|
||||
// Le joueur, au trait : un pion sur sa case 13, quatorze sur sa case 6.
|
||||
// L'ordinateur : un blot sur la case 10 du joueur (sa case 15), quatorze sur
|
||||
// sa case 6.
|
||||
const PLAYER_CAN_HIT = "4P8HEADg/wcEAA";
|
||||
|
||||
// Le joueur, au trait : un pion sur la barre, quatorze sur sa case 6.
|
||||
// L'ordinateur : un blot sur la case 21 du joueur (sa case 4), quatorze sur
|
||||
// sa case 6.
|
||||
const PLAYER_ON_BAR = "yP8PAADg/wcAQA";
|
||||
|
||||
// Le joueur, au trait, n'a plus que deux pions, sur ses cases 6 et 5.
|
||||
const PLAYER_ALMOST_DONE = "/38AAABQAAAAAA";
|
||||
|
||||
/** Une partie où le joueur, au trait, joue le jet dice à la position. */
|
||||
async function playerMove(position: PositionId, dice: [number, number], move: string) {
|
||||
const engine = await loadNodeEngine();
|
||||
const game = Game.start(engine, {
|
||||
dice: fixedDice(dice),
|
||||
setup: { position, firstTurn: "player" },
|
||||
});
|
||||
await game.roll();
|
||||
for (const halfMove of parseHalfMoves(move)) {
|
||||
assert.equal(await game.playHalfMove(halfMove), true, formatHalfMoves([halfMove]));
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
||||
/** Le nombre de pions d'un camp sur une case (1 à 24), sur la barre ou sortis. */
|
||||
function at(side: BoardSide, point: number | "bar" | "off"): number {
|
||||
if (point === "bar") {
|
||||
return side.bar;
|
||||
}
|
||||
return point === "off" ? side.off : (side.points[point - 1] ?? 0);
|
||||
}
|
||||
|
||||
test("un coup simple : un plateau par demi-coup, sans pion frappé", async () => {
|
||||
const engine = await loadNodeEngine();
|
||||
const game = Game.start(engine, { dice: fixedDice([3, 1]) });
|
||||
for (const halfMove of parseHalfMoves("8/5 6/5")) {
|
||||
await game.playHalfMove(halfMove);
|
||||
}
|
||||
const steps = await game.lastMoveSteps();
|
||||
assert.ok(steps !== null);
|
||||
assert.equal(steps.side, "player");
|
||||
assert.equal(formatHalfMoves(steps.halfMoves), "8/5 6/5");
|
||||
assert.deepEqual(steps.hits, [false, false]);
|
||||
const counts = steps.boards.map((b) => [at(b.player, 8), at(b.player, 6), at(b.player, 5)]);
|
||||
assert.deepEqual(counts, [
|
||||
[3, 5, 0],
|
||||
[2, 5, 1],
|
||||
[2, 4, 2],
|
||||
]);
|
||||
// Le dernier plateau est celui de la partie après le coup.
|
||||
assert.deepEqual(steps.boards[2], await game.displayedBoard());
|
||||
});
|
||||
|
||||
test("un pion frappé part sur la barre au demi-coup qui le frappe", async () => {
|
||||
const game = await playerMove(PLAYER_CAN_HIT, [3, 1], "13/10 10/9");
|
||||
const steps = await game.lastMoveSteps();
|
||||
assert.ok(steps !== null);
|
||||
assert.deepEqual(steps.hits, [true, false]);
|
||||
const bar = steps.boards.map((b) => at(b.computer, "bar"));
|
||||
assert.deepEqual(bar, [0, 1, 1]);
|
||||
// Le blot était sur la case 10 du joueur, la case 15 de l'ordinateur.
|
||||
assert.equal(at(steps.boards[0]?.computer as BoardSide, 15), 1);
|
||||
assert.equal(at(steps.boards[1]?.computer as BoardSide, 15), 0);
|
||||
assert.equal(at(steps.boards[2]?.player as BoardSide, 9), 1);
|
||||
});
|
||||
|
||||
test("un coup depuis la barre, qui frappe en rentrant", async () => {
|
||||
const game = await playerMove(PLAYER_ON_BAR, [4, 2], "bar/21 6/4");
|
||||
const steps = await game.lastMoveSteps();
|
||||
assert.ok(steps !== null);
|
||||
assert.deepEqual(steps.hits, [true, false]);
|
||||
assert.deepEqual(
|
||||
steps.boards.map((b) => [at(b.player, "bar"), at(b.player, 21), at(b.computer, "bar")]),
|
||||
[
|
||||
[1, 0, 0],
|
||||
[0, 1, 1],
|
||||
[0, 1, 1],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("une sortie de pions, jusqu'à la fin de la partie", async () => {
|
||||
const game = await playerMove(PLAYER_ALMOST_DONE, [6, 5], "6/off 5/off");
|
||||
assert.equal(game.state.phase, "over");
|
||||
const steps = await game.lastMoveSteps();
|
||||
assert.ok(steps !== null);
|
||||
assert.deepEqual(steps.hits, [false, false]);
|
||||
assert.deepEqual(
|
||||
steps.boards.map((b) => at(b.player, "off")),
|
||||
[13, 14, 15],
|
||||
);
|
||||
});
|
||||
|
||||
test("le coup de l'ordinateur, vu dans sa propre numérotation", async () => {
|
||||
const engine = await loadNodeEngine();
|
||||
// Vous commencez avec 3-1, puis l'ordinateur tire 6-4.
|
||||
const game = Game.start(engine, { dice: fixedDice([3, 1, 6, 4]) });
|
||||
for (const halfMove of parseHalfMoves("8/5 6/5")) {
|
||||
await game.playHalfMove(halfMove);
|
||||
}
|
||||
await game.playComputerTurn();
|
||||
const steps = await game.lastMoveSteps();
|
||||
assert.ok(steps !== null);
|
||||
assert.equal(steps.side, "computer");
|
||||
assert.equal(steps.boards.length, steps.halfMoves.length + 1);
|
||||
// Chaque demi-coup déplace un pion de l'ordinateur de sa case de départ.
|
||||
for (const [i, h] of steps.halfMoves.entries()) {
|
||||
const from = h.from === BAR ? "bar" : h.from;
|
||||
const before = steps.boards[i]?.computer as BoardSide;
|
||||
const after = steps.boards[i + 1]?.computer as BoardSide;
|
||||
assert.equal(at(after, from), at(before, from) - 1);
|
||||
}
|
||||
assert.deepEqual(steps.boards.at(-1), await game.displayedBoard());
|
||||
});
|
||||
|
||||
test("avant le premier coup, il n'y a rien à montrer", async () => {
|
||||
const engine = await loadNodeEngine();
|
||||
const game = Game.start(engine, { dice: fixedDice([3, 1]) });
|
||||
assert.equal(await game.lastMoveSteps(), null);
|
||||
});
|
||||
Reference in New Issue
Block a user