From 376dde44b5038fdfbe27bd0d37e0526279d05ae9 Mon Sep 17 00:00:00 2001 From: hypercross Date: Wed, 8 Jul 2026 00:37:01 +0800 Subject: [PATCH] feat: add range support for spark table cell matching --- src/components/utils/spark-table.ts | 47 ++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/components/utils/spark-table.ts b/src/components/utils/spark-table.ts index 9dd75f6..7b4fadd 100644 --- a/src/components/utils/spark-table.ts +++ b/src/components/utils/spark-table.ts @@ -188,6 +188,43 @@ export function scanSparkTables( })); } +// --------------------------------------------------------------------------- +// Range parsing +// --------------------------------------------------------------------------- + +/** + * Parse a cell value like "1-3", "4-6", or "10-20" into { min, max }. + * Returns null if the cell doesn't represent a range. + */ +function parseRange(cell: string): { min: number; max: number } | null { + const trimmed = cell.trim(); + const m = /^(\d+)\s*-\s*(\d+)$/.exec(trimmed); + if (!m) return null; + const min = parseInt(m[1], 10); + const max = parseInt(m[2], 10); + if (isNaN(min) || isNaN(max)) return null; + return { min, max }; +} + +/** Test whether a rolled total matches a cell value */ +function matchesCell(diceCell: string, rolledValue: number): boolean { + const trimmed = diceCell.trim(); + + // Exact integer match + const cellNum = parseInt(trimmed, 10); + if (!isNaN(cellNum) && rolledValue === cellNum) return true; + + // Exact string match (for non-numeric labels) + if (String(rolledValue) === trimmed) return true; + + // Range match (e.g. "1-3") + const range = parseRange(trimmed); + if (range && rolledValue >= range.min && rolledValue <= range.max) + return true; + + return false; +} + // --------------------------------------------------------------------------- // Rolling // --------------------------------------------------------------------------- @@ -207,17 +244,11 @@ export function rollSparkTable(meta: SparkTableMeta): SparkTableResult { const roll = rollFormula(meta.notation); const rolledValue = roll.result.total; - // Find the row matching the rolled value — compare as integers, - // falling back to string comparison. + // Find the row matching the rolled value let value = `(no row for ${rolledValue})`; for (const row of meta.rows) { const diceCell = row[0] ?? ""; - const cellNum = parseInt(diceCell.trim(), 10); - const match = - !isNaN(cellNum) && rolledValue === cellNum - ? true - : String(rolledValue) === diceCell.trim(); - if (match) { + if (matchesCell(diceCell, rolledValue)) { value = row[colIdx + 1] ?? ""; break; }