UNPKG

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
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;