/**
* Returns a random number in range [a, b) (i.e. a included, b excluded)
* If only one parameter is passed, the random number will be generated in range [0, a)
* If no parameters are passed, the random number will be generated in range [0, 1)
*
* @param {number|null} [a=null] if two parameters are passed, minimum range value; maximum range value otherwise
* @param {number|null} [b=null] maximum range value
* @returns {number} random number
*/
const random = (a = null, b = null) => {
if (a == null && b == null) {
a = 0;
b = 1;
} else if (b == null) {
b = a;
a = 0;
}
return Math.random() * (b - a) + a;
};
/**
* Return a random integer in range [a, b) (i.e. a included, b excluded)
* If only one parameter is passed, the random number will be generated in range [0, a)
* If no parameters are passed, the random number will be generated in range [0, 1]
*
* @param {number|null} [a=null] if two parameters are passed, minimum range value; maximum range value otherwise
* @param {number|null} [b=null] maximum range value
* @returns {number} random number
*/
const random_int = (a = null, b = null) => {
if (a == null && b == null) {
a = 0;
b = 2;
} else if (b == null) {
b = a;
a = 0;
}
return Math.floor(Math.random() * (b - a)) + a;
};
/**
* Return a random integer in range (average - interval, average + interval)
* If only one parameter is passed, the random number will be generated in range (average - 0.5, average + 0.5)
* If no parameters are passed, the random number will be generated in range [0, 1]
*
* @param {number} [average=0.5] average value of the random numbers
* @param {number} [interval=0.5] semi interval of the random numbers
* @returns {number} random number
*/
const random_interval = (average = 0.5, interval = 0.5) => {
return Math.random() * (interval * 2) + (average - interval);
};
/**
* Return a random number generated with a gaussian distribution
*
* @param {number} [min=0] minimum value of the random numbers
* @param {number} [max=1] minimum value of the random numbers
* @param {number} [skew=0] skew of the gaussian function
* @returns {number} random number
*/
const random_normal = (min = 0, max = 1, skew = 0) => {
// Box–Muller transform;
let u, v;
u = 0;
v = 0;
while (u == 0) u = Math.random(); // convert [0,1) to (0,1)
while (v == 0) v = Math.random();
let num;
num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
num = num / 10.0 + 0.5; // translate to 0 -> 1
if (num > 1 || num < 0) {
num = random_normal(min, max, skew); // resample between 0 and 1 if out of range
} else {
num = Math.pow(num, skew); // skew
num *= max - min; // stretch to fill range
num += min; // offset to min
}
return num;
};
/**
* Returns a random item from the provided array
*
* @param {Array} a an array
* @returns {*} item from input array
*/
const random_from_array = (a) => {
return a[Math.floor(Math.random() * a.length)];
};
/**
* Shuffles the provided array in place (the original array gets shuffled)
*
* @param {Array} a an array
*/
const shuffle_array = (a) => {
a.map((x) => ({ val: x, order: Math.random() }))
.sort((a, b) => a.order - b.order)
.map((x) => x.val);
};
/**
* Returns the square distance between two coordinates
*
* @param {number} x1 first coordinate x value
* @param {number} y1 first coordinate y value
* @param {number} x2 second coordinate x value
* @param {number} y2 second coordinate y value
* @returns {number} square distance between the coordinates
*/
const dist_sq = (x1, y1, x2, y2) => {
return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
};
/**
* Returns the manhattan distance between two coordinates
*
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @returns {number} manhattan distance between the coordinates
*/
const manhattan_dist = (x1, y1, x2, y2) => {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
};
/**
* Returns the distance between two coordinates
*
* @param {number} x1 first coordinate x value
* @param {number} y1 first coordinate y value
* @param {number} x2 second coordinate x value
* @param {number} y2 second coordinate y value
* @returns {number} distance between the coordinates
*/
const dist = (x1, y1, x2, y2) => {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
};
/**
* Maps (rescales) a value from an old interval to a new interval
*
* @param {number} value value in the old interval
* @param {number} old_min minimum value of the old interval
* @param {number} old_max maximum value of the old interval
* @param {number} new_min minimum value of the new interval
* @param {number} new_max maximum value of the new interval
* @returns {number} mapped value
*
* @example
* map(192, 0, 255, 1024, 2048);
* // => 1795.0117647058823
*/
const map = (value, old_min, old_max, new_min, new_max) => {
return (
((value - old_min) * (new_max - new_min)) / (old_max - old_min) + new_min
);
};
/**
* Wraps a value into an interval, like the modulo expression but in both directions of the interval
*
* @param {number} value value to be wrapped
* @param {number} [min_val=0] minimum value in the interval
* @param {number} [max_val=1] maximum value in the interval
* @returns {number}
*
* @example
* wrap(65, 0, 60);
* // => 5
*/
const wrap = (value, min_val = 0, max_val = 1) => {
while (value > max_val) value -= max_val - min_val;
while (value < min_val) value += max_val - min_val;
return value;
};
/**
* Clamps (constrains) a value into an interval
*
* @param {number} value value to be clamped
* @param {number} [min_val=0] minimum value in the interval
* @param {number} [max_val=1] maximum value in the interval
* @returns {number}
*
* @example
* clamp(-5, -3, 10);
* // => -3
*/
const clamp = (value, min_val = 0, max_val = 1) => {
return Math.min(Math.max(min_val, value), max_val);
};
/**
* Returns true if the function is called by a mobile browser
*
* @returns {boolean}
*/
const is_mobile = () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
};
/**
* Returns the value of a CSS variable used in the current page
*
* @param {string} property CSS property name
* @returns {string} CSS property value
*/
const get_css_var = (property) => {
return getComputedStyle(document.documentElement)
.getPropertyValue(property)
.split(" ")
.join("");
};
/**
* Returns the (x, y) coordinates of a 1D data structure treated as a 2D structure (a grid)
*
* @param {number} i index of the item
* @param {number} width width of the data structure
* @returns {Object}
*
* @example
* xy_from_index(25, 10);
* // => {x: 5, y: 2}
*/
const xy_from_index = (i, width) => {
const x = i % width;
const y = Math.floor(i / width);
return { x: x, y: y };
};
/**
* Returns the index corresponding to the (x, y) coordinates of a 2D data structure (a grid) treated as a 1D structure
*
* @param {number} x x coordinate of the point
* @param {number} y y coordinate of the point
* @param {number} width width of the data structure
* @returns {number}
*
* @example
* index_from_xy(7, 4, 12);
* // => 55
*/
const index_from_xy = (x, y, width) => {
return x + width * y;
};
/**
* Returns the hexadecimal conversion of a decimal number
*
* @param {number} dec the base 10 number
* @param {number} [padding=0] digits of zero padding in front of the hex number
* @param {boolean} [prefix=false] if true, 0x gets added in front of the hex number
* @param {boolean} [round=true] if true, the number gets rounded before conversion
* @returns {string}
*
* @example
* dec_to_hex(232);
* // => "E8"
*
* @example
* dec_to_hex(12, 2);
* // => "0C"
*
* @example
* dec_to_hex(14, 2, true);
* // => "0x0E"
*/
const dec_to_hex = (dec, padding = 0, prefix = false, round = true) => {
if (round) dec = Math.floor(dec);
let hex = dec.toString(16).padStart(padding, "0").toUpperCase();
if (prefix) hex = "0x" + hex;
return hex;
};
/**
* Returns the decimal conversion of a hexadecimal number
*
* @param {number} hex the base 16 number
* @returns {number}
*
* @example
* hex_to_dec("E8");
* // => 232
* @example
* hex_to_dec("0xF6");
* // => 246
* @example
* hex_to_dec("64h");
* 100
*/
const hex_to_dec = (hex) => parseInt(hex, 16);
/**
* Shuffles and returns a string
*
* @param {string} string the string to be shuffled
* @returns {string}
*/
const shuffle_string = (string) =>
string
.split("")
.map((s) => ({ val: s, order: Math.random() }))
.sort((a, b) => a.order - b.order)
.map((s) => s.val)
.join("");
/**
* Generates a random alphanumeric string of set length
*
* @param {number} len length of the random string
* @returns {string}
*/
const random_string = (len) => {
let result = "";
let characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
for (let i = 0; i < len; i++) result += random_from_array(characters);
return result;
};
/**
* In and out polynomial easing
*
* @param {number} x in range [0, 1]
* @param {number} [n=2] degree of the polynomial
* @returns x smoothed
*/
const poly_ease_inout = (x, n = 2) => {
if (x < 0.5) return x ** n * 2 ** (n - 1);
return 1 - (-2 * x + 2) ** n / 2 ** (n - 1);
};
/**
* In polynomial easing
*
* @param {number} x in range [0, 1]
* @param {number} [n=2] degree of the polynomial
* @returns x smoothed
*/
const poly_ease_in = (x, n = 2) => {
return x ** n;
};
/**
* Out polynomial easing
*
* @param {number} x in range [0, 1]
* @param {number} [n=2] degree of the polynomial
* @returns x smoothed
*/
const poly_ease_out = (x, n = 2) => {
return 1 - (1 - x) ** n;
};