尊重博主版权,该博客引用了以下博客内容:详谈JS中实现种子随机数及作用
最近开发遇到了一个新功能,要求做到相同随机数。何为相同随机数,即每次使用相同的随机种子,产生相同的数字。在网上找了一下,只找到上面一篇博客,所以,这里为了让更多的人了解,博主决定自己写一篇博客,将原博客内容引用过来,让更多的人能够了解到这一内容。
其实,我们平时在开发过程中,经常会遇到随机数问题,例如,随机抽奖,微信飞机大战中,随机产生敌人位置等等。但实际上这些都是伪随机,用C语言开发的时候,使用random函数的时候,会发现,当我们直接调用这个方法的时候,每次运行都产生相同的随机数,所以,在调用这个方法的时候,都会用时间来做随机种子。当然,在JS中直接调用Math.random方法,就能直接产生随机数,这里不需要设置时间种子,JS底层已经为我们设置了随机种子,而且,每次是不一样的。既然是伪随机,那么,我们可以根据这个特性,每次设置相同的随机数,每次运行的时候,都产生相同的随机数。
Math.seed = 5;
Math.seededRandom = function(max, min) {
max = max || 1;
min = min || 0;
Math.seed = (Math.seed * 9301 + 49297) % 233280;
var rnd = Math.seed / 233280.0;
return min + rnd * (max - min);
};
for (var i= 0; i<10; i++) {
console.log(Math.seededRandom());
}
运行如上代码你会发现如果种子Math.seed不变,那么生成的随机数是不会变化的,哦了,如果引入这个函数,那么重现游戏场景可以实现了,虽然还需要做更多的细节处理,但机制上是能保证的,本文的重点不是实现一个这样的游戏。
本文的重点是:(Math.seed * 9301 + 49297) % 233280,为什么会是这三个值,而不是其它的到底这三个数字有什么神秘的
来历呢?
像Math.seededRandom这种伪随机数生成器叫做线性同余生成器(LCG, Linear Congruential Generator),几乎所有的运行库
提供的rand都是采用的LCG,形如:
I n+1=aI n+c(mod m)
生成的伪随机数序列最大周期m,范围在0到m-1之间。要达到这个最大周期,必须满足:
1.c与m互质
2.a - 1可以被m的所有质因数整除
3.如果m是4的倍数,a - 1也必须是4的倍数
以上三条被称为Hull-Dobell定理。作为一个伪随机数生成器,周期不够大是不好意思混的,所以这是要求之一。因此才有了:
a=9301, c = 49297, m = 233280这组参数,以上三条全部满足。
下面,我们来继续讨论刚刚上面的恢复敌人位置的问题。现在我们可以自己产生随机数了,而且,按照我们的要
求,可以每次都产生相同的随机数了。也就是说,我们可以每次随机敌人的位置了。
这时有人会问,为什么要每次产生相同的随机数呢?其实,这里有很大的用处,例如,我们在玩一个游戏的时候,随机产生了几个敌人的位置,这个时候,如果网络不好,重新连接进来,会发现,敌人的位置变了,此时,玩家看起来,就会发小屏幕上敌人位置发生了跳动,从玩家体验的角度来说,这是一种很差的体验。有人会说,为什么不在恢复现场的时候,直接将敌人的位置全部发送回来呢?下面,我来举一个简单的例子,来说明这个问题:
如果我们将敌人定义为1~10种类型,那么,正常情况下,如果我们为了在重新进入游戏的时候,继续上面的进度进行游戏,我们会将敌人类型和敌人位置都传回来,此时,我们传回来的数据格式,我用JSON简单来表示,大概应该是这种:
{
'enemyList':[{
'enemyType':1,
'x':33,
'y':25
},{
'enemyType':2,
'x':45,
'y':67
},{
'enemyType':3,
'x':84,
'y':12
}]
}
那么,如果我们需要在服务器记录所有敌人的位置,如果用户量比较大,其实这数据量就挺大的了,而且,在重新进入游戏的时候,其实传回来的数据也是不少的。那下面,我们换一种思路,这次,我们每次计算玩家的位置都用随机数来计算,然后,当我们重新进入游戏的时候,服务器将相同的随机数种子发送到客户端,让服务器自己计算位置,同时将敌人的类型发回来,大概的数据格式如下:
{
'seed': 12345,
'enemyList':[1,2,3]
}
以上就是产生相同随机数的方法,如果有什么描述不清楚地地方,欢迎大家留言交流!