关于真随机和伪随机函数,为什么普遍认为在游戏

随机数是骗人的,.Net、Java、C为我作证
-05%-12%-31%-44%-51%-65%-82%-94%
四、各语言的实现
&&&&我们看到.Net的Random类有一个int类型参数的构造函数:
public&Random(int&Seed)
就是和我们写的MyRand一样接受一个"随机数种子"。而我们之前调用的无参构造函数就是给Random(int&Seed)传递Environment.TickCount类进行构造的,代码如下:
&public&Random()&:&this(Environment.TickCount)
&&&&这下我们终于明白最开始的疑惑了。 &
同样道理,在C/C++中生成10个随机数不应该如下调用:
for(i=0;i&10;i++) {
&&&&srand( (unsigned)time( NULL ) );
&&&&printf("%d\n",rand());
srand( (unsigned)time( NULL ) ); //把当前时间设置为"随机数种子"
for(i=0;i&10;i++)
printf("%d\n",rand());
&五、"奇葩"的Java
Java学习者可能会提出问题了,在Java低版本中,如下使用会像.Net、C/C++中一样产生相同的随机数:&
for(int&i=0;i&100;i++)
&&&&Random rand = new&Random();
&&&&System.out.println(rand.nextInt());
&因为低版本Java中Rand类的无参构造函数的实现同样是用当前时间做种子:
public&Random()&
this(System.currentTimeMillis());&
但是在高版本的Java中,比如Java1.8中,上面的"错误"代码执行却是没问题的:
&&&&为什么呢?我们来看一下这个Random无参构造函数的实现代码:
public Random()
this(seedUniquifier() ^ System.nanoTime());
private static long seedUniquifier() {
long current = seedUniquifier.get();
long next = current * 652981L;
if (seedUniquifier.compareAndSet(current, next))
privatestaticfinal AtomicLong seedUniquifier
= new AtomicLong(8012L);
&&&&&这里不再是使用当前时间来做"随机数种子",而是使用System.nanoTime()这个纳秒级的时间量并且和采用原子量AtomicLong根据上次调用构造函数算出来的一个数做异或运算。关于这段代码的解释详细参考这篇文章《》
最核心的地方就在于使用static变量AtomicLong来记录每次调用Random构造函数时使用的种子,下次再调用Random构造函数的时候避免和上次一样。
六、高并发系统中的问题
&&&&前面我们分析了,对于使用系统时间做"随机数种子"的随机数生成器,如果要产生多个随机数,那么一定要共享一个"随机数种子"才会避免生成的随机数短时间之内生成重复的随机数。但是在一些高并发的系统中一个不注意还会产生问题,比如一个网站在服务器端通过下面的方法生成验证码:
Random rand = new Random();
Int code = rand.Next();
&&&&当网站并发量很大的时候,可能一个毫秒内会有很多个人请求验证码,这就会造成这几个人请求到的验证码是重复的,会给系统带来潜在的漏洞。
&&&&&再比如我今天看到的一篇文章《当随机不够随机:一个在线扑克游戏的教训》里面就提到了"由于随机数产生器的种子是基于服务器时钟的,黑客们只要将他们的程序与服务器时钟同步就能够将可能出现的乱序减少到只有&200,000&种。到那个时候一旦黑客知道&5&张牌,他就可以实时的对&200,000&种可能的乱序进行快速搜索,找到游戏中的那种。所以一旦黑客知道手中的两张牌和&3&张公用牌,就可以猜出转牌和河牌时会来什么牌,以及其他玩家的牌。" &
&&&&这种情况有如下几种解决方法:
把Random对象作为一个全局实例(static)来使用。Java中Random是线程安全的(内部进行了加锁处理);.Net中Random不是线程安全的,需要加锁处理。不过加锁会存在会造成处理速度慢的问题。而且由于初始的种子是确定的,所以攻击者存在着根据得到的若干随机数序列推测出"随机数种子"的可能性。
因为每次生成Guid的值都不样,网上有的文章说可以创建一个Guid计算它的HashCode或者MD5值的方式来做种子:&new Random(Guid.NewGuid().GetHashCode())&。但是我认为Guid的生成算法是确定的,在条件充足的情况下也是可以预测的,这样生成的随机数也有可预测的可能性。当然只是我的猜测,没经过理论的证明。
采用"真随机数发生器",快看下一节分解!
&七、真随机数发生器
&&&&根据我们之前的分析,我们知道这些所谓的随机数不是真的"随机",只是看起来随机,因此被称为"伪随机算法"。在一些对随机要求高的场合会使用一些物理硬件采集物理噪声、宇宙射线、量子衰变等现实生活中的真正随机的物理参数来产生真正的随机数。
当然也有聪明的人想到了不借助增加"随机数发生器"硬件的方法生成随机数。我们操作计算机时候鼠标的移动、敲击键盘的行为都是不可预测的,外界命令计算机什么时候要执行什么进程、处理什么文件、加载什么数据等也是不可预测的,因此导致的CPU运算速度、硬盘读写行为、内存占用情况的变化也是不可预测的。因此如果采集这些信息来作为随机数种子,那么生成的随机数就是不可预测的了。
在Linux/Unix下可以使用"/dev/random"这个真随机数发生器,它的数据主来来自于硬件中断信息,不过产生随机数的速度比较慢。
Windows下可以调用系统的CryptGenRandom()函数,它主要依据当前进程Id、当前线程Id、系统启动后的TickCount、当前时间、QueryPerformanceCounter返回的高性能计数器值、用户名、计算机名、CPU计数器的值等等来计算。和"/dev/random"一样CryptGenRandom()的生成速度也比较慢,而且消耗比较大的系统资源。
当然.Net下也可以使用RNGCryptoServiceProvider&类(System.Security.Cryptography命名空间下)来生成真随机数,根据StackOverflow上一篇帖子介绍RNGCryptoServiceProvider&并不是对CryptGenRandom()函数的封装,但是和CryptGenRandom()原理类似。 &
有人可能会问:既然有"/dev/random" 、CryptGenRandom()这样的"真随机数发生器",为什么还要提供、使用伪随机数这样的"假货"?因为前面提到了"/dev/random" 、CryptGenRandom()生成速度慢而且比较消耗性能。在对随机数的不可预测性要求低的场合,使用伪随机数算法即可,因为性能比较高。对于随机数的不可预测性要求高的场合就要使用真随机数发生器,真随机数发生器硬件设备需要考虑成本问题,而"/dev/random"、CryptGenRandom()则性能较差。
万事万物都没有完美的,没有绝对的好,也没有绝对的坏,这才是多元世界美好的地方。&
2 / 2 页 查看其它分页:
上一篇:下一篇:
评论功能关闭
根据国家法律法规要求,本站暂时关闭文章评论功能。开放时间不确定。我们将谋求一种可以让大家更好的发表意见的方式。
根据国家法律法规要求,只有实名认证后才可以发表评论。
22:48 的评论:
说实话:你这还是伪随机数。电脑真正的理论随机数是非常难模拟的。
一般要进行物理实验
几乎所有编程语言中都提供了&生成一个随机数&的方法,也就是调用这个方法会生成一个数,我们事先也不知道它生成什么数。比如在.Net中编写下面的代码: Random rand = newRandom(); Console.WriteLine(rand.Next()); 运行后结果如下:
Next()方法用来返回一个随机数。同样的代码你执行和我的结果很可能不一样,而且我多次运行的结果也很可能不一样,这就是随机数。 一、陷阱 看似很简单的东西,使用的时候有陷阱。我编写下面的代码想生成100个随机数: for (inti=0;i100;i++) { Random rand = newRandom(); Console.WriteLine(rand.Next
分享到微信
打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。
请将我们加入您的广告过滤器的白名单,请支持开源站点。谢谢您。war3伪随机_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
war3伪随机
阅读已结束,下载本文需要
想免费下载更多文档?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩4页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢272被浏览34,385分享邀请回答112 条评论分享收藏感谢收起122 条评论分享收藏感谢收起后使用快捷导航没有帐号?
 论坛入口:
  |   |    |   | 
游戏中所存在的“真随机”与“伪随机”
算法逻辑/智能AI&
  写这篇随笔的动机,在于最近看了不少对于游戏中概率事件的提问
  在这些相关讨论里,总是能频繁看到“真随机”和“伪随机”这两个词汇。
  其中最常见的句子则莫过于宝典一般的——“程序里没有真随机”。
  这句话本身当然是没有问题的。
  但是大多数时候,用这句话去回复别人的疑问就有点风马牛不相及了。
  导致这种情况的原因就在于,在不同的范畴,“真随机”和“伪随机”实际上是有着不同的定义。
  对概率提出疑问的游戏玩家问的基本上都是应用层面的随机问题,而回复者提到的却是程序原理上的真伪随机。
  这样一来一回非但不能解释清楚游戏里的随机问题,反而会让看的人越来越迷茫。
  所以,要想讲清楚这件事情,就必须把程序原理上的真伪随机和应用领域的真伪随机都详细说明一番。
  程序原理上的真伪随机
  对计算机有点了解的朋友都知道,在程序里,0就是0,1就是1,程序中是不存在可能为0也可能为1的数据的。
  所以,程序也就不能自己生成“随机”的东西。
  在程序原理上,真随机的定义是指,通过外置的观测设备,观测某个真正随机的事物的状态。
  在需要产生随机数的时候,记录该事物的状态值,再以此值经过一定的算法,得到一个真正的随机数。
  (也有部分人认为宇宙中不存在真正随机的事物,所以宇宙中也没有真随机……这个争论太玄学,咱们就不搀和了。)
  所以,采用真随机对于程序来说,成本极高效率极低,在制作游戏的时候,没有人会蛋疼的买设备去做真随机。
  而相对于真随机的伪随机,就是指在系统内部抓取一个程序员自身无法预料准确值的值,把该值作为种子,放进随机数生成器,由此得到一连串随机数的方法。
  这句话听起来很拗口,实际上过程非常简单。在此我举一个最简单例子来说明:
  随机数生成器的核心部分是一个函数,函数就可以写成f(x)的形式
  这个x,就是一个随机种子
  x的确定标准就是要无法预测,比如说可以选取系统开始运行之后的时间(单位毫秒)
  然后把x放进f()
  根据函数的特效,x的值确定,f(x)的值就唯一且确定
  这个f(x)就是该随机数生成器生成的第一个随机数,我们记作R_1
  (注:R1只是一个胚体,在实际调用的时候,还会用一个不可逆的处理方法使其变成我们需要的随机数——比如50到300之间的随机数,这个过程在此就略过不写了)
  然后如果还需要第二个随机数,就把R_1放进f(),得到第二个随机数R_2 = f(R_1)
  然后如果还需要第三个随机数,就把R_2放进f(),得到第三个随机数R_3 = f(R_2)
  以此类推
  R_1 = f(x)
  R_2 = f(R_1)
  R_3 = f(R_2)
  R_n = f(R_ n-1)
  由此可以看出,一旦某个随机数生成器的种子确定,他之后所产生的每一个随机数就都确定了
  随机数生成器就好比一副空白扑克,放入种子的过程就好比给每一张扑克都写上一个数字
  然后等着程序在需要的时候去一张张抽取调用
  进而可以得到一个推论:如果两个随机数生成器的种子是一样的,那么他们这两副扑克的牌序也就都是一样的了。
1.png (37.86 KB, 下载次数: 74)
14:27 上传
在同一时间创建三个随机数生成器,让他们的种子一致
  然后让他们轮流生成4个100以内的随机整数
2.png (22.77 KB, 下载次数: 75)
14:27 上传
  可以看到,这三个随机数生成器在每一次生成的值都是一样的
  这个推论在游戏中最常见的运用场景就是replay回放
  比如war3的录像回放,一个几十分钟的录像,大小只有几十K
  这个录像文件中存放的实际上只有每一个玩家的有效操作,以及每一个随机数生成器的种子值
  然后根据这些内容,创建一场游戏,模拟重现整场战斗
  录像文件是不会去记录每一个野怪的掉落,剑圣的每一刀是否暴击,牛头人是否能打出粉碎等等信息的,否则容量就会大大超标。这些随机的内容全部都是通过set种子值来重现。
  看到这里可能就会有不少玩家觉得很没劲
  如果每一次随机的结果在游戏开始时就已经确定了,那随机还有什么意义
  这,就是一个很哲学的问题了。
  如果有一副空白扑克,上帝在每一张的上面都已经随便写上了一个数字。
  如果你无法查看也无法修改这些数字,那么对于需要一张张摸牌的你来说,这些数字究竟算随机的还是确定的?
  每个人的看法或许都不一样。
  但是,无论你的看法如何,至少在表现出来的效果上,这副牌就是随机的。
  同理对于程序中的种子随机:
  如果你无法查看也无法修改随机种子,那么程序用伪随机方法所产生的随机数在表现出来的效果上就等同于真随机。
  用伪随机进行大规模的模拟,其统计结果也会与数学计算出来的期望相符。
  如果你暴击率35%,每次攻击时程序产生一个1到10000之间的数,处于1到3500之间时就暴击
  连续攻击一万次,你暴击的次数就会在3500附近。
  至于第一万零一次会不会暴击?仍然是35%的几率暴,65%的几率不暴。
3.png (19.72 KB, 下载次数: 76)
14:27 上传
模拟攻击一万次,暴击时计数+1
4.png (26.3 KB, 下载次数: 75)
14:27 上传
暴击的次数
  应用层面的真伪随机
  在应用层面上,真随机就是指每一次几率判断都是独立的。
  比如说一个游戏角色的暴击率是20%,那么在真随机的机制下,他的每一次攻击都会是20%的几率暴击。
  前一刀暴击了,下一刀是20%暴率,前一刀没爆,下一刀也仍然是20%暴率。
  伪随机就是指同一类的概率事件,彼此之间存在关联性。
  比如说一个游戏角色的暴击率是20%,那么在伪随机的机制下,这个角色每一次攻击的暴击率都是动态变化的。
  前一刀暴击了,后一刀的爆率就会降低;前一刀没爆,后一刀的爆率就会提升。但是,当这个玩家进行足够多次攻击之后,统计上的暴击率还是会等于20%。
  可以说,真随机是一种自然的随机机制,用代码来实现也非常容易,只需要用一个随机数与一个常量进行比较,根据大于小于等于分别触发不同的结果就行了。
  而伪随机则是人为创造出来的一种机制,他需要程序员写下更多的代码,也需要数值设计者做更多的计算。
  那么,既然伪随机费时费力,还反自然,为什么在应用领域还要引入各种伪随机的算法呢?
  其目的就在于——让用户得到更好的体验。
  我以抽奖为例,比如说某个游戏内置抽奖系统:
  抽奖每次消耗1块钱,有1%的几率得到一个价值90块的东西。
  有相当一部分参与者就会觉得,我先抽一下碰下运气,万一抽不到,我连抽100次,总归会拿到的吧,小亏一点点而已。
  但是实际上,连续抽奖100次而不中的概率高达36.6%——超过1/3的比例。
  甚至于即使连续抽300次,也仍然有4.9%的几率不中。
  也就是说,如果这个游戏有10万玩家,就有4900个人连续抽奖300次都中不了。
  而这部分玩家通常都不会心甘情愿的接受自己运气不好这个事实——他们之中一部分可能会心理受挫,删除游戏成为流失玩家;
  还有一部分则很可能会在网络上联合起来,产生一定的舆论压力。
  无论那一种情况,都是游戏设计者所不愿意见到的。而设计者为了避免这样的问题,就不得不考虑引入伪随机。
  从用户体验上来说,伪随机就是介于“真随机”和“不随机”之间的一种感觉。
  对于1%几率的抽奖,真随机就是上面我描述的情况。
  不随机就是固定的每隔99次之后中奖1次。
  伪随机就是中奖事件会分布得比真随机更加均匀,但还是具有一定的随机性。
  所以,伪随机并不是一个负面词汇。它存在的意义是为了让几率事件分布得更加均匀,避免让用户遇到极端走运或极端倒霉的情况。
  在讨论游戏概率的时候,让伪随机来为某些负面情绪背锅显然是不对的。
  最后,我大致说明一下最为常见的几种伪随机算法
  这是伪随机在游戏中最常见的用法,因此直接就被玩家用Pseudo Random Distribution的缩写PRD来指代了
  其中最为典型的案例就是WAR3,以及用WAR3编辑器制作的DOTA。
  在WAR3中,一个暴击率20%的英雄,并不是每一刀都20%暴击率的。
  而是以5.57%作为初始暴率,如果第一刀不暴,则第二刀的暴率增加到初始值的2倍:11.14%;
  如果还是不暴,就继续增加到初始值的3倍:16.71%,以此类推。
  而如果在这个过程中任何一次攻击打出了暴击,就会把暴击率重置到5.57%。
5.jpg (13.72 KB, 下载次数: 78)
14:27 上传
6.jpg (50.12 KB, 下载次数: 76)
14:27 上传
  通过验算可以看到,暴雪以这种方式实现的暴击,最终表现出来的暴率仍然是20%。
  不过通常来说,PRD并不会在游戏中做实时的概率推算——做这样的逆运算会消耗太多的计算资源。
  据我推测,暴雪应该也是建立了一张lookup对照表,在游戏中根据理论暴率查表然后获得动态暴率的基础值。
  洗牌算法最典型的应用莫过于音乐播放器的随机播放。
  在最早期的时候,播放器的随机播放就是采用的真随机。
  但是用户很快就发现,经常会遇到接连播放同一首歌,或者连续多次在几首歌之间来回切换,而另外某些歌曲几百次也放不到。
  为了解决这个问题,播放器就把真随机改为了洗牌算法。
  所谓的洗牌算法就是:如果你的歌单有20首歌,就建立一个1到20的数组,再把这20个数字像洗牌一样洗成乱序。
  在洗完之后,如果第一个数字是n,第一次就播放歌单里的第n首歌。以此类推。
7.png (33.25 KB, 下载次数: 74)
14:27 上传
8.png (37.64 KB, 下载次数: 76)
14:27 上传
  说实话,其实我不确定这个能不能算作是一种伪随机。
  但是这种做法在现在的游戏界太过普遍,不得不拿出来说明一下。
  所谓的组合随机,典型的应用就是在抽奖的时候进行两次判断:
  一次不随机:根据预设好的确定数组,给予玩家对应的chest。
  这一次主要是用于确定奖品品质。
  一次真随机:从选中的chest中随机抽取一件物品给玩家。
  这一次就是从对应品质的奖品堆中随机获取一件物品。
  最典型的例子就是《我叫MT》的手游。
  在这个游戏里,你第几次抽奖能中紫卡是完全确定的,但是你具体抽到哪一张紫卡则是随机的。
  相关阅读:
关注我们官方微信公众号
下载我们官方APP-游戏行
关注手游动态微信公众号
暴雪那个随机还是第一次见到,那个属于策划考虑的内容了吧
hdd9998 发表于
暴雪那个随机还是第一次见到,那个属于策划考虑的内容了吧
是的,策划考虑表现,程序实现功能
1月29日—2月4日共有68款游戏开测|GameRes专访《斗兽战棋》:三端互通PVP快速战斗,i简单说说文案&剧情策划的能力要求315亿美元的市场盘子,钱都被谁赚了,还有《女神异闻录5》宠物养成系统分析20年前索尼与两款软件的官司,最终改变了模
微信扫一扫关注我们→

我要回帖

更多关于 游戏中的伪随机 的文章

 

随机推荐