Create random numbers for RPG games

I am wondering if there is an algorithm for generating random numbers, which are likely to be low in the range from min to max. For example, if you generate a random number from 1 to 100, then in most cases it should be below 30 if you call a function with f(min: 1, max: 100, avg: 30) , but if you call it with f(min: 1, max: 200, avg: 10) , then the highest value should be 10. Many games do this, but I just can’t find a way to do this using the formula. Most of the examples I've seen use a “drag and drop table” or something like that.

I came up with a fairly simple way to evaluate the result of a roll, but it is not very effective, and you do not have much control over it.

 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 

The function simply selects a random number between min and max N times, where for each iteration it updates max with the last throw. Therefore, if you call it with N = 2, and max = 100 , then it must flip 100 twice in a row to return 100

I looked at some distributions on wikipedia, but I don’t quite understand them enough to know how I can control the outputs of min and max, etc.

Any help is greatly appreciated.

+5
source share
13 answers

The probability distribution function is simply a function that, when you add an X value, will return the probability of getting this X value. A cumulative distribution function is the probability of getting a number less than or equal to X. A CDF is a PDF integral. CDF is almost always a one-to-one function, therefore it almost always has the opposite.

To create a PDF, plot the value on the x axis and the probability on the y axis. The sum (discrete) or integral (continuous) of all the probabilities should be 1. Find some function that correctly models this equation. To do this, you may have to search for some PDF files.

Basic algorithm

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

This algorithm is based on the inverse transform. The idea of ​​ITS is that you randomly select a value on the y-axis of the CDF and find the x value to which it corresponds. This makes sense because the sooner the value is randomly selected, the more “space” it will occupy the y-axis of the CDF.

  • Come up with a probability distribution formula. For example, if you want the chances of choice to increase with increasing number, you can use something like f (x) = x or f (x) = x ^ 2. If you want something that is convex in the middle, you You can use the Gaussian distribution or 1 / (1 + x ^ 2). If you want to limit the formula, you can use the beta distribution or the Kumaraswamy distribution.
  • PDF integration for cumulative distribution function.
  • Find the back of the CDF.
  • Create a random number and paste it in the back of the CDF.
  • Multiply this result by (max-min) and then add min
  • Complete the result to the nearest integer.

Steps 1 through 3 are what you need for the hard code in the game. The only way to get around it for any PDF file is to solve the form parameters corresponding to its average value and keep the restrictions on what you want the form parameters to be. If you want to use Kumaraswamy Distribution, you set it so that the form parameters a and b are always more than one.

I would suggest using the Kumaraswamy distribution because it is limited and has a very nice closed form and a closed inverse form. It has only two parameters: a and b, and it is extremely flexible because it can model many different scenarios, including polynomial behavior, bell-shaped behavior, and pool behavior that has a peak at both edges. In addition, with this function it is not too difficult to model. The higher the shape parameter b, the more inclined it will be on the left, and the higher the shape parameter a, the more it will be directed to the right. If a and b are less than one, the distribution will look like a deflection or a pool. If a or b is equal to unity, the distribution will be a polynomial that does not change the concavity from 0 to 1. If both a and b are equal to unity, the distribution is a straight line. If a and b are greater than unity, then the function will look like a bell curve. The best you can do to learn this is to actually draw these functions or just run the reverse fetch algorithm.

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

For example, if I want to have a probability distribution like this with a = 2 and b = 5 going from 0 to 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

His CDF will be:

VLOOKUP (x) = 1- (1-x ^ 2) ^ 5

His converse would be:

VLOOKUP ^ -1 () = (1- (1-) ^ (1/5)) ^ (1/2)

The general inverse distribution by Coomaraswamy: KOR ^ -1 (x) = (1- (1-x) ^ (1 / b)) ^ (1 / a)

Then I would generate a number from 0 to 1, put it in CDF ^ -1 (x) and multiply the result by 100.

