Add three-member short alliance prototype
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
cdc47ed2e1
commit
ef92ddf2b9
12 files changed
+343
-12
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
answerCoalitionAdmission, beginCoalitionAdmission, coalitionOf, emptyCoalitionState,
|
||||
leaveCoalition, type CoalitionSeat, type CoalitionState, type CoalitionTransition,
|
||||
} from '../game/coalition-alliance';
|
||||
|
||||
function seats(): CoalitionSeat[] {
|
||||
return [{ id: 'a', ally: 'b', allySinceTurn: 2 }, { id: 'b', ally: 'a', allySinceTurn: 2 },
|
||||
{ id: 'c', ally: null }, { id: 'd', ally: null }];
|
||||
}
|
||||
|
||||
function apply(players: CoalitionSeat[], transition: CoalitionTransition): CoalitionSeat[] {
|
||||
return players.map(player => {
|
||||
const result = { ...player };
|
||||
if (Object.hasOwn(transition.allyUpdates, player.id)) result.ally = transition.allyUpdates[player.id];
|
||||
if (Object.hasOwn(transition.allySinceUpdates, player.id)) {
|
||||
const turn = transition.allySinceUpdates[player.id];
|
||||
if (turn === null) delete result.allySinceTurn;
|
||||
else result.allySinceTurn = turn;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function begin(players: CoalitionSeat[], state: CoalitionState, entrant: string, event: string): CoalitionTransition {
|
||||
const group = coalitionOf(players, state, 'a'); assert.ok(group);
|
||||
return beginCoalitionAdmission(players, state, { event, nexus: 'nexus', turn: 5,
|
||||
groupId: group.id, entrant, proposer: 'a', expectedRevision: state.revision });
|
||||
}
|
||||
|
||||
function answer(players: CoalitionSeat[], state: CoalitionState, actor: string, event: string): CoalitionTransition {
|
||||
return answerCoalitionAdmission(players, state, { event, nexus: 'nexus', turn: 5,
|
||||
actor, accept: true, expectedRevision: state.revision });
|
||||
}
|
||||
|
||||
function triple(variant: CoalitionState['variant']) {
|
||||
const players = seats(), original = emptyCoalitionState(variant);
|
||||
const offered = begin(players, original, 'c', 'first');
|
||||
const approved = answer(players, offered.state, 'b', 'first');
|
||||
const completed = answer(players, approved.state, 'c', 'first');
|
||||
return { players: apply(players, completed), state: completed.state, original, offered, approved };
|
||||
}
|
||||
|
||||
void test('three-short preserves its saved variant and the actual invitation revision across unanimous admission', () => {
|
||||
const { players, state, original, offered, approved } = triple('three-short');
|
||||
assert.deepEqual(original, { version: 1, variant: 'three-short', revision: 0, groups: [], proposal: null, usedEvents: [] });
|
||||
assert.equal(offered.state.variant, 'three-short');
|
||||
assert.deepEqual(offered.state.proposal?.approvals, ['a']);
|
||||
assert.equal(approved.state.variant, 'three-short');
|
||||
assert.deepEqual(approved.state.proposal?.approvals, ['a', 'b']);
|
||||
assert.equal(state.variant, 'three-short');
|
||||
assert.equal(state.revision, 3);
|
||||
assert.deepEqual(state.usedEvents, ['first']);
|
||||
assert.deepEqual(coalitionOf(players, JSON.parse(JSON.stringify(state)) as CoalitionState, 'a')?.members, ['a', 'b', 'c']);
|
||||
assert.equal(state.proposal, null);
|
||||
});
|
||||
|
||||
void test('three-short refuses a fourth member while unlimited preserves its separate six-member capacity', () => {
|
||||
for (const variant of [undefined, 'three-short'] as const) {
|
||||
const { players, state } = triple(variant), before = structuredClone({ players, state });
|
||||
assert.throws(() => begin(players, state, 'd', 'second'));
|
||||
assert.deepEqual({ players, state }, before);
|
||||
}
|
||||
const { players, state } = triple('unlimited');
|
||||
const offer = begin(players, state, 'd', 'second');
|
||||
let next = answer(players, offer.state, 'b', 'second');
|
||||
next = answer(players, next.state, 'c', 'second');
|
||||
next = answer(players, next.state, 'd', 'second');
|
||||
assert.equal(next.state.variant, 'unlimited');
|
||||
assert.deepEqual(coalitionOf(apply(players, next), next.state, 'a')?.members, ['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
void test('three-short departure retains variant and rejects a stale invitation revision or replayed event', () => {
|
||||
const { players, state } = triple('three-short');
|
||||
const before = structuredClone({ players, state });
|
||||
const left = leaveCoalition(players, state, { actor: 'c', expectedRevision: state.revision });
|
||||
const reduced = apply(players, left);
|
||||
assert.equal(left.state.variant, 'three-short');
|
||||
assert.equal(left.state.revision, state.revision + 1);
|
||||
const group = coalitionOf(reduced, left.state, 'a'); assert.ok(group);
|
||||
const request = { event: 'first', nexus: 'nexus', turn: 5, groupId: group.id,
|
||||
entrant: 'c', proposer: 'a', expectedRevision: left.state.revision };
|
||||
assert.throws(() => beginCoalitionAdmission(reduced, left.state, request));
|
||||
assert.throws(() => beginCoalitionAdmission(reduced, left.state, {
|
||||
...request, event: 'fresh', expectedRevision: state.revision,
|
||||
}));
|
||||
const again = beginCoalitionAdmission(reduced, left.state, { ...request, event: 'fresh' });
|
||||
assert.equal(again.state.variant, 'three-short');
|
||||
assert.deepEqual(again.state.usedEvents, ['first', 'fresh']);
|
||||
assert.deepEqual({ players, state }, before);
|
||||
});
|
||||
|
||||
void test('older missing variants keep their exact shape and invalid variant strings are rejected', () => {
|
||||
const legacy = emptyCoalitionState();
|
||||
assert.equal(Object.hasOwn(legacy, 'variant'), false);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(legacy)), legacy);
|
||||
const invalid = { ...legacy, variant: 'short' } as unknown as CoalitionState;
|
||||
assert.throws(() => coalitionOf(seats(), invalid, 'a'));
|
||||
});
|
||||
Reference in new issue
Block a user