Share Your Code

Portable Seedable Random Number Generator

Posted by OderWat, Posted on July 20, 2010, Last updated October 7, 2010

Here I present a portable pseudo random number generator based on MD5 Hashes.

I am using an interesting way to create the objects very lightweight and encapsulate all of the functionality in a simple to use module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
-- "GameRand.lua"
--
-- This is an implementation of a md5 based
-- pseudo random number generator which creates the same
-- sequence of values for implementations on different
-- platforms (Hans Raaf)
 
-- copy what we need as local
local floor=math.floor
local byte=string.byte
local sub=string.sub
local random=math.random
local tostring=tostring
local assert=assert
 
local crypto = require('crypto')
 
local digest=crypto.digest
local md5=crypto.md5
 
-- make it into a module
module(...)
 
-- All functions are local and used only inside the objects I create
 
local randInt = function(self,min,max)
        assert(self.pos > 0) -- is 0 if neither seed nor randomize was called
        assert(max-min < 256 and max-min > 0) -- only possible values
        if self.pos>16 then
                self.digest=digest(md5,self.digest,true)
                self.pos=1
        end
        local x=floor(byte(sub(self.digest,self.pos,self.pos))
                *(max-min+1)/256)+min
        self.pos=self.pos+1
        self.my_step=self.my_step+1
        return x
end
 
local seed = function(self,s)
        self.my_step=0
        self.my_seed=s
        self.digest=digest(md5,self.my_seed,true)
        self.pos=1
end
 
local randomize = function(self)
        self:seed(tostring(random()))
end
 
local step = function(self,step)
        assert(self.pos>0) -- is 0 if neither seed nor randomize was called
        -- fast forward to a position
        local i
        -- shortcut for full 16 steps
        for i=1, floor(step/16) do
                self.digest=digest(md5,self.digest,true)
                self.pos=1
                self.my_step=self.my_step+16
        end
        -- set the offsets to the right position
        self.pos=self.pos+step%16
        self.my_step=self.my_step+step%16
end
 
 
return function()
        -- create a new Object :)
        return {
                my_step=0,
                my_seed=nil,
                digest=nil,
                pos=0,
 
                randInt=randInt,
                seed=seed,
                randomize=randomize,
                step=step
        }
end

Here some code which demonstrates its basic usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
local GameRand = require "GameRand"
 
-- create a GameRand object
gr1=GameRand()
 
-- seed with "test"
gr1:seed('test')
 
print('Show 20 Random Values of gr1 ')
for x=1, 20 do
        print(x..': '..gr1:randInt(0,9))
end
 
-- now use a second GameRand object and "catch up"
gr2=GameRand()
print('\nStart gr2 at Step 18')
gr2:seed('test') -- seed with same seed as gr1
gr2:step(17) -- step 17 steps..
for x=18, 20 do
        print(x..': '..gr2:randInt(0,9))
end
 
-- now have both running besides each other
print('\nPrint gr1 and gr2 from step 21')
for x=21, 33 do
        print(x..': '..gr1:randInt(0,9)..' - '..gr2:randInt(0,9))
end
 
-- dump the tables (uses my other example code for 'print_r()'
print_r(gr1)
print_r(gr2)

Compatibility: 
Corona 1.0

Replies

jensto
User offline. Last seen 43 weeks 4 days ago. Offline
Joined: 23 Jan 2011

Hi, this is very interesting code. I see that this is usefull in many situations.

I ran some statistics on this and i get a certain pattern that is repeated no matter of which seed I use. It is from 0 to 9 and I place these numbers in statistics bins. The statistics is roughly the same for each seed used. Quite strange, any thoughts?

First pass:

1
2
3
4
5
6
7
8
9
10
11
2013-03-19 16:58:42.693 Corona Simulator[1848:f03] Stats for 10000 Random Values of gr1, seed is: test1
2013-03-19 16:58:42.830 Corona Simulator[1848:f03] Bin 1: 10166
2013-03-19 16:58:42.830 Corona Simulator[1848:f03] Bin 2: 10169
2013-03-19 16:58:42.831 Corona Simulator[1848:f03] Bin 3: 9715
2013-03-19 16:58:42.831 Corona Simulator[1848:f03] Bin 4: 10304
2013-03-19 16:58:42.831 Corona Simulator[1848:f03] Bin 5: 9720
2013-03-19 16:58:42.831 Corona Simulator[1848:f03] Bin 6: 10206
2013-03-19 16:58:42.831 Corona Simulator[1848:f03] Bin 7: 10137
2013-03-19 16:58:42.832 Corona Simulator[1848:f03] Bin 8: 9763
2013-03-19 16:58:42.832 Corona Simulator[1848:f03] Bin 9: 10073
2013-03-19 16:58:42.832 Corona Simulator[1848:f03] Bin 10: 9747

Second pass:

1
2
3
4
5
6
7
8
9
10
11
2013-03-19 17:00:52.963 Corona Simulator[1848:f03] Stats for 100000 Random Values of gr1, seed is: fungus
2013-03-19 17:00:53.104 Corona Simulator[1848:f03] Bin 1: 10141
2013-03-19 17:00:53.104 Corona Simulator[1848:f03] Bin 2: 10067
2013-03-19 17:00:53.104 Corona Simulator[1848:f03] Bin 3: 9700
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 4: 10111
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 5: 9776
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 6: 10188
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 7: 10174
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 8: 9784
2013-03-19 17:00:53.105 Corona Simulator[1848:f03] Bin 9: 10372
2013-03-19 17:00:53.106 Corona Simulator[1848:f03] Bin 10: 9687