Pros

  • Very accurate
  • Continuous, not restrained
  • Uses one formula and very little space
  • Gives you great control over how randomness is distributed.
  • Many of these formulas have some kind of inverse CDF
  • There are ways to connect functions from both ends. For example, the Kumaraswamy distribution is limited from 0 to 1, so you simply enter a float between zero and one, then multiply the result by (max-min) and add min. The beta distribution is limited in different ways depending on what values ​​you pass into it. For something like PDF (x) = x, CDF (x) = (x ^ 2) / 2, so you can generate a random value from CDF (0) to CDF (max-min).

vs

  • You need to come up with the exact distributions and their forms that you plan to use.
  • Each general formula that you plan to use must be hard-coded in the game. In other words, you can program the overall Kumaraswamy distribution in the game and have a function that generates random numbers based on the distribution and its parameters, a and b, but not a function that generates a distribution for you based on the average. If you want to use the x distribution, you need to find out which values ​​a and b are best for the data you want to view, and hard-code these values ​​in the game.
+3
source

A simple way to generate a random number with a given distribution is to select a random number from the list where the numbers that should occur more often are repeated in accordance with the desired distribution.

For example, if you create a list [1,1,1,2,2,2,3,3,3,4] and select a random index from 0 to 9 to select an item from this list, you will get a number <4 with probability 90%

Alternatively, using the distribution from the above example, generate an array [2,5,8,9] and select a random integer from 0 to 9 if it ≤2 (this will happen with a probability of 30%), then return 1 if it >2 and ≤5 (this will also happen with a probability of 30%) return 2 , etc.

Explained here: https://softwareengineering.stackexchange.com/a/150618

+8
source

You can combine two random processes. For instance:

first rand R1 = f (min: 1, max: 20, avg: 10); second rand R2 = f (min: 1, max: 10, avg: 1);

and then multiply R1 * R2 by the result between [1-200] and the average value of about 10 (the average value will be slightly changed)

Another option is to find the inverse of the random function you want to use. This option should be initialized when your program starts, but it does not need to be recounted. The math used here can be found in many Math libraries. I will explain point by point, taking an example of an unknown random function where only four points are known:

  • First, set up a four-point curve with a polynomial function of order 3 or higher.
  • Then you should have a parameterized function like: ax + bx ^ 2 + cx ^ 3 + d.
  • Find the indefinite integral of the function (the integral form has the form a / 2x ^ 2 + b / 3x ^ 3 + c / 4x ^ 4 + dx, which we will call quarticEq ).
  • Calculate the integral from the polynomial from your minimum to maximum.
  • Take a uniform random number between 0-1, then multiply by the value of the integral calculated in step 5. (let's call the result " R ")
  • Now solve the equation R = quarticEq for x.

We hope that the last part is well known, and you can find a library that can perform this calculation (see wiki ). If the inverse integral function does not have a closed-form solution (as in any general polynomial with degree five or higher), you can use a root search method, such as Newton’s Method .

This type of calculation can be used to create an arbitrary distribution.

Edit:

You can find the Inverse transform described above on wikipedia , and I found this implementation (I have not tried it.)

+1
source

I would use a simple mathematical function for this. From what you describe, you need an exponential progression such as y = x ^ 2. on average (the average is x = 0.5, since rand gets a number from 0 to 1), you get 0.25. If you want a lower average, you can use a higher indicator such as y = x ^ 3, which will lead to y = 0.125 at x = 0.5 Example: 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: I adjusted the function to calculate the required indicator to get the average result. Code example:

 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)); 
+1
source

You can save the average value of what you returned from the function so far, and based on this, in the while loop, get the next random number that corresponds to the average value, adjust the current average and return the number

0
source

Using the drag and drop table allows you to quickly roll that in the game in real time. In fact, this is only one random generation of a number from the range, then according to the probability table (Gaussian distribution for this range) the if operator is multiple-choice. Something like that:

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

This is not very clean, but when you have a finite number of options, it can be very fast.

0
source

There are many ways to do this, all of which basically come down to creating a distribution from the right skew (aka with a positive skew). You have not made it clear whether you want integers or floating-point, but there are discrete and continuous distributions that correspond to the count.

