/** * @author Richard Davey <rich@photonstorm.com> * @copyright 2015 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * Utility functions for dealing with Arrays. * * @class Phaser.ArrayUtils * @static */ Phaser.ArrayUtils = { /** * Fetch a random entry from the given array. * * Will return null if there are no array items that fall within the specified range * or if there is no item for the randomly choosen index. * * @method * @param {any[]} objects - An array of objects. * @param {integer} startIndex - Optional offset off the front of the array. Default value is 0, or the beginning of the array. * @param {integer} length - Optional restriction on the number of values you want to randomly select from. * @return {object} The random object that was selected. */ getRandomItem: function (objects, startIndex, length) { if (objects == null) { // undefined or null return null; } if (startIndex === undefined) { startIndex = 0; } if (length === undefined) { length = objects.length; } var randomIndex = startIndex + Math.floor(Math.random() * length); return objects[randomIndex] === undefined ? null : objects[randomIndex]; }, /** * Removes a random object from the given array and returns it. * * Will return null if there are no array items that fall within the specified range * or if there is no item for the randomly choosen index. * * @method * @param {any[]} objects - An array of objects. * @param {integer} startIndex - Optional offset off the front of the array. Default value is 0, or the beginning of the array. * @param {integer} length - Optional restriction on the number of values you want to randomly select from. * @return {object} The random object that was removed. */ removeRandomItem: function (objects, startIndex, length) { if (objects == null) { // undefined or null return null; } if (startIndex === undefined) { startIndex = 0; } if (length === undefined) { length = objects.length; } var randomIndex = startIndex + Math.floor(Math.random() * length); if (randomIndex < objects.length) { var removed = objects.splice(randomIndex, 1); return removed[0] === undefined ? null : removed[0]; } else { return null; } }, /** * A standard Fisher-Yates Array shuffle implementation which modifies the array in place. * * @method * @param {any[]} array - The array to shuffle. * @return {any[]} The original array, now shuffled. */ shuffle: function (array) { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = array[i]; array[i] = array[j]; array[j] = temp; } return array; }, /** * Transposes the elements of the given matrix (array of arrays). * * @method * @param {Array<any[]>} array - The matrix to transpose. * @return {Array<any[]>} A new transposed matrix */ transposeMatrix: function (array) { var sourceRowCount = array.length; var sourceColCount = array[0].length; var result = new Array(sourceColCount); for (var i = 0; i < sourceColCount; i++) { result[i] = new Array(sourceRowCount); for (var j = sourceRowCount - 1; j > -1; j--) { result[i][j] = array[j][i]; } } return result; }, /** * Rotates the given matrix (array of arrays). * * Based on the routine from {@link http://jsfiddle.net/MrPolywhirl/NH42z/}. * * @method * @param {Array<any[]>} matrix - The array to rotate; this matrix _may_ be altered. * @param {number|string} direction - The amount to rotate: the roation in degrees (90, -90, 270, -270, 180) or a string command ('rotateLeft', 'rotateRight' or 'rotate180'). * @return {Array<any[]>} The rotated matrix. The source matrix should be discarded for the returned matrix. */ rotateMatrix: function (matrix, direction) { if (typeof direction !== 'string') { direction = ((direction % 360) + 360) % 360; } if (direction === 90 || direction === -270 || direction === 'rotateLeft') { matrix = Phaser.ArrayUtils.transposeMatrix(matrix); matrix = matrix.reverse(); } else if (direction === -90 || direction === 270 || direction === 'rotateRight') { matrix = matrix.reverse(); matrix = Phaser.ArrayUtils.transposeMatrix(matrix); } else if (Math.abs(direction) === 180 || direction === 'rotate180') { for (var i = 0; i < matrix.length; i++) { matrix[i].reverse(); } matrix = matrix.reverse(); } return matrix; }, /** * Snaps a value to the nearest value in an array. * The result will always be in the range `[first_value, last_value]`. * * @method * @param {number} value - The search value * @param {number[]} arr - The input array which _must_ be sorted. * @return {number} The nearest value found. */ findClosest: function (value, arr) { if (!arr.length) { return NaN; } else if (arr.length === 1 || value < arr[0]) { return arr[0]; } var i = 1; while (arr[i] < value) { i++; } var low = arr[i - 1]; var high = (i < arr.length) ? arr[i] : Number.POSITIVE_INFINITY; return ((high - value) <= (value - low)) ? high : low; }, /** * Moves the element from the start of the array to the end, shifting all items in the process. * The "rotation" happens to the left. * * @method Phaser.ArrayUtils.rotate * @param {any[]} array - The array to shift/rotate. The array is modified. * @return {any} The shifted value. */ rotate: function (array) { var s = array.shift(); array.push(s); return s; }, /** * Create an array representing the inclusive range of numbers (usually integers) in `[start, end]`. * This is equivalent to `numberArrayStep(start, end, 1)`. * * @method Phaser.ArrayUtils#numberArray * @param {number} start - The minimum value the array starts with. * @param {number} end - The maximum value the array contains. * @return {number[]} The array of number values. */ numberArray: function (start, end) { var result = []; for (var i = start; i <= end; i++) { result.push(i); } return result; }, /** * Create an array of numbers (positive and/or negative) progressing from `start` * up to but not including `end` by advancing by `step`. * * If `start` is less than `stop` a zero-length range is created unless a negative `step` is specified. * * Certain values for `start` and `end` (eg. NaN/undefined/null) are currently coerced to 0; * for forward compatibility make sure to pass in actual numbers. * * @method Phaser.ArrayUtils#numberArrayStep * @param {number} start - The start of the range. * @param {number} end - The end of the range. * @param {number} [step=1] - The value to increment or decrement by. * @returns {Array} Returns the new array of numbers. * @example * Phaser.ArrayUtils.numberArrayStep(4); * // => [0, 1, 2, 3] * * Phaser.ArrayUtils.numberArrayStep(1, 5); * // => [1, 2, 3, 4] * * Phaser.ArrayUtils.numberArrayStep(0, 20, 5); * // => [0, 5, 10, 15] * * Phaser.ArrayUtils.numberArrayStep(0, -4, -1); * // => [0, -1, -2, -3] * * Phaser.ArrayUtils.numberArrayStep(1, 4, 0); * // => [1, 1, 1] * * Phaser.ArrayUtils.numberArrayStep(0); * // => [] */ numberArrayStep: function(start, end, step) { start = +start || 0; // enables use as a callback for functions like `_.map` var type = typeof end; if ((type === 'number' || type === 'string') && step && step[end] === start) { end = step = null; } step = step == null ? 1 : (+step || 0); if (end === null) { end = start; start = 0; } else { end = +end || 0; } // use `Array(length)` so engines like Chakra and V8 avoid slower modes // http://youtu.be/XAqIpGU8ZZk#t=17m25s var index = -1; var length = Math.max(Phaser.Math.roundAwayFromZero((end - start) / (step || 1)), 0); var result = new Array(length); while (++index < length) { result[index] = start; start += step; } return result; } };