Files
Dune/game/truthtrance-freeform-facts-v4.ts
T
Quintine 4f990db52e
Verify and publish checkpoint container / container (push) Canceled after 0s
Expand Basic Semuta and Truthtrance rules
2026-10-29 05:57:46 +11:00

29 lines
1.6 KiB
TypeScript

import { TRUTH_CARD_NAMES } from './truthtrance-card-names';
import { parseFreeformFactV3 } from './truthtrance-freeform-facts-v3';
import type { CardCountFact } from './truthtrance-card-count';
import type { HandInventoryFact } from './truthtrance-hand-inventory';
/**
* Recognize only new, unambiguous present-tense negation forms. Earlier
* parsers remain unchanged for previously stamped questions. The caller
* limits interpretation to fact-scoped questions and displays the result.
*/
export function parseFreeformFactV4(text: string): CardCountFact | HandInventoryFact | null {
if (typeof text !== 'string' || text.length > 500) return null;
const match = /^do[ \t]+you[ \t]+(?:have[ \t]+no|not[ \t]+have[ \t]+(a|an|any))[ \t]+(.+?)[ \t]*\??$/i.exec(text.trim());
if (!match) return null;
const subject = match[2].trim();
// Reuse v3's bounded primary-role vocabulary and zero-count semantics.
const category = parseFreeformFactV3(`Do you have exactly 0 ${subject}`);
const inventory = category?.kind === 'handInventory' ? category : null;
const nameInput = subject.replace(/[ \t]+in[ \t]+your[ \t]+hand$/i, '').trim();
if (inventory && /^(a|an)$/i.test(match[1] ?? '') && /s$/i.test(nameInput)) return null;
const names = TRUTH_CARD_NAMES.filter(name => name.toLowerCase() === nameInput.toLowerCase());
// Refuse if the words could mean both a named face and a category.
if ((inventory && names.length > 0) || names.length > 1) return null;
if (inventory) return inventory;
return names.length === 1
? { kind: 'handCount', name: names[0], compare: 'eq', value: 0 }
: null;
}