Portable Seedable Random Number Generator

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

1 vote

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