116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { baseDeck, ixBattleCards, type Card } from '../game/cards';
|
|
import { richeseCards } from '../game/richese-cards';
|
|
import {
|
|
isDefenseCard,
|
|
isPortableSnooper,
|
|
defenseTypes,
|
|
defaultVoiceMatch,
|
|
playedVoiceMatch,
|
|
validBattleCardPair,
|
|
weaponKills,
|
|
} from '../game/battle-cards';
|
|
import { portableSnooperPlanBlock } from '../game/portable-snooper';
|
|
import type { Plan } from '../game/engine';
|
|
|
|
const portable = richeseCards().find((c) => c.effect === 'portableSnooper')!;
|
|
const cards = [...baseDeck(), ...ixBattleCards()];
|
|
const get = (kind: Card['kind']) => cards.find((c) => c.kind === kind)!;
|
|
const plan: Plan = {
|
|
leader: 'leader',
|
|
dial: 1,
|
|
support: 0,
|
|
weapon: null,
|
|
defense: null,
|
|
};
|
|
|
|
void test('only the canonical persisted Portable identity gains the poison-defense role', () => {
|
|
assert.equal(portable.kind, 'special');
|
|
assert.ok(isPortableSnooper(portable));
|
|
assert.ok(isDefenseCard(portable));
|
|
for (const c of [
|
|
{ ...portable, id: 'forged' },
|
|
{ ...portable, name: 'Snooper' },
|
|
{ ...portable, effect: 'other' },
|
|
...richeseCards().filter((c) => c.id !== portable.id),
|
|
])
|
|
assert.equal(isPortableSnooper(c), false);
|
|
assert.deepEqual(defenseTypes(portable), ['snooper']);
|
|
assert.ok(validBattleCardPair(get('projectile'), portable));
|
|
assert.equal(validBattleCardPair(portable), false);
|
|
assert.ok(defaultVoiceMatch(portable, 'snooper'));
|
|
assert.ok(playedVoiceMatch(portable, 'defense', 'snooper'));
|
|
assert.equal(playedVoiceMatch(portable, 'weapon', 'snooper'), false);
|
|
assert.ok(defaultVoiceMatch(portable, 'portableSnooper'));
|
|
assert.ok(playedVoiceMatch(portable, 'defense', 'portableSnooper'));
|
|
assert.equal(playedVoiceMatch(portable, 'weapon', 'portableSnooper'), false);
|
|
assert.equal(defaultVoiceMatch(get('snooper'), 'portableSnooper'), false);
|
|
assert.equal(defaultVoiceMatch({ ...portable, id: 'forged' }, 'portableSnooper'), false);
|
|
});
|
|
void test('Portable stops ordinary poison and weapon Chemistry but no Tooth, Lasgun, projectile or Artillery', () => {
|
|
for (const kind of ['poison', 'chemistry'] as const)
|
|
assert.equal(weaponKills(get(kind), portable), false);
|
|
for (const kind of [
|
|
'projectile',
|
|
'poisonBlade',
|
|
'poisonTooth',
|
|
'lasgun',
|
|
'artillery',
|
|
] as const)
|
|
assert.equal(weaponKills(get(kind), portable), true);
|
|
});
|
|
void test('a late defense uses a spare slot, allowing lone Worthless in either original slot', () => {
|
|
const worthless = get('worthless'),
|
|
weapon = get('projectile'),
|
|
hand = [portable, worthless, weapon];
|
|
for (const p of [
|
|
plan,
|
|
{ ...plan, weapon: weapon.id },
|
|
{ ...plan, weapon: worthless.id },
|
|
{ ...plan, defense: worthless.id },
|
|
])
|
|
assert.equal(portableSnooperPlanBlock(p, hand, portable), null);
|
|
for (const p of [
|
|
{ ...plan, weapon: weapon.id, defense: worthless.id },
|
|
{ ...plan, leader: null },
|
|
{ ...plan, defense: 'missing' },
|
|
])
|
|
assert.ok(portableSnooperPlanBlock(p, hand, portable));
|
|
assert.ok(
|
|
portableSnooperPlanBlock(
|
|
{ ...plan, defense: get('snooper').id },
|
|
[...hand, get('snooper')],
|
|
portable,
|
|
),
|
|
);
|
|
const two = baseDeck()
|
|
.filter((c) => c.kind === 'worthless')
|
|
.slice(0, 2);
|
|
assert.ok(
|
|
portableSnooperPlanBlock(
|
|
{ ...plan, weapon: two[0].id, defense: two[1].id },
|
|
[portable, ...two],
|
|
portable,
|
|
),
|
|
);
|
|
});
|
|
void test('late eligibility obeys forbidden poison defense while preserving unrelated Voice commands', () => {
|
|
assert.match(
|
|
portableSnooperPlanBlock(plan, [portable], portable, {
|
|
target: 'p',
|
|
kind: 'snooper',
|
|
must: false,
|
|
})!,
|
|
/Voice/,
|
|
);
|
|
assert.equal(
|
|
portableSnooperPlanBlock(plan, [portable], portable, {
|
|
target: 'p',
|
|
kind: 'shield',
|
|
must: false,
|
|
}),
|
|
null,
|
|
);
|
|
});
|