教程:高级随机数

教程:高级随机数

我们大多数人在应用程序开发中的某个时候都处理过生成随机数的问题。在本教程中,我们将研究生成随机数的核心语法。稍后,我们将演练一个“掷骰子”机制,以用于更高级的用例。

播种生成器

请求随机数时,最常见的错误之一是未能播种随机数生成器。除了某些高科技、数学密集、复杂的方法之外,大多数计算机/设备都会生成伪随机数。因此,我们必须使用一个值播种随机数生成器以开始生成。如果我们使用相同的值来播种生成器,我们将得到相同的随机数模式。这在某些情况下可能有用,但在大多数情况下,我们希望得到不同的数字。为了实现这一点,我们可以将以下行添加到我们的main.lua文件中

由于os.time() 每次我们运行应用程序时都会返回不同的值,因此此调用将使用相当随机的起始值来播种随机数生成器。方便的是,我们只需要执行一次此操作,通常在我们主代码的顶部附近。

说完这些,让我们看一下具有三种“模式”的math.random()函数

  • value = math.random() — 返回一个介于 0 和 1 之间的浮点数。
  • value = math.random( maxVal ) — 返回一个介于 1 和 maxVal 之间的整数。
  • value = math.random( startVal, maxVal ) — 返回一个介于 startValmaxVal 之间的整数。

第一种模式是大多数编程语言的工作方式,但由于大多数人实际上不需要 0 到 1 之间的浮点数,因此我们传统上需要将此数字乘以最大值。例如,如果我们想要一个介于 1 和 10 之间的数字,C 代码将如下所示

幸运的是,Lua 提供了一种方便的方法来帮助我们解决这个问题,即上述的第二种结构。只需传入最大值即可获得一个介于 1 和 maxVal 之间的数字。

在范围应从 1 开始的情况下,Lua 提供了第三种结构。例如,如果我们想在屏幕的中间三分之一处生成一个敌人,那么这段代码对于生成其 x 位置非常有用

掷骰子!

上述函数很有用,但是如果我们需要更复杂的功能怎么办?例如,在许多游戏中,例如龙与地下城™和类似的 RPG 游戏中,玩家需要掷三个六面骰子来确定角色的力量。在此示例中,角色力量值的范围为 3-18,但只有少数玩家会掷出 3 或 18 的力量。大多数角色最终会在 10 到 11 的范围内。许多棋盘游戏,例如大富翁™,使用两个六面骰子,因此掷出总和为 2 或 12 的情况很少见,而 6 到 8 之间的总和更为常见。在统计学中,这称为钟形曲线。但是,标准的随机数生成器会生成一条数字的平线,其中每个数字都有相同的概率被选中。因此,掷骰子的需求可能会变得非常复杂。此外,除了传统的六面骰子外,一些游戏还具有许多种类的骰子 — 有 4 面、8 面、10 面、12 面、20 面甚至 30 面的骰子!

有时我们还需要在结果中添加或减去“修改器”。这些游戏系统使用一种相当常见的“语言”来指定骰子和修改器。例如,考虑

  • 3d6 — 通过掷三个六面骰子来生成一个数字,然后将它们加起来。
  • 2d8+3 — 通过掷两个八面骰子来生成一个数字,将它们加起来,然后将总数加 3。

掷实体骰子可能很有趣,但是在 Corona SDK 中如何实现呢?首先,我们应该建立骰子的“语言”。在本教程中,我们将使用Myth-Weavers系统的变体。在这个系统中,我们从骰子数量开始,然后写字母d,后跟骰子的面数。后面是可选的+和一个被加或减的数字。为了使其更具功能性,该系统还支持保留某些骰子结果而放弃其他骰子结果的想法。在角色扮演示例中,由于大多数英雄需要比普通人更好的属性,因此他们的角色属性是使用四个六面骰子掷出的,但仅保留并加在一起最高的 3 个骰子结果。此骰子字符串将表示为4d6^3。现在让我们检查一下 Lua 代码

代码解析

让我们逐步分析代码,看看它是如何工作的

这里,我们创建一个表来保存每次掷骰子的结果。我们还局部化了 `math.random()` 函数以获得最佳性能,并将 `total` 值设置为 0。

接下来,我们需要使用 string.match() 解析 `dicePattern` 参数

在 Lua 中, `string.match()` 需要两个参数:我们要搜索的字符串和一个要搜索的**模式**。所有匹配项都会返回到 `=` 符号左侧的变量中。在这个例子中,我们搜索一个**数字** (`(%d+)`),后跟字母 `d` (`[dD]`)。请注意,我们支持小写 `d` 和大写 `D` 作为次要的故障保护。接下来,我们查找**骰子的面数** (`(%d+)`)。在 Lua 模式匹配中, `%d` 表示一个数字,而 `%d+` 表示**一个或多个**数字的任意数字。

