Files
Dune/game/truthtrance-freeform-facts-v3.ts
T
Quintine 7a083ac9aa
Verify and publish checkpoint container / container (push) Failing after 10m53s
Extend Truthtrance facts and Basic Semuta Tech
2026-10-29 05:15:04 +11:00

42 lines
2.2 KiB
TypeScript

import { parseFreeformFactV2, type FreeformFactV2 } from './truthtrance-freeform-facts-v2';
import type { HandInventoryFact } from './truthtrance-hand-inventory';
export type FreeformFactV3 = FreeformFactV2 | HandInventoryFact;
/** Exact category phrases only. These mean the existing printed primary role,
* not every card a faction might use in that slot through a special power. */
const categories: Record<string, HandInventoryFact['category']> = {
weapon: 'weapon', weapons: 'weapon',
defense: 'defense', defenses: 'defense',
'worthless card': 'worthless', 'worthless cards': 'worthless',
'projectile weapon': 'projectileWeapon', 'projectile weapons': 'projectileWeapon',
'poison weapon': 'poisonWeapon', 'poison weapons': 'poisonWeapon',
'projectile defense': 'projectileDefense', 'projectile defenses': 'projectileDefense',
'poison defense': 'poisonDefense', 'poison defenses': 'poisonDefense',
};
/**
* Version 3 extends the stable v2 grammar with anchored, current hand-category
* questions. Saved v1 and v2 receipts must continue using their own parsers.
* The caller restricts this to fact-scoped questions and displays the primary
* role interpretation before an answer is checked.
*/
export function parseFreeformFactV3(text: string): FreeformFactV3 | null {
if (typeof text !== 'string' || text.length > 500) return null;
const existing = parseFreeformFactV2(text);
if (existing) return existing;
const input = text.trim();
const match = /^do[ \t]+you[ \t]+have[ \t]+(?:(a|an|any)|(at[ \t]+least|exactly|at[ \t]+most)[ \t]+([0-9]+))[ \t]+([a-z]+(?:[ \t]+[a-z]+)*?)(?:[ \t]+in[ \t]+your[ \t]+hand)?[ \t]*\??$/i.exec(input);
if (!match) return null;
const label = match[4].toLowerCase().replace(/[ \t]+/g, ' ');
if (!Object.hasOwn(categories, label)) return null;
const category = categories[label];
if (match[1] && /^(?:a|an)$/i.test(match[1]) && label.endsWith('s')) return null;
const count = match[3] === undefined ? 1 : Number(match[3]);
if (!Number.isSafeInteger(count) || count < 0) return null;
const comparison = match[2]?.toLowerCase().replace(/[ \t]+/g, ' ');
return { kind: 'handInventory', category,
compare: comparison === 'exactly' ? 'eq' : comparison === 'at most' ? 'lte' : 'gte',
value: count };
}