为RPG游戏生成随机数

时间:2016-07-16 13:23:45

标签: algorithm random

我想知道是否有算法生成随机数,这些随机数很可能在从min到max的范围内很低。例如,如果您生成1到100之间的随机数,如果您使用f(min: 1, max: 100, avg: 30)调用该函数,则大部分时间应该低于30,但如果您使用f(min: 1, max: 200, avg: 10)调用该函数,则平均值应该最高10.许多游戏都是这样做的,但我找不到用公式做到这一点的方法。我见过的大多数例子都使用了“drop table”或类似的东西。

我已经提出了一种相当简单的方法来衡量一个滚动的结果,但它效率不高而且你没有很多控制它

var pseudoRand = function(min, max, n) {
    if (n > 0) {
        return pseudoRand(min, Math.random() * (max - min) + min, n - 1)
    }

    return max;
}

rands = []
for (var i = 0; i < 20000; i++) {
    rands.push(pseudoRand(0, 100, 1))
}

avg = rands.reduce(function(x, y) { return x + y } ) / rands.length
console.log(avg); // ~50

该函数只需在最小和最大N次之间选取一个随机数,其中每次迭代都会在最后一次滚动时更新最大值。因此,如果您使用N = 2, and max = 100调用它,那么它必须连续两次滚动100才能返回100

我已经在维基百科上看了一些发行版,但我不太了解它们,知道如何控制最小和最大输出等。

非常欢迎任何帮助

13 个答案:

答案 0 :(得分:8)

生成具有给定分布的随机数的简单方法是从列表中选择一个随机数,其中应更频繁出现的数字根据所需的分布重复。

例如,如果您创建列表[1,1,1,2,2,2,3,3,3,4]并从09选择一个随机索引,从该列表中选择一个元素,您将得到一个<4个数字概率为90%。

或者,使用上面示例中的分布,生成数组[2,5,8,9]并从09选择一个随机整数,如果它是≤2 (这将以30%的概率发生)然后返回1,如果它>2≤5(这也会以30%的概率发生)返回2等等。 / p>

在此解释:https://softwareengineering.stackexchange.com/a/150618

答案 1 :(得分:3)

概率分布函数只是一个函数,当你输入一个值X时,它将返回获得该值X的概率。累积分布函数是得到一个小于或等于X的数字的概率。 CDF是PDF的组成部分。 CDF几乎总是一对一的功能,因此它几乎总是具有反转。

要生成PDF,请绘制x轴上的值和y轴上的概率。所有概率的和(离散)或积分(连续)应加起来1.找到一些能正确模拟该方程的函数。为此,您可能需要查找一些PDF。

基本算法

https://en.wikipedia.org/wiki/Inverse_transform_sampling

