diff --git a/src/client/scripts/esm/chess/logic/movepiece.js b/src/client/scripts/esm/chess/logic/movepiece.js index 6a12065d5..1672925da 100644 --- a/src/client/scripts/esm/chess/logic/movepiece.js +++ b/src/client/scripts/esm/chess/logic/movepiece.js @@ -27,6 +27,7 @@ import gameloader from '../../game/chess/gameloader.js'; * Type Definitions * @typedef {import('./gamefile.js').gamefile} gamefile * @typedef {import('../util/moveutil.js').Move} Move + * @typedef {import('../util/coordutil.js').Coords} Coords */ "use strict"; @@ -40,7 +41,7 @@ import gameloader from '../../game/chess/gameloader.js'; * The Piece Object. * @typedef {Object} Piece * @property {string} type - The type of the piece (e.g. `queensW`). - * @property {[number,number]} coords - The coordinates of the piece: `[x,y]` + * @property {Coords} coords - The coordinates of the piece: `[x,y]` * @property {number} index - The index of the piece within the gamefile's piece list. */ @@ -222,8 +223,7 @@ function addPiece(gamefile, type, coords, desiredIndex, { updateData = true } = if (isPieceAtCoords) throw new Error("Can't add a piece on top of another piece!"); // Remove the undefined from the undefineds list - const deleteSuccussful = jsutil.deleteValueFromOrganizedArray(gamefile.ourPieces[type].undefineds, desiredIndex) !== false; - if (!deleteSuccussful) throw new Error("Index to add a piece has an existing piece on it!"); + gamefile.ourPieces[type].undefineds = jsutil.deleteElementFromOrganizedArray(gamefile.ourPieces[type].undefineds, desiredIndex); list[desiredIndex] = coords; } diff --git a/src/client/scripts/esm/chess/logic/movesets.ts b/src/client/scripts/esm/chess/logic/movesets.ts index 9cce72fca..1d7f556c1 100644 --- a/src/client/scripts/esm/chess/logic/movesets.ts +++ b/src/client/scripts/esm/chess/logic/movesets.ts @@ -97,8 +97,7 @@ type BlockingFunction = (friendlyColor: string, blockingPiece: Piece, gamefile?: /** The default blocking function of each piece's sliding moves, if not specified. */ -// eslint-disable-next-line no-unused-vars -function defaultBlockingFunction(friendlyColor: string, blockingPiece: Piece, gamefile?: gamefile): 0 | 1 | 2 { +function defaultBlockingFunction(friendlyColor: string, blockingPiece: Piece): 0 | 1 | 2 { const colorOfBlockingPiece = colorutil.getPieceColorFromType(blockingPiece.type); const isVoid = blockingPiece.type === 'voidsN'; if (friendlyColor === colorOfBlockingPiece || isVoid) return 1; // Block where it is if it is a friendly OR a void square. diff --git a/src/client/scripts/esm/chess/util/gamefileutility.js b/src/client/scripts/esm/chess/util/gamefileutility.js index bf33c0584..f5766a8f4 100644 --- a/src/client/scripts/esm/chess/util/gamefileutility.js +++ b/src/client/scripts/esm/chess/util/gamefileutility.js @@ -224,8 +224,7 @@ function getPieceCount_IncludingUndefineds(gamefile) { function deleteIndexFromPieceList(list, pieceIndex) { list[pieceIndex] = undefined; // Keep track of where the undefined indices are! Have an "undefineds" array property. - const undefinedsInsertIndex = jsutil.binarySearch_findSplitPoint(list.undefineds, pieceIndex); - list.undefineds.splice(undefinedsInsertIndex, 0, pieceIndex); + list.undefineds = jsutil.addElementToOrganizedArray(list.undefineds, pieceIndex); } diff --git a/src/client/scripts/esm/chess/variants/variant.ts b/src/client/scripts/esm/chess/variants/variant.ts index c06e148e5..e7b37d1e0 100644 --- a/src/client/scripts/esm/chess/variants/variant.ts +++ b/src/client/scripts/esm/chess/variants/variant.ts @@ -14,7 +14,6 @@ import omega4generator from './omega4generator.js'; import colorutil from '../util/colorutil.js'; // @ts-ignore import typeutil from '../util/typeutil.js'; -// @ts-ignore import jsutil from '../../util/jsutil.js'; // @ts-ignore import timeutil from '../../util/timeutil.js'; @@ -509,7 +508,7 @@ function getMovesets(movesetModifications: Movesets = {}, defaultSlideLimitForOl } = {}; for (const [piece, moves] of Object.entries(origMoveset)) { - pieceMovesets[piece] = movesetModifications[piece] ? () => jsutil.deepCopyObject(movesetModifications[piece]) + pieceMovesets[piece] = movesetModifications[piece] ? () => jsutil.deepCopyObject(movesetModifications[piece]!) : () => jsutil.deepCopyObject(moves); } diff --git a/src/client/scripts/esm/game/chess/game.ts b/src/client/scripts/esm/game/chess/game.ts index b9b0497de..5ca09dd41 100644 --- a/src/client/scripts/esm/game/chess/game.ts +++ b/src/client/scripts/esm/game/chess/game.ts @@ -51,7 +51,6 @@ import dragAnimation from '../rendering/draganimation.js'; import piecesmodel from '../rendering/piecesmodel.js'; // @ts-ignore import loadbalancer from '../misc/loadbalancer.js'; -// @ts-ignore import jsutil from '../../util/jsutil.js'; import highlights from '../rendering/highlights/highlights.js'; import gameslot from './gameslot.js'; diff --git a/src/client/scripts/esm/game/chess/gameloader.ts b/src/client/scripts/esm/game/chess/gameloader.ts index a6db17c3d..b6cb7dd2c 100644 --- a/src/client/scripts/esm/game/chess/gameloader.ts +++ b/src/client/scripts/esm/game/chess/gameloader.ts @@ -26,7 +26,6 @@ import onlinegame from "../misc/onlinegame.js"; import drawoffers from "../misc/drawoffers.js"; // @ts-ignore import localstorage from "../../util/localstorage.js"; -// @ts-ignore import jsutil from "../../util/jsutil.js"; // @ts-ignore import perspective from "../rendering/perspective.js"; diff --git a/src/client/scripts/esm/game/misc/loadbalancer.js b/src/client/scripts/esm/game/misc/loadbalancer.js index ed41577e6..e96b04067 100644 --- a/src/client/scripts/esm/game/misc/loadbalancer.js +++ b/src/client/scripts/esm/game/misc/loadbalancer.js @@ -108,13 +108,13 @@ function updateDeltaTime(runtime) { lastFrameTime = runTime; } -// Deletes frame timestamps from out list over 1 second ago +// Deletes frame timestamps from our list over 1 second ago function trimFrames() { // What time was it 1 second ago const splitPoint = runTime - fpsWindow; // Use binary search to find the split point. - const indexToSplit = jsutil.binarySearch_findValue(frames, splitPoint); + const indexToSplit = jsutil.findIndexOfPointInOrganizedArray(frames, splitPoint); // This will not delete a timestamp if it falls exactly on the split point. frames.splice(0, indexToSplit); diff --git a/src/client/scripts/esm/game/rendering/spritesheet.ts b/src/client/scripts/esm/game/rendering/spritesheet.ts index 095207e10..1bcd606c7 100644 --- a/src/client/scripts/esm/game/rendering/spritesheet.ts +++ b/src/client/scripts/esm/game/rendering/spritesheet.ts @@ -11,7 +11,6 @@ import { generateSpritesheet } from '../../chess/rendering/spritesheetGenerator. import { convertSVGsToImages } from '../../chess/rendering/svgtoimageconverter.js'; // @ts-ignore import typeutil from '../../chess/util/typeutil.js'; -// @ts-ignore import jsutil from '../../util/jsutil.js'; // @ts-ignore import texture from './texture.js'; diff --git a/src/client/scripts/esm/util/jsutil.js b/src/client/scripts/esm/util/jsutil.js deleted file mode 100644 index 15a749dad..000000000 --- a/src/client/scripts/esm/util/jsutil.js +++ /dev/null @@ -1,226 +0,0 @@ - -/** - * This scripts contains utility methods for working with javascript objects. - * - * ZERO dependancies. - */ - -/** - * Deep copies an entire object, no matter how deep its nested. - * No properties will contain references to the source object. - * Use this instead of structuredClone() because of browser support, - * or when that throws an error due to functions contained within the src. - * - * SLOW. Avoid using for very massive objects. - * @param {Object | string | number | bigint | boolean} src - The source object - * @returns {Object | string | number | bigint | boolean} The copied object - */ -function deepCopyObject(src) { - if (typeof src !== "object" || src === null) return src; - - const copy = Array.isArray(src) ? [] : {}; // Create an empty array or object - - for (const key in src) { - const value = src[key]; - copy[key] = deepCopyObject(value); // Recursively copy each property - } - - return copy; // Return the copied object -} - -/** - * Deep copies a Float32Array. - * @param {Float32Array} src - The source Float32Array - * @returns {Float32Array} The copied Float32Array - */ -function copyFloat32Array(src) { - if (!src || !(src instanceof Float32Array)) { - throw new Error('Invalid input: must be a Float32Array'); - } - - const copy = new Float32Array(src.length); - - for (let i = 0; i < src.length; i++) { - copy[i] = src[i]; - } - - return copy; -} - -/** - * Performs a binary search on a sorted array to find the index where a given value could be inserted, - * maintaining the array's sorted order. MUST NOT ALREADY CONTAIN THE VALUE!! - * @param {number[]} sortedArray - The array to search, which must be sorted in ascending order. - * @param {number} value - The value to search for. - * @returns {number} The index where the value can be inserted, maintaining order. - */ -function binarySearch_findSplitPoint(sortedArray, value) { - if (value === undefined) throw new Error('Cannot binary search when value is undefined!'); - - let left = 0; - let right = sortedArray.length - 1; - - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const midValue = sortedArray[mid]; - - if (value < midValue) right = mid - 1; - else if (value > midValue) left = mid + 1; - else if (midValue === value) { - throw new(`Cannot find split point of sortedArray when it already contains the value! ${value}. List: ${JSON.stringify(sortedArray)}`); - } - } - - // The left is the index at which you could insert the new value at the correct location! - return left; -} - -/** - * Calculates the index at which you could insert the given value - * and keep the array organized, OR returns the index of the given value. - * @param {number[]} sortedArray - An Array of NUMBERS. If not all numbers, this will crash. - * @param {number} value - The number to find the split point of, or exact index position of. - * @returns {number} The index - */ -function binarySearch_findValue(sortedArray, value) { - let left = 0; - let right = sortedArray.length - 1; - - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const midValue = sortedArray[mid]; - - if (value < midValue) right = mid - 1; - else if (value > midValue) left = mid + 1; - else if (midValue === value) return mid; - } - - // The left is the index at which you could insert the new value at the correct location! - return left; -} - -// Returns the index if deletion was successful. -// false if not found -function deleteValueFromOrganizedArray(sortedArray, value) { // object can't be an array - - let left = 0; - let right = sortedArray.length - 1; - - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const midValue = sortedArray[mid]; - - if (value === midValue) { - sortedArray.splice(mid, 1); - return mid; - } else if (value < midValue) { // Set the new left - right = mid - 1; - } else if (value > midValue) { - left = mid + 1; - } - } -} - -// Removes specified object from given array. Throws error if it fails. The object cannot be an object or array, only a single value. -function removeObjectFromArray(array, object) { // object can't be an array - const index = array.indexOf(object); - if (index !== -1) array.splice(index, 1); - else throw new Error(`Could not delete object from array, not found! Array: ${JSON.stringify(array)}. Object: ${object}`); -} - -// Returns true if provided object is a float32array -function isFloat32Array(param) { - return param instanceof Float32Array; -} - -/** - * Copies the properties from one object to another, - * without overwriting the existing properties on the destination object. - * @param {Object} objSrc - The source object - * @param {Object} objDest - The destination object - */ -function copyPropertiesToObject(objSrc, objDest) { - const objSrcKeys = Object.keys(objSrc); - for (let i = 0; i < objSrcKeys.length; i++) { - const key = objSrcKeys[i]; - objDest[key] = objSrc[key]; - } -} - -/** - * O(1) method of checking if an object/dict is empty - * I think??? I may be wrong. I think before the first iteration of - * a for-in loop the program still has to calculate the keys... - * @param {Object} obj - * @returns {Boolean} - */ -function isEmpty(obj) { - for (const prop in obj) { - if (Object.prototype.hasOwnProperty.call(obj, prop)) { - return false; - } - } - - return true; -} - -/** - * Tests if a string is in valid JSON format, and can thus be parsed into an object. - * @param {string} str - The string to test - * @returns {boolean} *true* if the string is in valid JSON fromat - */ -function isJson(str) { - try { - JSON.parse(str); - } catch { - return false; - } - return true; -} - -/** - * Returns a new object with the keys being the values of the provided object, and the values being the keys. - * @param {Object} obj - The object to invert - * @returns {Object} The inverted object - */ -function invertObj(obj) { - const inv = {}; - for (const key in obj) { - inv[obj[key]] = key; - } - return inv; -} - -/** - * Checks if array1 contains all the strings that array2 has and returns a list of missing strings. - * @param {string[]} array1 - The first array to check against. - * @param {string[]} array2 - The second array whose elements need to be present in array1. - * @returns {string[]} - Returns an array of missing strings from array1. If none are missing, returns an empty array. - */ -function getMissingStringsFromArray(array1, array2) { - // Convert array1 to a Set for efficient lookup - const set1 = new Set(array1); - const missing = []; - - // Check if each element in array2 is present in set1 - for (const item of array2) { - if (!set1.has(item)) missing.push(item); // If element from array2 is missing in array1, add it to the missing list - } - - return missing; // Return the list of missing strings -} - -export default { - deepCopyObject, - copyFloat32Array, - binarySearch_findSplitPoint, - binarySearch_findValue, - deleteValueFromOrganizedArray, - isFloat32Array, - copyPropertiesToObject, - isEmpty, - isJson, - invertObj, - removeObjectFromArray, - getMissingStringsFromArray, -}; \ No newline at end of file diff --git a/src/client/scripts/esm/util/jsutil.ts b/src/client/scripts/esm/util/jsutil.ts new file mode 100644 index 000000000..d61c09896 --- /dev/null +++ b/src/client/scripts/esm/util/jsutil.ts @@ -0,0 +1,209 @@ + +/** + * This scripts contains utility methods for working with javascript objects. + * + * ZERO dependancies. + */ + +/** + * Deep copies an entire object, no matter how deep its nested. + * No properties will contain references to the source object. + * Use this instead of structuredClone() because of browser support, + * or when that throws an error due to functions contained within the src. + * + * SLOW. Avoid using for very massive objects. + */ +function deepCopyObject(src: T): T { + if (typeof src !== "object" || src === null) return src; + + const copy: any = Array.isArray(src) ? [] : {}; // Create an empty array or object + + for (const key in src) { + const value = src[key]; + copy[key] = deepCopyObject(value); // Recursively copy each property + } + + return copy as T; // Return the copied object +} + +/** + * Deep copies a Float32Array. + */ +function copyFloat32Array(src: Float32Array): Float32Array { + if (!src || !(src instanceof Float32Array)) { + throw new Error('Invalid input: must be a Float32Array'); + } + + const copy = new Float32Array(src.length); + + for (let i = 0; i < src.length; i++) { + copy[i]! = src[i]!; + } + + return copy; +} + +/** + * Searches an organized array and returns an object telling + * you the index the element could be added at for the array to remain + * organized, and whether the element was already found in the array. + * @param sortedArray - The array sorted in ascending order + * @param value - The value to find in the array. + * @returns An object telling you whether the value was found, and the index of that value, or where it can be inserted to remain organized. + */ +function binarySearch(sortedArray: number[], value: number): { found: boolean, index: number } { + let left = 0; + let right = sortedArray.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const midValue = sortedArray[mid]!; + + if (value < midValue) right = mid - 1; + else if (value > midValue) left = mid + 1; + else return { found: true, index: mid }; + } + + // The left is the correct index to insert at, while retaining order! + return { found: false, index: left }; +} + +/** + * Uses binary search to quickly find and insert the given number in the + * organized array. + * + * MUST NOT ALREADY CONTAIN THE VALUE!! + * @param sortedArray - The array to search, which must be sorted in ascending order. + * @param value - The value add in the correct place, retaining order. + * @returns The new array with the sorted element. + */ +function addElementToOrganizedArray(sortedArray: number[], value: number): number[] { + const { found, index } = binarySearch(sortedArray, value); + if (found) throw Error(`Cannot add element to sorted array when it already contains the value! ${value}. List: ${JSON.stringify(sortedArray)}`); + sortedArray.splice(index, 0, value); + return sortedArray; +} + +/** + * Calculates the index in the given organized array at which you could insert + * the point and the array would still be organized. + * @param sortedArray - An array of numbers organized in ascending order. + * @param point - The point in the array to find the index for. + * @returns The index + */ +function findIndexOfPointInOrganizedArray(sortedArray: number[], point: number): number { + return binarySearch(sortedArray, point).index; +} + +/** + * Deletes an element from an organized array. MUST CONTAIN THE ELEMENT. + * @param sortedArray - An array of numbers organized in ascending order. + * @param value - The value to search for and delete + * @returns The new array with the element deleted + */ +function deleteElementFromOrganizedArray(sortedArray: number[], value: number): number[] { + const { found, index } = binarySearch(sortedArray, value); + if (!found) throw Error(`Cannot delete value "${value}" from organized array (not found). Array: ${JSON.stringify(sortedArray)}`); + sortedArray.splice(index, 1); + return sortedArray; +} + +// Removes specified object from given array. Throws error if it fails. The object cannot be an object or array, only a single value. +function removeObjectFromArray(array: any[], object: any) { // object can't be an array + const index = array.indexOf(object); + if (index !== -1) array.splice(index, 1); + else throw Error(`Could not delete object from array, not found! Array: ${JSON.stringify(array)}. Object: ${object}`); +} + +// Returns true if provided object is a float32array +function isFloat32Array(param: any) { + return param instanceof Float32Array; +} + +/** + * Copies the properties from one object to another, + * without overwriting the existing properties on the destination object. + * @param objSrc - The source object + * @param objDest - The destination object + */ +function copyPropertiesToObject(objSrc: Record, objDest: Record) { + const objSrcKeys = Object.keys(objSrc); + for (let i = 0; i < objSrcKeys.length; i++) { + const key = objSrcKeys[i]!; + objDest[key] = objSrc[key]; + } +} + +/** + * O(1) method of checking if an object/dict is empty + * I think??? I may be wrong. I think before the first iteration of + * a for-in loop the program still has to calculate the keys... + */ +function isEmpty(obj: object): boolean { + for (const prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) return false; + } + + return true; +} + +/** + * Tests if a string is in valid JSON format, and can thus be parsed into an object. + */ +function isJson(str: string): boolean { + try { + JSON.parse(str); + } catch { + return false; + } + return true; +} + +/** + * Returns a new object with the keys being the values of the provided object, and the values being the keys. + * @param obj - The object to invert + * @returns The inverted object + */ +function invertObj(obj: Record): Record { + const inv: Record = {}; + for (const key in obj) { + inv[obj[key]!] = key; + } + return inv; +} + +/** + * Checks if array1 contains all the strings that array2 has and returns a list of missing strings. + * @param array1 - The first array to check against. + * @param array2 - The second array whose elements need to be present in array1. + * @returns - An array of missing strings from array1. If none are missing, returns an empty array. + */ +function getMissingStringsFromArray(array1: string[], array2: string[]): string[] { + // Convert array1 to a Set for efficient lookup + const set1 = new Set(array1); + const missing: string[] = []; + + // Check if each element in array2 is present in set1 + for (const item of array2) { + if (!set1.has(item)) missing.push(item); // If element from array2 is missing in array1, add it to the missing list + } + + return missing; // Return the list of missing strings +} + + + +export default { + deepCopyObject, + copyFloat32Array, + addElementToOrganizedArray, + findIndexOfPointInOrganizedArray, + deleteElementFromOrganizedArray, + isFloat32Array, + copyPropertiesToObject, + isEmpty, + isJson, + invertObj, + removeObjectFromArray, + getMissingStringsFromArray, +}; \ No newline at end of file diff --git a/src/server/middleware/rateLimit.js b/src/server/middleware/rateLimit.js index cd46c9a22..cfe8860be 100644 --- a/src/server/middleware/rateLimit.js +++ b/src/server/middleware/rateLimit.js @@ -200,7 +200,7 @@ setInterval(() => { } // Use binary search to find the index to split at - const indexToSplitAt = jsutil.binarySearch_findValue(timestamps, currentTimeMillis - minuteInMillis); + const indexToSplitAt = jsutil.findIndexOfPointInOrganizedArray(timestamps, currentTimeMillis - minuteInMillis); // Remove all timestamps to the left of the found index timestamps.splice(0, indexToSplitAt); @@ -231,7 +231,7 @@ function countRecentRequests() { setInterval(() => { // Delete recent requests longer than 2 seconds ago const twoSecondsAgo = Date.now() - requestWindowToToggleAttackModeMillis; - const indexToSplitAt = jsutil.binarySearch_findValue(recentRequests, twoSecondsAgo); + const indexToSplitAt = jsutil.findIndexOfPointInOrganizedArray(recentRequests, twoSecondsAgo); recentRequests.splice(0, indexToSplitAt + 1); if (recentRequests.length > requestCapToToggleAttackMode) { diff --git a/src/server/socket/socketUtility.ts b/src/server/socket/socketUtility.ts index 10d67fbbc..efef942dd 100644 --- a/src/server/socket/socketUtility.ts +++ b/src/server/socket/socketUtility.ts @@ -3,7 +3,6 @@ // @ts-ignore import { ensureJSONString } from '../utility/JSONUtils.js'; -// @ts-ignore import jsutil from '../../client/scripts/esm/util/jsutil.js';