接下来,我们必须搜索字符串的可选部分。下一个模式集 (`([%+%-]*)`) 查找 `+` 或 `−` 符号,并将结果存储在名为 `sign` 的变量中。这些都是特殊符号,因此我们需要使用前导 `%` 符号对其进行**转义**。通过将它们放在方括号内,Lua 知道两者都是有效的。最后,末尾的 `*` 表示允许**零个或多个**匹配项,因此它是可选的。下一个模式集 (`(%d*)`) 捕获修饰符值的可选数字。在此之后,我们搜索插入符号 `^` 或字母 `k`,以确定要从集合中保留的骰子数量。插入符号是另一个特殊符号,因此需要用百分号进行转义。最后,我们捕获要保留的骰子数量。在此匹配中,我们实际上会为 `modifier` 和 `keep` 返回一个空字符串。通过使用 `tonumber()` 将该字符串转换为数字,它将返回一个数字或 `nil`。如果修饰符为 `nil`,则将其设为 0。然后检查 `sign` 变量,如果它是减号,则将 `modifier` 乘以 -1。

现在让我们生成掷骰子结果

第一个 `for` 循环使用从骰子的指定面数随机生成的数字加载骰子数组。如果我们要求 `4d6`,我们将得到一个包含 4 个数字的数组,每个数字的范围为 1-6。然后,如果指定了 `keep`,我们将获得值的总和,最多为 `keep` 的数量。要计算出要保留哪些骰子,必须使用 `table.sort()` 函数对数组进行排序。通过检查从字符串解析的 `keepHiLo` 值的,我们可以将表从低到高或反之排序。最后,我们循环遍历排序后的数字数组,并且仅计算我们要保留的数字。最后,为了完成函数,我们添加 `modifier`,返回掷骰子的总和,并且还返回掷骰子的数组,以防万一我们希望调用例程显示这些值。

掷骰子

建立函数后,我们可以使用有效的骰子字符串调用该函数,然后在调用后访问骰子的值

结论

希望您对随机数生成有了更清晰的理解,并掌握了一个可以用作高级掷骰子机制的函数。有了这些知识,您可以实现分布不均匀的随机数范围,使您的应用程序可以轻松支持常见、不常见甚至罕见的事件。

标签
,
罗布·米拉克
[email protected]

罗布是 Corona Labs 的开发者关系经理。除了热衷于帮助其他开发者使用 Corona 制作优秀游戏外,他还喜欢在业余时间制作游戏。自 1979 年以来,罗布一直在编写游戏代码,从个人电脑到大型机。他在游戏行业拥有超过 16 年的专业经验。

5 条评论
  • 内森·戈奇
    发布于 10:23, 3 月 13 日

    这真是一个非常棒且易于理解的教程,非常感谢您的分享。

  • 克里斯蒂安·斯泽斯尼
    发布于 03:29, 9 月 6 日

    感谢您的教程,很棒。
    但是,每个人都应该记住,lua 随机数生成……简直是垃圾。
    我知道即使调用随机函数,如果它连续 5 次返回“1”,它仍然被认为是随机的,但这意味着随机生成器运行不正常。
    今天的一个简单例子:我需要生成 12 个介于 1 到 400 之间的随机数。大多数时候,我最终会在我的列表中得到至少 3 个相等的数字。是的,我确实使用了 randomseed。否则我每次都会得到相同的数字集。
    有一次我们忘记在游戏中加入 randomseed……看到每次玩家开始游戏时都会创建完全相同的布局,真是太酷了。

  • kk
    发布于 06:50, 5 月 26 日

    我是一个新手,您能否展示一个例子,说明如何从一个组(第一轮)中抽取一些随机数,然后将其放入另一个组(第二轮)以进一步随机抽取。

    • 罗布·米拉克
      发布于 07:32, 5 月 26 日

      您在这里看到的是一种洗牌方法。本教程是关于处理角色扮演类游戏的骰子。您需要这个教程:/blog/2014/09/30/tutorial-how-to-shuffle-table-items/
      洗牌会随机化一个数字索引的表。然后,当您需要项目时,您可以根据表的新顺序,一次一个地从表中取出它们。将它们插入到您的第二个表中,当第二个表完成后,洗牌该表,然后按照新表顺序使用该表中的值。

  • kk
    发布于 09:04, 5 月 26 日

    再次感谢您。