此算法基于逆变换采样。 ITS背后的想法是你在CDF的y轴上随机选取一个值并找到它对应的x值。这是有道理的,因为随机选择一个值的可能性越大,空间越多&#34;它将占据CDF的y轴。

  1. 想出一些概率分布公式。例如,如果你想要它,那么随着数字越来越高,选择它们的几率会增加,你可以使用像f(x)= x或f(x)= x ^ 2这样的东西。如果你想要在中间凸起的东西,你可以使用高斯分布或1 /(1 + x ^ 2)。如果您需要有界公式,可以使用Beta Distribution或Kumaraswamy Distribution。
  2. 整合PDF以获得累积分布函数。
  3. 找到CDF的反转。
  4. 生成随机数并将其插入CDF的反转中。
  5. 将结果乘以(max-min)然后再添加min
  6. 将结果舍入为最接近的整数。
  7. 步骤1到3是你必须硬编码到游戏中的东西。围绕它的任何PDF的唯一方法是求解与其平均值相对应的形状参数,并保持对形状参数所需的约束。如果要使用Kumaraswamy分布,则将其设置为使形状参数a和b始终大于1。

    我建议使用Kumaraswamy发行版,因为它是有界的,它有一个非常好的封闭形式和封闭形式反转。它只有两个参数a和b,它非常灵活,因为它可以模拟许多不同的场景,包括多项式行为,钟形曲线行为,以及在两个边都有峰值的盆状行为。此外,使用此功能进行建模并不困难。形状参数b越高,向左倾斜的越多,形状参数a越高,向右倾斜的越多。如果a和b都小于1,则分布看起来像一个槽或盆。如果a或b等于1,则分布将是不会将凹度从0改变为1的多项式。如果a和b都等于1,则分布是直线。如果a和b大于1,则函数看起来像钟形曲线。您可以做的最好的事情是实际绘制这些函数的图形,或者只运行逆变换采样算法。

    https://en.wikipedia.org/wiki/Kumaraswamy_distribution

    例如,如果我想要一个形状如此的概率分布,其中a = 2且b = 5从0到100:

    https://www.wolframalpha.com/input/?i=2*5*x%5E(2-1)*(1-x%5E2)%5E(5-1)+from+x%3D0+to+x%3D1

    其CDF将是:

    CDF(x)= 1-(1-X ^ 2)^ 5

    它的反转是:

    CDF ^ -1(X)=(1-(1-X)^(1/5))^(1/2)

    Kumaraswamy分布的一般逆向是: CDF ^ -1(X)=(1-(1-X)^(1 / B))^(1 / A)

    然后我会生成一个从0到1的数字,将它放入CDF ^ -1(x),并将结果乘以100.

    <强>赞成

    • 非常准确
    • 连续,不谨慎
    • 使用一个公式和非常小的空间
    • 让您可以很好地控制随机性如何展开
    • 这些公式中的许多公式都有某种反转的CDF
    • 有两种方法可以绑定两端的功能。例如,Kumaraswamy Distribution的范围从0到1,因此您只需在0和1之间输入一个浮点数,然后将结果乘以(max-min)并加上min。 Beta分布根据您传入的值而有所不同。对于类似PDF(x)= x的内容,CDF(x)=(x ^ 2)/ 2,因此您可以生成从CDF(0)到CDF(max-min)的随机值。

    <强>缺点

    • 您需要提出您计划使用的确切分布及其形状
    • 您计划使用的每个通用公式都需要硬编码到游戏中。换句话说,您可以将一般Kumaraswamy分布编程到游戏中,并具有基于分布及其参数a和b生成随机数的函数,但不是基于平均值为您生成分布的函数。如果你想使用Distribution x,你必须找出a和b的哪个值最适合你想要看到的数据,并将这些值硬编码到游戏中。

答案 2 :(得分:1)

您可以组合2个随机过程。例如:

第一个兰特R1 = f(最小值:1,最大值:20,平均值:10); 第二个兰特R2 = f(最小值:1,最大值:10,平均值:1);

然后乘以R1 * R2得到[1-200]和平均值大约10之间的结果(平均值会稍微偏移)

