mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 13:53:57 -07:00
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
161 lines
No EOL
4.3 KiB
JavaScript
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])),
|
|
[[]]
|
|
);
|
|
} |