[SOLVED] How to make `Math.random()` deterministic?

Hey,

I have a fairly large codebase for a game which makes heavy use of Math.random() and Std.random().

For the purpose of testing, I’d like to be able to make the return values of these methods deterministic, so that they always return the same values each run. How would you go about it?

I know I can probably change every call to something like MyMath.random() etc. but that would be very labor intensive and also would require changing some linked libraries, so I’d like to avoid this. Maybe a macro of some sort could “override” an existing std method? or is it possible to seed Math.random somehow?

Thanks!

I don’t think it is possible to seed it without using your own methods instead of Math.random().

But you can override it with no-spoon and make it a proxy to a seeded PRNG.

You should really use a PRNG method though.

There is a library on Haxelib called seedyrng, if I remember correctly, it allows you to set a seed.

Mersenne twister generator from Kha

(to be replaced shortly, I just recently read up on random number generators and in comparison to alternatives the mersenne twister only has its cool name going for it)

1 Like

Thanks everyone for your help!

Your suggestions for PRNG methods are interesting and much appreciated. However, the main aspect of my question was to find a way to control the output of Math.random() without requiring me to replace each call. Thanks to @kLabz for pointing me in the right direction with the no-spoon lib.

Inspired by it, I came up with a somewhat simpler (yet probably not as safe) solution. By using a macro to attach the dynamic access modifier to Math.random() I was able to re-bind it in my code to a custom PRNG. In case anyone is interested, see this gist.

4 Likes

Very clever!

You can replace field.access.push(ADynamic) in your macro with

fields.remove(field);
fields.push((macro class Dummy {
  static public function random() {
    //your code here
  } 
}).fields[0]);

And get rid of __init__ code.

While we’re at it, --macro haxe.macro.Compiler.addMetadata(...) can be replaced with just --macro addMetadata(...). :slight_smile:

I don’t think that works on targets where Math is extern.

I think, in case of externs adding inline will make it work.