77 lines
1.8 KiB
TypeScript
77 lines
1.8 KiB
TypeScript
export interface ParsedDice {
|
|
count: number;
|
|
sides: number;
|
|
modifier: number;
|
|
raw: string;
|
|
}
|
|
|
|
export interface RollResult {
|
|
rolls: number[];
|
|
modifier: number;
|
|
total: number;
|
|
expression: string;
|
|
error?: string;
|
|
}
|
|
|
|
export function parseDice(expression: string): ParsedDice | null {
|
|
const cleaned = expression.trim().toLowerCase().replace(/\s/g, "");
|
|
const match = cleaned.match(/^(\d*)d(\d+)([+-]\d+)?$/);
|
|
if (!match) return null;
|
|
|
|
const count = match[1] ? parseInt(match[1], 10) : 1;
|
|
const sides = parseInt(match[2], 10);
|
|
const modifier = match[3] ? parseInt(match[3], 10) : 0;
|
|
|
|
if (count < 1 || count > 100 || sides < 1 || sides > 100) return null;
|
|
|
|
return { count, sides, modifier, raw: expression.trim() };
|
|
}
|
|
|
|
export function rollDice(
|
|
expression: string,
|
|
options?: { advantage?: boolean; disadvantage?: boolean },
|
|
): RollResult {
|
|
const parsed = parseDice(expression);
|
|
if (!parsed) {
|
|
return {
|
|
rolls: [],
|
|
modifier: 0,
|
|
total: 0,
|
|
expression,
|
|
error: `Couldn't parse: ${expression}`,
|
|
};
|
|
}
|
|
|
|
const { count, sides, modifier } = parsed;
|
|
|
|
if (
|
|
(options?.advantage || options?.disadvantage) &&
|
|
count === 1 &&
|
|
sides === 20
|
|
) {
|
|
const roll1 = Math.floor(Math.random() * sides) + 1;
|
|
const roll2 = Math.floor(Math.random() * sides) + 1;
|
|
const chosen = options.advantage
|
|
? Math.max(roll1, roll2)
|
|
: Math.min(roll1, roll2);
|
|
return {
|
|
rolls: [roll1, roll2],
|
|
modifier,
|
|
total: chosen + modifier,
|
|
expression,
|
|
};
|
|
}
|
|
|
|
const rolls: number[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
rolls.push(Math.floor(Math.random() * sides) + 1);
|
|
}
|
|
const sum = rolls.reduce((a, b) => a + b, 0);
|
|
|
|
return {
|
|
rolls,
|
|
modifier,
|
|
total: sum + modifier,
|
|
expression,
|
|
};
|
|
}
|