use seedrandom library

This commit is contained in:
evanpelle
2025-08-14 20:15:39 -07:00
parent 7f0148b5e5
commit b5744f685a
3 changed files with 31 additions and 78 deletions
+14 -78
View File
@@ -1,61 +1,13 @@
export class PseudoRandom {
// Internal state (two 32-bit integers)
private state0: number;
private state1: number;
import seedrandom from "seedrandom";
// Keep these variables to maintain the exact same interface
private m: number = 0x80000000; // 2**31
private a: number = 1103515245;
private c: number = 12345;
private state: number;
export class PseudoRandom {
private rng: seedrandom.PRNG;
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
this.state1 = 0x6e2d786c; // Fixed value as second seed (arbitrary prime)
// Ensure non-zero state
if (this.state0 === 0) this.state0 = 1;
// Also set the LCG state variable to maintain interface
this.state = seed % this.m;
if (this.state < 0) this.state += this.m;
// Warm up the generator to improve initial distribution
for (let i = 0; i < 20; i++) {
this._nextIntInternal();
}
}
/**
* Internal function that implements XorShift algorithm
* @returns A 32-bit integer
*/
private _nextIntInternal(): number {
// Get current state
let s1 = this.state0;
const s0 = this.state1;
// Update state using XorShift algorithm (all operations are bitwise)
this.state0 = s0;
s1 ^= s1 << 23;
s1 ^= s1 >>> 17;
s1 ^= s0;
s1 ^= s0 >>> 26;
this.state1 = s1;
// Generate output (force 32-bit integer)
return (this.state0 + this.state1) | 0;
}
/**
* Optimized version that directly returns unsigned 32-bit integer
*/
private _nextUInt32(): number {
return this._nextIntInternal() >>> 0;
// Initialize seedrandom with the seed
this.rng = seedrandom(String(seed));
}
/**
@@ -63,46 +15,30 @@ export class PseudoRandom {
* @returns A number between 0 (inclusive) and 1 (exclusive).
*/
next(): number {
// Get a 32-bit integer and convert to [0,1) range
// Using >>> 0 to get unsigned interpretation (positive number)
const int = this._nextUInt32();
// Update the state variable to maintain compatibility with original interface
this.state = int % this.m;
// Convert to [0,1) range - using division for same interface
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;
return this.rng();
}
/**
* Generates a random integer between min (inclusive) and max (exclusive).
*/
nextInt(min: number, max: number): number {
// keep max exclusive, min inclusive round down to get an int
return Math.floor(this._nextFloat() * (max - min)) + min;
return Math.floor(this.rng() * (max - min)) + min;
}
/**
* Generates a random float between min (inclusive) and max (exclusive).
*/
nextFloat(min: number, max: number): number {
return this._nextFloat() * (max - min) + min;
return this.rng() * (max - min) + min;
}
/**
* Generates a random ID (8 characters, alphanumeric).
*/
nextID(): string {
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
return Math.floor(this.rng() * PseudoRandom.POW36_8)
.toString(36)
.padStart(8, "0");
}
/**
@@ -112,7 +48,7 @@ export class PseudoRandom {
if (arr.length === 0) {
throw new Error("array must not be empty");
}
return arr[Math.floor(this._nextFloat() * arr.length)];
return arr[Math.floor(this.rng() * arr.length)];
}
/**
@@ -141,7 +77,7 @@ export class PseudoRandom {
* Returns true with probability 1/odds.
*/
chance(odds: number): boolean {
return Math.floor(this._nextFloat() * odds) === 0;
return Math.floor(this.rng() * odds) === 0;
}
/**
@@ -150,7 +86,7 @@ export class PseudoRandom {
shuffleArray<T>(array: T[]): T[] {
const result = [...array];
for (let i = result.length - 1; i >= 0; i--) {
const j = Math.floor(this._nextFloat() * (i + 1));
const j = Math.floor(this.rng() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;