什么是好的游戏随机数生成器?
Posted
技术标签:
【中文标题】什么是好的游戏随机数生成器?【英文标题】:What is a good random number generator for a game? 【发布时间】:2010-11-06 00:17:29 【问题描述】:什么是用于 C++ 游戏的好的随机数生成器?
我的考虑是:
-
需要大量随机数,所以速度不错。
玩家总是会抱怨随机数,但我希望能够向他们指出一个参考资料,说明我确实做了我的工作。
由于这是一个我没有太多时间的商业项目,如果算法 a) 相对容易实现或 b) 具有良好的非 GPL 实现,那就太好了。
我已经在很多地方使用了
rand()
,因此任何其他生成器最好能够证明它需要进行的所有更改。
我对这个主题了解不多,所以我能想到的唯一选择是Mersenne Twister;它满足所有这些要求吗?还有什么更好的吗?
编辑: Mersenne Twister 似乎是共识选择。但是第 4 点呢?真的比rand()
好很多吗?
编辑 2:让我对第 2 点更清楚一点:玩家无法通过知道随机数来作弊。时期。我希望它足够随机,以至于人们(至少是那些理解随机性的人)不能抱怨它,但我并不担心预测。 这就是为什么我将速度作为首要考虑因素。
编辑 3:我现在倾向于 Marsaglia RNG,但我仍然希望获得更多信息。因此,我设置了赏金。
编辑 4: 请注意:我打算在今天 UTC 午夜之前接受一个答案(以避免弄乱某人的代表上限)。因此,如果您正在考虑回答,请不要等到最后一分钟! 另外,我喜欢 Marsaglia 的 XORshift 生成器的外观。有人对他们有意见吗?
【问题讨论】:
这在一定程度上取决于您希望随机数具有什么配置文件;你想让它们均匀分布吗?高斯?离散的还是连续的? Stobor,你可以很好地从均匀随机数生成其他分布。 是的,我知道您可以将任何类型转换为任何其他类型,但如果您知道您主要在离散二元决策(真/假、左/右等)中使用随机变量,那么您可以通过使用二进制生成器而不是使用整数 1 并每次计算 (x > RAND_MAX/2) 来提高速度。 我希望有一个替代 rand() 的替代品(尤其是所有那些“rand() % 10”行),所以我更喜欢均匀分布和离散的。 我以前用过这个类,超级好用。 A Mersenne Twister Class是由梅森Twister算法的发明者的原始C代码创建的一个类。 【参考方案1】:Sometimes 游戏开发者不想要真正的随机性,shuffle bag 更合适。
如果您确实想要随机性,Mersenne twister 可以满足您的要求。它速度快、统计随机、周期长并且有很多实现。
编辑:rand()
通常实现为linear congruential generator。如果您对它是否足以满足您的目的做出明智的选择,这可能是最好的选择。
【讨论】:
正确。没有这个,玩家会不断地抱怨事情太随意了。 我已经阅读了这个问题(事实上,它引发了导致这个问题的讨论),但我认为这不是解决方案。这里的“命中”是从玩家那里抽象出来的,所以玩家可能根本不会注意到。 “空体 for 循环是撒旦的后代,但我们暂时忽略它。” +1 笑声和一个好的答案。 (具有讽刺意味的是,我曾经在 Perl 中经常使用它们......)【参考方案2】:Mersenne Twister 是行业中的典型代表,尤其是因为它非常适合 SIMD 并且可以超快地制造。 Knuth 也很受欢迎(谢谢,David)。
在大多数游戏应用程序中,速度确实是关键因素,因为玩家抱怨低帧率的次数要多于抱怨在生成 3 之前有 7 时存在轻微偏差的事实、2 和 9 的顺序。
当然,赌博是个例外,但您的相关许可机构会专门列出您可以使用的算法。
【讨论】:
他们可能会抱怨很多(而且相当大声)3 似乎从来没有出现在他们面前。取决于使用情况。真正的随机对伤害之类的事情有好处,但对于物品掉落之类的事情,使用@David Johnstone 发布的洗牌算法是有意义的。 确实如此;您需要不同的算法,具体取决于“公平”还是真正的随机性更重要。 他们并没有真正看到随机数,但他们确实注意到有时他们会在没想到时输掉。 (我相信它也会以另一种方式发生,但他们似乎几乎没有注意到它。)也许对于战斗部分来说,正态分布会更好。 此外,大多数语言都有一个很好的现成的机器翻译实现,并且可能有一个许可证不妨碍您将其整合到商业软件中。【参考方案3】:Mersenne Twister 非常好,而且速度也很快。我在游戏中使用过,实现或使用一点都不难。
WELL random algorithm 被设计为对 Mersenne Twister 的改进。 Game Gems 7 有更多信息。就在上面,如果你能借到或者有的话。
在我链接到的那个 WELL 页面上,数字是算法的周期。也就是说,您可以在需要重新播种之前获得 2^N - 1 个数字,其中 N 是:512、1024、19937 或 44497。Mersenne Twister 的周期为 N = 19937,或 2^19937 - 1。您将看到这是一个非常大的数字 :)
我唯一能指出的另一件事是boost 有一个random library,你应该会觉得它很有用。
响应您的编辑,是的,Twister 或 WELL 比 rand() 好得多。此外,旧的模数技巧会损害数字的分布。使用 boost 的更多理由:)
【讨论】:
提升随机数是否值得?我们现在根本不用它。 Boost 非常值得。干净的 C++ 使用(只需查看页面),以及 boost 提供的所有其他东西,如智能指针、boost::functions、lambda、线程等......我基本上将 boost 视为 STL。一旦设置好了,这并不难,您甚至可以忘记它的存在,并在您确实需要它时感到高兴。 请记住,boost 不是非此即彼。您只需包含您需要的单个头文件,这就是您所得到的。大多数 boost 库甚至是只有头文件的库,所以不需要链接任何东西,一切都在头文件中。【参考方案4】:在实时游戏中,玩家无法确定“好”生成器和“坏”生成器之间的区别。在回合制游戏中,你是对的——少数***者会抱怨。他们甚至会详细地告诉你你是如何用一个糟糕的随机数生成器毁掉他们的生活的。
如果您需要一堆真正的随机数(并且您是一个在线游戏),您可以通过Random.org 获得一些。将它们用于回合制游戏,或作为实时游戏的种子。
【讨论】:
它是半实时的。例如,在战斗中,“骰子”每 5 个游戏日重新掷一次。不过,战斗通常会持续一个月或更长时间。 哦,它不是基于网络的,所以 random.org 不是一个选项。 在实时游戏中体验真正随机的挫败感是完全可能的。如果每次你按下“攻击”按钮,你的剑都错过了巨鼠,你会很生气,就好像每次错过都必须“转身”一样。 @ojrac。没错,你可以体验一下。我的意思是用户无法识别出现在他身上的随机数。但是你说得对,玩家通常不想要真正的随机数。他想要有趣的数字,这些数字可能具有完全不同的分布。但是程序员可以通过将“真实”随机数后处理成不同的序列或分布来解决这个问题。 即使您的游戏不是基于网络的,random.org 仍然可能是一个选择。如果您需要任何类型的互联网连接,您可以按需连接到 random.org,或者您可以请求指定范围内的一长串随机数,存储它们并根据需要使用。如果你用完了预取的随机数,你可以回退到旧的 rand()。最后一点:random.org 使用来自声音输入的静态噪声,因此您也可以尝试将其用作随机种子。【参考方案5】:我是Isaac 的粉丝,与 mersense twister 不同,它在密码学上是安全的(你*不能通过观察滚动来破解周期)
IBAA (rc4?) 也是used by blizzard 以防止人们预测用于战利品卷的随机数。我想当你在战斗中玩暗黑破坏神 II 时会做类似的事情.net 服务器。
*不能在任何合理的时间范围内(几个世纪?)
【讨论】:
【参考方案6】:购买便宜的网络摄像头,电离烟雾探测器。拆开它们,烟雾探测器包含很少的放射性物质 - 伽马波的来源 - 这将导致您的网络摄像头发射光子。那是你真正随机性的来源:)
【讨论】:
再说一次,我并不真正关心真正的随机性。我只是希望它非常接近且非常快。 这个想法在随机时间生成事件,而不是按需生成随机位。此外,烟雾探测器网络摄像头硬件将被客户群视为复制保护加密狗。 我买这款游戏只是因为它有一个带有放射性警告标签的“防复制”加密狗!【参考方案7】:George Marsaglia 开发了一些目前可用的最好和最快的 RNG Multiply-with-carry 以均匀分布着称。
=== 2018-09-12 更新 ===
对于我自己的工作,我现在使用 Xoshiro256**,这是 Marsaglia 的 XorShift 的一种进化/更新。
=== 2021-02-23 更新 ===
在 .NET 6(目前为预览版)中,System.Random 的实现已更改为使用 xoshiro256**,但仅适用于无参数构造函数。采用种子的构造函数使用旧的 PRNG 以保持向后兼容性。欲了解更多信息,请参阅Improve Random (performance, APIs, ...)
【讨论】:
看到 Marsaglia 在关于 Mersenne Twister 的 Wikipedia 文章中的批评实际上让我提出了这个问题。不过,真的有人用过吗? 不确定这些的广泛使用,但我在我的宠物项目中使用了 Marsaglia 的 xor-shift RNG,并将 RNG 发布为:codeproject.com/KB/cs/fastrandom.aspx 随后此代码在 RNG 代码库中使用:@987654326 @ 我还被要求修改许可,以便在 Novell 在 Mono 上运行的某些项目中使用它。 我接受了这一点,因为在我的简单测试中,Marsaglia 提出的 XOR-shift 生成器在相当程度上是竞争对手中最快的。我也会考虑在某个时候切换到 WELL512;我已将对rand
的所有调用抽象为一个封装类,因此更改实现非常容易。
谢谢,我选择它也是为了速度,同时它也通过了 Marsaglia 的 RNG 测试套件。另一个好处是种子可以快速重置,而其他 RNG 需要在设置种子时运行初始化例程。如果您需要重新生成完全相同的数字序列(以相同的种子开始)并且需要经常切换种子,这是一个有用的功能。【参考方案8】:
基于 Ian C. Bullard 的随机数生成器:
// utils.hpp
namespace utils
void srand(unsigned int seed);
void srand();
unsigned int rand();
// utils.cpp
#include "utils.hpp"
#include <time.h>
namespace
static unsigned int s_rand_high = 1;
static unsigned int s_rand_low = 1 ^ 0x49616E42;
void utils::srand(unsigned int seed)
s_rand_high = seed;
s_rand_low = seed ^ 0x49616E42;
void utils::srand()
utils::srand(static_cast<unsigned int>(time(0)));
unsigned int utils::rand()
static const int shift = sizeof(int) / 2;
s_rand_high = (s_rand_high >> shift) + (s_rand_high << shift);
s_rand_high += s_rand_low;
s_rand_low += s_rand_high;
return s_rand_high;
为什么?
非常非常快 比大多数标准rand()
实现更高的熵
简单易懂
【讨论】:
Ian 有一篇有趣的文章比较了几个生成器。 ianbullard.squarespace.com/journal/2009/4/28/… 是不是假设给它一个稍微不同的种子,你只会得到稍微不同的结果?【参考方案9】:我希望它足够随机,以至于人们(至少是那些理解随机性的人) 不能抱怨,但我不担心预测。
啊哈!
这是你真正的要求!
没有人会因为在此应用程序中使用 Mersenne Twister 而责怪您。
【讨论】:
【参考方案10】:我也会投票给 Mersenne Twister。实现广泛可用,它的周期非常大,为 2^19937 -1,速度相当快,并且通过了大多数随机性测试,包括 Marsaglia 开发的 Diehard 测试。 rand() 和 Co. 作为 LCG,会产生较低质量的偏差,并且可以很容易地推断出它们的连续值。
然而,需要注意的一点是将 MT 正确地播种到通过随机性测试的状态。通常使用 drand48() 之类的 LCG 来实现此目的。
我会说 MT 满足您设置的所有要求(可证明),而使用 MWCG imo 之类的东西就有点过头了。
【讨论】:
【参考方案11】:根据目标操作系统,您可能可以使用 /dev/random。它实际上不需要任何实现,并且在 Linux(可能还有其他一些操作系统)上它是真正随机的。读取会阻塞,直到有足够的熵可用,因此您可能希望读取文件并将其存储在缓冲区或使用另一个线程的东西中。如果不能使用阻塞读取调用,可以使用 /dev/urandom。它生成的随机数据几乎和 /dev/random 一样好,但它重用了一些随机数据来立即提供输出。它不那么安全,但它可以正常工作,具体取决于您打算用它做什么。
【讨论】:
目标操作系统是 Windows,稍后会有 Mac 端口。【参考方案12】:现在有比 Mersenne Twister 更好的选择。这是一款名为 WELL512 的 RNG,由 Mersenne 的设计师设计,历经 10 年的发展,是一款全方位更好的游戏选择。该代码由 Chris Lomont 博士置于公共领域。他声称这种实现比 Mersenne 快 40%,当状态包含许多 0 位时,不会受到扩散和捕获不良的影响,并且显然是更简单的代码。它的周期为 2^512;一台 PC 需要超过 10^100 年的时间来循环遍历各个州,因此它足够大。
这是一篇概述 PRNG 的论文,我在其中找到了 WELL512 实施。 http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
所以 - 更快、更简单,由同一位设计师在 10 年后创造,并且比 Mersenne 产生更好的数字。你怎么会弄错? :)
更新 (11-18-14):修复错误(将 0xDA442D20UL 更改为 0xDA442D24UL,如上面链接的论文中所述)。
/* initialize state to random bits */
static unsigned long state[16];
/* init should also reset this to 0 */
static unsigned int index = 0;
/* return 32 bit random number */
unsigned long WELLRNG512(void)
unsigned long a, b, c, d;
a = state[index];
c = state[(index+13)&15];
b = a^c^(a<<16)^(c<<15);
c = state[(index+9)&15];
c ^= (c>>11);
a = state[index] = b^c;
d = a^((a<<5)&0xDA442D24UL);
index = (index + 15)&15;
a = state[index];
state[index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
return state[index];
【讨论】:
我浪费了一个晚上来理解为什么我的代码不起作用:在 64 位机器上,此代码产生 64 位数字!使用sizeof(unsigned long) * 8
@wiso,用sizeof(unsigned long) * 8
代替哪个部分?
常数应该是(根据原论文):0xDA442D24UL
@MitchWheat 的评论是正确的:0xDA442D20UL 应该是 0xDA442D24UL。 (我在一年前试图编辑它,但由于某种原因被拒绝了。)这个有问题的版本在上面链接的 Lomont_PRNG_2008.pdf 论文中给出,后来更正了(见论文末尾的历史部分)。但是这里的代码仍然显示不正确的版本。为了比较,WELL 创建者的原始实现在这里:iro.umontreal.ca/~panneton/well/WELL512a.c(请注意,这种变化很小,但这些东西会对随机生成器算法产生巨大影响。)
@Shahbaz(以及其他可能阅读此内容的人):我认为他的意思是此代码的用户应该在他们的机器/编译器上检查 sizeof(unsigned long)*8 的结果......如果结果为 64(即 unsigned long 为 64 位宽),那么此代码将产生意外结果,因为它假定 unsigned long 为 32 位宽。在这里比较原始实现,它使用无符号整数而不是无符号长整数:iro.umontreal.ca/~panneton/well/WELL512a.c【参考方案13】:
您应该考虑的另一个标准是线程安全。 (并且您应该在当今的多核环境中使用线程。)仅从多个线程调用 rand 可能会扰乱它的确定性行为(如果您的游戏依赖于此)。至少我建议你切换到 rand_r。
【讨论】:
【参考方案14】:你知道吗?如果您认为这个答案完全糟糕,请原谅我……但我一直(因为上帝只知道是什么原因……)使用DateTime.Now.Milliseconds
作为获取随机数的一种方式。我知道这不是完全随机的,但它似乎是……
我只是为了得到一个随机数而费心输入这么多! :P
【讨论】:
速度对 OP 来说很重要,所以它可能会被非常频繁地调用,因此可以很容易地多次获得相同的毫秒值。无论如何,重复采样时间,尤其是在游戏中典型的固定速度循环中,会表现得非常不随机。【参考方案15】:GameRand 实现此处发布的算法 http://www.flipcode.com/archives/07-15-2002.shtml
这是我最初在 80 年代后期开发的。 它在数值质量方面轻松击败了 rand(),并且作为可能的最快随机算法的附带好处。
【讨论】:
以上是关于什么是好的游戏随机数生成器?的主要内容,如果未能解决你的问题,请参考以下文章