Pseudorandom and -Gaussian

An extension to Pseudorandom number generator

I wanted to see if the random generator I use in my Turtles (by David Bau) has similar properties as the one Reinder uses.

When it seemed to have those properties I thought use them to extend the 'Random wrapper' I use to generate values that form a normal (Gaussian) distributed collection of values instead of a 'random collection'.

I used the Box-Muller transform to get from pseudorandom numbers to normal distributed values from 0 to 1, by code at the first listed URL below. There's also a (faster) method listed that approximates normal distributed values (less precise) using the central limit theorem (using ctrlLimSampleSize).

For completeness I included the random number generator by Randall Munroe (last URL).

I also added a way to skew values from any generator method.

stackoverflow.com/qu…-gaussian-bell-curve
en.wikipedia.org/wiki/box%e2%80%93muller_transform
en.wikipedia.org/wiki/central_limit_theorem
davidbau.com/archive…nd_quintillions.html
xkcd.com/221/

Log in to post a comment.

// Forked from "Pseudorandom number generator" by reinder
// https://turtletoy.net/turtle/a2274fd1fe

// Pseudorandom number generator. Created by Reinder Nijhoff 2024 - @reindernijhoff
// The MIT License
//
// https://turtletoy.net/turtle/a2274fd1fe
//

const turtle = new Turtle();

const generator = 2; //min=0 max=5 step=1 (Pseudorandom used by Reinder - see description, Pseudorandom used by Jurgen - see description, Normal distribution using Box-Muller transform - see description, Normal distribution using central limit theorem - see description, Random number generator by Randall Munroe - see description, SquareBiased (using variable d))

let seed = 1005640; // min=1, max=1000000, step=1
const bits = 20; // min=1, max=30, step=1
const samples = 150000; // min=1, max=500000, step=1

const mod = 1<<bits;
const bins = Math.min(1<<8, 1<<bits);
const ksAlpha = 0.05; // 95% confidence level
const acLag = 1; // min=1, max=128, step=1

const ctrlLimSampleSize = 6; //min=2 max=10 step=1

const skew = 0; //min=-.999 max=.999 step=.001
const d = 0;//min=0 max=1 step=.001

const values = [];

init();
if(generator > 0) { //only when not Reinder's random
    R.seed(seed);
}
function distributed_random(d, x = Math.random()) {
    return (Math.random() < 0.5)? x ** (1-d) /2: (1-(1-x) ** (1-d)) / 2 + .5;
}
////////////////////////////////////////////////////////////////
// Pseudorandom number generator. Created by Reinder Nijhoff 2024
// https://turtletoy.net/turtle/a2274fd1fe
////////////////////////////////////////////////////////////////
function random() { // returns a number [0, 1[
    let r = 1103515245 * (((seed+=12345) >> 1) ^ seed);
    r = 1103515245 * (r ^ (r >> 3));
    r = r ^ (r >> 16);
    return (r % mod) / mod;
}

//https://www.xkcd.com/221/
function getRandomNumber()
{
   return 4; // chosen by fair dice roll.
             // guaranteed to be random.
}

const oseed = seed;

