Allow group departures with bribe commitments
Verify and publish checkpoint container / container (push) Successful in 10m51s
Verify and publish checkpoint container / container (push) Successful in 10m51s
This commit is contained in:
1 parent
3e0fa2a8a2
commit
0faa7923b0
13 files changed
+437
-37
No files matched your search
@@ -0,0 +1,76 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { baseDeck } from '../game/cards';
|
||||
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
|
||||
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
|
||||
const send = (game:Game, actor:string, action:Action) => applyAction(game, actor, action);
|
||||
const seat = (game:Game, id:string) => game.players.find(player => player.id === id)!;
|
||||
|
||||
/** Stage the Nexus, respondent's wallet and the two physical Truthtrance
|
||||
* cards. Alliance formation, both questions and all guided actions are real. */
|
||||
function initial(): {game:Game; cards:string[]} {
|
||||
let game = createGame('GROUPBOTH', 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,ready:[],
|
||||
order:['a','b','c','d','e'],deck:baseDeck()});
|
||||
game.coalitionState = emptyCoalitionState('unlimited'); seat(game,'a').spice = 8;
|
||||
game = send(game,'a',{type:'alliance',target:'b'});
|
||||
game = send(game,'b',{type:'alliance',target:'a'});
|
||||
const pair = coalitionOf(game.players,game.coalitionState,'a'); assert.ok(pair);
|
||||
game = send(game,'a',{type:'coalitionPropose',groupId:pair.id,entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
for (const actor of ['b','c']) {
|
||||
const offer = game.coalitionState!.proposal!;
|
||||
game = send(game,actor,{type:'coalitionAnswer',event:offer.event,nexus:offer.nexus,
|
||||
turn:offer.turn,accept:true,expectedRevision:game.coalitionState!.revision});
|
||||
}
|
||||
const cards:string[] = [];
|
||||
for (let count=0;count<2;count++) {
|
||||
const index = game.deck.findIndex(card => card.effect === 'truthtrance'); assert.ok(index >= 0);
|
||||
const [card] = game.deck.splice(index,1); cards.push(card.id); seat(game,'e').hand.push(card);
|
||||
}
|
||||
return {game,cards};
|
||||
}
|
||||
function ask(game:Game, card:string, question:unknown, answer:'yes'|'no'):Game {
|
||||
game = send(game,'e',{type:'card',card});
|
||||
while(game.truthtrance?.stage === 'priority') {
|
||||
const next = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(next);
|
||||
game = send(game,next.id,{type:'truthPass'});
|
||||
}
|
||||
game = send(game,'e',{type:'truthAsk',question});
|
||||
const view = viewGame(game,'a');
|
||||
const answers = game.truthtrance?.question?.kind === 'bribe' ? view.truthBribeAnswers : view.truthAllianceAnswers;
|
||||
assert.ok(answers?.includes(answer), `The ${answer} answer must be legal with both commitments.`);
|
||||
return send(game,'a',{type:'truthAnswer',answer});
|
||||
}
|
||||
|
||||
void test('a group alliance No and positive bribe answer retain both legal guides',()=>{
|
||||
const fixture = initial();
|
||||
let game = ask(fixture.game,fixture.cards[0],
|
||||
{kind:'bribe',target:'a',turn:fixture.game.turn,recipient:'d',minimum:2},'yes');
|
||||
game = ask(game,fixture.cards[1],
|
||||
{kind:'alliance',target:'a',turn:game.turn,partner:'b'},'no');
|
||||
const bribe = game.bribePromises!.find(row=>row.player === 'a')!;
|
||||
const alliance = game.alliancePromises!.find(row=>row.player === 'a')!;
|
||||
assert.ok(!bribe.released && !bribe.fulfilled && !alliance.released && !alliance.fulfilled);
|
||||
const beforeReady = structuredClone(game);
|
||||
assert.throws(()=>send(game,'a',{type:'ready'}));
|
||||
assert.deepEqual(game,beforeReady);
|
||||
const guide = viewGame(game,'a').alliancePromiseGuide; assert.ok(guide);
|
||||
assert.equal(guide.action.type,'coalitionLeave');
|
||||
game = send(game,'a',guide.action);
|
||||
assert.equal(coalitionOf(game.players,game.coalitionState,'a'),null);
|
||||
assert.deepEqual(coalitionOf(game.players,game.coalitionState,'b')!.members,['b','c']);
|
||||
assert.ok(!game.alliancePromises!.find(row=>row.event === alliance.event)!.released);
|
||||
assert.ok(!game.bribePromises!.find(row=>row.event === bribe.event)!.released);
|
||||
const payment = viewGame(game,'a').bribePromiseGuide; assert.ok(payment);
|
||||
assert.equal(payment.action.type,'bribe'); assert.equal(payment.action.target,'d');
|
||||
game = send(game,'a',payment.action);
|
||||
assert.equal(game.bribePromises!.find(row=>row.event === bribe.event)!.fulfilled,true);
|
||||
assert.equal(seat(game,'d').bribes,2);
|
||||
game = send(game,'a',{type:'ready'});
|
||||
assert.ok(game.ready.includes('a'));
|
||||
for(const card of fixture.cards) assert.equal(game.discard.filter(row=>row.id === card).length,1);
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createElement } from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { baseDeck } from '../game/cards';
|
||||
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
|
||||
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { CoalitionNexus, coalitionNexusOptions } from '../components/coalition-nexus';
|
||||
|
||||
const send = (game: Game, actor: string, action: Action) => applyAction(game, actor, action);
|
||||
const seat = (game: Game, id: string) => game.players.find(player => player.id === id)!;
|
||||
|
||||
/** The quiet Nexus, wallet and physical Truthtrance are staged. Pair/group
|
||||
* formation, question, answer, departure and payment are actual actions. */
|
||||
function group(): Game {
|
||||
let game = createGame('GROUPBRIBE', 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:[], deck:baseDeck()});
|
||||
game.coalitionState = emptyCoalitionState('unlimited');
|
||||
seat(game, 'a').spice = 8;
|
||||
game = send(game, 'a', {type:'alliance', target:'b'});
|
||||
game = send(game, 'b', {type:'alliance', target:'a'});
|
||||
const pair = coalitionOf(game.players, game.coalitionState, 'a'); assert.ok(pair);
|
||||
game = send(game, 'a', {type:'coalitionPropose', groupId:pair.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
for (const id of ['b','c']) {
|
||||
const offer = game.coalitionState!.proposal!;
|
||||
game = send(game, id, {type:'coalitionAnswer', event:offer.event, nexus:offer.nexus,
|
||||
turn:offer.turn, accept:true, expectedRevision:game.coalitionState!.revision});
|
||||
}
|
||||
const index = game.deck.findIndex(card => card.effect === 'truthtrance'); assert.ok(index >= 0);
|
||||
const [card] = game.deck.splice(index, 1); seat(game, 'e').hand.push(card);
|
||||
return game;
|
||||
}
|
||||
function ask(answer: 'yes' | 'no', expression = false): Game {
|
||||
let game = group();
|
||||
const card = seat(game, 'e').hand.find(row => row.effect === 'truthtrance')!;
|
||||
game = send(game, 'e', {type:'card', card:card.id});
|
||||
while (game.truthtrance?.stage === 'priority') {
|
||||
const passer = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(passer);
|
||||
game = send(game, passer.id, {type:'truthPass'});
|
||||
}
|
||||
const question = expression
|
||||
? {kind:'bribeExpression', target:'a', turn:game.turn, claim:{kind:'and', terms:[
|
||||
{kind:'payment', recipient:'d', minimum:2}, {kind:'payment', recipient:'e', minimum:1}]}}
|
||||
: {kind:'bribe', target:'a', turn:game.turn, recipient:'d', minimum:2};
|
||||
game = send(game, 'e', {type:'truthAsk', question});
|
||||
const view = viewGame(game, 'a');
|
||||
const answers = expression ? view.truthBribeExpressionAnswers : view.truthBribeAnswers;
|
||||
assert.ok(answers?.includes(answer));
|
||||
return send(game, 'a', {type:'truthAnswer', answer});
|
||||
}
|
||||
function leave(game:Game, actor:string):Game {
|
||||
return send(game, actor, {type:'coalitionLeave', expectedRevision:game.coalitionState!.revision});
|
||||
}
|
||||
function blockedProposal(game:Game) {
|
||||
const alliance = coalitionOf(game.players, game.coalitionState, 'a'); assert.ok(alliance);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'a', {type:'coalitionPropose', groupId:alliance.id,
|
||||
entrant:'d', expectedRevision:game.coalitionState!.revision}));
|
||||
assert.deepEqual(game, before);
|
||||
}
|
||||
|
||||
void test('a positive bribe answer allows owned group departure and remains payable', () => {
|
||||
let game = ask('yes');
|
||||
const projected = viewGame(game, 'a').coalitionNexus; assert.ok(projected);
|
||||
const options = coalitionNexusOptions(projected);
|
||||
assert.equal(options.mayLeave, true); assert.equal(options.invitations.length, 0);
|
||||
const markup = renderToStaticMarkup(createElement(CoalitionNexus, {view:projected, onIntent:()=>{}}));
|
||||
assert.match(markup, /Leave/); assert.doesNotMatch(markup, /Invite .* to /);
|
||||
blockedProposal(game);
|
||||
game = leave(game, 'a');
|
||||
assert.equal(coalitionOf(game.players, game.coalitionState, 'a'), null);
|
||||
assert.deepEqual(coalitionOf(game.players, game.coalitionState, 'b')!.members, ['b','c']);
|
||||
const promise = game.bribePromises!.find(row => row.player === 'a')!;
|
||||
assert.equal(promise.released ?? false, false);
|
||||
const guide = viewGame(game, 'a').bribePromiseGuide; assert.ok(guide);
|
||||
assert.equal(guide.action.type, 'bribe'); assert.equal(guide.action.target, 'd');
|
||||
game = send(game, 'a', guide.action);
|
||||
assert.equal(game.bribePromises!.find(row => row.event === promise.event)!.fulfilled, true);
|
||||
assert.equal(seat(game, 'd').bribes, 2);
|
||||
});
|
||||
|
||||
void test('a negative bribe answer survives another member departing and retains its cap', () => {
|
||||
let game = ask('no');
|
||||
blockedProposal(game);
|
||||
game = leave(game, 'b');
|
||||
assert.deepEqual(coalitionOf(game.players, game.coalitionState, 'a')!.members, ['a','c']);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'a', {type:'bribe', target:'d', amount:2}));
|
||||
assert.deepEqual(game, before);
|
||||
game = send(game, 'a', {type:'bribe', target:'d', amount:1});
|
||||
assert.equal(seat(game, 'd').bribes, 1);
|
||||
assert.equal(game.bribePromises!.find(row => row.player === 'a')!.released ?? false, false);
|
||||
});
|
||||
|
||||
void test('a positive bribe expression remains binding and payable after departure', () => {
|
||||
let game = ask('yes', true);
|
||||
blockedProposal(game);
|
||||
game = leave(game, 'a');
|
||||
const promise = game.bribeExpressionPromises!.find(row => row.player === 'a')!;
|
||||
assert.equal(promise.released ?? false, false);
|
||||
for (let step = 0; step < 2; step++) {
|
||||
const guide = viewGame(game, 'a').bribePromiseGuide; assert.ok(guide);
|
||||
game = send(game, 'a', guide.action);
|
||||
}
|
||||
assert.equal(game.bribeExpressionPromises!.find(row => row.event === promise.event)!.fulfilled, true);
|
||||
assert.equal(seat(game, 'd').bribes, 2); assert.equal(seat(game, 'e').bribes, 1);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createElement } from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { CoalitionNexus, coalitionNexusOptions, type CoalitionNexusView } from '../components/coalition-nexus';
|
||||
import type { CoalitionGroup } from '../game/coalition-alliance';
|
||||
|
||||
const players = ['a', 'b', 'c', 'd'].map(id => ({id, name:id.toUpperCase()}));
|
||||
const group: CoalitionGroup = {id:'group', members:['a','b','c'], formedTurn:1, edges:[
|
||||
{members:['a','b'], formedTurn:1}, {members:['a','c'], formedTurn:1},
|
||||
{members:['b','c'], formedTurn:1},
|
||||
]};
|
||||
const view = (me:string, admissionBlocked = false): CoalitionNexusView => ({
|
||||
me, players, groups:[group], proposal:null, revision:3, variant:'unlimited', admissionBlocked,
|
||||
});
|
||||
const render = (value:CoalitionNexusView) => renderToStaticMarkup(createElement(CoalitionNexus, {
|
||||
view:value, onIntent() {},
|
||||
}));
|
||||
|
||||
void test('a live bribe commitment hides new invitations while retaining the owned leave control', () => {
|
||||
const member = view('a', true), choices = coalitionNexusOptions(member), html = render(member);
|
||||
assert.deepEqual(choices.invitations, []);
|
||||
assert.equal(choices.mayLeave, true);
|
||||
assert.match(html, /Invitations are unavailable while a bribe commitment remains unresolved/);
|
||||
assert.match(html, /Leave A, B, C/);
|
||||
assert.doesNotMatch(html, /Invite D to/);
|
||||
|
||||
const entrant = view('d', true);
|
||||
assert.deepEqual(coalitionNexusOptions(entrant).invitations, []);
|
||||
assert.equal(coalitionNexusOptions(entrant).mayLeave, false);
|
||||
assert.doesNotMatch(render(entrant), /Ask to join/);
|
||||
});
|
||||
|
||||
void test('invitations return after the block clears; a blocked pending offer can be declined', () => {
|
||||
const member = view('a');
|
||||
assert.deepEqual(coalitionNexusOptions(member).invitations.map(row => row.entrant), ['d']);
|
||||
assert.match(render(member), /Invite D to A, B, C/);
|
||||
assert.doesNotMatch(render(member), /bribe commitment remains unresolved/);
|
||||
|
||||
const pending = view('d', true);
|
||||
pending.proposal = {event:'event', nexus:'nexus', turn:2, groupId:group.id,
|
||||
original:group, entrant:'d', proposer:'a', approvals:['a']};
|
||||
assert.equal(coalitionNexusOptions(pending).mayAccept, false);
|
||||
assert.equal(coalitionNexusOptions(pending).mayDecline, true);
|
||||
assert.doesNotMatch(render(pending), /Accept invitation/);
|
||||
assert.match(render(pending), /may decline this invitation/);
|
||||
});
|
||||
@@ -88,7 +88,7 @@ void test('an unresolved prior-turn Truthtrance record still blocks group Nexus'
|
||||
const game = originalPair();
|
||||
game.bribePromises = [{event:'unreconciled-bribe', player:'d', asker:'a', turn:game.turn - 1,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus, null);
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus?.admissionBlocked, true);
|
||||
const pair = coalitionOf(game.players, game.coalitionState, 'a')!;
|
||||
rejected(game, 'a', {type:'coalitionPropose', groupId:pair.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
@@ -98,7 +98,7 @@ void test('a current unanswered group-incompatible Truthtrance commitment still
|
||||
const game = originalPair();
|
||||
game.bribePromises = [{event:'live-bribe', player:'d', asker:'a', turn:game.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus, null);
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus?.admissionBlocked, true);
|
||||
const pair = coalitionOf(game.players, game.coalitionState, 'a')!;
|
||||
rejected(game, 'a', {type:'coalitionPropose', groupId:pair.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { baseDeck } from '../game/cards';
|
||||
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
|
||||
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
|
||||
const act = (game:Game, id:string, action:Action) => applyAction(game,id,action);
|
||||
const seat = (game:Game, id:string) => game.players.find(player => player.id === id)!;
|
||||
|
||||
/** The Basic Nexus/ledger, phase boundary and wallets are staged; the pair,
|
||||
* admission, held physical Truthtrance and every question/payment are real. */
|
||||
function fixture(phase: 'bidding' | 'movement'):Game {
|
||||
let game=createGame('OUTSIDERFUND',newPlayer('a','Atreides','atreides'),false);
|
||||
game.players.push(newPlayer('b','Emperor','emperor'),newPlayer('c','Fremen','fremen'),
|
||||
newPlayer('d','Guild','guild'),newPlayer('e','Harkonnen','harkonnen'));
|
||||
Object.assign(game,{status:'playing',phase:1,turn:2,nexus:true,order:['a','b','c','d','e'],
|
||||
ready:[],storm:18,deck:baseDeck(),discard:[],response:null,decision:null,phaseOpening:null});
|
||||
game.coalitionState=emptyCoalitionState('unlimited');
|
||||
game=act(game,'a',{type:'alliance',target:'b'});
|
||||
game=act(game,'b',{type:'alliance',target:'a'});
|
||||
const group=coalitionOf(game.players,game.coalitionState,'a'); assert.ok(group);
|
||||
game=act(game,'a',{type:'coalitionPropose',groupId:group.id,entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
for(const id of ['b','c']) {
|
||||
const offer=game.coalitionState!.proposal!;
|
||||
game=act(game,id,{type:'coalitionAnswer',event:offer.event,nexus:offer.nexus,
|
||||
turn:offer.turn,accept:true,expectedRevision:game.coalitionState!.revision});
|
||||
}
|
||||
for(const player of game.players) {
|
||||
player.spice=10; player.hand=[]; player.forces={}; player.reserves=20; player.tanks=0;
|
||||
player.traitors=[]; player.traitorChoices=[]; player.shipped=false; player.moved=0;
|
||||
}
|
||||
seat(game,'a').spice=2;
|
||||
if(phase==='bidding') {
|
||||
Object.assign(game,{phase:2,nexus:false,active:null,ready:[]});
|
||||
for(const player of game.players) game=act(game,player.id,{type:'ready'});
|
||||
assert.equal(game.phase,3); assert.equal(game.auction?.active,'a');
|
||||
} else Object.assign(game,{phase:5,nexus:false,active:'a',ready:[],
|
||||
movementRemaining:['a','b','c','d','e'],guildTimingGranted:false,guildTimingLocked:false});
|
||||
const index=game.deck.findIndex(card=>card.effect==='truthtrance'); assert.ok(index>=0);
|
||||
seat(game,'a').hand.push(game.deck.splice(index,1)[0]);
|
||||
return game;
|
||||
}
|
||||
function ask(game:Game,question:Record<string,unknown>):Game {
|
||||
const card=seat(game,'a').hand.find(row=>row.effect==='truthtrance'); assert.ok(card);
|
||||
game=act(game,'a',{type:'card',card:card.id});
|
||||
while(game.truthtrance?.stage==='priority') {
|
||||
const actor=game.players.find(player=>!game.truthtrance!.passed.includes(player.id)); assert.ok(actor);
|
||||
game=act(game,actor.id,{type:'truthPass'});
|
||||
}
|
||||
return act(game,'a',{type:'truthAsk',question});
|
||||
}
|
||||
function pledge(game:Game):Game {
|
||||
game=act(game,'b',{type:'coalitionPledge',recipient:'a',amount:4});
|
||||
return act(game,'c',{type:'coalitionPledge',recipient:'a',amount:3});
|
||||
}
|
||||
function passResponses(game:Game):Game {
|
||||
for(let count=0;game.response && count<30;count++) {
|
||||
const actor=game.players.find(player=>!game.response!.passed.includes(player.id)); assert.ok(actor);
|
||||
game=act(game,actor.id,{type:'passResponse'});
|
||||
}
|
||||
assert.equal(game.response ?? null,null); return game;
|
||||
}
|
||||
const shipment:Action={type:'ship',territory:'imperial_basin',sector:9,amount:3,elite:0,allyPayment:0,
|
||||
coalitionContributions:[{donor:'b',amount:3},{donor:'c',amount:2}]};
|
||||
|
||||
void test('an unrelated outsider bribe No leaves group shipment funding available',()=>{
|
||||
let game=fixture('movement');
|
||||
game=ask(game,{kind:'bribe',target:'d',turn:game.turn,recipient:'e',minimum:1});
|
||||
assert.ok(viewGame(game,'d').truthBribeAnswers?.includes('no'));
|
||||
game=act(game,'d',{type:'truthAnswer',answer:'no'});
|
||||
const promise=game.bribePromises!.find(row=>row.player==='d')!;
|
||||
assert.equal(promise.fulfilled ?? false,false);
|
||||
game=pledge(game);
|
||||
assert.equal(viewGame(game,'a').coalitionFunding?.incoming.length,2);
|
||||
game=passResponses(act(game,'a',shipment));
|
||||
assert.equal(seat(game,'a').forces['imperial_basin:9'],3);
|
||||
assert.equal(seat(game,'d').spice,16);
|
||||
assert.ok(game.bribePromises?.some(row=>row.event===promise.event && !row.fulfilled && !row.released));
|
||||
});
|
||||
|
||||
void test('an unrelated outsider whole-turn payment No leaves group shipment funding available',()=>{
|
||||
let game=fixture('movement');
|
||||
game=ask(game,{kind:'turnActions',target:'d',turn:game.turn,
|
||||
mixed:{kind:'action',action:{kind:'payment',recipient:'e',minimum:1}}});
|
||||
assert.ok(viewGame(game,'d').truthTurnActionAnswers?.includes('no'));
|
||||
game=act(game,'d',{type:'truthAnswer',answer:'no'});
|
||||
const promise=game.turnActionPromises!.find(row=>row.player==='d')!;
|
||||
game=pledge(game); game=passResponses(act(game,'a',shipment));
|
||||
assert.equal(seat(game,'a').forces['imperial_basin:9'],3);
|
||||
assert.ok(game.turnActionPromises?.some(row=>row.event===promise.event && !row.fulfilled && !row.released));
|
||||
});
|
||||
|
||||
void test('an unrelated outsider phase-bid No leaves group bid funding available',()=>{
|
||||
let game=fixture('bidding');
|
||||
game=ask(game,{kind:'phaseBid',target:'d',turn:game.turn,minimum:10});
|
||||
assert.ok(viewGame(game,'d').truthPhaseBidAnswers?.includes('no'));
|
||||
game=act(game,'d',{type:'truthAnswer',answer:'no'});
|
||||
const promise=game.phaseBidPromises!.find(row=>row.player==='d')!;
|
||||
game=pledge(game);
|
||||
game=act(game,'a',{type:'bid',amount:6,allyPayment:0,
|
||||
coalitionContributions:[{donor:'b',amount:3},{donor:'c',amount:2}]});
|
||||
assert.equal(game.auction?.bidder,'a');
|
||||
assert.ok(game.phaseBidPromises?.some(row=>row.event===promise.event && !row.fulfilled && !row.released));
|
||||
});
|
||||
|
||||
void test('a group member commitment still fences the entire group funding path',()=>{
|
||||
let game=fixture('movement');
|
||||
game=ask(game,{kind:'bribe',target:'b',turn:game.turn,recipient:'e',minimum:1});
|
||||
assert.ok(viewGame(game,'b').truthBribeAnswers?.includes('no'));
|
||||
game=act(game,'b',{type:'truthAnswer',answer:'no'});
|
||||
assert.equal(viewGame(game,'a').coalitionFunding,null);
|
||||
const before=structuredClone(game);
|
||||
assert.throws(()=>act(game,'c',{type:'coalitionPledge',recipient:'a',amount:1}));
|
||||
assert.deepEqual(game,before);
|
||||
});
|
||||
@@ -121,8 +121,8 @@ void test('fulfilled and released Truthtrance records leave group shipment fundi
|
||||
|
||||
void test('an unreconciled prior-turn commitment blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribePromises = [{event:'unreconciled-bribe', player:'d', asker:'a', turn:game.turn - 1,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
game.bribePromises = [{event:'unreconciled-bribe', player:'b', asker:'a', turn:game.turn - 1,
|
||||
recipient:'d', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
@@ -131,8 +131,8 @@ void test('an unreconciled prior-turn commitment blocks group shipment funding',
|
||||
|
||||
void test('a live No bribe-expression commitment blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribeExpressionPromises = [{event:'live-expression-no', player:'d', asker:'a',
|
||||
turn:game.turn, claim:{kind:'payment', recipient:'c', minimum:1}, answer:false, progress:[]}];
|
||||
game.bribeExpressionPromises = [{event:'live-expression-no', player:'b', asker:'a',
|
||||
turn:game.turn, claim:{kind:'payment', recipient:'d', minimum:1}, answer:false, progress:[]}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
@@ -141,8 +141,8 @@ void test('a live No bribe-expression commitment blocks group shipment funding',
|
||||
|
||||
void test('a current unsupported Truthtrance commitment still blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribePromises = [{event:'live-bribe', player:'d', asker:'a', turn:game.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
game.bribePromises = [{event:'live-bribe', player:'b', asker:'a', turn:game.turn,
|
||||
recipient:'d', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
|
||||
Reference in new issue
Block a user