Files
2D6-Dungeon/src/rules/dice.ts
Keith Solomon 6bf48df74c Feature: add character creation logic and tests
- Implement character creation functions to handle adventurer setup.
- Add validation for adventurer name and selection of starting items.
- Introduce new items in sample content pack: Flint and Steel, Pouch, Wax Sealing Kit, and Backpack.
- Create tests for character creation functionality to ensure valid options and error handling.

Feature: implement dice rolling mechanics and tests

- Add functions for rolling various dice types (d3, d6, 2d6, d66) with validation.
- Implement modifier application with clamping for roll results.
- Create tests to verify correct behavior of dice rolling functions.

Feature: add table lookup functionality and tests

- Implement table lookup and resolution logic for defined tables.
- Support for modified ranges and fallback to nearest entries.
- Create tests to ensure correct table entry resolution based on roll results.
2026-03-15 11:59:50 -05:00

114 lines
2.6 KiB
TypeScript

import type { DiceKind, RollResult } from "@/types/rules";
export type DiceRoller = (sides: number) => number;
const defaultDiceRoller: DiceRoller = (sides) =>
Math.floor(Math.random() * sides) + 1;
function assertRollInRange(value: number, sides: number) {
if (!Number.isInteger(value) || value < 1 || value > sides) {
throw new Error(`Dice roller returned invalid value ${value} for d${sides}.`);
}
}
function clampTotal(total: number, min: number, max: number) {
return Math.max(min, Math.min(max, total));
}
export function rollD3(roller: DiceRoller = defaultDiceRoller): RollResult {
const value = roller(6);
assertRollInRange(value, 6);
const total = Math.ceil(value / 2);
return {
diceKind: "d3",
rolls: [value],
total,
modifiedTotal: total,
};
}
export function rollD6(roller: DiceRoller = defaultDiceRoller): RollResult {
const value = roller(6);
assertRollInRange(value, 6);
return {
diceKind: "d6",
rolls: [value],
total: value,
modifiedTotal: value,
};
}
export function roll2D6(roller: DiceRoller = defaultDiceRoller): RollResult {
const first = roller(6);
const second = roller(6);
assertRollInRange(first, 6);
assertRollInRange(second, 6);
const total = first + second;
return {
diceKind: "2d6",
rolls: [first, second],
primary: first,
secondary: second,
total,
modifiedTotal: total,
};
}
export function rollD66(roller: DiceRoller = defaultDiceRoller): RollResult {
const primary = roller(6);
const secondary = roller(6);
assertRollInRange(primary, 6);
assertRollInRange(secondary, 6);
const total = primary * 10 + secondary;
return {
diceKind: "d66",
rolls: [primary, secondary],
primary,
secondary,
total,
modifiedTotal: total,
};
}
export function applyRollModifier(
roll: RollResult,
modifier = 0,
limits?: { min: number; max: number },
): RollResult {
if (roll.total === undefined) {
throw new Error("Cannot apply a modifier to a roll without a total.");
}
const nextTotal = roll.total + modifier;
const boundedTotal = limits
? clampTotal(nextTotal, limits.min, limits.max)
: nextTotal;
return {
...roll,
modifier,
modifiedTotal: boundedTotal,
clamped: limits ? boundedTotal !== nextTotal : false,
};
}
export function rollDice(
diceKind: DiceKind,
roller: DiceRoller = defaultDiceRoller,
): RollResult {
switch (diceKind) {
case "d3":
return rollD3(roller);
case "d6":
return rollD6(roller);
case "2d6":
return roll2D6(roller);
case "d66":
return rollD66(roller);
}
}