| name | rng-seeding-patterns |
| description | Document when to use seeded vs. unseeded RNG, provide patterns for RNG seed management, include examples of deterministic game logic, document seed regeneration patterns, and provide test patterns for RNG behavior. Use when implementing game features that use random number generation. |
RNG Seeding Patterns
Overview
Patterns for managing random number generation (RNG) in games, including when to use seeded vs. unseeded RNG, seed management, and deterministic game logic.
When to Use Seeded vs. Unseeded RNG
Use Seeded RNG When
- Testing: Need deterministic behavior for tests
- Replay functionality: Want to recreate same game state
- Debugging: Need reproducible bugs
- Deterministic gameplay: Same seed = same game
Example:
const seed = 42;
seedRNG(seed);
const maze = generateMaze(seed);
Use Unseeded RNG When
- Production gameplay: Each game should be different
- Random events: Need true randomness
- Non-deterministic features: Don't need reproducibility
Example:
const seed = Math.floor(Math.random() * 1000000);
const maze = generateMaze(seed);
RNG Seed Management Patterns
Pattern 1: Scene-Level Seed
Generate seed in init() or create() (not constructor):
export class GameScene extends Phaser.Scene {
private mazeSeed: number;
constructor() {
super('GameScene');
this.mazeSeed = Math.floor(Math.random() * 1000000);
}
init() {
this.mazeSeed = Math.floor(Math.random() * 1000000);
seedRNG(this.mazeSeed);
}
create() {
const maze = generateMaze(this.mazeSeed);
}
}
Pattern 2: URL Parameter Seed
Use seed from URL for testing:
init() {
const params = new URLSearchParams(window.location.search);
const seedParam = params.get('seed');
this.mazeSeed = seedParam
? parseInt(seedParam)
: Math.floor(Math.random() * 1000000);
seedRNG(this.mazeSeed);
}
Usage:
http://localhost:3000?scene=GameScene&seed=42
Pattern 3: Test Mode Seed
Use fixed seed in test mode:
init() {
const isTestMode = new URLSearchParams(window.location.search).has('test');
if (isTestMode) {
this.mazeSeed = 42;
} else {
this.mazeSeed = Math.floor(Math.random() * 1000000);
}
seedRNG(this.mazeSeed);
}
Deterministic Game Logic Examples
Example 1: Maze Generation
Same seed = same maze:
function generateMaze(seed: number): MazeData {
seedRNG(seed);
return maze;
}
Example 2: Enemy Spawning
Same seed = same enemy positions:
function spawnEnemies(seed: number, count: number): Enemy[] {
seedRNG(seed);
const enemies: Enemy[] = [];
for (let i = 0; i < count; i++) {
const x = seededRandom() * width;
const y = seededRandom() * height;
enemies.push(new Enemy(x, y));
}
return enemies;
}
Example 3: Item Placement
Same seed = same item positions:
function placeItems(seed: number, itemCount: number): Item[] {
seedRNG(seed);
const items: Item[] = [];
for (let i = 0; i < itemCount; i++) {
const position = getRandomEmptyPosition();
items.push(new Item(position.x, position.y));
}
return items;
}
Seed Regeneration Patterns
Pattern 1: Regenerate on Scene Restart
Regenerate seed every time scene starts:
init() {
this.mazeSeed = Math.floor(Math.random() * 1000000);
seedRNG(this.mazeSeed);
}
Pattern 2: Preserve Seed Across Restarts
Keep same seed for testing:
init(data?: any) {
this.mazeSeed = data?.seed || Math.floor(Math.random() * 1000000);
seedRNG(this.mazeSeed);
}
scene.restart({ seed: this.mazeSeed });
Pattern 3: Regenerate on New Game
New seed for new game, preserve for restart:
startNewGame() {
this.mazeSeed = Math.floor(Math.random() * 1000000);
seedRNG(this.mazeSeed);
this.scene.start('GameScene', { seed: this.mazeSeed });
}
restartGame() {
this.scene.restart({ seed: this.mazeSeed });
}
Test Patterns for RNG Behavior
Test 1: Deterministic Behavior
Verify same seed produces same result:
describe('Maze Generation', () => {
it('generates same maze with same seed', () => {
const seed = 42;
const maze1 = generateMaze(seed);
const maze2 = generateMaze(seed);
expect(maze1).toEqual(maze2);
});
});
Test 2: Different Seeds Produce Different Results
Verify different seeds produce different results:
it('generates different mazes with different seeds', () => {
const maze1 = generateMaze(42);
const maze2 = generateMaze(43);
expect(maze1).not.toEqual(maze2);
});
Test 3: Seed Regeneration
Verify seed regenerates on scene restart:
it('regenerates seed on scene restart', () => {
const scene = new GameScene();
const seed1 = scene.mazeSeed;
scene.init();
const seed2 = scene.mazeSeed;
expect(seed2).not.toBe(seed1);
});
Seeded RNG Implementation
Simple Seeded RNG
class SeededRNG {
private seed: number;
constructor(seed: number) {
this.seed = seed;
}
next(): number {
this.seed = (this.seed * 9301 + 49297) % 233280;
return this.seed / 233280;
}
nextInt(max: number): number {
return Math.floor(this.next() * max);
}
}
let globalRNG: SeededRNG | null = null;
export function seedRNG(seed: number) {
globalRNG = new SeededRNG(seed);
}
export function seededRandom(): number {
if (!globalRNG) {
seedRNG(Math.floor(Math.random() * 1000000));
}
return globalRNG.next();
}
Using Phaser's Seeded RNG
const rng = new Phaser.Math.RandomDataGenerator([seed]);
const value = rng.between(0, 100);
Best Practices
- Use seeded RNG for testing: Deterministic behavior
- Regenerate seed in init(): Not constructor
- Preserve seed for restarts: If needed for testing
- Use URL parameters for test seeds: Easy testing
- Document seed usage: Explain when/why seeded
Resources
writing-phaser-3-games skill - Scene lifecycle patterns
phaser-game-testing skill - Testing patterns