darkwatch/server/src/dice.ts

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,
};
}