xorshift for rng

2024-04-03

I was creating a game in Rust and wanted access to an "RNG"; a random number generator. There are a few crates and options and what not but I wanted something super simple and something that could be seeded. It did not have to be crypto secure, it just had to appear random (like all things random do..). I ended up using the simple xorshift algorithm.

The game in question is Factini and you can play it here.

I can tell you all kinds of things about xorshift but honestly I know nothing about it. Here's a wikipedia article about it.

To me the point was something that would generate pseudo-random numbers for a game. And something that could be seeded so you can reproduce a certain map or behavior. The player only needs to feel like they're seeing random input. This is very much like ye olde console days where consoles like the NES used player input to form (P)RNG. Here's a random video explaining it in depth for Super Mario World on the SNES. The clock-less PRNG is what allows Tool Assisted Speedruns (TAS) to be a thing. But I digress.

Here's the Rust code for what I used in Factini:

Code:
pub fn xorshift(z: usize) -> usize {
if z == 0 { log!("Warning: do not call xorshift with zero"); }
let z = z ^ z << 13;
let z = z ^ z >> 17;
let z = z ^ z << 5;
return z;
}

As you can see, the only real condition is that you can't give it a zero since the result will always be zero.

While that's a minor downside, a real downside (or upside) is that it's stateless; it's just a number. So in order to replay the PRNG you have to remember the last result and feed it back in.

Code:
world.last_prng = xorshift(world.last_prng);
world.last_prng = xorshift(world.last_prng);
world.last_prng = xorshift(world.last_prng);

The nature of the xor-operation does mean that it'll never output a zero so you don't have to worry about that when using the xorshift function. Although you probably do want to take that into account when using the result, since most stuff will be offset-zero.

To seed it you can either use a hardcoded value, a given value, or something like the current timestamp.

To the player of a game this sort of RNG will appear random enough which is what we aim for. It's obviously not suited for anything serious but that wasn't the point here :)