rot-js
Version:
A roguelike toolkit in JavaScript
137 lines (136 loc) • 4.05 kB
JavaScript
/**
* This code is an implementation of Alea algorithm; (C) 2010 Johannes Baagøe.
* Alea is licensed according to the http://en.wikipedia.org/wiki/MIT_License.
*/
const FRAC = 2.3283064365386963e-10; /* 2^-32 */
class RNG {
constructor() {
this._seed = 0;
this._s0 = 0;
this._s1 = 0;
this._s2 = 0;
this._c = 0;
}
getSeed() { return this._seed; }
/**
* Seed the number generator
*/
setSeed(seed) {
seed = (seed < 1 ? 1 / seed : seed);
this._seed = seed;
this._s0 = (seed >>> 0) * FRAC;
seed = (seed * 69069 + 1) >>> 0;
this._s1 = seed * FRAC;
seed = (seed * 69069 + 1) >>> 0;
this._s2 = seed * FRAC;
this._c = 1;
return this;
}
/**
* @returns Pseudorandom value [0,1), uniformly distributed
*/
getUniform() {
let t = 2091639 * this._s0 + this._c * FRAC;
this._s0 = this._s1;
this._s1 = this._s2;
this._c = t | 0;
this._s2 = t - this._c;
return this._s2;
}
/**
* @param lowerBound The lower end of the range to return a value from, inclusive
* @param upperBound The upper end of the range to return a value from, inclusive
* @returns Pseudorandom value [lowerBound, upperBound], using ROT.RNG.getUniform() to distribute the value
*/
getUniformInt(lowerBound, upperBound) {
let max = Math.max(lowerBound, upperBound);
let min = Math.min(lowerBound, upperBound);
return Math.floor(this.getUniform() * (max - min + 1)) + min;
}
/**
* @param mean Mean value
* @param stddev Standard deviation. ~95% of the absolute values will be lower than 2*stddev.
* @returns A normally distributed pseudorandom value
*/
getNormal(mean = 0, stddev = 1) {
let u, v, r;
do {
u = 2 * this.getUniform() - 1;
v = 2 * this.getUniform() - 1;
r = u * u + v * v;
} while (r > 1 || r == 0);
let gauss = u * Math.sqrt(-2 * Math.log(r) / r);
return mean + gauss * stddev;
}
/**
* @returns Pseudorandom value [1,100] inclusive, uniformly distributed
*/
getPercentage() {
return 1 + Math.floor(this.getUniform() * 100);
}
/**
* @returns Randomly picked item, null when length=0
*/
getItem(array) {
if (!array.length) {
return null;
}
return array[Math.floor(this.getUniform() * array.length)];
}
/**
* @returns New array with randomized items
*/
shuffle(array) {
let result = [];
let clone = array.slice();
while (clone.length) {
let index = clone.indexOf(this.getItem(clone));
result.push(clone.splice(index, 1)[0]);
}
return result;
}
/**
* @param data key=whatever, value=weight (relative probability)
* @returns whatever
*/
getWeightedValue(data) {
let total = 0;
for (let id in data) {
total += data[id];
}
let random = this.getUniform() * total;
let id, part = 0;
for (id in data) {
part += data[id];
if (random < part) {
return id;
}
}
// If by some floating-point annoyance we have
// random >= total, just return the last id.
return id;
}
/**
* Get RNG state. Useful for storing the state and re-setting it via setState.
* @returns Internal state
*/
getState() { return [this._s0, this._s1, this._s2, this._c]; }
/**
* Set a previously retrieved state.
*/
setState(state) {
this._s0 = state[0];
this._s1 = state[1];
this._s2 = state[2];
this._c = state[3];
return this;
}
/**
* Returns a cloned RNG
*/
clone() {
let clone = new RNG();
return clone.setState(this.getState());
}
}
export default new RNG().setSeed(Date.now());