feat: seedable dice core with pools
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user