Files
Dune/tests/basic-native-leaderless-richese.test.ts
T
Quintine f73a6e0a9c
Verify and publish checkpoint container / container (push) Successful in 10m14s
Announce leaderless native Basic battles before plans
2026-10-29 20:33:33 +11:00

74 lines
4.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Game } from '../game/engine';
import { advanceNativeLeaderless, basicNativeLeaderlessFixture, nativeLeaderlessClean,
nativeLeaderlessPlayer as player } from './fixture-basic-native-early-leaderless';
import { advanceBasicSemutaAmbassador, basicSemutaAmbassadorInventory } from './fixture-basic-semuta-ambassador';
const announce = (game: Game, id: string) => game.log.filter(entry =>
entry.text === `${player(game, id).name} has no available leader or Cheap Hero for this battle.`);
/** Public setup, real movement and battle choice come from the native fixture.
* The tests stage only prior leader use/death and conserved physical cards. */
void test('Basic Richese keeps its shared preparation when a battle leader is dead', () => {
const fixture = basicNativeLeaderlessFixture('richese', {dead: true, ghola: true});
let game = advanceNativeLeaderless(fixture.game, state => !!state.battle && nativeLeaderlessClean(state) &&
!state.battle.preparation && !!state.battle.preLeader && !state.battle.preLeader.closed);
const battle = game.battle!;
assert.equal(battle.basicEarlyLeaderlessVersion, 1);
assert.equal(battle.basicDeadLeaderPreparation, true);
assert.deepEqual(battle.preLeader!.ready, [], 'Richese Sapho preparation keeps both combatants involved');
assert.equal(announce(game, 'richese').length, 0);
const event = battle.preLeader!.event;
game = applyAction(game, 'richese', {type:'battlePreparationReady', event});
assert.equal(game.battle!.preLeader!.closed, false);
assert.equal(announce(game, 'richese').length, 0);
game = applyAction(game, 'guild', {type:'battlePreparationReady', event});
assert.equal(game.battle!.preLeader!.closed, true);
assert.equal(announce(game, 'richese').length, 1);
const richese = player(game, 'richese'), ghola = richese.hand.find(card => card.effect === 'ghola');
const leader = richese.leaders.find(item => item.dead); assert.ok(ghola && leader);
game = applyAction(game, 'richese', {type:'card', card:ghola.id, leader:leader.id});
game = advanceBasicSemutaAmbassador(game, state => !!state.battle && nativeLeaderlessClean(state));
assert.equal(player(game, 'richese').leaders.find(item => item.id === leader.id)!.dead, false);
assert.ok(!(viewGame(game, 'guild').battle!.earlyLeaderless ?? []).includes('richese'));
});
void test('Basic Distrans receipt of a Cheap Hero corrects an unsealed early announcement', () => {
const fixture = basicNativeLeaderlessFixture('richese');
let game = structuredClone(fixture.beforeBattle);
const recipient = player(game, 'guild'), donor = player(game, 'emperor');
for (const leader of recipient.leaders) {
assert.ok(!leader.dead && !leader.capturedBy);
leader.usedAt = fixture.territory === 'carthag' ? 'arrakeen' : 'carthag';
}
// Keep the recipient below its hand limit with no existing Hero, then move
// the original Distrans and Hero into the donor's hand without creating a card.
for (let i = recipient.hand.length - 1; i >= 0; i--)
if (recipient.hand[i].kind === 'hero' || recipient.hand.length >= 4)
game.deck.push(...recipient.hand.splice(i, 1));
while (donor.hand.length > 2) game.deck.push(donor.hand.pop()!);
const distransIndex = game.richeseCache!.findIndex(card => card.effect === 'distrans');
assert.ok(distransIndex >= 0);
const distrans = game.richeseCache!.splice(distransIndex, 1)[0];
const heroIndex = game.deck.findIndex(card => card.kind === 'hero'); assert.ok(heroIndex >= 0);
const hero = game.deck.splice(heroIndex, 1)[0];
donor.hand.push(distrans, hero);
const inventory = basicSemutaAmbassadorInventory(game);
game = applyAction(game, fixture.chooser, fixture.choose);
game = advanceNativeLeaderless(game, state => !!state.battle && nativeLeaderlessClean(state) &&
!state.battle.preparation && (!state.battle.preLeader || state.battle.preLeader.closed));
assert.ok(viewGame(game, 'richese').battle!.earlyLeaderless?.includes('guild'));
assert.equal(announce(game, 'guild').length, 1);
assert.equal(viewGame(game, 'emperor').distrans?.blocked, null);
game = applyAction(game, 'emperor', {type:'card', card:distrans.id, target:'guild', give:hero.id});
game = advanceBasicSemutaAmbassador(game, state => !!state.battle && nativeLeaderlessClean(state));
assert.ok(player(game, 'guild').hand.some(card => card.id === hero.id));
assert.ok(!(viewGame(game, 'richese').battle!.earlyLeaderless ?? []).includes('guild'));
assert.equal(game.log.filter(entry => entry.text ===
`${player(game, 'guild').name} now has an available Cheap Hero for this battle.`).length, 1);
assert.deepEqual(basicSemutaAmbassadorInventory(game), inventory);
game = applyAction(game, 'guild', {type:'battlePlan', leader:hero.id, dial:0, support:0, weapon:null, defense:null});
assert.equal(game.battle!.plans.guild.leader, hero.id);
});