Offer Semuta after clean Sapho battle chooser discard
This commit is contained in:
1 parent
989a986d68
commit
ab5c78226a
10 files changed
+196
-32
No files matched your search
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { viewGame, type Action, type Game } from "../game/engine";
|
||||
import { SEMUTA_DRUG_ID } from "../game/semuta-drug";
|
||||
import type { RoomsClock } from "../db/rooms";
|
||||
import { unitStore } from "./fixture-nexus-room-store";
|
||||
import {
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
saphoBattleCustody,
|
||||
saphoBattleOrderAction,
|
||||
saphoBattleOrderGame,
|
||||
takeSaphoBattleCard,
|
||||
} from "./fixture-sapho-battle-order";
|
||||
|
||||
const clock: RoomsClock = { now: () => 87000, sleep: async () => {} };
|
||||
@@ -282,3 +284,36 @@ void test("authenticated first and last claims survive CAS, a defender-owned bat
|
||||
assert.deepEqual(rows(f.sqlite).seats, seatRows);
|
||||
}
|
||||
});
|
||||
|
||||
void test("authenticated Semuta claim restores one between-battle chooser order", async (t) => {
|
||||
const f = await fixture(t, false, 2);
|
||||
let game = await f.restart().readRoom(f.code);
|
||||
game.semutaPreview = true;
|
||||
takeSaphoBattleCard(game, f.ids[3], SEMUTA_DRUG_ID);
|
||||
f.sqlite.prepare("UPDATE rooms SET state=? WHERE code=?")
|
||||
.run(JSON.stringify(game), f.code);
|
||||
const seatRows = rows(f.sqlite).seats;
|
||||
game = await act(f, f.holder, saphoBattleOrderAction(game, f.holder, "first"));
|
||||
const event = game.pendingTreacheryDiscard!.batch.event;
|
||||
assert.equal(game.pendingTreacheryDiscard?.reaction?.stage, "offer");
|
||||
assert.equal(game.active, f.holder);
|
||||
assert.equal(game.battleOrder?.uses.length, 1);
|
||||
const version = game.version;
|
||||
await f.restart().continueRoomAutomatic(f.code, clock);
|
||||
assert.equal((await f.restart().readRoom(f.code)).version, version);
|
||||
const ownerView = await f.restart().readSeatView(f.code, f.auths[3]);
|
||||
const rivalView = await f.restart().readSeatView(f.code, f.auths[1]);
|
||||
assert.equal(ownerView.semutaReaction?.canCommit, true);
|
||||
assert.equal(rivalView.semutaReaction?.canCommit, false);
|
||||
game = await act(f, f.ids[3], { type: "semutaCommit", event });
|
||||
assert.equal(game.pendingTreacheryDiscard, null);
|
||||
assert.equal(game.active, f.holder);
|
||||
assert.equal(game.battleOrder?.uses.length, 1);
|
||||
assert.equal(game.players[3].hand.filter(card => card.id === SAPHO_BATTLE_CARD).length, 1);
|
||||
assert.equal(game.discard.filter(card => card.id === SEMUTA_DRUG_ID).length, 1);
|
||||
assert.deepEqual(rows(f.sqlite).seats, seatRows);
|
||||
const choice = chooseSaphoBattleAction(game);
|
||||
game = await act(f, choice.player, choice.action);
|
||||
assert.ok(game.battle);
|
||||
assert.equal(game.battleOrder?.uses.length, 1);
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type Action,
|
||||
type Game,
|
||||
} from '../game/engine';
|
||||
import { SEMUTA_DRUG_ID } from '../game/semuta-drug';
|
||||
import {
|
||||
battleChooserEvent,
|
||||
quoteBattleChoosers,
|
||||
@@ -95,6 +96,40 @@ void test('first and last reorder Basic/Advanced remaining choosers without chan
|
||||
}
|
||||
});
|
||||
|
||||
void test('clean between-battle Sapho discard offers Semuta before chooser acts', () => {
|
||||
for (const [owner, mode, nextChooser] of [
|
||||
['c', 'first', 'c'],
|
||||
['a', 'last', 'b'],
|
||||
] as const) {
|
||||
let g = saphoBattleOrderGame({ holder: owner, semutaPreview: true });
|
||||
takeSaphoBattleCard(g, 'r', SEMUTA_DRUG_ID);
|
||||
const initialPairs = pairs(g);
|
||||
g = applyAction(g, owner, saphoBattleOrderAction(g, owner, mode));
|
||||
const event = g.pendingTreacheryDiscard!.batch.event;
|
||||
assert.equal(g.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
||||
assert.equal(g.active, nextChooser);
|
||||
assert.equal(g.battleOrder?.uses.length, 1);
|
||||
assert.deepEqual(pairs(g), initialPairs);
|
||||
assert.equal(viewGame(g, 'r').semutaReaction?.canCommit, true);
|
||||
assert.equal(viewGame(g, 'b').semutaReaction?.canCommit, false);
|
||||
assert.deepEqual(normalizeAutomaticGame(restore(g)), restore(g));
|
||||
let declined = restore(g);
|
||||
for (const id of ['a', 'b', 'c', 'r'])
|
||||
declined = applyAction(declined, id, { type: 'semutaPass', event });
|
||||
assert.equal(declined.pendingTreacheryDiscard, null);
|
||||
assert.equal(declined.battleOrder?.uses.length, 1);
|
||||
const claimed = applyAction(restore(g), 'r', { type: 'semutaCommit', event });
|
||||
assert.equal(claimed.pendingTreacheryDiscard, null);
|
||||
assert.equal(claimed.players.find(p => p.id === 'r')!.hand.filter(c => c.id === SAPHO_BATTLE_CARD).length, 1);
|
||||
assert.equal(claimed.discard.filter(c => c.id === SEMUTA_DRUG_ID).length, 1);
|
||||
assert.deepEqual(claimed.battleOrder, declined.battleOrder);
|
||||
const chosen = choose(claimed);
|
||||
assert.ok(chosen.battle);
|
||||
assert.equal(chosen.battleOrder?.uses.length, 1);
|
||||
reject(claimed, 'r', { type: 'semutaCommit', event }, /fresh|available/i);
|
||||
}
|
||||
});
|
||||
|
||||
void test('a later physical defender chooses its opponent while original combat roles, powers and tie owner stay fixed', () => {
|
||||
for (const advanced of [false, true]) {
|
||||
let g = saphoBattleOrderGame({ advanced });
|
||||
|
||||
Reference in new issue
Block a user