Home > database >  randomseed problem - same seed, different numbers on different platforms
randomseed problem - same seed, different numbers on different platforms

Time:04-25

We try make same random levels in our game. So, we use randomseed (Lua) with same seed number. We tried more iOS devices and everything is OK. We see same numbers, same levels for each seed. But if we tried same randomseed on Android devices, it is not same like on iOS devices.

math.randomseed( 6000001 )

Is there any way to have same sequence numbers on both platforms? Thanks a lot for help.

CodePudding user response:

You're out of luck here as Solar2D appears to be using Lua 5.1, which does not make any portability guarantees for it's random generator.

You have to use/implement your own pseudo-random generator if you want platform-independent seeded random number sequences. There are some packages on Luarocks that implement such RNGs, such as lrandom. If you need a pure-Lua random number generator, you can use randomlua.

CodePudding user response:

Lua 5.4 implements its own PRNG which gives the same pseudo-random numbers on any platform.
But on Lua 5.3 (and older Lua versions) PRNG is platform-dependent as math.random is just a wrap-around for RNG provided by C library.

To create a cross-platform PRNG for Lua 5.3 and earlier Lua versions, you have to write it yourself.

This is an example of such PRNG implementation:

-- Pseudo-random number generator
-- Produces identical sequences of pseudo-random numbers across all platforms and versions of Lua: 5.1, 5.2, 5.3, 5.4, LuaJIT

-- Functions:
--    math.random()         -- standard Lua function was redefined with cross-platform implementation
--    math.randomseed()     -- standard Lua function was redefined with cross-platform implementation
--    math.getrandomseed()  -- new function, it returns the current position of the PRN sequence to be able to continue its generation later

-- Internal state (seed): 53 bits, can be read or modified at any time

-- Good statistical properties of PRN sequence:
--    uniformity
--    long period of 255 * 2^45 (approximately 2^53)
--    unpredictability (probably better than xoshiro)

-- Non-standard Lua forks having 32-bit "float" Lua numbers (instead of 64-bit "double") are not supported

do
   -- all parameters in PRNG formula are derived from these 57 secret bits:
   local secret_key_6  = 59            -- 6-bit  arbitrary integer (0..63)
   local secret_key_7  = 115           -- 7-bit  arbitrary integer (0..127)
   local secret_key_44 = 3580861008713 -- 44-bit arbitrary integer (0..17592186044415)

   local function primitive_root_257(idx)
      -- returns primitive root modulo 257 (one of 128 existing roots, idx = 0..127)
      local g, m, d = 1, 128, 2 * idx   1
      repeat
         g, m, d = g * g * (d >= m and 3 or 1) % 257, m / 2, d % m
      until m < 1
      return g
   end

   local param_mul_8 = primitive_root_257(secret_key_7)
   local param_mul_45 = secret_key_6 * 4   1
   local param_add_45 = secret_key_44 * 2   1

   -- state of PRNG (53 bits in total)
   local state_45 = 0 -- 0..(2^45-1)
   local state_8 = 2  -- 2..256

   local function get_random_uint32()
      -- returns pseudo-random 32-bit integer 0..4294967295

      -- A linear congruental generator with period of 2^45
      state_45 = (state_45 * param_mul_45   param_add_45) % 2^45

      -- Lehmer RNG having period of 256
      repeat
         state_8 = state_8 * param_mul_8 % 257
      until state_8 ~= 1  -- skip one value to reduce period from 256 to 255 (we need it to be coprime with 2^45)

      -- Idea taken from PCG: shift and rotate "state_45" by varying number of bits to get 32-bit result
      local r = state_8 % 32
      local n = state_45 / 2^(13 - (state_8 - r) / 32)
      n = (n - n % 1) % 2^32 / 2^r
      r = n % 1
      return r * 2^32   (n - r)
   end

   local address = tonumber(tostring{}:match"%x%x%x ", 16)

   function math.randomseed(seed1, seed2)
      -- arguments may be integers or floating point numbers
      -- without arguments: set initial seed to os.time()
      if not (seed1 or seed2) then
         seed1, seed2 = os.time(), address
      end
      local seed = (seed1 or 0)   (seed2 or 0)
      local lo = seed % 1 * 2^53
      local mi = seed % 9007199254740992  -- 2^53
      local hi = (seed - mi) / 2^53
      seed = (lo   mi   hi) % 2^53
      seed = seed - seed % 1
      state_45 = seed % 2^45
      state_8 = (seed - state_45) / 2^45 % 255   2
      return seed
   end

   function math.getrandomseed()
      -- returns current seed as 53-bit integer
      -- you can pass this number later to math.randomseed to continue the sequence
      return (state_8 - 2) * 2^45   state_45
   end

   local two32 = 65536 * 65536  -- 2^32
   local Lua_has_integers = two32 * two32 == 0
   local Lua_has_int64 = Lua_has_integers and two32 ~= 0
   local math_floor = math.floor

   local function get_random_full_int()
      local hi22 = math_floor(get_random_uint32() / 2^10)
      local mi21 = math_floor(get_random_uint32() / 2^11)
      local lo21 = math_floor(get_random_uint32() / 2^11)
      local two21 = 2097152  -- 2^21
      return (hi22 * two21   mi21) * two21   lo21
   end

   local function get_random_float()
      local hi21 = get_random_uint32() / 2^21 % 1
      local lo32 = get_random_uint32() / 2^53
      return hi21   lo32
   end

   function math.random(m, n)
      if not m then
         -- returns pseudo-random 53-bit floating point number 0 <= x < 1
         return get_random_float()
      elseif m == 0 and not n and Lua_has_integers then
         -- returns an integer with all bits pseudo-random
         return get_random_full_int()
      end
      if not n then
         m, n = 1, m
      end
      -- returns pseudo-random integer in the range m..n
      m, n = m - m % 1, n - n % 1
      if n < m then
         error("Invalid arguments for function 'math.random()'"..": interval is empty", 2)
      elseif m >= -2^53 and n <= 2^53 and m   2^52 > n - 2^52 then
         return math_floor(m   get_random_float() * 2^53 % (0.0   n - m   1))
      elseif m >= -2^63 and n < 2^63 and Lua_has_int64 then
         m, n = math_floor(m), math_floor(n)
         local k = n - m   1
         if k > 0 then
            return m   get_random_full_int() % k
         end
      end
      error("Invalid arguments for function 'math.random()'", 2)
   end

   -- set initial random seed
   math.randomseed()  -- math.randomseed() without arguments derives seed from os.time()

end

Usage example:

math.randomseed(1234567890) -- any integer or floating point value as random seed
for k = 1, 100 do
   local rnd = math.random()
end
local saved_seed = math.getrandomseed()  -- save the current seed to be able to continue this sequence later
-- ...
-- we need 10 more pseudo-random values from the same sequence
math.randomseed(saved_seed)  -- continue the sequence from the moment where we saved the seed
for k = 1, 10 do
   print(math.random())
end
-- ...
-- we need again the same 10 pseudo-random values
math.randomseed(saved_seed)  -- continue the sequence from the moment where we saved the seed
for k = 1, 10 do
   print(math.random())
end
  • Related