108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
import type { Card } from './cards';
|
|
import {
|
|
CARD_CATEGORIES,
|
|
printedCardCategory,
|
|
type CardCategory,
|
|
} from './card-category';
|
|
import { defenseTypes, weaponTypes } from './battle-cards';
|
|
|
|
export const HAND_INVENTORY_TYPES = [
|
|
'projectileWeapon', 'poisonWeapon', 'projectileDefense', 'poisonDefense',
|
|
] as const;
|
|
type HandInventoryType = (typeof HAND_INVENTORY_TYPES)[number];
|
|
|
|
/** A current custody question, not a promise to retain cards or use them. */
|
|
export type HandInventoryFact = {
|
|
kind: 'handInventory';
|
|
category: 'all' | CardCategory | HandInventoryType;
|
|
compare: 'eq' | 'gte' | 'lte';
|
|
value: number;
|
|
};
|
|
|
|
export const HAND_INVENTORY_LABELS: Record<
|
|
HandInventoryFact['category'],
|
|
string
|
|
> = {
|
|
all: 'All cards in hand',
|
|
weapon: 'Weapons · primary role',
|
|
defense: 'Defenses · primary role',
|
|
worthless: 'Worthless cards',
|
|
hero: 'Cheap Heroes and Heroines',
|
|
special: 'Special cards without a primary battle role',
|
|
projectileWeapon: 'Projectile weapons · primary role',
|
|
poisonWeapon: 'Poison weapons · primary role',
|
|
projectileDefense: 'Projectile defenses · primary role',
|
|
poisonDefense: 'Poison defenses · primary role',
|
|
};
|
|
|
|
export function parseHandInventoryFact(
|
|
v: Record<string, unknown>,
|
|
): HandInventoryFact {
|
|
if (
|
|
v.kind !== 'handInventory' ||
|
|
(v.category !== 'all' &&
|
|
!CARD_CATEGORIES.includes(v.category as CardCategory) &&
|
|
!HAND_INVENTORY_TYPES.includes(v.category as HandInventoryType))
|
|
)
|
|
throw new Error('Choose all held cards or a known primary card role.');
|
|
if (v.compare !== 'eq' && v.compare !== 'gte' && v.compare !== 'lte')
|
|
throw new Error('Compare held cards using exactly, at least, or at most.');
|
|
if (
|
|
typeof v.value !== 'number' ||
|
|
!Number.isSafeInteger(v.value) ||
|
|
v.value < 0
|
|
)
|
|
throw new Error(
|
|
'Choose a nonnegative whole card count within the safe integer range.',
|
|
);
|
|
return {
|
|
kind: 'handInventory',
|
|
category: v.category as HandInventoryFact['category'],
|
|
compare: v.compare,
|
|
value: v.value,
|
|
};
|
|
}
|
|
|
|
export function handInventoryFactMatches(
|
|
hand: readonly Card[],
|
|
fact: HandInventoryFact,
|
|
): boolean {
|
|
const category = fact.category;
|
|
const count = category === 'all' ? hand.length : hand.filter((card) => {
|
|
const primary = printedCardCategory(card);
|
|
if (category === 'projectileWeapon' || category === 'poisonWeapon')
|
|
return primary === 'weapon' && weaponTypes(card).includes(category === 'projectileWeapon' ? 'projectile' : 'poison');
|
|
if (category === 'projectileDefense' || category === 'poisonDefense')
|
|
return primary === 'defense' && defenseTypes(card).includes(category === 'projectileDefense' ? 'shield' : 'snooper');
|
|
return primary === category;
|
|
}).length;
|
|
return fact.compare === 'eq'
|
|
? count === fact.value
|
|
: fact.compare === 'gte'
|
|
? count >= fact.value
|
|
: count <= fact.value;
|
|
}
|
|
|
|
export function handInventoryFactText(fact: HandInventoryFact): string {
|
|
const comparison =
|
|
fact.compare === 'eq'
|
|
? 'exactly'
|
|
: fact.compare === 'gte'
|
|
? 'at least'
|
|
: 'at most';
|
|
const cards = fact.value === 1 ? 'card' : 'cards';
|
|
const description: Record<HandInventoryFact['category'], string> = {
|
|
all: `${cards} in your hand`,
|
|
weapon: `${cards} whose primary role is a weapon`,
|
|
defense: `${cards} whose primary role is a defense`,
|
|
worthless: `Worthless ${cards}`,
|
|
hero: `Cheap Hero or Cheap Heroine ${cards}`,
|
|
special: `Special ${cards} without a primary battle role`,
|
|
projectileWeapon: `${cards} whose primary role is a projectile weapon`,
|
|
poisonWeapon: `${cards} whose primary role is a poison weapon`,
|
|
projectileDefense: `${cards} whose primary role is a projectile defense`,
|
|
poisonDefense: `${cards} whose primary role is a poison defense`,
|
|
};
|
|
return `you currently hold ${comparison} ${fact.value} ${description[fact.category]}`;
|
|
}
|