One of the simplest options may be a discrete or continuous right - a triangular distribution , but while this will give you a narrowing of large values, it will not give you independent control over the average value.

Another choice would be a truncated exponential (for continuous) or geometric (for discrete) distribution. You need to truncate because the raw exponential or geometric distribution has a range from zero to infinity, so you have to catch the upper tail. This, in turn, will require you to calculate to find speed and lambda; which gives the desired average after truncation.

The third option is to use a mixture of distributions, for example, choose a number uniformly in the lower range with some probability p and in the upper range with probability (1-p). In general, the average p value is equal to the average value of the lower range + (1-p) times the average value of the upper range, and you can type in the required total average value by adjusting the ranges and p value. This approach will also work if you use uneven distribution selection for subbands. It all comes down to how much work you are willing to make in choosing the right options.

0
source

One method would not be the most accurate method, but it could be considered "good enough" depending on your needs.

The algorithm would have to choose a number between min and moving max. It would be guaranteed max g_max and potential max p_max . Your true max will slide based on the results of another random call. This will give you the skewed distribution you are looking for. Below is the solution in 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) 

Below is a histogram of the function performed 100,000 times with (1, 10, 20).

enter image description here

0
source
 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; } 

Explanation:

  • roll
  • check if the roll is higher, lower or exactly 30
  • if higher, repeat 3 times and set the roll according to the new roll, if lower, but> = 30
  • if lower, repeat 3 times and set the roll according to the new roll, if higher, but <= 30
  • if exactly 30, do not re-install the roll
  • return roll

Pros:

  • just
  • effective
  • works well

Minuses:

  • You will naturally have more results that are in the range of 30-40 than you will have in the range of 20-30, simply because of the ratio of 30-70.

Testing:

You can verify this using the following method in combination with the roll() method. Data is stored in a hash map (to match the number with the number of occurrences).

 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; } } } 

Results (100,000 rolls):

It was as expected. Note that you can always fine-tune the results by simply changing the number of iterations in the roll() method (the closer to the average value of 30, the more iterations should be included (note that this can hurt performance to a certain extent)). Also note that 30 was (as expected) the number with the highest number of occurrences to date.

  • <10: 4994
  • <20: 9425
  • <30: 18184
  • <40: 29640
  • <50: 18283
  • <60: 10426
  • <70: 5396
  • <80: 2532
  • <90: 897
  • <100: 223
0
source

The algorithm used by simulation software, such as SLX to create pseudo-data, is the so-called linear congruence generator . Wikipedialink here .

The equation, as well as the explanation, is very good there. You will need to write this equation to the method and set the corresponding return value.

0
source

Try this, generate a random number for a range of numbers below average, and generate a second random number for a range of numbers above average.

Then randomly select one of them, each range will be selected in 50% of cases.

 var psuedoRand = function(min, max, avg) { var upperRand = (int)(Math.random() * (max - avg) + avg); var lowerRand = (int)(Math.random() * (avg - min) + min); if (math.random() < 0.5) return lowerRand; else return upperRand; } 
0
source

Having seen a lot of good explanations and some good ideas, I still think this can help you:

You can use any distribution function f around 0 and substitute the interval of interest of interest to your desired interval [1,100] : ff .

Then pass C++ discrete_distribution with the results of f ' .

I have an example with a normal distribution below, but I can not get my result in this function: -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; } } 

With the following result, if I let them build a graph:

 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 * 

So - if you could give this result discrete_distribution<int> distribution {} , you got everything you want ...

0
source

Well, from what I see in your problem, I would like the solution to meet these criteria:

a) Belonging to the same distribution: if we need to “steer” (call math.Random) more than once to call a function, and then aggregate or discard some results, it will cease to be truly distributed in accordance with the given function.

b) Does not require computational costs: some solutions use integrals (gamma distribution, Gaussian distribution), and they are computationally intensive. , " ", ( , O (1)).

c) " ", . , , min max .

d) , drop.

, :

  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; } 

float, ints,

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

, . Hope this helps. Good luck.

0
source

All Articles