另一种选择是找到要使用的随机函数的反函数。此程序必须在程序启动时初始化,但不需要重新计算。这里使用的数学可以在很多数学库中找到。我将逐一解释一个未知随机函数的例子,其中只知道四个点:

  1. 首先,使用3阶或更高阶的多项式函数拟合四点曲线。
  2. 然后你应该有一个类型的参数化函数:ax + bx ^ 2 + cx ^ 3 + d。
  3. 找到函数的不定积分(积分的形式是a / 2x ^ 2 + b / 3x ^ 3 + c / 4x ^ 4 + dx,我们称之为 quarticEq )。
  4. 计算从min到max的多项式的积分。
  5. 取0-1之间的均匀随机数,然后乘以步骤5中计算的积分值。(我们将结果命名为&#34; R &#34;)
  6. 现在求解x的等式 R = quarticEq
  7. 希望最后一部分是众所周知的,你应该能够找到一个可以进行这种计算的库(参见wiki)。如果积分函数的逆矩阵没有闭合形式解(如任何五度或更高的一般多项式),则可以使用根Newton's Method等根寻找方法。

    这种计算可用于创建任何类型的随机分布。

    编辑:

    你可以在wikipedia中找到上面描述的逆变换采样,我发现了implementation(我还没有尝试过。)

答案 3 :(得分:1)

我会使用一个简单的数学函数。根据您的描述,您需要一个指数级数,如y = x ^ 2。平均值(平均值是x = 0.5,因为兰特得到的数字从0到1)你会得到0.25。如果你想要一个较低的平均数,你可以使用更高的指数,如y = x ^ 3,在x = 0.5时会导致y = 0.125 例: http://www.meta-calculator.com/online/?panel-102-graph&data-bounds-xMin=-2&data-bounds-xMax=2&data-bounds-yMin=-2&data-bounds-yMax=2&data-equations-0=%22y%3Dx%5E2%22&data-rand=undefined&data-hideGrid=false

PS:我调整了函数来计算所需的指数以得到平均结果。 代码示例:

function expRand (min, max, exponent) {
    return Math.round( Math.pow( Math.random(), exponent) * (max - min) + min);
}

function averageRand (min, max, average) {
    var exponent = Math.log(((average - min) / (max - min))) / Math.log(0.5);
    return expRand(min, max, exponent);
}

alert(averageRand(1, 100, 10));

答案 4 :(得分:0)

您可以保持到目前为止从函数返回的运行平均值,并基于while循环中的运行平均值获得满足平均值的下一个随机数,调整运行平均值并返回数字

答案 5 :(得分:0)

使用丢弃表允许非常快速的滚动,这在实时游戏中很重要。实际上,它只是一个范围内的一个数字的随机生成,然后根据概率表(该范围的高斯分布)和多个选择的if语句。这样的事情:

num = random.randint(1,100)
if num<10 :
    case 1
if num<20 and num>10 :
    case 2
...

它不是很干净但是当你的选择数量有限时,它可以非常快。

答案 6 :(得分:0)

有很多方法可以做到这一点,所有这些方法基本上都归结为从右边生成 - skewed(a.k.a. positive-skewed)分布。您没有明确表示是否需要整数或浮点结果,但是存在适合该法案的离散和连续分布。

最简单的选择之一是离散的或连续的右边 - triangular distribution,但是虽然这会让你逐渐减少对更大值的渴望,但它不会让你独立控制平均值。 / p>

另一种选择是截断指数(对于连续)或几何(对于离散)分布。你需要截断,因为原始指数或几何分布的范围从零到无穷大,所以你必须砍掉上尾。这反过来要求你做一些微积分来找到一个速率λ,它在截断后产生所需的平均值。

第三种选择是使用分布的混合,例如,在较低范围内以一定概率p均匀地选择数字,并且在概率较高的范围内选择一个数字(1-p)。然后,总平均值是较低范围的平均值的p倍乘以上限范围的平均值的+(1-p)倍,并且您可以通过调整范围和p的值来拨入所需的总体均值。如果对子范围使用非均匀分布选择,则此方法也适用。这一切都归结为你愿意为推导出适当的参数选择投入多少工作。

答案 7 :(得分:0)

一种方法不是最精确的方法,但根据您的需要可以被视为“足够好”。

算法将是在最小和最大滑动之间选择一个数字。保证最大g_max和潜在最大p_max。您的真实最大值将根据另一个随机调用的结果滑动。这将为您提供您正在寻找的偏差分布。以下是Python中的解决方案。

import random

def get_roll(min, g_max, p_max)

    max = g_max + (random.random() * (p_max - g_max))

    return random.randint(min, int(max))

get_roll(1, 10, 20)

以下是使用(1,10,20)运行100,000次的函数的直方图。

enter image description here

答案 8 :(得分:0)

private int roll(int minRoll, int avgRoll, int maxRoll) {
    // Generating random number #1
    int firstRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1);

    // Iterating 3 times will result in the roll being relatively close to
    // the average roll.
    if (firstRoll > avgRoll) {
        // If the first roll is higher than the (set) average roll:
        for (int i = 0; i < 3; i++) {
            int verificationRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1);

            if (firstRoll > verificationRoll && verificationRoll >= avgRoll) {
                // If the following condition is met:
                // The iteration-roll is closer to 30 than the first roll
                firstRoll = verificationRoll;
            }
        }
    } else if (firstRoll < avgRoll) {
        // If the first roll is lower than the (set) average roll:
        for (int i = 0; i < 3; i++) {
            int verificationRoll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1);

            if (firstRoll < verificationRoll && verificationRoll <= avgRoll) {
                // If the following condition is met:
                // The iteration-roll is closer to 30 than the first roll
                firstRoll = verificationRoll;
            }
        }
    }
    return firstRoll;
}

