// Le coup évident du double toucher (charte, ecrans-etape-1.md, 1i), sur le // vrai moteur WebAssembly : un seul coup légal complet commence par le pion // touché, ou le moteur ne propose qu'un coup. // // 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 { Engine } from "../src/domain/game/engine.js"; import { Game } from "../src/domain/game/game.js"; import { formatHalfMoves, type HalfMove } 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 6, un sur sa case 1. const SIX_AND_ONE = "BQAACAIAAAAAAA"; // Le joueur, au trait : deux pions, sur ses cases 6 et 5. const PLAYER_ALMOST_DONE = "/38AAABQAAAAAA"; let engine: Engine | null = null; async function loaded(): Promise { engine ??= await loadNodeEngine(); return engine; } /** Une partie à partir d'une position, joueur au trait, dés lancés. */ async function from(position: string, dice: [number, number]): Promise { const game = Game.start(await loaded(), { setup: { position, firstTurn: "player" }, dice: fixedDice(dice), }); await game.roll(); return game; } function text(halfMoves: readonly HalfMove[] | null): string | null { return halfMoves === null ? null : formatHalfMoves(halfMoves); } test("au départ avec 3-1 : plusieurs coups commencent par chaque pion, rien d'évident", async () => { const game = Game.start(await loaded(), { dice: fixedDice([3, 1]) }); for (const point of [24, 13, 8, 6]) { assert.equal(await game.obviousMove(point), null, `pion de la ${point}`); } // Rien n'a été saisi. assert.deepEqual(game.pendingHalfMoves, []); }); test("6-1 avec des pions sur 6 et 1 : seul le pion de la 1 a un coup évident", async () => { const game = await from(SIX_AND_ONE, [6, 1]); // 1/off, puis le 6 sort l'autre pion : un seul coup. assert.equal(text(await game.obviousMove(1)), "1/off 6/off"); // Depuis la 6 : 6/off 1/off, ou 6/5/off (le pion de la 1 reste). assert.equal(await game.obviousMove(6), null); // Une case sans pion n'a pas de coup. assert.equal(await game.obviousMove(3), null); }); test("un seul coup légal : il est évident depuis chaque pion, même à moitié saisi", async () => { const game = await from(PLAYER_ALMOST_DONE, [6, 6]); assert.equal((await (await loaded()).legalMoves(game.state.position, [6, 6])).length, 1); const fromFive = await game.obviousMove(5); const fromSix = await game.obviousMove(6); assert.ok(fromFive !== null && fromSix !== null); assert.equal(fromFive.length, 2); assert.equal(fromSix.length, 2); // Une fois 6/off saisi, il ne reste que 5/off. assert.equal(await game.playHalfMove({ from: 6, to: 0 }), true); assert.equal(text(await game.obviousMove(5)), "5/off"); // Le coup évident se joue tel quel, demi-coup par demi-coup. for (const halfMove of (await game.obviousMove(5)) ?? []) { assert.equal(await game.playHalfMove(halfMove), true); } assert.equal(game.moveComplete, true); }); test("hors du tour de jeu du joueur, aucun coup évident", async () => { const game = Game.start(await loaded(), { dice: fixedDice([1, 3]) }); assert.equal(game.state.turn, "computer"); assert.equal(await game.obviousMove(13), null); });