function walk(i) {
    let r = 0;
    switch(generator) {
        case 0: r = R.skew(random(), skew); break;
        case 1: r = R.skew(R.get(), skew); break;
        case 2: r = R.getNormalDistributed(skew); break;
        case 3: r = R.skew([ctrlLimSampleSize].map(n => Array.from({length: n}, e => R.get()).reduce((a,c) => a + c, 0)/n).pop(), skew); break;
        case 4: r = (getRandomNumber() - 1) / 6; break; //transform dice roll to [0, 1>
        case 5: r = R.skew(distributed_random(d), skew); break;
    }

    values.push(r);
    
    drawPoint( i/samples, r, transform(10, -80, 70, 70) );
    
    if (i >= samples) {
        const text = new Text(HersheySans1);
        turtle.jump(-80,-5);
        text.print(turtle, 'Histogram', .35);
        turtle.jump(10,-5);
        text.print(turtle, 'Noise', .35);
        
        const histogram = createHistogram(values, bins);
        drawHistogram(histogram, transform(-80, -80, 70, 70));
        
        const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
        const stddev = Math.sqrt(values.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / values.length);
        const corr = autocorrelation(values, acLag);

        const stdDevIntervalCounts = Array.from({length: 6}, (e,i) => 
            values.filter(e => mean+((i-3)*stddev) <= e && e < mean+((i-2)*stddev)).length/values.length
        );

        const criticalValue = ksCriticalValue(values, ksAlpha);
        const ksStatistic = ksTest(values);
        
        turtle.jump(-80, 10);
        text.print(turtle, `Mean: ${mean.toFixed(4)}, Mode: ${((maxCount = 0, maxCounts = []) => {const countedValues = values.reduce((a,c) => {if(a[c] == undefined) a[c] = 0;maxCount = Math.max(++a[c], maxCount);return a;}, {});Object.keys(countedValues).forEach(e => {if(countedValues[e] == maxCount) maxCounts.push(typeof e == 'number'? e: Number.parseFloat(e));});return maxCounts.reduce((a,c) => a+c, 0)/maxCounts.length;})().toFixed(4)}, Median ${[values.sort()].map(sorted => sorted.length%2 == 0? sorted[values.length/2|0]: (sorted[values.length/2|0]+sorted[(values.length/2|0)+1])/2).pop().toFixed(4)}
StdDev: ${stddev.toFixed(4)}
Values in StdDev intervals from mean (-3 to 3):
${stdDevIntervalCounts.map(e => (100*e).toFixed(2)).join('% ')}%

Autocorrelation: ${corr.toFixed(4)} (lag ${acLag})
K-S Statistic: ${ksStatistic.toFixed(4)} (${ksStatistic < criticalValue ? '<':'!! >='} ${criticalValue.toFixed(4)})

Seed: ${oseed}
Bits: ${bits}
Samples: ${samples}`, .52);
    }
    
    return i < samples;
}


function transform(l, t, w, h) {
    return (x, y) => [l+w*x, t+h*y];
}

function drawPoint(x, y, t) {
    const d = 0.0002;
    turtle.jump( t(x-d, y-d) );
    turtle.goto( t(x+d, y+d) );
}

//
// Noise Statistics, thanks to chatGPT
//
function createHistogram(a, bins) {
    const histogram = new Array(bins).fill(0);
    for (let value of a) {
        histogram[value * bins | 0]++;
    }
    return histogram;
}

function drawHistogram(histogram, t) {
    const n = histogram.filter(v => v > 0).length;
    const mean = histogram.reduce((sum, value) => sum + value, 0) / n;

    const values = histogram.map(e => e/mean)
    const max = values.reduce((a,c) => Math.max(a,c), 0);
    const ratio = max < 2? 1: 2/max;

    for (let i=0; i<bins; i++) {
        turtle.jump( t(i/bins, 1) );
        turtle.goto( t(i/bins, 1 - 0.5 * values[i] * ratio));
    }
}

function autocorrelation(a, lag) {
    const n = a.length;
    const mean = a.reduce((sum, value) => sum + value, 0) / n;
    let numerator = 0;
    let denominator = 0;
    for (let i = 0; i < n - lag; i++) {
        numerator += (a[i] - mean) * (a[i + lag] - mean);
    }
    for (let i = 0; i < n; i++) {
        denominator += (a[i] - mean) ** 2;
    }
    return numerator / denominator;
}

function ksCriticalValue(a, alpha) {
    return Math.sqrt(-0.5 * Math.log(alpha / 2) / a.length);
}

function ksTest(a) {
    a.sort((x, y) => x - y); // Sort the array in ascending order
    const n = a.length;
    let d = 0;
    
    for (let i = 0; i < n; i++) {
        const empiricalCDF = (i + 1) / n; // Empirical CDF
        const uniformCDF = a[i]; // Uniform CDF (assuming values in [0, 1])
        d = Math.max(d, Math.abs(empiricalCDF - uniformCDF)); // Update K-S statistic
    }
    
    return d;
}

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2024
// https://turtletoy.net/turtle/0f84fd3ae4
////////////////////////////////////////////////////////////////

