import test from "node:test"; import assert from "node:assert/strict"; import { GameStore } from "../src/game.js"; const setup = () => { const store = new GameStore({ random: () => 0.42 }); const first = store.createRoom("甲"); const second = store.joinRoom(first.room.id, "乙"); const third = store.joinRoom(first.room.id, "丙"); store.start(first.room.id, first.playerId); return { store, room: first.room, ids: [first.playerId, second.playerId, third.playerId] }; }; test("deals two hidden cards and four cards to every player", () => { const { room } = setup(); assert.equal(room.hidden.length, 2); assert.deepEqual(room.players.map((player) => player.hand.length), [4, 4, 4]); assert.equal([...room.hidden, ...room.players.flatMap((player) => player.hand)].length, 14); }); test("a room view never reveals another player's hand or hidden cards", () => { const { store, room, ids } = setup(); const view = store.view(room, ids[0]); assert.equal(view.hidden, null); assert.ok(Array.isArray(view.players[0].hand)); assert.equal(view.players[1].hand, undefined); assert.equal(view.players[2].hand, undefined); }); test("one-card request logs both requested values but hides the transferred value", () => { const { store, room, ids } = setup(); room.players[1].hand = [2, 3, 4, 5]; store.requestCards(room.id, ids[0], { targetId: ids[1], values: [2, 3], mode: "one" }); assert.match(room.logs.at(-1).message, /“取一张”请求/); assert.match(room.logs.at(-1).message, /数字 2 或 3/); const result = store.answerRequest(room.id, ids[1], { value: 3 }); assert.equal(room.players[0].hand.at(-1) >= 2, true); assert.equal(room.players[1].hand.includes(3), false); assert.match(room.logs.at(-1).message, /交出了 1 张牌/); assert.doesNotMatch(room.logs.at(-1).message, /数字 3/); assert.equal(result.privateEvents.length, 2); }); test("all-card request can be declined unless target holds both values", () => { const { store, room, ids } = setup(); room.players[1].hand = [2, 2, 4, 5]; store.requestCards(room.id, ids[0], { targetId: ids[1], values: [2, 3], mode: "all" }); assert.match(room.logs.at(-1).message, /“取同号全部”请求/); assert.match(room.logs.at(-1).message, /数字 2 或 3/); assert.doesNotThrow(() => store.answerRequest(room.id, ids[1], { value: null })); }); test("a player cannot request the same unordered pair on consecutive personal turns", () => { const { store, room, ids } = setup(); room.players.forEach((player) => { player.hand = [4, 4, 5, 5]; }); store.requestCards(room.id, ids[0], { targetId: ids[1], values: [2, 3], mode: "one" }); store.answerRequest(room.id, ids[1], { value: null }); store.requestCards(room.id, ids[1], { targetId: ids[2], values: [2, 4], mode: "one" }); store.answerRequest(room.id, ids[2], { value: 4 }); store.requestCards(room.id, ids[2], { targetId: ids[0], values: [3, 5], mode: "one" }); store.answerRequest(room.id, ids[0], { value: 5 }); assert.throws( () => store.requestCards(room.id, ids[0], { targetId: ids[2], values: [3, 2], mode: "all" }), /不能连续两个回合请求相同的两种牌/ ); assert.doesNotThrow(() => store.requestCards(room.id, ids[0], { targetId: ids[2], values: [2, 4], mode: "all" }) ); }); test("correct guess scores and finishes game", () => { const { store, room, ids } = setup(); store.proposeGuess(room.id, ids[0], room.hidden); store.answerGuess(room.id, ids[1], { choice: "agree", guesserId: ids[0] }); const result = store.answerGuess(room.id, ids[2], { choice: "agree", guesserId: ids[0] }); assert.equal(result.correct, true); assert.equal(room.phase, "finished"); assert.equal(room.players[0].score, 3); assert.equal(room.players[1].score, 1); assert.equal(room.players[2].score, 1); }); test("a later player can agree with a counter guess", () => { const { store, room, ids } = setup(); const wrongGuess = room.hidden[0] === 2 && room.hidden[1] === 2 ? [2, 3] : [2, 2]; store.proposeGuess(room.id, ids[0], wrongGuess); store.answerGuess(room.id, ids[1], { choice: "counter", values: room.hidden }); const result = store.answerGuess(room.id, ids[2], { choice: "agree", guesserId: ids[1] }); assert.equal(result.correct, true); assert.equal(room.players[0].score, -1); assert.equal(room.players[1].score, 3); assert.equal(room.players[2].score, 1); }); test("the only abandoning player must agree or counter on a second action", () => { const { store, room, ids } = setup(); store.proposeGuess(room.id, ids[0], room.hidden); store.answerGuess(room.id, ids[1], { choice: "abandon" }); const firstRound = store.answerGuess(room.id, ids[2], { choice: "agree", guesserId: ids[0] }); assert.equal(firstRound.resolved, false); assert.equal(room.pending.forcedPlayerId, ids[1]); assert.throws( () => store.answerGuess(room.id, ids[1], { choice: "abandon" }), /必须选择认同或另猜/ ); const result = store.answerGuess(room.id, ids[1], { choice: "agree", guesserId: ids[0] }); assert.equal(result.correct, true); assert.equal(room.players[1].score, 1); }); test("if both later players abandon, a wrong initiator is the only player eliminated", () => { const { store, room, ids } = setup(); const wrongGuess = room.hidden[0] === 2 && room.hidden[1] === 2 ? [2, 3] : [2, 2]; store.proposeGuess(room.id, ids[0], wrongGuess); store.answerGuess(room.id, ids[1], { choice: "abandon" }); const result = store.answerGuess(room.id, ids[2], { choice: "abandon" }); assert.equal(result.correct, false); assert.equal(room.players[0].eliminated, true); assert.equal(room.players[1].eliminated, false); assert.equal(room.players[2].eliminated, false); assert.equal(room.currentPlayerId, undefined); assert.equal(store.view(room, ids[1]).currentPlayerId, ids[1]); });