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); });