114 lines
6.4 KiB
TypeScript
114 lines
6.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import { actTruthTravel as act, createTruthTravelFixture as fixture,
|
|
truthTravelInventory as inventory, truthTravelPlayer as player } from './fixture-truth-travel';
|
|
|
|
function reject(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => act(game, actor, action));
|
|
assert.deepEqual(game, before, 'Conflicting consent must leave the source game unchanged.');
|
|
}
|
|
function travelOffer(game: Game, proposer: string, traveler: string, dueTurn: number) {
|
|
const next = act(game, proposer, {type:'dealOffer', target:traveler, terms:'Travel in a future turn.',
|
|
travel:{kind:'ship', territory:'polar_sink', minimum:1, answer:true, dueTurn}});
|
|
const deal = next.deals?.at(-1); assert.ok(deal);
|
|
return {game:next, id:deal.id};
|
|
}
|
|
function paymentOffer(game: Game, proposer: string, target: string, payer: string, recipient: string, dueTurn: number) {
|
|
const next = act(game, proposer, {type:'dealOffer', target, terms:'Pay a future bribe.',
|
|
payment:{kind:'deferredBribe', payer, recipient, amount:2, dueTurn}});
|
|
const deal = next.deals?.at(-1); assert.ok(deal);
|
|
return {game:next, id:deal.id};
|
|
}
|
|
function accept(game: Game, recipient: string, id: string) {
|
|
return act(game, recipient, {type:'dealRespond', deal:id, accept:true});
|
|
}
|
|
function deal(game: Game, id: string) {
|
|
const found = game.deals?.find(row => row.id === id); assert.ok(found); return found;
|
|
}
|
|
|
|
/** Each case uses the genuine Basic Atreides/Emperor setup and actual native
|
|
* first-turn pre-shipment window. These are independent two-party offers and
|
|
* acceptances; no wallet, force, pending debt or travel receipt is staged. */
|
|
void test('a same-turn deferred debt cannot be accepted after the payer schedules travel', () => {
|
|
const f = fixture(), dueTurn = f.game.turn + 1, originalCards = inventory(f.game);
|
|
const future = travelOffer(f.game, f.asker, f.target, dueTurn);
|
|
let game = accept(future.game, f.target, future.id);
|
|
assert.equal(viewGame(game, f.target).dealTravelStatus[future.id], 'scheduled');
|
|
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, dueTurn);
|
|
game = payment.game;
|
|
const purse = player(game, f.target).spice;
|
|
reject(game, f.target, {type:'dealRespond', deal:payment.id, accept:true});
|
|
assert.equal(deal(game, future.id).status, 'active');
|
|
assert.equal(deal(game, payment.id).status, 'offered');
|
|
assert.equal(game.travelPromises?.filter(row => row.sourceDeal === future.id).length, 1);
|
|
assert.equal(player(game, f.target).spice, purse);
|
|
assert.deepEqual(inventory(game), originalCards);
|
|
});
|
|
|
|
void test('a same-turn scheduled journey cannot be accepted after the traveler owes a deferred debt', () => {
|
|
const f = fixture(), dueTurn = f.game.turn + 1, originalCards = inventory(f.game);
|
|
const payment = paymentOffer(f.game, f.asker, f.target, f.target, f.asker, dueTurn);
|
|
let game = accept(payment.game, f.target, payment.id);
|
|
assert.equal(deal(game, payment.id).paymentStatus, 'pending');
|
|
const future = travelOffer(game, f.asker, f.target, dueTurn);
|
|
game = future.game;
|
|
const purse = player(game, f.target).spice;
|
|
reject(game, f.target, {type:'dealRespond', deal:future.id, accept:true});
|
|
assert.equal(deal(game, payment.id).status, 'active');
|
|
assert.equal(deal(game, payment.id).paymentStatus, 'pending');
|
|
assert.equal(deal(game, future.id).status, 'offered');
|
|
assert.equal(game.travelPromises?.some(row => row.sourceDeal === future.id) ?? false, false);
|
|
assert.equal(player(game, f.target).spice, purse);
|
|
assert.deepEqual(inventory(game), originalCards);
|
|
});
|
|
|
|
for (const order of ['travel-first', 'payment-first'] as const) void test(
|
|
`different future due turns coexist when accepted ${order}`, () => {
|
|
const f = fixture(), travelDue = f.game.turn + 1, paymentDue = travelDue + 1;
|
|
let game = f.game, travelId: string, paymentId: string;
|
|
if (order === 'travel-first') {
|
|
const future = travelOffer(game, f.asker, f.target, travelDue); travelId = future.id;
|
|
game = accept(future.game, f.target, travelId);
|
|
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, paymentDue); paymentId = payment.id;
|
|
game = accept(payment.game, f.target, paymentId);
|
|
} else {
|
|
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, paymentDue); paymentId = payment.id;
|
|
game = accept(payment.game, f.target, paymentId);
|
|
const future = travelOffer(game, f.asker, f.target, travelDue); travelId = future.id;
|
|
game = accept(future.game, f.target, travelId);
|
|
}
|
|
assert.equal(deal(game, travelId).status, 'active');
|
|
assert.equal(viewGame(game, f.target).dealTravelStatus[travelId], 'scheduled');
|
|
assert.equal(game.travelPromises?.find(row => row.sourceDeal === travelId)?.turn, travelDue);
|
|
assert.equal(deal(game, paymentId).paymentStatus, 'pending');
|
|
assert.equal(player(game, f.target).spice, player(f.game, f.target).spice);
|
|
assert.deepEqual(inventory(game), f.originalCards);
|
|
},
|
|
);
|
|
|
|
for (const order of ['travel-first', 'payment-first'] as const) void test(
|
|
`the same future due turn can involve a different payment payer when accepted ${order}`, () => {
|
|
const f = fixture(), dueTurn = f.game.turn + 1;
|
|
let game = f.game, travelId: string, paymentId: string;
|
|
if (order === 'travel-first') {
|
|
const future = travelOffer(game, f.asker, f.target, dueTurn); travelId = future.id;
|
|
game = accept(future.game, f.target, travelId);
|
|
const payment = paymentOffer(game, f.asker, f.target, f.asker, f.target, dueTurn); paymentId = payment.id;
|
|
game = accept(payment.game, f.target, paymentId);
|
|
} else {
|
|
const payment = paymentOffer(game, f.asker, f.target, f.asker, f.target, dueTurn); paymentId = payment.id;
|
|
game = accept(payment.game, f.target, paymentId);
|
|
const future = travelOffer(game, f.asker, f.target, dueTurn); travelId = future.id;
|
|
game = accept(future.game, f.target, travelId);
|
|
}
|
|
assert.equal(deal(game, travelId).status, 'active');
|
|
assert.equal(viewGame(game, f.target).dealTravelStatus[travelId], 'scheduled');
|
|
assert.equal(deal(game, paymentId).paymentStatus, 'pending');
|
|
assert.equal(player(game, f.asker).spice, player(f.game, f.asker).spice);
|
|
assert.equal(player(game, f.target).spice, player(f.game, f.target).spice);
|
|
assert.deepEqual(inventory(game), f.originalCards);
|
|
},
|
|
);
|