Prototype charity Truthtrance and coalition rule foundations
Verify and publish checkpoint container / container (push) Successful in 10m14s
Verify and publish checkpoint container / container (push) Successful in 10m14s
This commit is contained in:
1 parent
e896570f07
commit
cea310b030
17 files changed
+1024
-35
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { beginCoalitionAdmission, answerCoalitionAdmission, coalitionOf, type CoalitionState } from '../game/coalition-alliance';
|
||||
import { createGame, newPlayer, type Game } from '../game/engine';
|
||||
import type { FactionId } from '../game/catalog';
|
||||
import { strongholdProgress, VictoryProgressError } from '../game/victory-progress';
|
||||
import { quoteVictory, VictoryQuoteError } from '../game/victory-quote';
|
||||
import { fremenSpecialVictory } from '../game/fremen-victory';
|
||||
import { createVictoryContinuation, voteVictoryContinuation } from '../game/victory-continuation';
|
||||
import { createTechTokens } from '../game/tech-tokens';
|
||||
|
||||
type CoalitionGame = Game & {coalitionState: CoalitionState};
|
||||
function game(factions: FactionId[] = ['atreides', 'emperor', 'harkonnen', 'beneGesserit']): CoalitionGame {
|
||||
const g = createGame('COALITIONVICTORY', newPlayer('p0', factions[0], factions[0]), false);
|
||||
factions.slice(1).forEach((faction, i) => g.players.push(newPlayer(`p${i + 1}`, faction, faction)));
|
||||
Object.assign(g, { status: 'playing', phase: 8, turn: 2, storm: 18,
|
||||
order: g.players.map(player => player.id) });
|
||||
for (const p of g.players) Object.assign(p, {forces: {}, ally: null});
|
||||
g.players[0].ally = 'p1'; g.players[1].ally = 'p0';
|
||||
const group = coalitionOf(g.players, undefined, 'p0')!;
|
||||
const start = beginCoalitionAdmission(g.players, undefined, {event: 'invite', nexus: 'nexus', turn: 2,
|
||||
groupId: group.id, entrant: 'p2', proposer: 'p0', expectedRevision: 0});
|
||||
const second = answerCoalitionAdmission(g.players, start.state, {event: 'invite', nexus: 'nexus',
|
||||
turn: 2, actor: 'p1', accept: true, expectedRevision: start.state.revision});
|
||||
const joined = answerCoalitionAdmission(g.players, second.state, {event: 'invite', nexus: 'nexus',
|
||||
turn: 2, actor: 'p2', accept: true, expectedRevision: second.state.revision});
|
||||
for (const p of g.players) if (Object.hasOwn(joined.allyUpdates, p.id)) p.ally = joined.allyUpdates[p.id];
|
||||
return Object.assign(g, {coalitionState: joined.state});
|
||||
}
|
||||
function hold(g: CoalitionGame, player: number, ...sites: string[]) {
|
||||
g.players[player].forces = Object.fromEntries(sites.map(site => [site, 1]));
|
||||
}
|
||||
const a = 'arrakeen:10', b = 'carthag:11', c = 'sietch_tabr:14', d = 'habbanya_ridge_sietch:17';
|
||||
|
||||
void test('three members share a distinct uncontested stronghold union with ordered members', () => {
|
||||
const g = game();
|
||||
g.order = ['p2', 'p0', 'p3', 'p1'];
|
||||
hold(g, 0, a, b); hold(g, 1, b, c); hold(g, 2, d);
|
||||
const before = structuredClone(g);
|
||||
const rows = strongholdProgress(g).progress.filter(row => row.members.length === 3);
|
||||
assert.equal(rows.length, 3);
|
||||
for (const row of rows) {
|
||||
assert.deepEqual(row.members, ['p2', 'p0', 'p1']);
|
||||
assert.equal(row.strongholds.length, 4); assert.equal(row.target, 4); assert.equal(row.qualifies, true);
|
||||
}
|
||||
assert.deepEqual(quoteVictory(g).winner, ['p2', 'p0', 'p1']);
|
||||
assert.deepEqual(g, before);
|
||||
hold(g, 3, d);
|
||||
assert.equal(strongholdProgress(g).progress.find(row => row.player === 'p0')!.qualifies, false);
|
||||
});
|
||||
|
||||
void test('two-member shortened target does not apply to three-member alliances', () => {
|
||||
const g = game(); hold(g, 0, a); hold(g, 1, b); hold(g, 2, c);
|
||||
let row = strongholdProgress(g).progress[0];
|
||||
assert.equal(row.target, 4); assert.equal(row.qualifies, false);
|
||||
g.shortAllianceVictory = true;
|
||||
row = strongholdProgress(g).progress[0]; assert.equal(row.target, 4); assert.equal(row.qualifies, false);
|
||||
g.victoryExtraStrongholds = 1;
|
||||
row = strongholdProgress(g).progress[0]; assert.equal(row.target, 5); assert.equal(row.qualifies, false);
|
||||
g.victoryExtraStrongholds = 0;
|
||||
g.techTokens = createTechTokens();
|
||||
for (const token of Object.values(g.techTokens)) token.owner = 'p2';
|
||||
row = strongholdProgress(g).progress[0]; assert.equal(row.techStronghold, true); assert.equal(row.qualifies, true);
|
||||
g.techTokens.production.owner = 'p1';
|
||||
assert.equal(strongholdProgress(g).progress[0].techStronghold, false);
|
||||
g.twoPlayerControllers = {version: 1, controllers: [
|
||||
{id: 'p0', seats: ['p0', 'p1']}, {id: 'p2', seats: ['p2', 'p3']},
|
||||
]};
|
||||
assert.equal(strongholdProgress(g).progress[0].target, 4);
|
||||
});
|
||||
|
||||
void test('ordinary prediction checks every coalition winner while final Fremen special still wins', () => {
|
||||
const g = game(); hold(g, 0, a, b); hold(g, 1, c); hold(g, 2, d);
|
||||
g.players[3].prediction = {faction: 'harkonnen', turn: 2};
|
||||
assert.deepEqual(quoteVictory(g).winner, ['p3']);
|
||||
g.players[3].prediction.turn = 3;
|
||||
assert.deepEqual(quoteVictory(g).winner, ['p0', 'p1', 'p2']);
|
||||
const f = game(['fremen', 'atreides', 'emperor', 'beneGesserit']);
|
||||
f.turn = 10; f.players[3].prediction = {faction: 'atreides', turn: 10};
|
||||
assert.deepEqual(quoteVictory(f).winner, ['p0', 'p1', 'p2']);
|
||||
});
|
||||
|
||||
void test('Fremen, Guild and no-Guild Fremen final branches award the full alliance', () => {
|
||||
const fremen = game(['fremen', 'atreides', 'emperor', 'guild']); fremen.turn = 10;
|
||||
assert.deepEqual(fremenSpecialVictory(fremen)!.members, ['p0', 'p1', 'p2']);
|
||||
assert.deepEqual(quoteVictory(fremen).winner, ['p0', 'p1', 'p2']);
|
||||
hold(fremen, 1, c);
|
||||
assert.equal(fremenSpecialVictory(fremen)!.qualifies, false);
|
||||
assert.deepEqual(quoteVictory(fremen).winner, ['p3']);
|
||||
const guild = game(['guild', 'atreides', 'emperor', 'beneGesserit']); guild.turn = 10;
|
||||
assert.deepEqual(quoteVictory(guild).winner, ['p0', 'p1', 'p2']);
|
||||
const noGuild = game(['fremen', 'atreides', 'emperor', 'harkonnen']); noGuild.turn = 10;
|
||||
hold(noGuild, 1, c);
|
||||
assert.equal(fremenSpecialVictory(noGuild)!.qualifies, false);
|
||||
assert.deepEqual(quoteVictory(noGuild).winner, ['p0', 'p1', 'p2']);
|
||||
});
|
||||
|
||||
void test('unsupported triples and malformed coalition records fail closed across quotes', () => {
|
||||
const g = game(); g.advanced = true;
|
||||
assert.throws(() => strongholdProgress(g), VictoryProgressError);
|
||||
assert.throws(() => fremenSpecialVictory(g), VictoryProgressError);
|
||||
assert.throws(() => quoteVictory(g), VictoryQuoteError);
|
||||
g.advanced = false; g.players[3].faction = 'ecaz';
|
||||
assert.throws(() => strongholdProgress(g), VictoryProgressError);
|
||||
assert.throws(() => quoteVictory(g), VictoryQuoteError);
|
||||
g.players[3].faction = 'beneGesserit'; g.coalitionState.groups[0].edges.pop();
|
||||
assert.throws(() => strongholdProgress(g), VictoryProgressError);
|
||||
assert.throws(() => quoteVictory(g), VictoryQuoteError);
|
||||
});
|
||||
|
||||
void test('a continuation deduplicates a triple side and requires every participant to consent', () => {
|
||||
let frame = createVictoryContinuation({event: 'continue', turn: 2, resume: 'mentat',
|
||||
order: ['p2', 'p0', 'p3', 'p1'], sides: [['p0', 'p1', 'p2'], ['p2', 'p0', 'p1'], ['p3']]});
|
||||
assert.deepEqual(frame.sides, [['p2', 'p0', 'p1'], ['p3']]);
|
||||
assert.deepEqual(frame.voters, ['p2', 'p0', 'p3', 'p1']);
|
||||
for (const [index, player] of frame.voters.entries()) {
|
||||
const vote = voteVictoryContinuation(frame, {event: 'continue', player, choice: 'continue',
|
||||
...(index === 0 ? {extraStrongholds: 1} : {})});
|
||||
if (vote.outcome === 'pending') frame = vote.frame;
|
||||
else { assert.equal(index, 3); assert.deepEqual(vote, {outcome: 'continue', extraStrongholds: 1}); }
|
||||
}
|
||||
assert.throws(() => createVictoryContinuation({event: 'x', turn: 2, resume: 'mentat',
|
||||
order: ['p0', 'p1', 'p2', 'p3'], sides: [['p0', 'p1', 'p2'], ['p2', 'p3']]}));
|
||||
});
|
||||
Reference in new issue
Block a user