From 9471fdaf1f60ad14b601a5b80e4ba8d4f16938e0 Mon Sep 17 00:00:00 2001 From: falc <76709589+falcolnic@users.noreply.github.com> Date: Fri, 30 May 2025 00:16:35 +0200 Subject: [PATCH] improved perfomance of PseudoRandom (#933) ## Description: Added XorShift algo for better random number generation ## Please complete the following: - [x] I have added screenshots for all UI updates - [ ] I have added relevant tests to the test directory - [x] I process any text displayed to the user through translateText() and i've added it to the en.json file - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: @qqkedsi ![Screenshot from 2025-05-29 00-25-20](https://github.com/user-attachments/assets/7e748c7f-3bc2-4275-8ffd-9adf3a224064) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/core/PseudoRandom.ts | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/core/PseudoRandom.ts b/src/core/PseudoRandom.ts index daf27497f..406cfb243 100644 --- a/src/core/PseudoRandom.ts +++ b/src/core/PseudoRandom.ts @@ -9,6 +9,9 @@ export class PseudoRandom { private c: number = 12345; private state: number; + private static readonly POW36_8 = Math.pow(36, 8); // Pre-compute 36^8 + private static readonly INV_2_32 = 1 / 4294967296; // 1 / 2^32 for float conversion + constructor(seed: number) { // Initialize the XorShift state with seed this.state0 = seed | 0; // Force to 32-bit integer with bitwise OR @@ -48,6 +51,13 @@ export class PseudoRandom { return (this.state0 + this.state1) | 0; } + /** + * Optimized version that directly returns unsigned 32-bit integer + */ + private _nextUInt32(): number { + return this._nextIntInternal() >>> 0; + } + /** * Generates the next pseudorandom number. * @returns A number between 0 (inclusive) and 1 (exclusive). @@ -55,7 +65,7 @@ export class PseudoRandom { next(): number { // Get a 32-bit integer and convert to [0,1) range // Using >>> 0 to get unsigned interpretation (positive number) - const int = this._nextIntInternal() >>> 0; + const int = this._nextUInt32(); // Update the state variable to maintain compatibility with original interface this.state = int % this.m; @@ -64,25 +74,33 @@ export class PseudoRandom { return this.state / this.m; } + /** + * Optimized version for internal use - directly converts to [0,1) without state update + */ + private _nextFloat(): number { + return this._nextUInt32() * PseudoRandom.INV_2_32; + } + /** * Generates a random integer between min (inclusive) and max (exclusive). */ nextInt(min: number, max: number): number { - return Math.floor(this.next() * (max - min) + min); + // keep max exclusive, min inclusive – round down to get an int + return Math.floor(this._nextFloat() * (max - min)) + min; } /** * Generates a random float between min (inclusive) and max (exclusive). */ nextFloat(min: number, max: number): number { - return this.next() * (max - min) + min; + return this._nextFloat() * (max - min) + min; } /** * Generates a random ID (8 characters, alphanumeric). */ nextID(): string { - return this.nextInt(0, Math.pow(36, 8)) // 36^8 possibilities + return Math.floor(this._nextFloat() * PseudoRandom.POW36_8) // 36^8 possibilities .toString(36) // Convert to base36 (0-9 and a-z) .padStart(8, "0"); // Ensure 8 chars by padding with zeros } @@ -94,25 +112,25 @@ export class PseudoRandom { if (arr.length === 0) { throw new Error("array must not be empty"); } - return arr[this.nextInt(0, arr.length)]; + return arr[Math.floor(this._nextFloat() * arr.length)]; } /** * Returns true with probability 1/odds. */ chance(odds: number): boolean { - return this.nextInt(0, odds) === 0; + return Math.floor(this._nextFloat() * odds) === 0; } /** * Returns a shuffled copy of the array using Fisher-Yates algorithm. */ shuffleArray(array: T[]): T[] { - for (let i = array.length - 1; i >= 0; i--) { - const j = this.nextInt(0, i + 1); - [array[i], array[j]] = [array[j], array[i]]; + const result = [...array]; + for (let i = result.length - 1; i >= 0; i--) { + const j = Math.floor(this._nextFloat() * (i + 1)); + [result[i], result[j]] = [result[j], result[i]]; } - - return array; + return result; } }