/**
* @summary Abstract base class representing a Psuedo Random Number Generator.
* @abstract
* @class
* Represents an abstract base class for psuedo-random number generators. This class shouldn't be instantiated directly.
* @description This class is intended to be extended by specific implementations such as {@link LCG}, {@link Mulberry32} or {@link XORShift32}.
*/
export class PRNG {
/**
* Creates an instance of PRNG.
* @param {number} [seed=Date.now()] - The initial seed value for the PRNG.
*/
constructor(seed = Date.now()) {
this.seed = seed;
}
/**
* Returns the maximum possible value that the PRNG can generate.
* @returns {number} - The maximum value of the PRNG.
* @throws {Error} - Must be implemented by subclasses.
*/
maxValue() {
throw new Error("Method maxValue() must be implemented.")
}
/**
* Generates the next psuedo-random number.
* @returns {number} - The next psuedo-random number.
* @throws {Error} - Must be implemented by subclasses.
*/
next() {
throw new Error("Method next() must be implemented.")
}
/**
* Generate a random float in the range [0, 1).
* @returns {number} - A number between 0 (inclusive) and 1 (exclusive).
*/
randomFloat() {
return this.next() / this.maxValue();
}
/**
* Generate a random float in the range [-1, 1).
* @returns {number} - A number between -1 (inclusive) and 1 (exclusive).
*/
randomBipolarFloat() {
return this.randomFloat() * 2 - 1;
}
/**
* Generate a random integer from a specified range of values.
* @param {number} min - The minimum integer value (inclusive).
* @param {number} max - The maximum integer value (exclusive).
* @returns {number} - A random integer between min (inclusive) and max (exclusive).
*/
randomInteger(min, max) {
const range = max - min;
return min + Math.floor(this.randomFloat() * range);
}
/**
* Select a random element from an array.
* @param {array} array - The array from which to select an element.
* @returns {*} - A randomly selected element from the array.
*/
randomElement(array) {
const index = Math.floor(this.randomFloat() * array.length);
return array[index];
}
/**
* Returns a boolean based on a specified probability.
* @param {number} [chance=0.5] - The probability of returning true (between 0 and 1).
* @returns {boolean} - True if the random float is less than the chance, otherwise false.
*/
randomChance(chance = 0.5) {
return this.randomFloat() < chance;
}
/**
* Selects an option probabalistically from a set of weighted choices.
* @param {Array<Object>} choices - An array of objects with `option` and `weight` properties.
* @param {function|number|string} choices[].option - The outcome of the choice.
* @param {number} choices[].weight - The weight of the choice. Higher weights (relative to the other choices) increase the likelihood of selection.
* @returns {function|number|string} - The selected option.
*/
randomWeighted(choices) {
const length = choices.length;
let totalWeight = 0;
for (let choice of choices) {
totalWeight += choice.weight;
}
const randomNumberInRange = this.randomFloat() * totalWeight;
let cumulativeWeight = 0;
for (let choice of choices) {
cumulativeWeight += choice.weight;
if (randomNumberInRange < cumulativeWeight) {
if (typeof choice.option === 'function') {
return choice.option();
}
return choice.option;
}
}
}
}
/**
* LCG (Linear Congruential Generator) Pseudorandom Number Generator Class
* @extends PRNG
*
* @summary Implements the LCG algorithm to generate pseudorandom numbers.
* @description
* This class provides the LCG method of generating psuedorandom
* numbers to the {@link PRNG} class.
*
* @example
* const rng = new LCG(123456789);
* console.log(rng.next()); // Generates a pseudorandom number
*
* @see https://en.wikipedia.org/wiki/Linear_congruential_generator
*/
export class LCG extends PRNG {
/**
* Creates an instance of LCG.
* @param {number} seed - The seed value for the LCG PRNG.
*/
constructor(seed) {
super(seed);
this.modulus = 2 ** 32;
this.multiplier = 1664525;
this.increment = 1013904223;
this.state = this.seed;
}
/**
* Generates the next pseudorandom number.
* @returns {number} A pseudorandom number in the range [0-1).
*/
next() {
this.state = (this.multiplier * this.state + this.increment) % this.modulus;
return this.state;
}
/**
* Returns the maximum possible value of the LCG.
* @returns {number} The maximum value (2^32).
*/
maxValue() {
return this.modulus;
}
}
/**
* Mulberry32 Pseudorandom Number Generator Class
* @extends PRNG
*
* @summary Implements the Mulberry32 algorithm to generate pseudorandom numbers.
* @description
* This class provides the Mulberry32 method of generating psuedorandom
* numbers to the {@link PRNG} class.
*
* The Mulberry32 algorithm was written by Tommy Ettinger in 2017 and is released to the public domain, meaning it can be freely used, modified, and distributed without restrictions.
*
* @example
* const rng = new Mulberry32(123456789);
* console.log(rng.next()); // Generates a pseudorandom number
*
* @see https://gist.github.com/tommyettinger/46a874533244883189143505d203312c
*/
export class Mulberry32 extends PRNG {
/**
* Creates an instance of Mulberry32.
* @param {number} seed - The seed value for the Mulberry32 PRNG.
*/
constructor(seed) {
super(seed);
this.state = this.seed;
}
/**
* Returns the maximum possible value of Mulberry32.
* @returns {number} The maximum value (2^32).
*/
maxValue() {
return 2 ** 32;
}
/**
* Generates the next psuedo-random number.
* @returns {number} A number in the range [0-1).
*
* @example
* // Returns a random number in the range [0-1)
* const rng = new Mulberry32();
* console.log(rng.next());
*/
next() {
let t = this.state += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, 1 | t);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
this.state = t ^ t >>> 14;
return this.state >>> 0;
}
}
/**
* XORShift32 Pseudorandom Number Generator Class
* @extends PRNG
*
* @summary Implements the XORShift32 algorithm to generate pseudorandom numbers.
* @description
* This class provides the XORShift32 method of generating psuedorandom
* numbers to the {@link PRNG} class. It uses bitwise operations
* to generate a sequence of pseudorandom 32-bit unsigned integers.
*
* The XORShift algorithm was written by Geogrge Marsaglia in 2003 and is released to the public domain, meaning it can be freely used, modified, and distributed without restrictions.
*
* @example
* const rng = new XORShift32(123456789);
* console.log(rng.next()); // Generates a pseudorandom number
*
* @see Marsaglia, G. (2003) "XORShift RNGs", Journal of Statistical Software
* https://www.jstatsoft.org/article/view/v008i14
*/
export class XORShift32 extends PRNG {
constructor(seed) {
super(seed);
this.state = this.seed;
}
/**
* Returns the maximum possible value of XORShift32.
* @returns {number} The maximum value (2^32).
*/
maxValue() {
return 2 ** 32;
}
/**
* Generates the next psuedo-random number.
* @returns {number} A number in the range [0-1).
*
* @example
* // Returns a random number in the range [0-1)
* const rng = new XORShift32();
* console.log(rng.next());
*/
next() {
let a = this.state;
a ^= a << 13;
a ^= a >>> 17;
a ^= a << 5;
this.state = a >>> 0; // Ensure it stays a 32-bit unsigned integer
return this.state;
}
}
/**
* @summary Create a random hexadecimal string of a specified length.
* @description Uses Math.random() to make a hexadecimal string for
* setting the seed of a {@link Plot} to be used in a seedable {@link PRNG} such as {@link LCG}
* @param {number} n - Number of digits
* @returns {string} - Hexadecimal string of length n
*/
export function unseededRandomHex(n) {
const hexArray = Array.from({
length: n
}, () => {
return Math.floor(Math.random() * 16).toString(16)
});
const hex = hexArray.join("");
const decimal = parseInt(hex, 16);
return {
hex, decimal
}
}