/**
 * generate two independent Gaussian numbers using Box-Muller transform.
 * mean and deviation specify the desired mean and standard deviation.
 * @param {number} [mean]
 * @param {number} [deviation]
 * @returns {number[]}
 */
function gaussianPair(mean = 0, deviation = 1) {
	const r = Math.sqrt(-2.0 * Math.log(1 - Math.random()));
	const sigma = 2.0 * Math.PI * (1 - Math.random());
	return [r * Math.cos(sigma), r * Math.sin(sigma)].map(val => val * deviation + mean);
}

/**
 * Generate a random integer with a normal distribution between min and max (both inclusive).
 * Default parameters result in truncating the standard normal distribution between -3 and +3.
 * Not specifying min/max results in rerolling val approximately 0.3% of the time.
 * @param {number} [mean]
 * @param {number} [deviation]
 * @param {number} [min]
 * @param {number} [max]
 * @returns {number}
 */
function normalRandInt(mean = 0, deviation = 1, min = mean - 3 * deviation, max = mean + 3 * deviation) {
	let val = gaussianPair(mean, deviation)[0];
	while (val < min || val > max) {
		val = gaussianPair(mean, deviation)[0];
	}
	return Math.round(val);
}

/**
 * Returns a random integer between min and max (both inclusive).
 * If count is defined, chooses that many random numbers between min and max and returns the average. This is an approximation of a normal distribution.
 * @param {number} min
 * @param {number} max
 * @param {number} [count]
 * @returns {number}
 */
function jsRandom(min, max, count = 1) {
	function rand() {
		return Math.random() * (max - min + 1) + min;
	}

	if (count === 1) {
		return Math.floor(rand());
	}

	let total = 0;
	for (let i = 0; i < count; i++) {
		total += rand();
	}
	return Math.floor(total / count);
}

/**
 * Chooses multiple random elements of an array.
 * @param {number[]} arr
 * @param {number} count
 * @returns {number[]}
 */
function jsRandomMany(arr, count) {
	let result = [];
	let _tmp = arr.slice();
	for (let i = 0; i < count; i++) {
		let index = Math.floor(Math.random() * _tmp.length);
		result.push(_tmp.splice(index, 1)[0]);
	}
	return result;
}

/**
 * Accepts both an array and a list, returns undefined if nothing is passed.
 * @param {any[]} choices
 * @param {any} [otherChoices]
 * @returns {any}
 */
function jsEither(choices, ...otherChoices) {
	if (otherChoices.length === 0 && Array.isArray(choices)) {
		return choices[Math.floor(Math.random() * choices.length)];
	}
	const allChoices = otherChoices;
	allChoices.push(choices);
	return allChoices[Math.floor(Math.random() * allChoices.length)];
}

js/random.js