function Text(fontData) {
    const decode = (data) => {
        const b = atob(data), a = new Int16Array(b.length / 2), g = {};
        for (let i = 0; i < b.length; i+=2) {
            a[i / 2 | 0] = b.charCodeAt(i) | (b.charCodeAt(i + 1) << 8);
        }
        for (let i = 0;i < a.length; i++) {
            let u = String.fromCharCode(a[i++]), x = a[i++], d = [], p = [];
            for (;a[i] !== -16384; i++) a[i] === 16384 ? (d.push(p), p = []) : p.push(a[i]);
            g[u] = { x, d };
        }
        return g;
    }
    const rotAdd = (a, b, h) => [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0],
                                 Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];

    const data = Object.fromEntries(Object.entries(fontData).map(([key, value]) =>
        [key, key === 'glyphs' ? decode(value) : value]));

    class Text {
        print (t, str, scale = 1) {
            let pos = t ? [t.x(), t.y()] : [0,0], h = t ? t.h() * Math.PI * 2 / t.fullCircle() : 0, 
                      o = pos, s = scale / data.unitsPerEm;
            str.split('').map(c => {
                if (c == '\n') {
                    pos = o = rotAdd([0, 14 * scale], o, h);
                    return;
                }
                const glyph = (data.glyphs[c] || data.glyphs[' ']), d = glyph.d;
                d.forEach( (p, k) => {
                    t && t.up();
                    for (let i=0; i<p.length; i+=2) {
                        t && t.goto(rotAdd([p[i]*s, p[i+1]*s], pos, h));
                        t && t.down();
                    }
                });
                pos = rotAdd([glyph.x*s, 0], pos, h);
            });
            return rotAdd([0, 10*scale], pos, h);
        }
    }

    return new Text();
}