<强>解释

  • 检查卷筒是在上面,下面还是正好30
  • 如果符合上述规定,请重新投票3次&amp;根据新卷设置卷,如果较低但是> = 30
  • 如果符合以下条件,则重播3次&amp;如果,根据新卷设置卷 更高但<= 30
  • 如果正好是30,请不要重新设置
  • 返回卷

<强>优点:

  • 简单
  • 有效
  • 表现良好

<强>缺点:

  • 你自然会有更多的结果,范围在30-40之间,而你的范围是20-30,很简单,因为30-70的关系。

<强>测试

您可以将以下方法与roll() - 方法结合使用来对此进行测试。数据保存在散列图中(将数字映射到出现次数)。

public void rollTheD100() {

    int maxNr = 100;
    int minNr = 1;
    int avgNr = 30;

    Map<Integer, Integer> numberOccurenceMap = new HashMap<>();

    // "Initialization" of the map (please don't hit me for calling it initialization)
    for (int i = 1; i <= 100; i++) {
        numberOccurenceMap.put(i, 0);
    }

    // Rolling (100k times)
    for (int i = 0; i < 100000; i++) {
        int dummy = roll(minNr, avgNr, maxNr);
        numberOccurenceMap.put(dummy, numberOccurenceMap.get(dummy) + 1);
    }

    int numberPack = 0;

    for (int i = 1; i <= 100; i++) {
        numberPack = numberPack + numberOccurenceMap.get(i);
        if (i % 10 == 0) {
            System.out.println("<" + i + ": " + numberPack);
            numberPack = 0;
        }
    }
}

结果(100.000卷):

这些都是预期的。请注意,您可以随时微调结果,只需修改roll() - 方法中的迭代计数(平均值应该越接近30,应该包含的迭代次数越多(请注意,这可能会损害表现到一定程度))。另请注意,到目前为止,30(是预期的)具有最高出现次数的数字。

  • &lt; 10:4994
  • &lt; 20:9425
  • &lt; 30:18184
  • &lt; 40:29640
  • &lt; 50:18283
  • &lt; 60:10426
  • &lt; 70:5396
  • &lt; 80:2532
  • &lt; 90:897
  • &lt; 100:223

答案 9 :(得分:0)

仿真软件(如SLX)用于创建伪数据的算法就是所谓的线性同余生成器。 Wikipedialink here

那里的等式以及explenation非常好。您必须将此等式写入方法并设置相应的返回值。

答案 10 :(得分:0)

试试这个, 为低于平均值的数字范围生成一个随机数,并为高于平均值的数字范围生成第二个随机数。

然后随机选择其中一个,每个范围将在50%的时间内被选中。

Bundle extras = new Bundle();
                    final ArrayList<CustomObject> objects = new ArrayList<CustomObject>();
                    objects.add(new CustomObject("Squat", "65%", "6", "150", false));
                    extras.putSerializable("w29w1", objects);

                    Intent intent = new Intent(getApplicationContext(), WorkoutDaysActivity.class);
                    intent.putExtra("args", extras);
                    startActivity(intent);

答案 11 :(得分:0)

看到很多好的解释和一些好的想法,我仍然认为这可以帮助你:

您可以在0附近使用任何分发函数 f ,并将您感兴趣的时间间隔替换为所需的时间间隔[1,100] f - &gt ;的 F'

然后使用 f'的结果提供C++ discrete_distribution

我在下面有一个正态分布的示例,但我无法将结果输入此函数:-S

#include <iostream>
#include <random>
#include <chrono>
#include <cmath>


using namespace std;


double p1(double x, double mean, double sigma); // p(x|x_avg,sigma)
double p2(int x, int x_min, int x_max, double x_avg, double z_min, double z_max); // transform ("stretch") it to the interval
int plot_ps(int x_avg, int x_min, int x_max, double sigma);

