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