76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
// Une partie complète sur le vrai moteur WebAssembly, avec des dés imposés :
|
|
// l'ordinateur contre une stratégie simple du joueur (toujours le premier
|
|
// coup légal, saisi demi-coup par demi-coup, et toujours prendre).
|
|
//
|
|
// 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 { Game } from "../src/domain/game/game.js";
|
|
import type { DiceSource } from "../src/domain/game/roll.js";
|
|
import { replay } from "../src/domain/game/state.js";
|
|
import { loadNodeEngine } from "./support/node-engine.js";
|
|
|
|
/** Des dés pseudo-aléatoires reproductibles (générateur congruentiel). */
|
|
function seededDice(seed: number): DiceSource {
|
|
let state = seed;
|
|
return () => {
|
|
state = (state * 1103515245 + 12345) % 2147483648;
|
|
return 1 + (Math.floor(state / 65536) % 6);
|
|
};
|
|
}
|
|
|
|
/** Joue jusqu'à la fin ; renvoie le nombre d'actions. */
|
|
async function playToTheEnd(game: Game, engine: Awaited<ReturnType<typeof loadNodeEngine>>) {
|
|
let actions = 0;
|
|
while (game.state.phase !== "over") {
|
|
assert.ok(++actions < 2000, "la partie ne finit pas");
|
|
const state = game.state;
|
|
if (state.turn === "computer" && state.phase === "doubled") {
|
|
game.take();
|
|
} else if (state.turn === "computer") {
|
|
await game.playComputerTurn();
|
|
} else if (state.phase === "rolling") {
|
|
await game.roll();
|
|
} else {
|
|
assert.ok(state.roll !== null);
|
|
const [first] = await engine.legalMoves(state.position, state.roll);
|
|
if (first === undefined) {
|
|
// Aucun coup possible : le joueur laisse passer son tour.
|
|
assert.equal(await game.cannotMove(), true);
|
|
await game.skipTurn();
|
|
continue;
|
|
}
|
|
for (const halfMove of first.halfMoves) {
|
|
assert.equal(await game.playHalfMove(halfMove), true);
|
|
}
|
|
assert.equal(game.moveComplete, true);
|
|
await game.confirmMove();
|
|
}
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
for (const [seed, ply] of [
|
|
[1, 0],
|
|
[7, 2],
|
|
] as const) {
|
|
test(`partie complète, dés ${seed}, ordinateur à ${ply} pli(s)`, async () => {
|
|
const engine = await loadNodeEngine();
|
|
const game = Game.start(engine, { dice: seededDice(seed), ply });
|
|
await playToTheEnd(game, engine);
|
|
|
|
const events = game.events;
|
|
const last = events.at(-1);
|
|
assert.equal(last?.type, "GameEnded");
|
|
const result = game.state.result;
|
|
assert.ok(result !== null);
|
|
assert.ok(result.points >= 1);
|
|
assert.ok(events.some((e) => e.type === "MovePlayed" && e.side === "player"));
|
|
assert.ok(events.some((e) => e.type === "MovePlayed" && e.side === "computer"));
|
|
// Rejouer le journal redonne le même état.
|
|
assert.deepEqual(replay(events), game.state);
|
|
});
|
|
}
|