107 lines
5.9 KiB
TypeScript
107 lines
5.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
createVictoryContinuation, validateVictoryContinuation, voteVictoryContinuation,
|
|
MAX_VICTORY_EXTRA_STRONGHOLDS, VictoryContinuationError,
|
|
type VictoryContinuation, type VictoryContinuationChoice,
|
|
} from '../game/victory-continuation';
|
|
|
|
const source = () => createVictoryContinuation({ event: 'victory:5', turn: 5, resume: 'mentat',
|
|
order: ['a', 'b', 'c', 'd', 'e'], sides: [['c', 'a'], ['a', 'c'], ['d']] });
|
|
function reject(frame: VictoryContinuation, choice: VictoryContinuationChoice) {
|
|
const before = structuredClone(frame);
|
|
assert.throws(() => voteVictoryContinuation(frame, choice), VictoryContinuationError);
|
|
assert.deepEqual(frame, before);
|
|
}
|
|
|
|
void test('allied rows are deduplicated and every qualifying member votes in public order', () => {
|
|
const frame = source();
|
|
assert.deepEqual(frame.sides, [['a', 'c'], ['d']]);
|
|
assert.deepEqual(frame.voters, ['a', 'c', 'd']);
|
|
assert.deepEqual(frame.accepted, []);
|
|
assert.equal(frame.extraStrongholds, null);
|
|
validateVictoryContinuation(frame);
|
|
});
|
|
|
|
void test('distinct solo sides may agree in turn ten and in a later extended turn', () => {
|
|
for (const turn of [10, 11]) {
|
|
const frame = createVictoryContinuation({ event: `victory:${turn}`, turn, resume: 'advance',
|
|
order: ['a', 'b', 'c'], sides: [['c'], ['a']] });
|
|
assert.deepEqual(frame.sides, [['a'], ['c']]);
|
|
assert.deepEqual(frame.voters, ['a', 'c']);
|
|
const first = voteVictoryContinuation(frame, { event: frame.event, player: 'a', choice: 'continue', extraStrongholds: 1 });
|
|
assert.equal(first.outcome, 'pending');
|
|
if (first.outcome === 'pending')
|
|
assert.deepEqual(voteVictoryContinuation(first.frame, { event: frame.event, player: 'c', choice: 'continue' }),
|
|
{ outcome: 'continue', extraStrongholds: 1 });
|
|
}
|
|
});
|
|
|
|
for (const extra of [0, 3, MAX_VICTORY_EXTRA_STRONGHOLDS]) void test(
|
|
`the exact shared proposal of ${extra} extra strongholds requires every qualifier's acceptance`, () => {
|
|
const initial = source(), original = structuredClone(initial);
|
|
const first = voteVictoryContinuation(initial, { event: initial.event, player: 'a', choice: 'continue', extraStrongholds: extra });
|
|
assert.equal(first.outcome, 'pending');
|
|
if (first.outcome !== 'pending') return;
|
|
assert.deepEqual(initial, original);
|
|
assert.deepEqual(first.frame.accepted, ['a']);
|
|
assert.equal(first.frame.extraStrongholds, extra);
|
|
const restored = JSON.parse(JSON.stringify(first.frame)) as VictoryContinuation;
|
|
validateVictoryContinuation(restored);
|
|
const second = voteVictoryContinuation(restored, { event: initial.event, player: 'c', choice: 'continue' });
|
|
assert.equal(second.outcome, 'pending');
|
|
if (second.outcome !== 'pending') return;
|
|
assert.deepEqual(second.frame.accepted, ['a', 'c']);
|
|
assert.deepEqual(voteVictoryContinuation(second.frame, { event: initial.event, player: 'd', choice: 'continue' }),
|
|
{ outcome: 'continue', extraStrongholds: extra });
|
|
},
|
|
);
|
|
|
|
void test('any qualifying participant may choose the original victory instead', () => {
|
|
const frame = source();
|
|
assert.deepEqual(voteVictoryContinuation(frame, { event: frame.event, player: 'a', choice: 'finish' }), { outcome: 'finish' });
|
|
const first = voteVictoryContinuation(frame, { event: frame.event, player: 'a', choice: 'continue', extraStrongholds: 1 });
|
|
assert.equal(first.outcome, 'pending');
|
|
if (first.outcome !== 'pending') return;
|
|
assert.deepEqual(voteVictoryContinuation(first.frame, { event: frame.event, player: 'c', choice: 'finish' }), { outcome: 'finish' });
|
|
});
|
|
|
|
void test('stale events, non-voters and unauthorized proposal changes reject without changing the receipt', () => {
|
|
const frame = source();
|
|
for (const choice of [
|
|
{ event: 'old', player: 'a', choice: 'continue', extraStrongholds: 0 },
|
|
{ event: frame.event, player: 'b', choice: 'continue', extraStrongholds: 0 },
|
|
{ event: frame.event, player: 'c', choice: 'continue', extraStrongholds: 0 },
|
|
{ event: frame.event, player: 'a', choice: 'continue' },
|
|
{ event: frame.event, player: 'a', choice: 'continue', extraStrongholds: -1 },
|
|
{ event: frame.event, player: 'a', choice: 'continue', extraStrongholds: 21 },
|
|
{ event: frame.event, player: 'a', choice: 'continue', extraStrongholds: 0.5 },
|
|
{ event: frame.event, player: 'a', choice: 'finish', extraStrongholds: 1 },
|
|
] as VictoryContinuationChoice[]) reject(frame, choice);
|
|
const first = voteVictoryContinuation(frame, { event: frame.event, player: 'a', choice: 'continue', extraStrongholds: 2 });
|
|
assert.equal(first.outcome, 'pending');
|
|
if (first.outcome !== 'pending') return;
|
|
reject(first.frame, { event: frame.event, player: 'a', choice: 'continue' });
|
|
reject(first.frame, { event: frame.event, player: 'c', choice: 'continue', extraStrongholds: 3 });
|
|
});
|
|
|
|
void test('unsupported source profiles and forged saved receipts are rejected', () => {
|
|
const frame = source(), snapshot = structuredClone(frame);
|
|
assert.throws(() => createVictoryContinuation({ event: 'invalid', turn: 0, resume: 'mentat',
|
|
order: ['a', 'b', 'c'], sides: [['a', 'b'], ['c']] }), VictoryContinuationError);
|
|
assert.throws(() => createVictoryContinuation({ event: 'single', turn: 2, resume: 'mentat',
|
|
order: ['a', 'b'], sides: [['a']] }), VictoryContinuationError);
|
|
assert.throws(() => createVictoryContinuation({ event: 'overlap', turn: 2, resume: 'mentat',
|
|
order: ['a', 'b', 'c'], sides: [['a', 'b'], ['b', 'c']] }), VictoryContinuationError);
|
|
for (const forged of [
|
|
{ ...frame, extraStrongholds: 0 },
|
|
{ ...frame, accepted: ['c'], extraStrongholds: 0 },
|
|
{ ...frame, voters: ['a', 'd'] },
|
|
{ ...frame, sides: [['a', 'c'], ['a', 'd']] },
|
|
{ ...frame, turn: 0 },
|
|
{ ...frame, accepted: ['a', 'c', 'd'], extraStrongholds: 0 },
|
|
{ ...frame, extra: true },
|
|
]) assert.throws(() => validateVictoryContinuation(forged as VictoryContinuation), VictoryContinuationError);
|
|
assert.deepEqual(frame, snapshot);
|
|
});
|