需要可预测的随机发生器
Posted
技术标签:
【中文标题】需要可预测的随机发生器【英文标题】:Need for predictable random generator 【发布时间】:2010-10-28 22:55:04 【问题描述】:我是一名网络游戏开发人员,我遇到了随机数问题。假设一个玩家有 20% 的机会用他的剑造成致命一击。这意味着,五分之一的命中应该是关键的。问题是我在现实生活中得到了非常糟糕的结果——有时玩家在 5 次命中中获得 3 个暴击,有时在 15 次命中中没有。战斗时间很短(3-10 次命中),因此获得良好的随机分布很重要。
目前我使用 php mt_rand()
,但我们只是将代码转移到 C++,所以我想在我们游戏的新引擎中解决这个问题。
我不知道解决方案是否是某种统一的随机生成器,或者可能要记住以前的随机状态以强制正确分布。
【问题讨论】:
假设真实随机数,大约有 0.5% 的几率恰好发生 3 个重击和 2 个非重击,以及 3.5% 的几率连续发生 15 个非重击。 +1 以上。随机数的一个特点是会出现异常值。 @Nixus:不,大约有 5% 的几率会造成 3 次暴击和 2 次非暴击,你忘记乘以 (5! / (3!*2!)) = 10。在 95% 的置信水平下,5 次打击中发生 3 次重击在统计上并非不可能。 起初我认为这是一个愚蠢的问题......再一次,我被 SO 弄丢了。 dilbert.com/strips/comic/2001-10-25 【参考方案1】:首先,定义“适当的”分布。随机数是随机的——你看到的结果与(伪)随机性完全一致。
对此进行扩展,我假设您想要的是某种“公平”的感觉,因此用户不能在没有成功的情况下转过 100 圈。如果是这样,我会跟踪自上次成功以来的失败次数,并对生成的结果进行加权。假设您希望五分之一的卷“成功”。所以你随机生成一个从 1 到 5 的数字,如果是 5,那就太好了。
如果不是,则记录失败,下一次,生成一个从 1 到 5 的数字,但添加例如 floor(numFailures / 2)。所以这一次,他们有五分之一的机会。如果他们失败了,下一次的获胜间隔是4 和 5; 5 分之二的成功机会。有了这些选择,8次失败后,一定会成功。
【讨论】:
关于那个注释......随机数的范围会影响分布......例如选择一个随机 r = new Random(); r.Next(1,5) 与 r.Next(1, 1000000) % 200000 +1 用于查看请求背后的问题,而不是告诉 OP 他误解了随机性。 请注意,如果您这样做,那么他们的总体成功比例将大于五分之一。解决此问题的方法是(例如)从范围 1 中随机选择 20 个不同的数字。 .100,并预先确定这些将是他们的关键。不过,这需要更多的簿记。 “将大于五分之一” - 我的意思是,从长远来看是这样。 你可以把起始概率缩小一点,这样整体比例就降低到1/5。我还没有计算出你必须减少多少,但一切都是连续的,所以必须有一个正确的答案。【参考方案2】:如何权衡价值?
例如,如果您有 20% 的几率发生重击,则生成一个介于 1 和 5 之间的数字,其中一个数字表示重击,或者生成一个介于 1 和 100 之间的数字,其中 20 个数字表示重击。
但是,只要您使用的是随机数或伪随机数,就无法避免您当前看到的结果。这是随机性的本质。
【讨论】:
那为什么会有什么不同呢?获得这两组数字的临界值的机会完全相同。 没错。对于他的 20% 示例,我只是为他提供了两个选项。虽然如果你处理整数百分比,100 可能会更好,因为你只需要模拟一个“死”,如果你想这样想的话。 你提供的选项正是他已经在做的。 不是为了他想要的。他想要一个非随机数生成器,即使他认为这叫做随机数生成器。【参考方案3】:不幸的是,您所要求的实际上是一个非随机数生成器 - 因为您希望在确定下一个数字时考虑以前的结果。恐怕这不是随机数生成器的工作方式。
如果您希望每 5 次命中中有 1 次是关键,那么只需在 1 到 5 之间选择一个数字,然后说该命中将是关键。
【讨论】:
他想要游戏友好的随机,如果你在某些情况下使用严格的随机生成数字,你最终会得到“韩国随机”的结果。这些是随机结果,让玩家经常生气和沮丧(询问任何血统 2 玩家);) 因此,如果第一击是暴击,那么接下来的四击就不会了。听起来这确实是 OP 想要的,但是当你这样说时,它听起来很迟钝。你得到我的紫外线。 -1 您似乎将“随机”与“无记忆”混淆了——en.wikipedia.org/wiki/Memorylessness【参考方案4】:肯定任何随机数生成都有机会产生这样的运行吗?您不会在 3-10 轮中获得足够大的样本集来查看适当的百分比。
也许你想要的是一个怜悯阈值......记住最后 10 次掷骰,如果他们没有重击,请给他们一个免费赠品。消除随机性的吊索和箭头。
【讨论】:
【参考方案5】:对于 C++ 随机数生成器,请使用 rand 或 boost::random
每当玩家被击中时,你只需检查 [0; 中是否有随机数; 1] 小于 0.2。 这意味着某人在 15 内无法获得暴击,但这是不可能的。
这将根据binomial distribution (p = 0.2) 的定律为您提供自然随机数
【讨论】:
弱数学 - 15 次尝试没有暴击仍然有 3.5% 的机会。不太可能但“不可能”不是您要查找的词。 等等,一个建议 boost::something 被否决的答案?我很震惊! 仅供参考:这并不能解决他的问题,只是用不同的语言使它成为同样的问题。【参考方案6】:在如此少量的测试中,您应该会得到这样的结果:
真正的随机性只能在一个巨大的集合大小上进行预测,因此很有可能第一次掷硬币并连续 3 次正面朝上,但是超过几百万次翻转你最终会得到大约 50 到 50 次。
【讨论】:
虽然在几百万次翻转后仍有机会看到硬币的一面。尽管如果发生这种情况,您可能离无限的可能性驱动器太近了:P 哈哈,是的,但机会是如此之低,以至于数学定律说你应该看到一个均匀(ish)的分布。【参考方案7】:您最好的解决方案可能是使用多种不同的非随机方案进行游戏测试,然后选择让玩家最开心的方案。
您也可以在给定的遭遇中尝试对相同号码的退避策略,例如,如果玩家在第一轮投掷 1
接受它。要获得另一个1
,他们需要连续滚动 2 个1
s。要获得第三个1
,他们需要连续 3 个,无限。
【讨论】:
【参考方案8】:用这样的东西替换 mt_rand() 怎么样?
(RFC 1149.5 将 4 指定为标准 IEEE 审查的随机数。)
来自XKCD。
【讨论】:
不错,但这会解决 OP 的随机数分布问题吗? ;-) 不是真的;他要求一个非随机的RNG,他可以使用这个函数;但他真正想要的是当前的最佳答案 (***.com/questions/910215/910224#910224) 这比 OP 想要的更随机 -1 因为 RFC1149 没有第 5 节(或者,没有 1149.5); +1 公平随机性。【参考方案9】:您误解了随机的含义。
其中哪个更随机?
虽然第二个图看起来分布更均匀,但实际上第一个图随机越多。人类思维经常看到随机模式,因此我们将第一个图中的团块视为模式,但它们不是 - 它们只是随机选择的样本的一部分。
【讨论】:
是的 - 这不是用雨滴解释的吗?^^ 从技术上讲,您无法衡量随机性。尽管我的猜测都是算法生成的,但这两种分布在我看来都相当随意。您可以在第一个图上执行许多测试,并确定它可能来自根据均匀分布放置点的过程,但您无法得出结论它更随机。作为反例,您可以使用线性同余发生器制作第一个图,然后使用齐纳二极管的放大噪声制作第二个图。尝试使用不相关而不是随机这个词。 您可以测量所述分布是随机的概率。 @Dietich: Of course you can measure randomness 公平地说,虽然 OP 可能没有使用正确的术语,但他知道随机数生成器给他的东西更像第一张图,而他想要更像第二张图的东西,因为它 感觉对用户来说更“公平”。【参考方案10】:不清楚你想要什么。可以创建一个函数,使您调用它的前 5 次,它以随机顺序返回数字 1-5。
但这并不是随机的。玩家将知道他将在接下来的 5 次攻击中恰好得到一个 5。不过,这可能是您想要的,在这种情况下,您只需自己编写代码即可。 (创建一个包含数字的数组,然后打乱它们)
或者,您可以继续使用当前的方法,并假设您当前的结果是由于随机生成器错误造成的。请注意,您当前的数字可能没有任何问题。随机值是随机的。有时您会连续获得 2、3 或 8 个相同值。因为它们是随机的。一个好的随机生成器只是保证平均而言,所有的数字都会以同样的频率返回。
当然,如果您一直在使用糟糕的随机生成器,那可能会导致结果出现偏差,如果是这样,只需切换到更好的随机生成器即可解决问题。 (查看 Boost.Random 库以获得更好的生成器)
或者,您可以记住随机函数返回的最后 N 个值,并通过这些值对结果进行加权。 (一个简单的例子是,“对于新结果的每次出现,我们有 50% 的机会应该丢弃该值并获得一个新结果”
如果我不得不猜测,我会说坚持“实际”随机性是您最好的选择。确保你使用了一个好的随机生成器,然后按照你现在的方式继续工作。
【讨论】:
其实他用的函数和boost库里最好的RNG是一样的 MT 不是“最好的”,我上次检查过。它很好、简单、快速,但它不能产生最好的分布。无论如何,抓取一百万个随机数并检查分布。看看你的随机函数是否真的给你一个均匀的分布。如果没有,请找到更好的发电机。如果是这样,要么忍气吞声,接受偶尔的一连串暴击,要么作弊,让结果更少随机且更可预测。【参考方案11】:鉴于您要求的行为,我认为您随机化了错误的变量。
与其随机化此一击是否会是致命的,而是尝试随机化轮数直到下一次致命一击发生。例如,每次玩家获得一个关键点时,只需在 2 和 9 之间选择一个数字,然后在经过这么多轮之后给他们下一个关键点。您还可以使用骰子方法来更接近正态分布 - 例如,您将在 2D4 回合中获得下一个临界值。
我相信这种技术也被用于在主世界随机遭遇的 RPG 中——你随机化一个计步器,然后在这么多步之后,你又被击中了。这感觉更公平,因为你几乎不会被连续两次遭遇击中 - 如果这种情况发生一次,玩家就会变得烦躁。
【讨论】:
我认为这是一个很好的解决方案,但是 80% 的机会呢? 我有时也喜欢使用多维随机生成器。击中机会+力量机会+暴击机会。就像在 D&D 中掷几个不同的骰子一样 我喜欢这个想法,而且你对计步器的事情是完全正确的,例如,它在最终幻想中已经使用了很长时间了,例如 这样做的一个问题是,它仅在命中之间的关键概率大致恒定时才有效。假设在战斗中,玩家施放了一个法术,使他们受到重击的可能性加倍。那怎么调整圈数呢? +1 非常好,也很容易处理小于 20% 的回合命中概率。【参考方案12】:mt_rand() 基于Mersenne Twister 实现,这意味着它会产生您可以获得的最佳随机分布之一。
显然,您想要的根本不是随机性,因此您应该从明确指定您想要的开始。您可能会意识到您有相互矛盾的期望——结果应该是真正随机的并且不可预测,但同时它们不应该表现出与所述概率的局部变化——但随后它就变得可预测了。如果您连续设置最多 10 个非暴击,那么您只是告诉玩家“如果您连续有 9 个非暴击,那么下一个将是 100% 确定的关键”——您可能会这样完全不用担心随机性。
【讨论】:
【参考方案13】:您可以创建一个包含从 1 到 5 的数字的列表,并让它们按随机性排序。然后只需浏览您创建的列表。您可以保证每个号码至少跑一次...当您完成前 5 个号码后,只需再创建 5 个号码...
【讨论】:
【参考方案14】:static int crit = 0;
public bool isCritical()
crit = crit++ % 5;
return (crit==0);
如果您仍然想要一些随机性,请在每次执行暴击时使用另一个静态变量更改模数。将其从 3 更改为 7 的概率相等,应将暴击之间的平均时间保持在 5 分之一,但它们之间的命中次数不得少于 3 次或超过 7 次。
【讨论】:
OP 已经在做这种事情了。他误解了“随机”,实际上想要一个考虑过去滚动的非随机生成器。 这根本不是随机的,它只是每 5 次命中是一个关键,他仍然想要一些随机性,他只是希望它对玩家“公平”(即不希望玩家对他们在超过 50 次攻击中没有受到攻击或类似情况感到恼火,这在使用标准 PRNG 时完全有可能) 不确定您的目标是什么语言,但在 C# 中,我相信 C++ 您的方法始终返回 true,因为 crit 始终等于 0。您需要将其更改为 ++crit。【参考方案15】:希望本文对您有所帮助: http://web.archive.org/web/20090103063439/http://www.gamedev.net:80/reference/design/features/randomness/
这种生成“随机数”的方法在 rpg/mmorpg 游戏中很常见。
它解决的问题是这样的(摘录):
刀片蜘蛛在你的喉咙里。它击中了,你错过了。它再次击中,你又错过了。一次又一次,直到没有任何东西可以击中。你死了,还有一条重达 2 吨的蜘蛛在你的尸体上幸灾乐祸。不可能的?不,不可能?是的。但是,如果有足够多的球员和足够的时间,那么不可能的事情就变得几乎可以肯定了。不是刀刃蜘蛛硬,只是运气不好。多么令人沮丧。足以让玩家想退出。
【讨论】:
我听说过一个变体——“百万分之一的事件在世界人口中发生了 6,000 次”。【参考方案16】:这意味着,五分之一的命中应该是关键的。问题是我在现实生活中得到了非常糟糕的结果 - 有时玩家在 5 次点击中获得 3 次暴击,有时在 15 次点击中没有。
您需要的是shuffle bag。它解决了真随机对于游戏来说太随机的问题。
算法大概是这样的:你把 1 个重击和 4 个非重击放在一个袋子里。然后你将它们在袋子里的顺序随机化,一次挑出一个。当袋子是空的时,你再次用相同的值填充它并随机化它。这样一来,您将平均每 5 次命中获得 1 次重击,并且连续最多 2 次重击和 8 次非重击。增加包中物品的数量以获得更多随机性。
这是我前段时间写的an implementation(Java 语言)和its test cases 的示例。
【讨论】:
+ 1 表示没有批评的好主意。缩放袋子以获得更高程度的随机性,并处理玩家之间关键机会的差异(如果变量 ofc) 你可以有一个大小为 10 的袋子。放入 1 次命中,外加 30% 的机会一秒。如果关键机会发生变化,您可以扔掉袋子并开始一个新的。请注意,任何此类计划都存在风险,即如果您(或您的对手)知道您的重击概率和袋子的大小,您有时可以确定您不会在一定数量的投掷中获得另一个重击。这可能会影响战术。 是的……在这样的事情中,您运行类似于算牌的筹码。我是冒着苦工的风险还是大杀特杀……一小部分固定的潜在结果可以降低损失的风险并增加被“玩弄”的机会 我喜欢洗牌袋的想法,但我不认为这符合游戏“精神”,因为你 20% 的暴击概率(这意味着你可以在 10 次命中中一无所获)不是概率了。每 5 次命中恰好变为 1 次命中。此外,如果重击概率发生变化,重新滚动袋子会导致游戏出现故障。如果我的重击已经完成,我会对自己施咒,以便更早地获得下一个评论家:p @Jonathan:将袋子做成你给它的大尺寸实际上会破坏整个袋子的想法:确保在可接受的投掷次数内发生某些事情(达到的关键)。使袋子变大 50000 与使用随机数生成器差不多。【参考方案17】:预先计算每个玩家的随机暴击。
// OBJECT
//...
// OnAttack()
//...
c_h = c_h -1;
if ( c_h == 0 )
// Yes, critical hit!
c_h = random(5) + 1 // for the next time
// ...
【讨论】:
【参考方案18】:如何使关键的机会取决于最后 N 次攻击。一种简单的方案是某种马尔可夫链:http://en.wikipedia.org/wiki/Markov_chain,但无论如何代码都很简单。
IF turns_since_last_critical < M THEN
critial = false
turns_since_last_critical++;
ELSE
critial = IsCritical(chance);
IF Critial THEN
turns_since_last_critica = 0;
ELSE
turns_since_last_critica++;
END IF;
END IF;
当然,您必须进行数学运算,因为一旦您知道自上一次以来已经有足够的转数,出现批评的机会就低于出现批评的机会
【讨论】:
您只需考虑最后一次攻击即可获得大部分效果。令 P 为观察到的命中率,R 为未命中后的命中率,R/2 为命中后的命中率。根据定义,在任何时候你都有机会达到 P = P*R+(1-P)*(R/2)。这意味着 P = R / (2-R)【参考方案19】:您想要的不是随机数,而是对人类来说似乎是随机的数字。其他人已经建议了可以帮助您的个别算法,例如 Shuffle Bad。
有关此域的详细而广泛的分析,请参阅AI Game Programming Wisdom 2。整本书值得任何游戏开发者阅读,章节中处理了“看似随机数”的概念:
AI 决策和游戏逻辑的过滤随机性:
摘要:传统观点认为,随机数生成器越好,您的游戏就越难以预测。然而,根据心理学研究,短期内真正的随机性在人类看来往往是绝对不随机的。本文展示了如何让随机 AI 决策和游戏逻辑在玩家看来更加随机,同时仍保持强大的统计随机性。
您可能还会发现另一章有趣:
随机数统计
摘要:随机数在人工智能和一般游戏中使用最多。忽视他们的潜力会使游戏变得可预测和无聊。错误地使用它们可能与完全忽略它们一样糟糕。了解随机数是如何生成的、它们的局限性和能力,可以消除在游戏中使用它们的许多困难。本文提供了对随机数、它们的生成以及区分好坏的方法的见解。
【讨论】:
【参考方案20】:反应: “问题是我在现实生活中得到了非常糟糕的结果——有时玩家在 5 次命中中获得 3 个暴击,有时在 15 次命中中没有。”
你有 3% 到 4% 的机会在 15 次点击中一无所获...
【讨论】:
当你让 3500 名玩家在一分钟内进行 10000 场战斗时,在 3% 的战斗中出现的问题是很常见的问题。 话说回来,3%的战斗中发生的厄运仍然只是厄运。【参考方案21】:我会提出以下“随机延迟的回退模具”:
维护两个数组,一个 (in-array
) 最初填充从 0 到 n-1 的值,另一个 (out-array
) 为空
请求结果时:
从in-array
中的所有定义 值中返回一个随机值
将此值从in-array
移动到out-array
将out-array
中的一个随机(超过所有 元素,包括未定义的!)元素移回in-array
这有一个属性,它会“反应”得越慢,n 越大。例如,如果您想要 20% 的机会,将 n 设置为 5 并点击 0 比将 n 设置为 10 并点击 0 或1,并将其从 1000 中的 0 到 199 与小样本的真正随机性几乎无法区分。您必须将 n 调整为您的样本大小。
【讨论】:
【参考方案22】:OP,
差不多,如果你希望它是公平的,它不会是随机的。
您的游戏的问题在于实际的匹配长度。比赛时间越长,您看到的随机性就越少(暴击率往往为 20%),并且会接近您的预期值。
您有两个选择,根据之前的掷骰预先计算攻击。每 5 次攻击你会获得一次暴击(基于你的 20%),但你可以让它发生的顺序随机。
listOfFollowingAttacks = 命中、命中、命中、未命中、暴击;
这就是你想要的模式。所以让它从那个列表中随机选择,直到它为空,他们重新创建它。
这是我为我的游戏创建的模式,效果很好,可以满足我的要求。
您的第二个选择是,增加暴击的机会,您可能会在所有攻击结束时看到一个更偶数(假设您的比赛结束得相当快)。几率越小,获得的 RNG 越多。
【讨论】:
【参考方案23】:我看到很多答案建议跟踪先前生成的数字或随机排列所有可能的值。
我个人不同意,连续 3 次暴击很糟糕。我也不同意连续 15 次非暴击是不好的。
我会通过在每个数字之后自行修改暴击几率来解决问题。 示例(为了演示这个想法):
int base_chance = 20;
int current_chance = base_chance;
int hit = generate_random_number(0, 100) + 1; // anything from 1 to 100
if(hit < current_chance)//Or whatever method you use to check
//crit!
if(current_chance > base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 0.8; // decrease the crit chance for the NEXT hit.
else
//no crit.
if(current_chance < base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 1.1; // increase the crit chance for the NEXT hit.
//raise the current_chance
您没有获得暴击的时间越长 - 您的下一个动作暴击的机会就越高。我包含的重置完全是可选的,它需要测试来判断它是否需要。在一个长的非暴击动作链之后,为连续的多个动作提供更高的暴击概率可能是可取的,也可能不是可取的。
只需投入我的 2 美分...
【讨论】:
我喜欢这种方法。我可能会用另一种方式来做。从较低的机会开始,并累积到最大值 20% + 一些增加的百分比,直到它击中并再次重置为某个较低的数量。【参考方案24】:我同意之前的回答,即在某些游戏的小运行中真正的随机性是不可取的——这对于某些用例来说似乎太不公平了。
我用 Ruby 编写了一个类似 Shuffle Bag 的简单实现并进行了一些测试。实现是这样的:
如果看起来仍然公平,或者我们没有达到最低掷骰阈值,它会根据正常概率返回公平命中。 如果从过去掷骰中观察到的概率使其看起来不公平,则会返回“公平化”命中。根据边界概率,它被认为是不公平的。例如,对于 20% 的概率,您可以将 10% 设置为下限,将 40% 设置为上限。
使用这些界限,我发现在运行 10 次命中时,14.2% 的时间真正的伪随机实现产生的结果超出了这些界限。大约 11% 的情况下,在 10 次尝试中得分为 0 次重击。 3.3% 的时间,10 次重击中有 5 次或更多命中。自然地,使用这种算法(最小掷骰数为 5),“Fairish”运行的数量(0.03%)少得多。 .即使下面的实现不合适(当然可以做更聪明的事情),值得注意的是,您的用户通常会觉得使用真正的伪随机解决方案是不公平的。
这是我用 Ruby 编写的 FairishBag
的内容。整个实现和快速蒙特卡罗模拟is available here (gist).
def fire!
hit = if @rolls >= @min_rolls && observed_probability > @unfair_high
false
elsif @rolls >= @min_rolls && observed_probability < @unfair_low
true
else
rand <= @probability
end
@hits += 1 if hit
@rolls += 1
return hit
end
def observed_probability
@hits.to_f / @rolls
end
更新:使用此方法确实会增加获得重击的总体概率,使用上述界限将其提高到大约 22%。您可以通过将其“真实”概率设置得低一点来抵消这一点。 17.5% 的概率与公平修改产生了观察到的大约 20% 的长期概率,并保持短期运行感觉公平。
【讨论】:
我认为这是最适合我需要的解决方案。最佳指向答案中提到的洗牌袋还不错,但需要大量计算,我喜欢最简单的解决方案,导致目标。 Steve Rabin 写了一篇关于游戏随机性的有趣文章。简而言之,真正的“随机”行为对大多数人来说并不是真的“感觉”随机,研究也支持了这一点。他的文章名为 Filtered Randomness for AI Decisions and Game Logic,出现在“AI Game Programming Wisdom 2”(2003 年)中。你应该检查一下,可能对你有用。 @IanTerrell 最好说明测试的样本量有多大,即确定这些概率的战斗次数。 @user677656:这是要点,但它是 100k【参考方案25】:前几个答案是很好的解释,所以我将只关注一种算法,它可以让您控制“坏条纹”的可能性,同时永远变得确定性。这是我认为你应该做的:
指定a和b,而不是指定p,伯努利分布的参数,即你的重击概率, β分布的参数,伯努利分布的“共轭先验”。您需要跟踪 A 和 B,即到目前为止的关键和非关键命中数。
现在,要指定 a 和 b,请确保 a/(a+b) = p,即暴击几率。巧妙的是,(a+b) 量化了您希望 A/(A+B) 与 p 的总体接近程度。
您可以这样进行采样:
设p(x)
为 beta 分布的概率密度函数。它在许多地方都可用,但您可以在 GSL 中以 gsl_ran_beta_pdf 的形式找到它。
S = A+B+1
p_1 = p((A+1)/S)
p_2 = p(A/S)
通过从概率为 p_1 / (p_1 + p_2) 的伯努利分布中采样来选择重击
如果发现随机数有太多“坏条纹”,放大a和b,但在极限内,为a em> 和 b 去无穷大,你就会有前面描述的 shuffle bag 方法。
如果你实现了这个,请告诉我它是怎么回事!
【讨论】:
【参考方案26】:如果您想要一个不鼓励重复值的分布,您可以使用简单的重复拒绝算法。
例如
int GetRand(int nSize)
return 1 + (::rand() % nSize);
int GetDice()
static int nPrevious=-1;
while (1)
int nValue = GetRand(6);
// only allow repeat 5% of the time
if (nValue==nPrevious && GetRand(100)<95)
continue;
nPrevious = nValue;
return nValue;
此代码在 95% 的情况下拒绝重复值,这使得重复不太可能但并非不可能。 从统计上看,它有点难看,但它可能会产生你想要的结果。当然,它不会阻止像“5 4 5 4 5”这样的分布。你可以变得更漂亮,拒绝倒数第二个(比如说)60% 的时间和倒数第三个(比如说)30% 的时间。
我不建议将其作为好的游戏设计。只是建议如何实现您想要的。
【讨论】:
我的游戏中的某些值(例如暴击)不会超过 50% 的机会,所以我完全阻止重复,但这会降低某些百分比的事件机会。【参考方案27】:我想你可能使用了错误的随机分布函数。 您可能不希望在数字上均匀分布。请尝试使用正态分布,以使重击比“常规”命中更罕见。
我使用 Java,所以我不确定在哪里可以找到 C++ 的东西,它可以为你提供具有正态分布的随机数,但必须有一些东西在那里。
【讨论】:
【参考方案28】:正如许多人所说,“随机”确实是个问题。你得到的结果是随机的,无论你如何制作游戏,一些玩家都会觉得你的计数器不公平,不随机。 ;)
一个可能的选择可能是保证每 n 次命中一次,并且在每次命中后在一定范围内随机生成 n。这完全是一个在游戏测试中“感觉”正确的问题。
【讨论】:
【参考方案29】:好吧,如果你有点数学,你可以试试Exponential distribution
例如,如果 lambda = 0.5,则预期值为 2(去阅读那篇文章吧!),这意味着你很可能每 2 回合就会击中/暴击/其他任何东西(比如 50%,对吧?)。但是在这样的概率分布下,你会在第 0 回合(事件已经发生并且 turn_counter 已重置的那个回合)明确地错过(或做任何相反的事情),有大约 40% 的机会击中下一回合,大约 65%有机会在第 2 回合(下一个之后)进行,大约 80% 达到第 3 个回合,依此类推。
这种分配的全部目的是,如果一个人有 50% 的命中率并且他连续 3 次未命中,那么他肯定会(嗯,超过 80% 的机会,并且每下一轮都会增加)命中。它会带来更“公平”的结果,保持 50% 的总体几率不变。
利用你 20% 的暴击几率,你有
17% 暴击第一回合 32% 到第二回合暴击,如果之前的所有暴击都没有发生的话。 45% 到第 3 回合暴击,如果之前的所有暴击都没有发生。 54% 到第 4 回合暴击,如果之前的所有暴击都没有发生。 ... 80% 到第 8 回合暴击,如果之前的所有暴击都没有发生。在 5 个后续回合中,3 次暴击 + 2 次非暴击的几率仍然约为 0.2%(相对于 5%)。 并且有 14% 的几率出现 4 次非暴击,5% 的 5 次,1.5% 的 6 次,0.3% 的 7 次,0.07% 的 8 次后续的非暴击。我敢打赌,它比 41%、32%、26%、21% 和 16%“更公平”。
希望你还没有无聊到死。
【讨论】:
这与我的解决方案非常相似,只是它只“记住”自上次重击以来的时间。在此解决方案下,就未来的概率而言,一连串 4 次重击与一连串 1 次重击相同。因此,如果重击不错,则此解决方案会限制您的下行风险,但不会限制您的上行风险。我的解决方案对两者都有影响。 很明显,不同的解决方案各有各的好处。从科学的角度来看,这一点侧重于保持随机性的清洁。这并不意味着它比洗牌包或其他任何东西更好。这只是一个似乎值得尝试的解决方案。【参考方案30】:我建议使用像暴雪这样的累进百分比系统: http://www.shacknews.com/onearticle.x/57886
通常您滚动一个 RNG,然后将其与一个值进行比较以确定是否成功。这可能看起来像:
if ( randNumber <= .2 )
//Critical
else
//Normal
您需要做的就是逐步增加基础几率...
if (randNumber <= .2 + progressiveChance )
progressiveChance = 0;
//Critical
else
progressiveChance += CHANCE_MODIFIER;
//Normal hit
如果您需要它更漂亮,添加更多内容非常容易。您可以限制progressiveChance 可以获得的数量以避免100% 的致命机会或在某些事件上重置它。您还可以使用诸如progressiveChance += (1 -progressiveChance) * SCALE 之类的方式以较小的量增加progressiveChance,其中SCALE
【讨论】:
以上是关于需要可预测的随机发生器的主要内容,如果未能解决你的问题,请参考以下文章