src/lib/demo/seeded-prng.ts

Total Symbols
10
Lines of Code
102
Avg Complexity
1.3
Symbol Types
3

File Relationships

graph LR createSeededRng["createSeededRng"] hashSeed["hashSeed"] int["int"] next["next"] pick["pick"] shuffle["shuffle"] char["char"] string["string"] createSeededRng -->|calls| hashSeed int -->|calls| next pick -->|calls| next shuffle -->|calls| next char -->|calls| next string -->|calls| next click createSeededRng "../symbols/71a63e2b315abc34.html" click hashSeed "../symbols/692a3518abc2bb4b.html" click int "../symbols/8a137cb3b13f3db8.html" click next "../symbols/732c70cebcef6006.html" click pick "../symbols/a0ef54292cb0bb75.html" click shuffle "../symbols/b08e26b08bf80195.html" click char "../symbols/b886b58e19700917.html" click string "../symbols/6ff2cacee6966a8a.html"

Symbols by Kind

method 6
function 3
interface 1

All Symbols

Name Kind Visibility Status Lines Signature
hashSeed function - 15-22 hashSeed(seed: string): : number
createSeededRng function exported- 38-85 createSeededRng(seed: string): : SeededRng
next function - 42-48 next(): : number
SeededRng interface exported- 88-101 interface SeededRng
next method - 90-90 next(): : number
int method - 92-92 int(min: number, max: number): : number
pick method - 94-94 pick(array: readonly T[]): : T
shuffle method - 96-96 shuffle(array: readonly T[]): : T[]
char method - 98-98 char(charset: string): : string
string method - 100-100 string(length: number, charset?: string): : string

Full Source

/**
 * Deterministic PRNG based on mulberry32.
 *
 * Used during replay so that every execution of a FlowScript with the
 * same `seed` string produces the exact same sequence of "random" values.
 *
 * The seed string is hashed via a simple FNV-1a variant to produce the
 * initial 32-bit state.
 */

/**
 * FNV-1a–style hash producing a 32-bit unsigned integer from a string.
 * Used purely for seeding — not for cryptographic purposes.
 */
function hashSeed(seed: string): number {
  let h = 0x811c9dc5; // FNV offset basis
  for (let i = 0; i < seed.length; i++) {
    h ^= seed.charCodeAt(i);
    h = Math.imul(h, 0x01000193); // FNV prime
  }
  return h >>> 0;
}

/**
 * Create a seeded PRNG instance.
 *
 * Returns a `SeededRng` object with utility methods that draw from the
 * same deterministic sequence.
 *
 * @example
 * ```ts
 * const rng = createSeededRng("demo-seed-42");
 * rng.next();         // 0 ≤ n < 1
 * rng.int(1, 100);    // 1 ≤ n ≤ 100
 * rng.pick(["a","b"]) // "a" or "b" deterministically
 * ```
 */
export function createSeededRng(seed: string): SeededRng {
  let state = hashSeed(seed);

  /** mulberry32 core — returns float [0, 1) */
  function next(): number {
    state |= 0;
    state = (state + 0x6d2b79f5) | 0;
    let t = Math.imul(state ^ (state >>> 15), 1 | state);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  }

  return {
    next,

    int(min: number, max: number): number {
      return Math.floor(next() * (max - min + 1)) + min;
    },

    pick<T>(array: readonly T[]): T {
      return array[Math.floor(next() * array.length)];
    },

    shuffle<T>(array: readonly T[]): T[] {
      const result = [...array];
      for (let i = result.length - 1; i > 0; i--) {
        const j = Math.floor(next() * (i + 1));
        [result[i], result[j]] = [result[j], result[i]];
      }
      return result;
    },

    char(charset: string): string {
      return charset[Math.floor(next() * charset.length)];
    },

    string(
      length: number,
      charset = "abcdefghijklmnopqrstuvwxyz0123456789",
    ): string {
      let result = "";
      for (let i = 0; i < length; i++) {
        result += charset[Math.floor(next() * charset.length)];
      }
      return result;
    },
  };
}

/** A deterministic pseudo-random number generator */
export interface SeededRng {
  /** Returns a float in [0, 1) */
  next(): number;
  /** Returns an integer in [min, max] (inclusive) */
  int(min: number, max: number): number;
  /** Pick a random element from the array */
  pick<T>(array: readonly T[]): T;
  /** Shuffle a copy of the array (Fisher-Yates) */
  shuffle<T>(array: readonly T[]): T[];
  /** Pick a random character from the charset */
  char(charset: string): string;
  /** Generate a random string of given length from charset */
  string(length: number, charset?: string): string;
}