I knew that pseudorandom number generators existed since my computer science studies, but practically I never had a use case to try them out on production till recently.
I used them for Blast Banners when was generating random-looking game sessions, which I can reproduced if needed only based on the session identifier.
It allowed me to achieve both reproducibility of randomness and compression—I only stored the session identifier in the database instead of all the randomly generated game data.
You can check out the code on GitHub, if you are curious and don’t want to read the whole article.
Just notice that when game starts, random number generator is seeded with the session identifier:
const generator = randomNumberGenerator(sessionKey);
Where randomNumberGenerator
is a function that returns a random number generator:
/**
* Do not use it for security-sensitive purposes.
*
* @param seed {string} The seed for random numbers.
*/
export function randomNumberGenerator(seed = "") {
const r = new Rand(seed);
return () => r.next();
}
And then in the game, I use the generator to move banners and look random:
const moveBanner = (timestamp: number) => {
if (timestamp - lastFrameTimeRef.current >= frameInterval) {
setCurrentBanner((prev) => {
const jump = generator() < 0.25;
const newTop =
prev.position.top + (jump ? 100 : generator() * 20) - 10;
const newLeft =
prev.position.left + (jump ? 100 : generator() * 20) - 10;
const boundedTop = Math.max(0, Math.min(400, newTop));
const boundedLeft = Math.max(0, Math.min(700, newLeft));
return {
...prev,
position: {
top: boundedTop,
left: boundedLeft,
},
};
});
lastFrameTimeRef.current = timestamp;
}
animationFrameRef.current = requestAnimationFrame(moveBanner);
};
There are many more use cases for pseudorandom number generators, but let’s grasp the basics first.
What is a pseudorandom number generator?
Important! Pseudorandom number generators are not suitable for security/cryptographic purposes.
A pseudorandom number generator (or PRNG) is just an algorithm for generating a sequence of numbers that appear random but are actually determined by a deterministic mathematical function.
In many cases, you don’t need real randomness at all, you just need something that looks random. And often you want it to be seeded, so it’s reproducible.
There is a lot of algorithms and tricks on how to achieve pseudorandomness. The most popular algorithms are:
sfc32
mulberry32
- and
xoshiro128ss
.
They are pretty simple usually and work super fast. For example, sfc32
, which was created by Chris Doty-Humphrey, looks like:
function sfc32(a: number, b: number, c: number, d: number): () => number {
return function (): number {
a |= 0;
b |= 0;
c |= 0;
d |= 0;
const t = (((a + b) | 0) + d) | 0;
d = (d + 1) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
}
How to generate pseudorandom numbers in TypeScript
In TypeScript, instead of implementing your own PRNG, you can use a library like rand-seed
:
npm install rand-seed
And then import it in your code and generate a random number:
import Rand from "rand-seed";
const r = new Rand();
console.log(r.next());
It uses sfc32
algorithm by default, but you can also use mulberry32
or xoshiro128ss
.
And one additional important idea is that you can seed it with a string, and it will generate a reproducible sequence of numbers.
For example if you run the following code many times, you will see that the numbers are reproducible:
const r = new Rand("hello");
console.log(r.next()); // 0.2282481105066836
console.log(r.next()); // 0.8841246573720127
It is obvious, but they are also reproducible if you use different instances of Rand
:
const r1 = new Rand("hello");
console.log(r1.next()); // 0.2282481105066836
console.log(r1.next()); // 0.8841246573720127
const r2 = new Rand("hello");
console.log(r2.next()); // 0.2282481105066836
console.log(r2.next()); // 0.8841246573720127
Reproducible randomness and compression
There are many uses cases for pseudorandom number generators, but reproducible randomness and compression are the most interesting for me.
As I mentioned in the beginning, when I was building Blast Banners—a game where you destroy cookie banners on websites, I needed to generate random-looking but reproducible banner animations and behaviors.
I used the pseudorandom number generator to generate the animations and behaviors. And I stored just the seed value in the database, so it was easy to reproduce the same banner animations and behaviors for the same user if I ever needed to.
It allowed me to achieve both reproducibility and compression.
Or for example, it can allow you to generate a random-looking labyrinth in a game based on the game session id, and you don’t need to store all the labyrinth data in the database.
Or if you want to show some list with items for your users in a random order, you can use the list identifier as a seed to generate the same order every time instead of storing the order itself.
Conclusion
Pseudorandom number generators are great for compressing the things that should like random. But shall not be used for any security/cryptographic purposes. Or where the true randomness is required.
They offer the repeatability and speed that real random processes often can’t, making them super cool in fields that need “good enough” randomness without relying on slower, hardware-based or true random sources.