UNPKG

toosoon-utils

Version:
248 lines (247 loc) 7.35 kB
/** * Produce a 128-bit hash value from a seed * * @param {string} seed Initial seed state * @returns {[number, number, number, number]} Hash numbers */ export function cyrb128(seed) { let h1 = 1779033703; let h2 = 3144134277; let h3 = 1013904242; let h4 = 2773480762; for (let i = 0, k; i < seed.length; i++) { k = seed.charCodeAt(i); h1 = h2 ^ Math.imul(h1 ^ k, 597399067); h2 = h3 ^ Math.imul(h2 ^ k, 2869860233); h3 = h4 ^ Math.imul(h3 ^ k, 951274213); h4 = h1 ^ Math.imul(h4 ^ k, 2716044179); } h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067); h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233); h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213); h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179); return [(h1 ^ h2 ^ h3 ^ h4) >>> 0, (h2 ^ h1) >>> 0, (h3 ^ h1) >>> 0, (h4 ^ h1) >>> 0]; } // ********************* // PRNG Algorithms // ********************* /** * Simple Fast Counter, Generator with a 128-bit state * * @param {number} a * @param {number} b * @param {number} c * @param {number} d * @returns {number} Pseudo-random number */ export function sfc32(a, b, c, d) { a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; let t = (a + b) | 0; a = b ^ (b >>> 9); b = (c + (c << 3)) | 0; c = (c << 21) | (c >>> 11); d = (d + 1) | 0; t = (t + d) | 0; c = (c + t) | 0; return (t >>> 0) / 4294967296; } /** * SplitMix32, Generator with a 32-bit state * * @param {number} a * @returns {number} Pseudo-random number */ export function splitmix32(a) { a |= 0; a = (a + 0x9e3779b9) | 0; var t = a ^ (a >>> 16); t = Math.imul(t, 0x21f0aaad); t = t ^ (t >>> 15); t = Math.imul(t, 0x735a2d97); return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296; } /** * Mulberry32, Generator with a 32-bit state * * @param {number} a * @returns {number} Pseudo-random number */ export function mulberry32(a) { let t = (a += 0x6d2b79f5); t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } /** * Jenkins' Small Fast, Generator with a 32-bit state * * @param {number} a * @returns {number} Pseudo-random number */ export function jsf32(a, b, c, d) { a |= 0; b |= 0; c |= 0; d |= 0; let t = (a - ((b << 27) | (b >>> 5))) | 0; a = b ^ ((c << 17) | (c >>> 15)); b = (c + d) | 0; c = (d + t) | 0; d = (a + t) | 0; return (d >>> 0) / 4294967296; } /** * xoshiro128**, Generator with a 128-bit state * * @param {number} a * @returns {number} Pseudo-random number */ export function xoshiro128ss(a, b, c, d) { let t = b << 9; let r = a * 5; r = ((r << 7) | (r >>> 25)) * 9; c ^= a; d ^= b; b ^= c; a ^= d; c ^= t; d = (d << 11) | (d >>> 21); return (r >>> 0) / 4294967296; } /** * Generate a pseudo-random number in the interval [0, 1] * PRNG equivalent of `Math.random()` * * @param {PRNGParameters} prng PRNG parameters * @returns {number} Pseudo-random number */ export function random(prng) { const seed = typeof prng === 'string' ? prng : prng.seed; const algorithm = typeof prng === 'string' ? splitmix32 : prng.algorithm; const hashes = cyrb128(seed); return algorithm(...hashes); } /** * Generate a pseudo-random boolean (true or false) * * @param {PRNGParameters} prng PRNG parameters * @param {number} [probability=0.5] Probability to get true * @returns {boolean} Either `true` or `false` */ export function randomBoolean(prng, probability = 0.5) { return random(prng) < probability; } /** * Generate a pseudo-random sign (1 or -1) * * @param {PRNGParameters} prng PRNG parameters * @param {number} [probability=0.5] Probability to get 1 * @returns {number} Either 1 or -1 */ export function randomSign(prng, probability = 0.5) { return randomBoolean(prng, probability) ? 1 : -1; } /** * Generate a pseudo-random floating-point number within a specified range * * @param {PRNGParameters} prng PRNG parameters * @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 */ export function randomFloat(prng, min = 0, max = 1, precision = 2) { return parseFloat(Math.min(min + random(prng) * (max - min), max).toFixed(precision)); } /** * Generate a pseudo-random integer number within a specified range * * @param {PRNGParameters} prng PRNG parameters * @param {number} min Minimum boundary * @param {number} max Maximum boundary * @returns {number} Generated integer */ export function randomInt(prng, min, max) { return Math.floor(random(prng) * (max - min + 1) + min); } /** * Generate a pseudo-random hexadecimal color * * @param {PRNGParameters} prng PRNG parameters * @returns {string} Generated hexadecimal color */ export function randomHexColor(prng) { return '#' + ('00000' + ((random(prng) * (1 << 24)) | 0).toString(16)).slice(-6); } /** * Pick a pseudo-random item from a given array * * @param {PRNGParameters} prng PRNG parameters * @param {T[]} array Array to pick the item from * @returns {T|undefined} Random item picked */ export function randomItem(prng, array) { if (array.length === 0) return undefined; return array[randomInt(prng, 0, array.length - 1)]; } /** * Pick a pseudo-random property value from a given object * * @param {PRNGParameters} prng PRNG parameters * @param {object} object Object to pick the property from * @returns {T|undefined} Random item picked */ export function randomObjectProperty(prng, object) { const keys = Object.keys(object); const key = randomItem(prng, keys); if (key && object.hasOwnProperty(key)) { return object[key]; } } /** * Select a pseudo-random index from an array of weighted items * * @param {PRNGParameters} prng PRNG parameters * @param {number[]} weights Array of weights * @returns {number} Random index based on weights */ export function randomIndex(prng, 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); return 0; } let weight = random(prng) * 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 {PRNGParameters} prng PRNG parameters * @param {number} [mean=0] Central value * @param {number} [spread=1] Standard deviation * @returns {number} Generated number */ export function randomGaussian(prng, mean = 0, spread = 1) { const seed = typeof prng === 'string' ? prng : prng.seed; const algorithm = typeof prng === 'string' ? splitmix32 : prng.algorithm; const hashes = cyrb128(seed); const u = algorithm(...hashes); const v = algorithm(...hashes.reverse()); const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); return mean + z * spread; }