Files
Dune/game/truthtrance-freeform-facts-v2.ts
T
Quintine 9dd386ff7a
Verify and publish checkpoint container / container (push) Canceled after 0s
Expand recognized Truthtrance fact questions
2026-10-29 04:34:29 +11:00

46 lines
2.3 KiB
TypeScript

import { parseFreeformFactV1 } from './truthtrance-freeform-facts';
import type { HandInventoryFact } from './truthtrance-hand-inventory';
import type { TruthFact } from './truthtrance';
type V1Fact = Extract<TruthFact, { kind: 'hand' | 'spice' }>;
export type FreeformFactV2 = V1Fact | HandInventoryFact |
{ kind: 'and' | 'or'; terms: [V1Fact, V1Fact] };
/**
* Version 2 preserves version 1 and recognizes a small additional set of
* complete current facts. Do not change this grammar for saved v2 questions.
* Callers must still restrict interpretation to fact-scoped questions.
*/
export function parseFreeformFactV2(text: string): FreeformFactV2 | null {
if (typeof text !== 'string' || text.length > 500) return null;
const input = text.trim();
// Check both whole-name and compound parses before deciding; a future
// printed name might itself contain a conjunction between known names.
const existing = parseFreeformFactV1(input);
const count = /^do[ \t]+you[ \t]+have[ \t]+(at[ \t]+least|exactly|at[ \t]+most)[ \t]+([0-9]+)[ \t]+cards?[ \t]+in[ \t]+your[ \t]+hand[ \t]*\??$/i.exec(input);
if (count) {
const value = Number(count[2]);
if (!Number.isSafeInteger(value) || value < 0) return null;
const relation = count[1].toLowerCase().replace(/[ \t]+/g, ' ');
return { kind: 'handInventory', category: 'all',
compare: relation === 'at least' ? 'gte' : relation === 'at most' ? 'lte' : 'eq', value };
}
const compound = /^do[ \t]+you[ \t]+have[ \t]+(.+?)[ \t]*\??$/i.exec(input);
if (!compound) return existing;
const body = compound[1];
const separator = /[ \t]+(and|or)[ \t]+/gi;
const matches: Array<Extract<FreeformFactV2, { kind: 'and' | 'or' }>> = [];
for (const match of body.matchAll(separator)) {
const index = match.index ?? 0;
const left = body.slice(0, index);
const right = body.slice(index + match[0].length);
const first = parseFreeformFactV1(`Do you have ${left}`);
const second = parseFreeformFactV1(/^do[ \t]+you[ \t]+have[ \t]+/i.test(right)
? right : `Do you have ${right}`);
if (first && second) matches.push({ kind: match[1].toLowerCase() as 'and' | 'or', terms: [first, second] });
}
// A whole printed name and a compound are also ambiguous if both fit.
if (matches.length > 1 || existing && matches.length > 0) return null;
return existing ?? matches[0] ?? null;
}