packages/random-fxhash/src/index.ts
import type { Fn0 } from "@thi.ng/api";
import { B58_CHARS_LC } from "@thi.ng/base-n/chars/58";
import type { IRandom } from "@thi.ng/random";
import { DEFAULT_SEED_128 } from "@thi.ng/random/constants";
import { pickRandom, pickRandomKey } from "@thi.ng/random/pick-random";
import { SFC32 } from "@thi.ng/random/sfc32";
import { pickRandomUnique } from "@thi.ng/random/unique-indices";
import {
weightedRandom,
weightedRandomKey,
} from "@thi.ng/random/weighted-random";
declare var fxhash: string;
declare var $fx: { hash: string };
/**
* Takes a `fxhash` compatible base58 transaction hash, parses it into 4
* truncated 32bit uints and returns these partial seeds as array. This returned
* array can then be passed to reseed the {@link RND} instance.
*
* @remarks
* Note: The first characters of the given `hash` will ALWAYS be ignored (same
* logic fxhash is using & due to Tezos hash structure).
*
* Also see
* [`BASE58_LC`](https://docs.thi.ng/umbrella/base-n/variables/BASE58_LC.html).
*
* @example
* ```ts
* import { seedFromHash, RND } from "@thi.ng/random-fxhash";
*
* RND.seed(seedFromHash("ookorwLedQrCTPesBcUPrR2oRbPHgsAxe9xgCSNq4XAuZSaCvaB"));
* ```
*
* @param hash
*/
export const seedFromHash = (hash: string) =>
hash
.slice(2)
.match(new RegExp(`.{${(hash.length - 2) >> 2}}`, "g"))!
.map((x) =>
[...x].reduce(
(acc, y) => (acc * 58 + B58_CHARS_LC.indexOf(y)) >>> 0,
0
)
);
let seed: number[];
if (typeof fxhash === "string") {
seed = seedFromHash(fxhash);
} else if (typeof $fx?.hash === "string") {
seed = seedFromHash($fx.hash);
} else {
seed = DEFAULT_SEED_128;
console.warn("fxhash PRNG not found, using default seed", seed, "\n\n");
}
/**
* Singleton replacement for global `fxrand` PRNG function and pre-seeded with
* same global `fxhash` (both injected by fxhash's script in HTML header)
*
* @remarks
* Should the need arise, the instance can be reseeded via `.seed([...])`.
*/
export const RND = new SFC32(seed);
/**
* Returns true if the next random float from {@link RND} is less than given `p`.
*
* @param p
* @param rnd - default {@link RND}
*/
export const probability = (p: number, rnd: IRandom = RND) =>
rnd.probability(p);
/**
* Wrapper for
* [pickRandom()](https://docs.thi.ng/umbrella/random/functions/pickRandom.html).
* Returns a random item from given `choices`.
*
* @param choices
* @param rnd - default {@link RND}
*/
export const pick = <T>(choices: T[], rnd: IRandom = RND) =>
pickRandom(choices, rnd);
/**
* Wrapper for
* [pickRandomKey()](https://docs.thi.ng/umbrella/random/functions/pickRandomKey.html).
* Returns a random key from given `object`.
*
* @param obj
* @param rnd - default {@link RND}
*/
export const pickKey = <T extends object>(obj: T, rnd: IRandom = RND) =>
pickRandomKey(obj, rnd);
/**
* Repeatedly calls given zero-arg `fn` until it returns a different value than
* `orig` (using strict equality `===`).
*
* @remarks
* Also see {@link pickUnique} for selecting multiple unique items.
*
* @param fn
* @param orig
*/
export const pickAlt = <T>(fn: Fn0<T>, orig: T) => {
let res: T;
do {
res = fn();
} while (res === orig);
return res;
};
/**
* Same as thi.ng/random's [pickRandomUnique](), but pre-configured to use
* {@link RND} as default PRNG. Picks up to `k` unique values from `src` array
* (each with `maxTrials`) and adds them to given `existing` array (or creates a
* new one by default) and returns it.
*
* @remarks
* Internally uses `Array.includes()` to check for duplicates.
*
* @param k
* @param src
* @param existing
* @param maxTrials
* @param rnd
*/
export const pickUnique = <T>(
k: number,
src: T[],
existing?: T[],
maxTrials = 100,
rnd: IRandom = RND
) => pickRandomUnique(k, src, existing, maxTrials, rnd);
/**
* One-shot wrapper for
* [`weightedRandom()`](https://docs.thi.ng/umbrella/random/functions/weightedRandom.html).
* Takes an array of `choices` and an array of `weights`, returns random item
* from `choices`, taking relative weights into account.
*
* @remarks
* If repeated use (with the same choices/weights) is required, then using
* [`weightedRandom()`](https://docs.thi.ng/umbrella/random/functions/weightedRandom.html)
* directly is more efficient since the pre-calculations will be cached.
*
* @param choices
* @param weights
* @param rnd - default {@link RND}
*/
export const weighted = <T>(
choices: T[],
weights: number[],
rnd: IRandom = RND
) => weightedRandom(choices, weights, rnd)();
/**
* One-shot wrapper for
* [`weightedRandomKey()`](https://docs.thi.ng/umbrella/random/functions/weightedRandomKey.html),
* accepting an object of weights. Return keys of given `choices` object, taking
* into account the weights given for each key.
*
* @remarks
* If repeated use (with the same choices/weights) is required, then using
* [`weightedRandomKey()`](https://docs.thi.ng/umbrella/random/functions/weightedRandomKey.html)
* directly is more efficient since the pre-calculations will be cached.
*
* @param choices
* @param rnd
*/
export const weightedKey = <T extends Record<string, number>>(
choices: T,
rnd: IRandom = RND
) => weightedRandomKey(choices, rnd)();