const HersheySans1 = {"name":"HersheySans1","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQBODE4MJOZODGj3AEBODIr9GAvF\/k4MAACEDcX+TgyK\/QBAAMAiALATmAgk5pgIxu4AQHASJOZwEsbuAEAAwCMA3Bk6ETjhmAiYCABAnBg44QQQmAgAQJgIPPHcGTzxAEBiB574nBie+ABAAMAkAJwYhA044YQN7AQAQHASOOFwEuwEAECcGNrpJhZk53ASJOaEDSTm2Alk52IH2uliB1DsmAjG7tgJ\/O9ODDzxsBOy8yYW6PRcFyj2nBie+JwYT\/wmFsX+cBIAAIQNAADYCcX+YgdP\/ABAAMAlAIgdiB0k5mIHAAAAQIQNJOYEEKToBBAQ68QOkO1ODMbu2AnG7mIHUOxiB9rpmAhk5xgLJOaEDSTmBBBk57ATpOhcF6ToEhtk54gdJOYAQJwYaPcmFp748BQU+\/AUiv1cFwAA3BkAAEgcxf6IHU\/8iB3U+RIbaPecGGj3AEAAwCYA\/h\/+Hzzx\/h\/878gexu6IHcbuSBz87xIbfPKcGJ74JhZP\/LATxf46EQAATgwAANgJxf6YCIr9YgcU+2IHnviYCCj22Ano9HAS\/O+wE8bu8BRQ7PAU2umwE2TnOhEk5sQOZOeEDdrphA1Q7MQO\/O86EbLzXBdP\/NwZxf5IHAAAyB4AAP4fxf7+H4r9AEAAwCcATgzYCaTomAhk59gJJOYYC2TnGAva6dgJUOyYCJDtAEAAwCgAOhE6ETjhxA64404MZOfYCVDsmAh88pgIaPfYCYr9Tgx2AsQOLAY6EZgIAEAAwCkAOhFiBzjh2Am4404MZOfEDlDsBBB88gQQaPfEDor9Tgx2AtgJLAZiB5gIAEAAwCoAsBOEDZDthA1P\/ABAYgc88bATnvgAQLATPPFiB574AEAAwCsA\/h+wE9rpsBMAAABAmAjo9Mge6PQAQADALADYCdgJFPuYCE\/8YgcU+5gI1PnYCRT72AmK\/WIHAAAAQADALQD+H5gI6PTIHuj0AEAAwC4A2AmYCNT5YgcU+5gIT\/zYCRT7mAjU+QBAAMAvABIbSBw44SwGmAgAQADAMACcGMQOJOYYC2TnmAgQ62IHPPFiB+j0mAgU+xgLxf7EDgAAOhEAAPAUxf5cFxT7nBjo9JwYPPFcFxDr8BRk5zoRJObEDiTmAEAAwDEAnBgYCxDrhA3a6ToRJOY6EQAAAEAAwDIAnBiYCFDsmAgQ69gJpOgYC2TnhA0k5nASJObwFGTnJhak6FwXEOtcF5DtJhb877ATsvNiBwAAnBgAAABAAMAzAJwY2Akk5lwXJOYEEPzvsBP87yYWPPFcF3zynBgo9pwYnvhcF0\/88BTF\/joRAACEDQAA2AnF\/pgIiv1iBxT7AEAAwDQAnBiwEyTmYgdo99wZaPcAQLATJOawEwAAAEAAwDUAnBgmFiTm2Akk5pgIPPHYCfzvhA3G7joRxu7wFPzvXBd88pwYKPacGJ74XBdP\/PAUxf46EQAAhA0AANgJxf6YCIr9YgcU+wBAAMA2AJwYXBfa6SYWZOdwEiTmBBAk5k4MZOfYCRDrmAg88ZgIaPfYCU\/8TgzF\/gQQAAA6EQAA8BTF\/lwXT\/ycGJ74nBho91wXsvPwFDzxOhH87wQQ\/O9ODDzx2Amy85gIaPcAQADANwCcGJwYJOZODAAAAEBiByTmnBgk5gBAAMA4AJwYhA0k5tgJZOeYCNrpmAhQ7NgJxu5ODPzvOhE88fAUfPJcF+j0nBho95wYFPtcF4r9JhbF\/nASAACEDQAA2AnF\/pgIiv1iBxT7Ygdo95gI6PQYC3zyxA488bAT\/O8mFsbuXBdQ7FwX2ukmFmTncBIk5oQNJOYAQADAOQCcGFwXxu4mFnzysBPo9AQQKPbEDij2GAvo9JgIfPJiB8buYgeQ7ZgI2ukYC2TnxA4k5gQQJOawE2TnJhba6VwXxu5cF+j0JhYU+7ATxf4EEAAAhA0AANgJxf6YCE\/8AEAAwDoA2AmYCDzxYgd88pgIsvPYCXzymAg88QBAmAjU+WIHFPuYCE\/82AkU+5gI1PkAQADAOwDYCZgIPPFiB3zymAiy89gJfPKYCDzxAEDYCRT7mAhP\/GIHFPuYCNT52AkU+9gJiv1iBwAAAEAAwDwAiB1IHNrpmAjo9EgcAAAAQADAPQD+H5gIPPHIHjzxAECYCJ74yB6e+ABAAMA+AIgdmAja6Ugc6PSYCAAAAEAAwD8AJhZiB1DsYgcQ65gIpOjYCWTnTgwk5joRJOawE2Tn8BSk6CYWEOsmFpDt8BT877ATPPHEDrLzxA5o9wBAxA6K\/YQNxf7EDgAABBDF\/sQOiv0AQADAQAA0IdwZ\/O+cGJDtJhZQ7HASUOwEEJDtxA7G7oQNfPKEDSj2xA6e+DoR1PnwFNT5XBee+JwYKPYAQHASUOwEEMbuxA588sQOKPYEEJ74OhHU+QBA3BlQ7JwYKPacGJ74EhvU+Ygd1Pn+H2j3NCGy8zQhPPH+H5DtyB4Q60gcpOjcGWTnJhYk5nASJObEDmTnTgyk6NgJEOuYCJDtYgc88WIH6PSYCJ742AkU+04Miv3EDsX+cBIAACYWAADcGcX+SByK\/YgdT\/wAQBIbUOzcGSj23Bme+BIb1PkAQADAQQAmFsQOJObsBAAAAEDEDiTmnBgAAABAmAho9\/AUaPcAQADAQgDcGZgIJOaYCAAAAECYCCTmsBMk5lwXZOecGKTo3BkQ69wZkO2cGPzvXBc88bATfPIAQJgIfPKwE3zyXBey85wY6PTcGWj33BkU+5wYiv1cF8X+sBMAAJgIAAAAQADAQwDcGdwZUOycGNrpJhZk57ATJObEDiTmTgxk59gJ2umYCFDsYgf872IHKPaYCNT52AlP\/E4Mxf7EDgAAsBMAACYWxf6cGE\/83BnU+QBAAMBEANwZmAgk5pgIAAAAQJgIJOY6ESTm8BRk51wX2umcGFDs3Bn879wZKPacGNT5XBdP\/PAUxf46EQAAmAgAAABAAMBFAFwXmAgk5pgIAAAAQJgIJOacGCTmAECYCHzycBJ88gBAmAgAAJwYAAAAQADARgAmFpgIJOaYCAAAAECYCCTmnBgk5gBAmAh88nASfPIAQADARwDcGdwZUOycGNrpJhZk57ATJObEDiTmTgxk59gJ2umYCFDsYgf872IHKPaYCNT52AlP\/E4Mxf7EDgAAsBMAACYWxf6cGE\/83BnU+dwZKPYAQLATKPbcGSj2AEAAwEgAEhuYCCTmmAgAAABA3Bkk5twZAAAAQJgIfPLcGXzyAEAAwEkA2AmYCCTmmAgAAABAAMBKALATcBIk5nAS1Pk6EYr9BBDF\/oQNAAAYCwAAmAjF\/mIHiv0sBtT5LAZo9wBAAMBLANwZmAgk5pgIAAAAQNwZJOaYCGj3AEDEDjzx3BkAAABAAMBMAPAUmAgk5pgIAAAAQJgIAABcFwAAAEAAwE0AiB2YCCTmmAgAAABAmAgk5nASAAAAQEgcJOZwEgAAAEBIHCTmSBwAAABAAMBOABIbmAgk5pgIAAAAQJgIJObcGQAAAEDcGSTm3BkAAABAAMBPABIbxA4k5k4MZOfYCdrpmAhQ7GIH\/O9iByj2mAjU+dgJT\/xODMX+xA4AALATAAAmFsX+nBhP\/NwZ1PkSGyj2Ehv879wZUOycGNrpJhZk57ATJObEDiTmAEAAwFAA3BmYCCTmmAgAAABAmAgk5rATJOZcF2TnnBik6NwZEOvcGcbunBg88VwXfPKwE7LzmAiy8wBAAMBRABIbxA4k5k4MZOfYCdrpmAhQ7GIH\/O9iByj2mAjU+dgJT\/xODMX+xA4AALATAAAmFsX+nBhP\/NwZ1PkSGyj2Ehv879wZUOycGNrpJhZk57ATJObEDiTmAEBwEhT73Bl2AgBAAMBSANwZmAgk5pgIAAAAQJgIJOawEyTmXBdk55wYpOjcGRDr3BmQ7ZwY\/O9cFzzxsBN88pgIfPIAQDoRfPLcGQAAAEAAwFMAnBicGNrpJhZk53ASJOaEDSTm2Alk52IH2uliB1DsmAjG7tgJ\/O9ODDzxsBOy8yYW6PRcFyj2nBie+JwYT\/wmFsX+cBIAAIQNAADYCcX+YgdP\/ABAAMBUALAThA0k5oQNAAAAQOwEJOYmFiTmAEAAwFUAEhuYCCTmmAie+NgJT\/xODMX+BBAAAHASAAAmFsX+nBhP\/NwZnvjcGSTmAEAAwFYAJhbsBCTmxA4AAABAnBgk5sQOAAAAQADAVwCIHSwGJOZODAAAAEBwEiTmTgwAAABAcBIk5pwYAAAAQMgeJOacGAAAAEAAwFgAnBhiByTmnBgAAABAnBgk5mIHAAAAQADAWQAmFuwEJObEDnzyxA4AAABAnBgk5sQOfPIAQADAWgCcGJwYJOZiBwAAAEBiByTmnBgk5gBAYgcAAJwYAAAAQADAWwA6EZgIOOGYCJgIAEDYCTjh2AmYCABAmAg44ToROOEAQJgImAg6EZgIAEAAwFwAOhGxAyTm8BSxAwBAAMBdADoRxA444cQOmAgAQAQQOOEEEJgIAEBiBzjhBBA44QBAYgeYCAQQmAgAQADAXgCwE4QNuOOxA+j0AECEDbjjXBfo9ABAAMBfACYWsQOYCNwZmAgAQADAYADYCdgJUOxiB8buYgc88ZgIfPLYCTzxmAj872IHPPEAQADAYQBcFyYWxu4mFgAAAEAmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBiAFwXmAgk5pgIAAAAQJgIfPIYC\/zvhA3G7joRxu6wE\/zvJhZ88lwXKPZcF574JhZP\/LATxf46EQAAhA0AABgLxf6YCE\/8AEAAwGMAJhYmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBkAFwXJhYk5iYWAAAAQCYWfPKwE\/zvOhHG7oQNxu4YC\/zvmAh88mIHKPZiB574mAhP\/BgLxf6EDQAAOhEAALATxf4mFk\/8AEAAwGUAJhZiByj2JhYo9iYWsvPwFDzxsBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBmAMQOBBAk5oQNJOYYC2Tn2AkQ69gJAAAAQCwGxu7EDsbuAEAAwGcAXBcmFsbuJhZ2AvAULAawE2IHOhGYCIQNmAgYC2IHAEAmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBoAFwXmAgk5pgIAAAAQJgIsvNODPzvxA7G7nASxu7wFPzvJhay8yYWAAAAQADAaQDYCWIHJOaYCGTn2Akk5pgI7uRiByTmAECYCMbumAgAAABAAMBqAE4M2Akk5hgLZOdODCTmGAvu5NgJJOYAQBgLxu4YC7ED2AliB2IHmAjsBJgIAEAAwGsA8BSYCCTmmAgAAABA8BTG7pgIFPsAQIQNKPYmFgAAAEAAwGwA2AmYCCTmmAgAAABAAMBtAOokmAjG7pgIAAAAQJgIsvNODPzvxA7G7nASxu7wFPzvJhay8yYWAAAAQCYWsvPcGfzvSBzG7v4fxu50IvzvtCOy87QjAAAAQADAbgBcF5gIxu6YCAAAAECYCLLzTgz878QOxu5wEsbu8BT87yYWsvMmFgAAAEAAwG8AXBeEDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/FwXnvhcFyj2JhZ88rAT\/O86EcbuhA3G7gBAAMBwAFwXmAjG7pgImAgAQJgIfPIYC\/zvhA3G7joRxu6wE\/zvJhZ88lwXKPZcF574JhZP\/LATxf46EQAAhA0AABgLxf6YCE\/8AEAAwHEAXBcmFsbuJhaYCABAJhZ88rAT\/O86EcbuhA3G7hgL\/O+YCHzyYgco9mIHnviYCE\/8GAvF\/oQNAAA6EQAAsBPF\/iYWT\/wAQADAcgAEEJgIxu6YCAAAAECYCCj22Al88k4M\/O\/EDsbucBLG7gBAAMBzAPAU8BR88rAT\/O8EEMbuTgzG7pgI\/O9iB3zymAjo9BgLKPY6EWj3sBOe+PAUFPvwFE\/8sBPF\/gQQAABODAAAmAjF\/mIHT\/wAQADAdADEDtgJJObYCRT7GAvF\/oQNAAAEEAAAAEAsBsbuxA7G7gBAAMB1AFwXmAjG7pgIFPvYCcX+TgwAAAQQAABwEsX+JhYU+wBAJhbG7iYWAAAAQADAdgCwEywGxu6EDQAAAEDwFMbuhA0AAABAAMB3ABIbYgfG7k4MAAAAQDoRxu5ODAAAAEA6EcbuJhYAAABAEhvG7iYWAAAAQADAeADwFGIHxu7wFAAAAEDwFMbuYgcAAABAAMB5ALATLAbG7oQNAAAAQPAUxu6EDQAAGAvsBJgIYgcsBpgI7ASYCABAAMB6APAU8BTG7mIHAAAAQGIHxu7wFMbuAEBiBwAA8BQAAABAAMB7ADoRxA444U4MeOIYC7jj2Akk5tgJpOgYCxDrTgxQ7IQNxu6EDTzxGAuy8wBATgx44hgL7uQYC2TnTgza6YQNEOvEDpDtxA7874QNfPKYCOj0hA1o98QO1PnEDk\/8hA3F\/k4MAAAYC3YCGAvsBE4MYgcAQBgLKPaEDZ74hA0U+04Miv0YC8X+2Ak7AdgJsQMYCywGTgxiB8QOmAgAQADAfADYCZgIOOGYCJgIAEAAwH0AOhHYCTjhTgx44oQNuOPEDiTmxA6k6IQNEOtODFDsGAvG7hgLPPGEDbLzAEBODHjihA3u5IQNZOdODNrpGAsQ69gJkO3YCfzvGAt88gQQ6PQYC2j32AnU+dgJT\/wYC8X+TgwAAIQNdgKEDewETgxiBwBAhA0o9hgLnvgYCxT7TgyK\/YQNxf7EDjsBxA6xA4QNLAZODGIH2AmYCABAAMB+AIgdYgee+GIHKPaYCHzyGAs88YQNPPEEEHzy8BQo9lwXaPfcGWj3SBwo9ogdsvMAQGIHKPaYCLLzGAt88oQNfPIEELLz8BRo91wXnvjcGZ74SBxo94gdsvOIHTzxAEAAwA=="};

