type DiceType = 'f' | 'r' | 't'; interface RollResult { input: string; value: number; rolls: number[]; // Kept dice discarded: number[]; // Discarded dice summary: string; // Formatted string of all dice steps?: string[]; // Arithmetic operations performed } function rollDie(diceType: DiceType): number { const roll = { f: Math.floor(Math.random() * 12) + 1 - 6, r: Math.floor(Math.random() * 18) + 1 - 9, t: Math.floor(Math.random() * 24) + 1 - 12, }; return roll[diceType]; } function rollDice(count: number, diceType: DiceType): number[] { return Array.from({ length: count }, () => rollDie(diceType)); } function getDieRange(diceType: DiceType): { min: number; max: number } { const ranges = { f: { min: -5, max: 6 }, r: { min: -8, max: 9 }, t: { min: -11, max: 12 }, }; return ranges[diceType]; } function formatSummary(rolls: number[], discarded: number[], diceType: DiceType): string { const { min, max } = getDieRange(diceType); const all = rolls.map(r => ({ value: r, kept: true })) .concat(discarded.map(r => ({ value: r, kept: false }))); return all .sort((a, b) => b.value - a.value) .map(({ value, kept }) => { const isMinOrMax = value === min || value === max; let formatted = `${value}`; if (isMinOrMax) formatted = `**${formatted}**`; if (!kept) formatted = `~${formatted}~`; return formatted; }) .join(' '); } function parseExpression(input: string): RollResult | RollResult[] { const batchMatch = input.match(/^(\d+)#(.+)$/); if (batchMatch) { const [, batchCountStr, innerExpr] = batchMatch; const batchCount = parseInt(batchCountStr, 10); return Array.from({ length: batchCount }, () => parseSingleExpression(innerExpr)); } else { return parseSingleExpression(input); } } function parseSingleExpression(expr: string): RollResult { const baseMatch = expr.match(/^(\d+)?([frt])(?:(kh|kl)(\d+))?/); if (!baseMatch) throw new Error(`Invalid expression: ${expr}`); const [, countStr, diceType, keepType, keepCountStr] = baseMatch; const diceExprLength = baseMatch[0].length; const arithmeticExpr = expr.slice(diceExprLength); const count = parseInt(countStr || '1', 10); const dice = diceType as DiceType; const keepCount = keepCountStr ? parseInt(keepCountStr, 10) : null; const allRolls = rollDice(count, dice); let rolls = [...allRolls]; let discarded: number[] = []; if (keepType && keepCount !== null) { const sorted = [...allRolls].sort((a, b) => keepType === 'kh' ? b - a : a - b); rolls = sorted.slice(0, keepCount); discarded = sorted.slice(keepCount); } const steps: string[] = []; let result = rolls.reduce((acc, val) => acc + val, 0); steps.push(`Initial sum: ${result}`); const opRegex = /([+\-*/])\s*(-?\d+(?:\.\d+)?)/g; let match; while ((match = opRegex.exec(arithmeticExpr)) !== null) { const [, op, valStr] = match; const val = parseFloat(valStr); const prev = result; switch (op) { case '+': result += val; break; case '-': result -= val; break; case '*': result *= val; break; case '/': result /= val; break; } steps.push(`${prev} ${op} ${val} = ${result}`); } return { input: expr, value: result, rolls, discarded, summary: formatSummary(rolls, discarded, dice), steps }; } function safeParseInput(message: string): RollResult | RollResult[] | null { const trimmed = message.trim(); const isSingleWord = !trimmed.includes(' '); if (isSingleWord && !trimmed.startsWith('[') && !trimmed.endsWith(']')) { try { return parseExpression(trimmed); } catch { return null; } } if (isSingleWord && trimmed.startsWith('[') && trimmed.endsWith(']')) { const inside = trimmed.slice(1, -1).trim(); try { return parseExpression(inside); } catch { return null; } } const words = trimmed.split(/\s+/); const expressions = words .filter(word => word.startsWith('[') && word.endsWith(']')) .map(word => word.slice(1, -1).trim()); if (expressions.length === 0) { return null; } const results: RollResult[] = []; for (const expr of expressions) { try { const result = parseExpression(expr); if (Array.isArray(result)) { results.push(...result); } else { results.push(result); } } catch { // skip invalid } } return results.length > 0 ? results : null; } console.log(safeParseInput('Hello I roll a 5rkh3+1')); console.log(safeParseInput('I ROLL asdf[2#3fkl1*2]asdf')); console.log(safeParseInput('[2#3fkl1*2] [2#3fkl1*2]'));