@seroh/roll
Version:
An RPG dice-rolling library with a variety of built-in roll mechanics.
62 lines • 2.41 kB
JavaScript
import { Randomizer } from "./Randomizer";
export class WeightedRandomizer extends Randomizer {
weights;
cumulativeWeights;
constructor(weights) {
super();
this.validateWeights(weights);
this.weights = Object.values(weights);
this.cumulativeWeights = this.convertToCumulativeWeights(this.weights);
}
generator() {
return this.mapToWeightedValue(Math.random(), this.cumulativeWeights);
}
validateWeights(weights) {
const entries = Object.entries(weights);
let maxFace = 0;
let weightSum = 0;
for (const [key, value] of entries) {
const face = Number(key);
// Validate key
if (key === "" || !Number.isInteger(face) || face <= 0) {
throw new Error(`Invalid key "${key}": All keys must be positive integers.`);
}
// Validate value
if (typeof value !== "number" || value > 1 || value < 0 || isNaN(value)) {
throw new Error(`Invalid value for key "${key}": All values must be a number between 0 and 1 (inclusive). Received "${value}".`);
}
if (face > maxFace) {
maxFace = face;
}
weightSum += value;
}
if (maxFace !== entries.length) {
throw new Error(`Incorrect number of weight entries: Expected entries from 1 to ${maxFace}, but received ${entries.length} entries total.`);
}
if (weightSum != 1) {
throw new Error(`Incorrect weights provided, the total of all weights expected to be 1. Got ${weightSum}`);
}
}
convertToCumulativeWeights(weights) {
const cumulativeWeights = [];
let cumulativeSum = 0;
for (let i = 0; i < weights.length; i++) {
cumulativeSum += weights[i];
cumulativeWeights.push(cumulativeSum);
}
return cumulativeWeights;
}
mapToWeightedValue(randomValue, cumulativeWeights) {
for (let i = 0; i < cumulativeWeights.length; i++) {
if (randomValue < cumulativeWeights[i]) {
return i + 1;
}
}
// Should not reach here if weights are valid
throw new Error("Invalid weights or random value.");
}
scaleToRange(weightedRoll) {
return weightedRoll;
}
}
//# sourceMappingURL=WeightedRandomizer.js.map