Mimicking the normal distribution

For a lot of stats and values in a game for which I may use simple random numbers, a normal distribution is better suited. Heights of trees, the amount of resources in a node, the value of an attack, and the random stats on an item a la Diablo are just a few examples. I decided to write a function to generate these normally-distributed random numbers.

The algorithm is simple, and has been used for decades by Dungeons and Dragons: adding multiple dice rolls together. In this case, the parameters are the min and max values, and the number of rolls, and we take an average as the result. The code itself is straightforward:

#include <cstdlib>
int random(int min, int max, unsigned rolls){
   unsigned span = max - min + 1;
   unsigned total = 0;
   for (unsigned i = 0; i != rolls; ++i)
   total += rand() % span;
   return (total + 0.5) / rolls + min;
}

Understandably, as the number of rolls increases, the result approaches the normal distribution. Also, because we’re essentially looking at sample means, the standard deviation of the distribution gets smaller as we add more rolls.

More rolls give a nicer, but narrower, distribution curve

Here is an approximation of how the standard deviation is affected by the number of rolls:

The impact of the number of rolls on the standard deviation of the distribution

This is obviously problematic, as the range of potential data becomes more and more narrow as we increase the number of rolls. The solution I chose was to “stretch” the distribution, by initially using a wider range, then rerolling whenever a final number isn’t in the actual, smaller range. The extra parameter to the function is a double, and indicates how much to stretch the curve. For example, a value of 1.0 maintains the original range, and 2.0 doubles that. In this way, the tails of the curve are cut off and redistributed over the middle, and the stretch parameter dictates how much to cut. The final code is as follows:

#include <cstdlib>

int random(int min, int max, unsigned rolls = 10, double stretch = 2){
   if (min > max)
      int temp = min, min = max, max = temp;
   if (stretch < 1)
      stretch = 1;
   if (rolls <= 1)
      return rand() % (max - min + 1) + min;

   int result;
   unsigned span = max - min + 1;
   unsigned stretchedSpan = span * stretch + .5;
   do{
      unsigned total = 0;
      for (unsigned i = 0; i != rolls; ++i)
         total += rand() % stretchedSpan;
      result = double(total) / rolls - (stretch - 1) * span / 2 + 0.5;
   }while(result > max);

   return result + min;
}

10 rolls and a stretch of 2.0 seems to give a pretty nice curve. Here are some results from arguments (1, 80, 10, 2):

Averages of 1500 samples, of size 10000

1500 single values

That last picture is what the actual results would look like when calling this function, giving a nice, heavy middle ground when determining the amount of copper in a mound of stone, or the +strength on a sword.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: