120 lines
6.6 KiB
TypeScript
120 lines
6.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame } from '../game/engine';
|
|
import { isAdvisor } from '../game/advisors';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { createAdvisorWormRideFixture as fixture, actAdvisorWormRide as act,
|
|
advisorWormRideInventory as inventory, advisorWormRidePlayer as player,
|
|
passAdvisorWormRide as pass } from './fixture-advisor-worm-ride-flip';
|
|
import { sandmasterRide, sandmasterWormGame } from './fixture-sandmaster-worm';
|
|
|
|
for (const canceled of [false,true]) void test(
|
|
`native worm ride remains committed when source advisor conversion is ${canceled ? 'prevented' : 'allowed'}`, () => {
|
|
const f = fixture(), original = structuredClone(f.game);
|
|
let game = act(f.game,f.fremen,f.action);
|
|
assert.deepEqual(f.game,original);
|
|
assert.equal(game.response?.kind,'advisorFlip');
|
|
assert.equal(game.response?.automaticAdvisorFlip?.producer,'wormRide');
|
|
assert.equal(player(game,f.fremen).forces[f.source] ?? 0,0);
|
|
assert.equal(player(game,f.fremen).forces[f.destination],2);
|
|
assert.equal(player(game,f.fremen).forces['hagga_basin:12'],1);
|
|
assert.ok(isAdvisor(player(game,f.bg),f.origin));
|
|
assert.ok(viewGame(game,f.holder).responseControls?.cancelCards.includes(f.karama));
|
|
const committed = structuredClone(player(game,f.fremen));
|
|
game = canceled ? act(game,f.holder,{type:'card',card:f.karama,mode:'cancel'}) : pass(game);
|
|
assert.deepEqual(player(game,f.fremen),committed);
|
|
assert.equal(isAdvisor(player(game,f.bg),f.origin),canceled);
|
|
assert.equal(game.pendingAutomaticAdvisorFlips,undefined);
|
|
assert.equal(game.decision?.kind,'wormRide');
|
|
if (game.decision?.kind === 'wormRide') assert.equal(game.decision.territory,f.second);
|
|
assert.deepEqual(game.wormRides,[]);
|
|
assert.equal(game.discard.filter(card => card.id === f.karama).length,Number(canceled));
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
},
|
|
);
|
|
|
|
void test('a partial ride leaves Fremen in the source and produces no alone conversion', () => {
|
|
const f=fixture({partial:true}), game=act(f.game,f.fremen,f.action);
|
|
assert.equal(player(game,f.fremen).forces[f.source],1);
|
|
assert.equal(player(game,f.fremen).forces[f.destination],1);
|
|
assert.ok(isAdvisor(player(game,f.bg),f.origin));
|
|
assert.equal(game.response?.automaticAdvisorFlip,undefined);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
});
|
|
|
|
void test('source conversion precedes the real destination intrusion and resumes it once', () => {
|
|
const f=fixture({intrusion:true});
|
|
let game=act(f.game,f.fremen,f.action);
|
|
assert.equal(game.response?.automaticAdvisorFlip?.producer,'wormRide');
|
|
assert.equal(game.decision,null);
|
|
game=pass(game);
|
|
assert.equal(game.decision?.kind,'intrusion');
|
|
if (game.decision?.kind === 'intrusion') assert.equal(game.decision.territory,'imperial_basin');
|
|
assert.equal(player(game,f.fremen).forces[f.destination],2);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
});
|
|
|
|
void test('Fremen cannot nest a special worm during the committed source conversion', () => {
|
|
const f=fixture(), source=structuredClone(f.game);
|
|
// Transfer the remaining physical Karama from the original deck to Fremen;
|
|
// the separately held Karama still keeps the advisor response open.
|
|
const index=source.deck.findIndex(card => card.effect === 'karama'); assert.ok(index >= 0);
|
|
const [card]=source.deck.splice(index,1); player(source,f.fremen).hand.push(card);
|
|
const cards=inventory(source);
|
|
const summon={type:'card',mode:'special',card:card.id,territory:'the_great_flat'};
|
|
let game=act(source,f.fremen,f.action);
|
|
assert.equal(game.response?.automaticAdvisorFlip?.producer,'wormRide');
|
|
const before=structuredClone(game);
|
|
assert.throws(() => act(game,f.fremen,summon));
|
|
assert.deepEqual(game,before);
|
|
assert.ok(player(game,f.fremen).hand.some(candidate => candidate.id === card.id));
|
|
assert.equal(player(game,f.fremen).specialKaramaUsed ?? false,false);
|
|
game=pass(game);
|
|
assert.equal(game.decision?.kind,'wormRide');
|
|
assert.equal(player(game,f.fremen).forces[f.destination],2);
|
|
game=act(game,f.fremen,summon);
|
|
assert.equal(player(game,f.fremen).specialKaramaUsed,true);
|
|
assert.equal(game.discard.filter(candidate => candidate.id === card.id).length,1);
|
|
assert.deepEqual(inventory(game),cards);
|
|
});
|
|
|
|
void test('all four bot profiles can resolve the original worm-ride conversion legally', () => {
|
|
const f=fixture(), pending=act(f.game,f.fremen,f.action);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view=viewGame(pending,f.holder);
|
|
view.players.find(seat => seat.id === f.holder)!.bot=difficulty;
|
|
const action=botActions(view)[0]; assert.ok(action);
|
|
const game=pass(act(structuredClone(pending),f.holder,action));
|
|
assert.equal(game.decision?.kind,'wormRide');
|
|
if (game.decision?.kind === 'wormRide') assert.equal(game.decision.territory,f.second);
|
|
assert.equal(player(game,f.fremen).forces[f.destination],2);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
}
|
|
});
|
|
|
|
for (const canceled of [false,true]) void test(
|
|
`Sandmaster's native worm-ride collection occurs once when origin conversion is ${canceled ? 'prevented' : 'allowed'}`, () => {
|
|
// This fixture uses genuine Leader Skills setup and a staged, conserved
|
|
// ride. Move the actual BG force from the destination to the ride origin,
|
|
// and transfer one physical Karama to Fremen for the response.
|
|
const source=sandmasterWormGame(true,true), bg=player(source,'h'), rider=player(source,'p');
|
|
bg.forces={'wind_pass:14':1}; bg.reserves=19; bg.advisors={wind_pass:{}};
|
|
const index=source.deck.findIndex(card => card.effect === 'karama'); assert.ok(index >= 0);
|
|
const [card]=source.deck.splice(index,1); rider.hand.push(card);
|
|
const cards=inventory(source), beforeSpice=rider.spice, beforePile=source.spice['red_chasm:7'];
|
|
let game=act(source,'p',sandmasterRide());
|
|
assert.equal(game.response?.automaticAdvisorFlip?.producer,'wormRide');
|
|
assert.equal(player(game,'p').spice,beforeSpice+1);
|
|
assert.equal(game.spice['red_chasm:7'],beforePile-1);
|
|
assert.equal(game.log.filter(entry => entry.automatic?.name === 'Sandmaster collection').length,1);
|
|
game=canceled ? act(game,'p',{type:'card',card:card.id,mode:'cancel'}) : pass(game);
|
|
assert.equal(player(game,'p').spice,beforeSpice+1);
|
|
assert.equal(game.spice['red_chasm:7'],beforePile-1);
|
|
assert.equal(player(game,'p').forces['red_chasm:7'],4);
|
|
assert.equal(isAdvisor(player(game,'h'),'wind_pass'),canceled);
|
|
assert.equal(game.log.filter(entry => entry.automatic?.name === 'Sandmaster collection').length,1);
|
|
assert.deepEqual(inventory(game),cards);
|
|
},
|
|
);
|