function init() {
    class Random {
        static #apply(seed) {
            // Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html
            !function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");
            Math.seedrandom(seed);
        }
        static seedRandom() { this.#apply(new Date().getMilliseconds()); }
        static seedDaily() { this.#apply(new Date().toDateString()); }
        static seed(seed) { this.#apply(seed); }
        static getInt(min, max) {
            if(max == undefined) {
                max = min + 1;
                min = 0;
            }
            const [mi, ma] = [Math.min(min, max), Math.max(min, max)];
            return (mi + Math.random() * (ma - mi)) | 0;
        }
        static get(min, max) {
            if(min == undefined) {
                return Math.random();
            }
            if(max == undefined) {
                max = min;
                min = 0;
            }
            const [mi, ma] = [Math.min(min, max), Math.max(min, max)];
            return mi + Math.random() * (ma - mi);
        }
        static getAngle(l = 1) { return l * this.get(0, 2*Math.PI); }

        // Standard Normal variate using Box-Muller transform.
        static getGaussian(mean=.5, stdev=.1) {
            const u = 1 - this.get(); // Converting [0,1) to (0,1]
            const v = this.get();
            const z = ( -2.0 * Math.log( u ) )**.5 * Math.cos( 2.0 * Math.PI * v );
            // Transform to the desired mean and standard deviation:
            return z * stdev + mean;
        }
        static skew(value, skew = 0) { //skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed
            return (skew < 0)? value - (this.skew(1-value, -skew) - (1-value)): Math.pow(value, 1-Math.abs(skew));
        }
        static getNormalDistributed(skew = 0) { //skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed
            let v = -1;
            while(v < 0 || 1 <= v) { v = this.getGaussian(.5, .1) };
            return this.skew(v, skew);
        }
    }
    this.R = Random;
}