【问题标题】: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 弄丢了。

标签: c++ algorithm random


【解决方案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++ 随机数生成器,请使用 randboost::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 个1s。要获得第三个1,他们需要连续 3 个,无限。

    【讨论】:

      【解决方案8】:

      用这样的东西替换 mt_rand() 怎么样?

      (RFC 1149.5 将 4 指定为标准 IEEE 审查的随机数。)

      来自XKCD

      【讨论】:

      • 不错,但这会解决 OP 的随机数分布问题吗? ;-)
      • 不是真的;他要求一个非随机的RNG,他可以使用这个函数;但他真正想要的是当前的最佳答案 (stackoverflow.com/questions/910215/910224#910224)
      • 这比 OP 想要的更随机
      • -1 因为 RFC1149 没有第 5 节(或者,没有 1149.5); +1 公平随机性。
      【解决方案9】:

      您误解了随机的含义。

      其中哪个更随机?

      虽然第二个图看起来分布更均匀,但实际上第一个图随机越多。人类思维经常看到随机模式,因此我们将第一个图中的团块视为模式,但它们不是 - 它们只是随机选择的样本的一部分。

      【讨论】:

      • 是的 - 这不是用雨滴解释的吗?^^
      • 从技术上讲,您无法衡量随机性。尽管我的猜测都是算法生成的,但这两种分布在我看来都相当随意。您可以在第一个图上执行许多测试,并确定它可能来自根据均匀分布放置点的过程,但您无法得出结论它更随机。作为反例,您可以使用线性同余发生器制作第一个图,然后使用齐纳二极管的放大噪声制作第二个图。尝试使用不相关而不是随机这个词。
      • 您可以测量所述分布是随机的概率。
      • 公平地说,虽然 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】:

                  前几个答案是很好的解释,所以我将只关注一种算法,它可以让您控制“坏条纹”的可能性,同时永远变得确定性。这是我认为你应该做的:

                  指定ab,而不是指定p,伯努利分布的参数,即你的重击概率, β分布的参数,伯努利分布的“共轭先验”。您需要跟踪 AB,即到目前为止的关键和非关键命中数。

                  现在,要指定 ab,请确保 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) 的伯努利分布中采样来选择重击

                  如果发现随机数有太多“坏条纹”,放大ab,但在极限内,为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

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2013-05-13
                          • 2015-01-25
                          • 1970-01-01
                          • 1970-01-01
                          • 2018-06-04
                          • 1970-01-01
                          相关资源
                          最近更新 更多