From cf028e9a08d46233fefcb5ff3cb75d164c8f66ca Mon Sep 17 00:00:00 2001 From: Tim Evans Date: Mon, 20 Jul 2026 13:49:54 +0100 Subject: [PATCH] feat: seedable dice core with pools --- src/dice/roller.js | 18 ++++++++++++++++++ tests/dice/roller.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/dice/roller.js create mode 100644 tests/dice/roller.test.js diff --git a/src/dice/roller.js b/src/dice/roller.js new file mode 100644 index 0000000..9669f0b --- /dev/null +++ b/src/dice/roller.js @@ -0,0 +1,18 @@ +export function expandDice(diceSpecs) { + const out = []; + for (const d of diceSpecs) { + const count = d.count ?? 1; + for (let i = 0; i < count; i++) out.push({ sides: d.sides }); + } + return out; +} + +export function rollDie(sides, rng) { + return Math.floor(rng() * sides) + 1; +} + +export function rollPool(diceSpecs, rng) { + const results = expandDice(diceSpecs).map((d) => ({ sides: d.sides, value: rollDie(d.sides, rng) })); + const total = results.reduce((s, r) => s + r.value, 0); + return { results, total }; +} diff --git a/tests/dice/roller.test.js b/tests/dice/roller.test.js new file mode 100644 index 0000000..b5251b2 --- /dev/null +++ b/tests/dice/roller.test.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { expandDice, rollDie, rollPool } from '../../src/dice/roller.js'; + +// deterministic rng that replays a fixed sequence +function seq(values) { let i = 0; return () => values[i++ % values.length]; } + +describe('dice core', () => { + it('expands count into individual dice', () => { + expect(expandDice([{ sides: 8, count: 3 }])).toEqual([{ sides: 8 }, { sides: 8 }, { sides: 8 }]); + expect(expandDice([{ sides: 6 }])).toEqual([{ sides: 6 }]); + }); + + it('maps rng to inclusive 1..sides', () => { + expect(rollDie(8, () => 0)).toBe(1); + expect(rollDie(8, () => 0.999)).toBe(8); + expect(rollDie(6, () => 0.5)).toBe(4); + }); + + it('rolls a pool and totals it', () => { + const rng = seq([0.9, 0.0, 0.7]); // d8 -> 8, 1, 6 + const { results, total } = rollPool([{ sides: 8, count: 3 }], rng); + expect(results.map((r) => r.value)).toEqual([8, 1, 6]); + expect(total).toBe(15); + expect(results[0]).toEqual({ sides: 8, value: 8 }); + }); +});