math/SeededRandom.js

import Alea from "alea";
import * as Simplex from "simplex-noise";

/**
 * A seedable random instance
 */
class SeededRandom {

    /**
     * @param {String|Number} seed
     */
    constructor(seed = null) {
        this.gen = new Alea(seed === null ? Math.random() : seed);
    }

    /**
     * Get a decimal number between min and max
     * @param {Number} min 
     * @param {Number} max 
     * @returns {Number}
     */
    float(min, max) {
        if (typeof min == "undefined" || typeof max == "undefined") return this.gen();
        if (max < min) {
            let t = min;
            min = max;
            max = t;
        }

        return min + this.gen() * (max - min);
    }

    /**
     * Get a boolean
     * @returns {Boolean}
     */
    bool() {
        return this.chance(0.5);
    }

    /**
     * Get a boolean with a defined chance
     * @returns {Boolean}
     */
    chance(p = 0.5) {
        return this.gen() < p;
    }

    /**
     * Get a random entry of an array
     * @param {Array} arr 
     * @param {Boolean} remove remove the entry from the array
     * @returns {mixed} an entry of the array
     */
    entry(arr, remove = false) {
        if (!arr.length) throw new Error("Cannot get entry of empty array");

        let i = Math.floor(this.float() * arr.length);
        let item = arr[i];
        if (remove) arr.splice(i, 1);
        return item;
    }

    /**
     * 
     * @param {Number} min 
     * @param {Number} max 
     * @returns {Number} an integer
     */
    int(min, max) {
        if (max < min) {
            let t = min;
            min = max;
            max = t;
        }

        return Math.floor(min + Math.floor(this.float() * (max - min + 1)));
    }

    /**
     * Return a key from a weighted map, this is useful for dynamic loot tables, the weights do not need to add up to 100%.
     * The following example demonstrates how the propability of the items is calculated by the weight
     * 
     * @example
     * let r = new SeededRandom("seed");
     * 
     * let loot = {
     *   stone_sword: 10,
     *   diamond_axe: 5,
     *   wooden_sword: 15
     * }
     * 
     * let item = r.weighted(loot);
     * 
     * // "wooden_sword" with a propability of 50%
     * // "diamond_axe" with a propability of 16.6%
     * // "stone_sword" with a propability of 33.3%
     * 
     * @param {Object} map the weight map
     * @returns {String} a key of the map
     */
    weighted(map) {
        let arr = [];
        let sum = 0;

        for (const m in map) {
            arr.push({
                chance: map[m],
                ret: m
            });
            sum += map[m];
        }

        let s = this.int(0, sum);

        for (let i = 0; i < arr.length; i++) {
            s -= arr[i].chance;
            if (s <= 0) return arr[i].ret;
        }
    }

    /**
     * Get a simplex noise instance
     * @returns {Simplex}
     * @see https://www.npmjs.com/package/simplex-noise
     */
    getSimplex() {
        let s = new Simplex(this.gen);

        //TODO this needs an own class
        s.sumOcatave = function(x, y, scale = 0.1, num_iterations = 16, persistence = 0.5, low = 0, high = 1) {
            let maxAmp = 0;
            let amp = 1;
            let freq = scale;
            let noise = 0;

            //add successively smaller, higher-frequency terms
            for (let i = 0; i < num_iterations; ++i) {
                noise += s.noise2D(x * freq, y * freq) * amp
                maxAmp += amp
                amp *= persistence
                freq *= 2
            }

            //take the average value of the iterations
            noise /= maxAmp

            //normalize the result
            noise = noise * (high - low) / 2 + (high + low) / 2

            return noise
        };

        return s;
    }

    /**
     * Export the state of the internal alea instance
     * @returns {Array}
     * @see https://www.npmjs.com/package/alea
     */
    exportState() {
        return this.gen.exportState();
    }

    /**
     * Import the state of the internal alea instance
     * @param {Array} state 
     * @returns {Alea} the alea instance
     * @see https://www.npmjs.com/package/alea
     */
    importState(state) {
        return this.gen = Alea.importState(state);
    }

    /**
     * Create a new instance from an exported state
     * @param {Array} state 
     * @returns {SeededRandom}
     */
    static fromState(state) {
        let s = new SeededRandom();
        s.importState(state);
        return s;
    }
}

export default SeededRandom;