Seeded Random
StableDeterministic RNG — same seed, same results, every time
by Probably Playable — v1.0.0
Description
The problem
Math.random() gives you different results every time. Your AI generates a procedural level, but you can't reproduce it. You can't test it, debug it, or share a seed with another player. Your replay system breaks because the random calls don't match.
The solution
A drop-in replacement for Math.random() that's deterministic. Same seed → same sequence, always. 44 lines. Zero dependencies.
- → next() — float in [0, 1)
- → int(min, max) — integer in range
- → pick(array) — random element
- → shuffle(array) — Fisher-Yates in place
- → chance(prob) — true/false with probability
Dependencies
None — zero external dependencies.
Technical Details
- Version
- 1.0.0
- Status
- Stable
- License
- MIT
- Size
- 1 KB
- Author
- Probably Playable
- Updated
- 2025-04-04
AI Integration Skill
Drop into .claude/skills/ — your AI handles the rest.
SeededRandom — Integration Skill
Use this skill when the user asks to "deterministic random", "seeded RNG",
"reproducible random", "procedural generation seed", or "repeatable randomness".
WHAT IT DOES
A deterministic pseudo-random number generator using the mulberry32 algorithm. Given the same integer seed, it always produces the exact same sequence of numbers. This makes it perfect for procedural generation that needs to be reproducible (world gen, loot tables, level layouts), replay systems where inputs must match, and deterministic testing where you need predictable "random" values.
REQUIREMENTS
- Framework: Any (Phaser, Three.js, vanilla JS, Node.js)
- Language: TypeScript or JavaScript
- Dependencies: None
- Files: 1 TypeScript file (~60 LOC)
INSTALL
- Copy the
SeededRandomclass into your project (e.g.,src/utils/SeededRandom.ts). - Import wherever needed:
- Create an instance with an integer seed:
- Seed must be an integer. Floats get truncated via
| 0. PassingNaNorundefinedwill produce a degenerate sequence. - State mutates with each call. Every call to
next(),int(),pick(),shuffle(), orchance()advances the internal state. Calling them in a different order produces a different sequence — keep call order deterministic. - Not cryptographically secure. This is for game logic and procedural generation, not security.
- One instance per independent stream. If your world gen and your particle system both use the same instance, adding a particle call will shift the entire world sequence. Use separate instances with separate seeds.
`typescript
import { SeededRandom } from './utils/SeededRandom';
`
`typescript
const rng = new SeededRandom(12345);
`
INTEGRATION
Basic Usage
class SeededRandom {
private state: number;
constructor(seed: number) {
this.state = seed | 0; // Ensure integer
}
/** Returns a float in [0, 1) — same seed always gives same sequence */
next(): number {
let t = (this.state += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
/** Random integer in [min, max] inclusive */
int(min: number, max: number): number {
return Math.floor(this.next() * (max - min + 1)) + min;
}
/** Pick a random element from an array */
pick<T>(arr: T[]): T {
return arr[Math.floor(this.next() * arr.length)];
}
/** Fisher-Yates shuffle (mutates array in place, returns it) */
shuffle<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(this.next() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
/** Returns true with the given probability (0-1) */
chance(probability: number): boolean {
return this.next() < probability;
}
}
Procedural Generation Example
// Same seed = same world every time
const worldRng = new SeededRandom(42);
function generateRoom(): Room {
return {
width: worldRng.int(5, 15),
height: worldRng.int(5, 15),
enemies: worldRng.int(0, 3),
hasTreasure: worldRng.chance(0.25),
theme: worldRng.pick(['cave', 'dungeon', 'forest', 'ruins']),
};
}
Replay System Example
// Store seed with replay data
const replaySeed = Date.now();
const rng = new SeededRandom(replaySeed);
// During playback, recreate with same seed
const playbackRng = new SeededRandom(replaySeed);
// All "random" events will be identical
CONFIGURATION
| Option | Type | Default | Description |
|---|---|---|---|
seed | number | *required* | Integer seed for the PRNG. Same seed = same sequence. |
There are no other configuration options. The algorithm is stateless beyond the seed — create multiple instances for independent streams (e.g., one for world gen, one for loot, one for particles).
API REFERENCE
| Method | Signature | Returns | Description |
|---|---|---|---|
constructor | new SeededRandom(seed: number) | SeededRandom | Create a new PRNG with the given integer seed |
next | .next() | number | Float in [0, 1) — advances internal state |
int | .int(min: number, max: number) | number | Random integer in [min, max] inclusive |
pick | .pick<T>(arr: T[]) | T | Random element from array |
shuffle | .shuffle<T>(arr: T[]) | T[] | Fisher-Yates in-place shuffle, returns same array |
chance | .chance(probability: number) | boolean | true with given probability (0 = never, 1 = always) |
GOTCHAS
This codon is provided "as is" without warranty of any kind, express or implied. Probably Playable assumes no responsibility for any damages, data loss, security vulnerabilities, or defects arising from its use. You are solely responsible for reviewing, testing, and validating this code before integrating it into your project. Use at your own risk.