Allow group admission with compatible commitments
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
0faa7923b0
commit
6524ed3bf2
14 files changed
+679
-53
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
|
||||
import { applyAction, createGame, newPlayer, type Action, type Game } from '../game/engine';
|
||||
|
||||
const act = (game: Game, actor: string, action: Action) => applyAction(game, actor, action);
|
||||
|
||||
/** A player's retained partner and unrelated bribe remain jointly feasible.
|
||||
* The empty joint ledger is significant: it delegates accepted-order
|
||||
* reconciliation to the scalar alliance planner. */
|
||||
function fixture(): Game {
|
||||
let game = createGame('ADMISSIONJOINT', newPlayer('a', 'Atreides', 'atreides'), false);
|
||||
game.players.push(newPlayer('b', 'Emperor', 'emperor'), newPlayer('c', 'Fremen', 'fremen'),
|
||||
newPlayer('d', 'Harkonnen', 'harkonnen'), newPlayer('e', 'Guild', 'guild'));
|
||||
Object.assign(game, {status:'playing', phase:1, turn:2, nexus:true,
|
||||
order:['a','b','c','d','e'], ready:[]});
|
||||
game.coalitionState = emptyCoalitionState('unlimited');
|
||||
game.players.find(player => player.id === 'a')!.spice = 8;
|
||||
game = act(game, 'a', {type:'alliance', target:'b'});
|
||||
game = act(game, 'b', {type:'alliance', target:'a'});
|
||||
const source = JSON.stringify(['ordinaryNexus', game.turn, game.spiceDiscard.map(pile => pile.length),
|
||||
game.spiceSequence?.pile ?? null, game.spiceSequence?.skipped?.length ?? 0]);
|
||||
game.alliancePromises = [{event:'alliance', source, player:'a', asker:'e', turn:game.turn,
|
||||
partner:'b', answer:true}];
|
||||
game.bribePromises = [{event:'bribe', player:'a', asker:'e', turn:game.turn,
|
||||
recipient:'e', minimum:1, answer:true, progress:0}];
|
||||
game.turnActionPromises = [];
|
||||
return game;
|
||||
}
|
||||
|
||||
function proposal(game: Game) {
|
||||
const group = coalitionOf(game.players, game.coalitionState, 'a'); assert.ok(group);
|
||||
return {type:'coalitionPropose', groupId:group.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision};
|
||||
}
|
||||
function answer(game: Game, accept: boolean) {
|
||||
const offer = game.coalitionState!.proposal; assert.ok(offer);
|
||||
return {type:'coalitionAnswer', event:offer.event, nexus:offer.nexus, turn:offer.turn,
|
||||
accept, expectedRevision:game.coalitionState!.revision};
|
||||
}
|
||||
function commitmentsLive(game: Game) {
|
||||
assert.equal(game.bribePromises![0].released ?? false, false);
|
||||
assert.equal(game.alliancePromises![0].released ?? false, false);
|
||||
}
|
||||
|
||||
void test('joint accepted-order promises cannot be released by group proposal or approval', () => {
|
||||
let game = fixture();
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => act(game, 'a', proposal(game)));
|
||||
assert.deepEqual(game, before);
|
||||
commitmentsLive(game);
|
||||
|
||||
// Another member may propose; the affected player can still decline, but
|
||||
// cannot approve a group while the scalar reconciliation is unsupported.
|
||||
game = act(game, 'b', proposal(game));
|
||||
commitmentsLive(game);
|
||||
game = act(game, 'c', answer(game, true));
|
||||
commitmentsLive(game);
|
||||
const pending = structuredClone(game);
|
||||
assert.throws(() => act(game, 'a', answer(game, true)));
|
||||
assert.deepEqual(game, pending);
|
||||
commitmentsLive(game);
|
||||
game = act(game, 'a', answer(game, false));
|
||||
commitmentsLive(game);
|
||||
assert.equal(game.coalitionState!.proposal, null);
|
||||
});
|
||||
Reference in new issue
Block a user