int main()
{
    int x_min = 1;
    int x_max = 20;
    int x_avg = 6;

    double sigma = 5;

    /*
    int p[]={2,1,3,1,2,5,1,1,1,1};

    default_random_engine generator (chrono::system_clock::now().time_since_epoch().count());
    discrete_distribution<int> distribution {p*};

    for (int i=0; i< 10; i++)
        cout << i << "\t" << distribution(generator) << endl;
    */
    plot_ps(x_avg, x_min, x_max, sigma);

    return 0; //*/
}

// Normal distribution function
double p1(double x, double mean, double sigma)
{
    return 1/(sigma*sqrt(2*M_PI))
         * exp(-(x-mean)*(x-mean) / (2*sigma*sigma));
}

// Transforms intervals to your wishes ;)
// z_min and z_max are the desired values f'(x_min) and f'(x_max)
double p2(int x, int x_min, int x_max, double x_avg, double z_min, double z_max)
{
    double y;
    double sigma = 1.0;
    double y_min = -sigma*sqrt(-2*log(z_min));
    double y_max =  sigma*sqrt(-2*log(z_max));
    if(x < x_avg)
        y = -(x-x_avg)/(x_avg-x_min)*y_min;
    else
        y = -(x-x_avg)/(x_avg-x_max)*y_max;
    return p1(y, 0.0, sigma);
}

//plots both distribution functions
int plot_ps(int x_avg, int x_min, int x_max, double sigma)
{
    double z = (1.0+x_max-x_min);

    // plot p1
    for (int i=1; i<=20; i++)
    {
        cout << i << "\t" <<
        string(int(p1(i, x_avg, sigma)*(sigma*sqrt(2*M_PI)*20.0)+0.5), '*')
        << endl;
    }

    cout << endl;

    // plot p2
    for (int i=1; i<=20; i++)
    {
        cout << i << "\t" <<
        string(int(p2(i, x_min, x_max, x_avg, 1.0/z, 1.0/z)*(20.0*sqrt(2*M_PI))+0.5), '*')
        << endl;
    }
}

如果我让他们绘制以下结果:

1   ************
2   ***************
3   *****************
4   ******************
5   ********************
6   ********************
7   ********************
8   ******************
9   *****************
10  ***************
11  ************
12  **********
13  ********
14  ******
15  ****
16  ***
17  **
18  *
19  *
20  

1   *
2   ***
3   *******
4   ************
5   ******************
6   ********************
7   ********************
8   *******************
9   *****************
10  ****************
11  **************
12  ************
13  *********
14  ********
15  ******
16  ****
17  ***
18  **
19  **
20  *

所以 - 如果你能把这个结果提供给discrete_distribution<int> distribution {},你得到了你想要的一切......

答案 12 :(得分:0)

好吧,从我能看到你的问题,我希望解决方案符合这些标准:

a)属于单个发行版:如果我们需要在每个函数调用中多次“滚动”(调用math.Random)然后聚合或丢弃某些结果,它将根据给定的函数停止真正分发。 / p> b)不是计算密集型的:有些解决方案使用积分(Gamma分布,高斯分布),而且计算量很大。在您的描述中,您提到您希望能够“使用公式计算”,这符合此描述(基本上,您需要O(1)函数)。

c)相对“分布均匀”,例如没有峰值和谷值,而是大多数结果聚集在均值周围,并且向下朝向末端具有良好的可预测斜率,但是具有最小和最大值的概率不为零。

d)不要求在内存中存储大型数组,如drop table。

我认为这个功能符合要求:

    var pseudoRand = function(min, max, avg )
    {
        var randomFraction = Math.random();
        var head = (avg - min);
        var tail = (max - avg);
        var skewdness = tail / (head + tail);
        if (randomFraction < skewdness)
            return min + (randomFraction / skewdness) * head;
        else
            return  avg + (1 - randomFraction) / (1 - skewdness) * tail;
    }

这将返回浮点数,但您可以通过调用

轻松将它们转换为整数

(int) Math.round(pseudoRand(...))

它在我的所有测试中返回了正确的平均值,并且它也很好地分发到最终。希望这可以帮助。祝你好运。