toosoon-prng
Version:
This project provides PRNG functions for generating pseudo-random values using a seed-based approach and various algorithms
191 lines (190 loc) • 5.97 kB
JavaScript
import { cyrb128, jsf32, mulberry32, sfc32, splitmix32, xoshiro128ss } from 'toosoon-utils/prng';
import { AlgorithmName } from './types';
/**
* Utility class for generating pseudo-random values
*
* @class PRNG
* @exports
*/
export class PRNG {
_seed = '';
_algorithm = splitmix32;
get seed() {
return this._seed;
}
set seed(seed) {
this._seed = `${seed}`;
}
get algorithm() {
return this._algorithm;
}
set algorithm(algorithm) {
this._algorithm = algorithm;
}
/**
* Set this PRNG seed
*
* @param {string|number} seed
*/
setSeed(seed) {
this.seed = `${seed}`;
}
/**
* Set this PRNG algorithm
*
* @param {AlgorithmName} algorithmName Algorithm name
*/
setAlgorithm(algorithmName) {
this.algorithm = this.getAlgorithmByName(algorithmName);
}
/**
* Generate a pseudo-random number in the interval [0, 1]
* PRNG equivalent of `Math.random()`
*
* @param {string|number} seed
* @returns {number}
*/
random(seed) {
const hashes = cyrb128(this.seed + `${seed}`);
return this.algorithm(...hashes);
}
/**
* Generate a pseudo-random boolean (true or false)
*
* @param {string|number} seed
* @param {number} [probability=0.5] Probability to get `true`
* @returns {boolean} Either `true` or `false`
*/
randomBoolean(seed, probability = 0.5) {
return this.random(seed) < probability;
}
/**
* Generate a pseudo-random sign (1 or -1)
*
* @param {string|number} seed
* @param {number} [probability=0.5] Probability to get 1
* @returns {number} Either 1 or -1
*/
randomSign(seed, probability = 0.5) {
return this.randomBoolean(seed, probability) ? 1 : -1;
}
/**
* Generate a pseudo-random floating-point number within a specified range
*
* @param {string|number} seed
* @param {number} [min=0] Minimum boundary
* @param {number} [max=1] Maximum boundary
* @param {number} [precision=2] Number of digits after the decimal point
* @returns {number} Generated float
*/
randomFloat(seed, min = 0, max = 1, precision = 2) {
return parseFloat(Math.min(min + this.random(seed) * (max - min), max).toFixed(precision));
}
/**
* Generate a pseudo-random integer number within a specified range
*
* @param {string|number} seed
* @param {number} min Minimum boundary
* @param {number} max Maximum boundary
* @returns {number} Generated integer
*/
randomInt(seed, min, max) {
return Math.floor(this.random(seed) * (max - min + 1) + min);
}
/**
* Generate a pseudo-random hexadecimal color
*
* @param {string|number} seed
* @returns {string} Generated hexadecimal color
*/
randomHexColor(seed) {
return '#' + ('00000' + ((this.random(seed) * (1 << 24)) | 0).toString(16)).slice(-6);
}
/**
* Pick a pseudo-random item from a given array
*
* @param {string|number} seed
* @param {T[]} array Array to pick the item from
* @returns {T|undefined} Random item picked
*/
randomItem(seed, array) {
if (array.length === 0)
return undefined;
return array[this.randomInt(seed, 0, array.length - 1)];
}
/**
* Pick a pseudo-random property value from a given object
*
* @param {string|number} seed
* @param {object} object Object to pick the property from
* @returns {T|undefined} Random item picked
*/
randomObjectProperty(seed, object) {
const keys = Object.keys(object);
const key = this.randomItem(seed, keys);
if (key && object.hasOwnProperty(key)) {
return object[key];
}
}
/**
* Select a pseudo-random index from an array of weighted items
*
* @param {string|number} seed
* @param {number[]} weights Array of weights
* @returns {number} Random index based on weights
*/
randomIndex(seed, weights) {
if (weights.length === 0)
return -1;
let totalWeight = 0;
for (let weight of weights) {
totalWeight += weight;
}
if (totalWeight <= 0)
console.warn('PRNG.randomIndex()', 'Weights must sum to > 0', totalWeight);
let weight = this.random(seed) * totalWeight;
for (let i = 0; i < weights.length; i++) {
if (weight < weights[i])
return i;
weight -= weights[i];
}
return 0;
}
/**
* Generate a pseudo-random number fitting a Gaussian (normal) distribution
*
* @param {string|number} seed
* @param {number} [mean=0] Central value
* @param {number} [spread=1] Standard deviation
* @returns Generated number
*/
randomGaussian(seed, mean = 0, spread = 1) {
const hashes = cyrb128(this.seed + `${seed}`);
const u = this.algorithm(hashes[0] + hashes[1]);
const v = this.algorithm(hashes[2] + hashes[3]);
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
return mean + z * spread;
}
/**
* Get the PRNG algorithm function by its name
*
* @param {AlgorithmName} algorithmName Algorithm name
* @returns {Function} PRNG algorithm function
*/
getAlgorithmByName(algorithmName) {
switch (algorithmName) {
case AlgorithmName.jsf32:
return jsf32;
case AlgorithmName.mulberry32:
return mulberry32;
case AlgorithmName.sfc32:
return sfc32;
case AlgorithmName.splitmix32:
return splitmix32;
case AlgorithmName.xoshiro128ss:
return xoshiro128ss;
}
}
}
const prng = new PRNG();
export default prng;