0ad/binaries/data/mods/public/globalscripts/utility.js
Atrik 065ecdbdf8 Implement level class sorting for formations
Add support for multi-level priority sorting of entity classes
in formations.

fixes #7547

Change formation template's default sorting order
using multi-level sorting.

fixes #6873
2026-01-23 17:20:11 +01:00

161 lines
No EOL
4.3 KiB
JavaScript

/**
* "Inside-out" implementation of Fisher-Yates shuffle
*/
function shuffleArray(source)
{
if (!source.length)
return [];
const result = [source[0]];
for (let i = 1; i < source.length; ++i)
{
const j = randIntInclusive(0, i);
result[i] = result[j];
result[j] = source[i];
}
return result;
}
/**
* Generates each permutation of the given array and runs the callback function on it.
* Uses the given clone function on each item of the array.
* Creating arrays with all permutations of the given array has a bad memory footprint.
* Algorithm by B. R. Heap. Changes the input array.
*/
function heapsPermute(array, cloneFunc, callback)
{
const c = new Array(array.length).fill(0);
callback(array.map(cloneFunc));
let i = 0;
while (i < array.length)
{
if (c[i] < i)
{
const swapIndex = i % 2 ? c[i] : 0;
const swapValue = cloneFunc(array[swapIndex]);
array[swapIndex] = array[i];
array[i] = swapValue;
callback(array.map(cloneFunc));
++c[i];
i = 0;
}
else
{
c[i] = 0;
++i;
}
}
}
/**
* Compare two variables recursively. This compares better than a quick
* JSON.stringify check since we also check undefineds, Sets and the like.
*
* @param first - Any javascript instance.
* @param second - Any javascript instance.
* @return {boolean} Whether first and second are equal.
*/
function deepCompare(first, second)
{
// If the value of either variable is empty we can instantly compare
// them and check for equality.
if (first === null || first === undefined ||
second === null || second === undefined)
return first === second;
// Make sure both variables have the same type.
if (first.constructor !== second.constructor)
return false;
// We know that the variables are of the same type so all we need to do is
// check what type one of the objects is, and then compare them.
// Check numbers seperately. Make sure this works with NaN, Infinity etc.
if (typeof first === "number")
return uneval(first) === uneval(second);
// Functions and RegExps must have the same reference to be equal.
if (first instanceof Function || first instanceof RegExp)
return first === second;
// If we are comparing simple objects, we can just compare them.
if (first === second || first.valueOf() === second.valueOf())
return true;
// Dates would have equal valueOf if they are equal.
if (first instanceof Date)
return false;
// From now we will assume we have some kind of objects so that
// we can do a recursive check of the keys and values.
if (!(first instanceof Object) || !(second instanceof Object))
return false;
// We cannot iterate over Sets, so collapse them to Arrays.
if (first instanceof Set)
{
first = Array.from(first);
second = Array.from(second);
}
// Objects need the same number of keys in order to be equal.
const firstKeys = Object.keys(first);
if (firstKeys.length != Object.keys(second).length)
return false;
// Make sure that all the object keys on this level of the object are the same.
// Finally, we pass all the values of our of each object recursively to
// make sure everything matches.
return Object.keys(second).every(i => firstKeys.includes(i)) &&
firstKeys.every(i => deepCompare(first[i], second[i]));
}
/**
* Removes prefixing path from a path or filename, leaving just the file's name (with extension)
*
* ie. a/b/c/file.ext -> file.ext
*/
function basename(path)
{
return path.split("/").pop();
}
/**
* Returns the directories of a given path.
*
* ie. a/b/c/file.ext -> a/b/c
*/
function dirname(path)
{
return path.split("/").slice(0, -1).join("/");
}
/**
* Returns names of files found in the given directory, stripping the directory path and file extension.
*/
function listFiles(path, extension, recurse)
{
return Engine.ListDirectoryFiles(path, "*" + extension, recurse).map(filename => filename.slice(path.length, -extension.length));
}
/**
* Computes the Cartesian Product of multiple arrays
*
* Generates all possible ordered combinations by taking one element from each input array.
* This is equivalent to nested loops over all input arrays, producing every possible combination.
*
* @example
* cartesianProduct([[1, 2], ['a', 'b']])
* -> [[1,'a'],[1,'b'],[2,'a'],[2,'b']]
*/
function cartesianProduct(arrays)
{
return arrays.reduce((acc, curr) =>
acc.flatMap(a => curr.map(c => [...a, c])),
[[]]
);
}