Random Number Generation

Introduction

In general software development, randomness is generally considered bad. People don't like it when the save button usually saves, but on some occasions, it randomly deletes files instead. Or in the 1/20 chance of a critical miss, it reformats your entire hard drive.

But in games, randomness is often the thing that makes games fun, and different every time you play it. Often, both.

Throughout many other tutorials, we talk about random number generation, and haven't given a whole lot of thought or consideration to it. That is what we'll do in this tutorial.

I'll first walk you through how computers pick random numbers, and then talk about how you can do this in XNA, followed by a few more advanced techniques that you will likely find helpful when you're working with random numbers.

Random and Pseudo-Random Numbers

We run into an interesting problem when we try to have computers select random numbers. As you've seen before, computers are very fast at math, and very good at following directions. But randomness doesn't exactly seem mathematical, and if we have to supply directions to the computer for picking random numbers, it's no longer random.

We've ran into a problem.

Fortunately, there are two possible ways for us to resolve this problem. On one hand, we can get the computer to measure something that, for all intents and purposes, is random. People have done this by measuring radiation from radioactive material, cosmic rays, or the motion of bubbles in lava lamps. This approach will definitely seem random, but it has a drawback of being time consuming to select a single random number.

There's another approach. Let's say we can come up with a process or algorithm that, given a previously determined random number, will be able to do some fancy math and calculate a second number that feels random?

An Example: John von Neumann's Middle-Square Method

John von Neumann was a freakin' genius, and one of the people who laid the groundwork for computer science. And he did it before computers even existed. (Probably using butterflies.) Von Neumann had this idea that you could generate seemingly random, or "pseudo-random" numbers using a technique called the middle-square method.

The idea was something like this. You take a four digit number, say 3432, and square it. This would result in 11778624. You take the middle four digits from this, 7786, as your new random number. Then you can continue on, squaring 7786. Your next random number is 6217 (the middle four digits of the number 60621796), and so on. You'd get a sequence like 3432, 7786, 6217, 6510, 3801…. Seems pretty random, huh?

Turns out, von Neumann's specific idea has some problems (and he acknowledged them) so it isn't used in practice, but the general idea behind it is. There's a variety of methods for selecting seemingly random numbers in a sequence.

Seeding Pseudo-Random Number Generation

One of the things about these psuedo-random number generation methods is that they can pick a new number, based on an old number. So where does it all start? At some point, the random number generation technique needs a number supplied from the outside. This initial number is called the seed and kicking off the random number generation with a particular number is called seeding the random number generator.

Ideally, this number would be randomly selected. Of course, if you choose a number, and type it into your game's code, then every time you run the game, you'll get the same "random" sequence. You'll repeatedly generate the same shuffled deck, repeatedly play the same game of Solitaire, and repeatedly play the same "random" Sudoku puzzle.

Perhaps you could ask the player to enter a random number. In fact, some games do exactly this. I mean, they don't directly ask, "Hey, type in a random number between 0 and 2,147,483,647." Rather, they tend to disguise it by allowing you to choose the "game number". Entering the same game number will allow you to replay a particular setup again, even though it is chosen randomly.

There's another way, though, that allows the user to not need to worry about selecting any particular number. We seed the random number generator with the current time. This is an extremely popular technique, and it is almost the only way it is done. In fact, as we'll see in a second, in C#, the easiest way to set up a new random number generator automatically seeds it with the current time for you.

Using the Random Class

The .NET framework includes a class that is specifically designed for doing pseudo-random number generation: the Random class. You can think of this class as a little random number generating engine, that can pick new random numbers whenever you ask it to.

Creating an Instance of the Random Class

The first step, like usual, is to create a new instance of the Random class, which we'll use throughout our game (or a part of our game). To do this, we would do something like the following:

Random random = new Random();

This line creates a new object, that is of the type Random, and stores it in a variable called random. This follows the same basic principles of using any class.

Even though you don't see it happening, this version of the constructor seeds the random number generator with the current time. Of course, you can create a random number generator with any seed you want, using an alternate constructor that has one parameter (the seed):

int seed = 10303;
Random anotherRandomGenerator = new Random(seed);

Generating Random Numbers

The simplest way to create a random number, once you have a Random object available, is with any of the three overloads of the Next method.

By the way, I like the name they chose for this method. It helps remind you that this is pseudo-random number generation, and that it is going to follow a known pattern if the same seed is chosen again. It's a sequence that is being generated, one at a time.

The first version of the Next method requires no parameters, and chooses a value between 0 and Int32.MaxValue, which is 2,147,483,647. This looks like this:

int randomNumber = random.Next();

This works great if you're just simply picking any old number, but more likely, you want the computer to pick a number within a certain range. For instance it is really common to want to, say, pick a number between 1 and 6, to simulate a die roll. This can be done with the second overload of the Next method, which has one parameter: the maximum value.

int dieValue = random.Next(6); // It's a trap!

This code would seem to pick a value between 1 and 6, but it doesn't quite do that. It really picks a number between 0 and 5. That's six total choices, but it is one off from the range you might expect. The value we pass to this method is the upper limit, but that number isn't included as an option. To get a value between 1 and 6, we'd want to do something like this:

int dieValue = random.Next(6) + 1; // Problem solved.

The third version of the Next method lets you put a lower and upper bound on the range:

int dieValue = random.Next(1, 7); // Gives you any of the values 1, 2, 3, 4, 5, 6, but not 7.

This lets you choose the lower limit and the upper limit, but like the previous version, the upper bound is not included as a choice. The lower limit is.

Try It Out: Dice Rolling

Try making a class called DiceRoller, which provides methods to simulate rolling dice. Add methods for the more common types of dice, including four-, six-, eight-, ten-, twelve-, and twenty-sided dice, as well as percentile dice (0-99).

Add overloads to to these methods to include a version that rolls a single die of that type, and another that rolls multiple of that type. For instance: public int RollD6(); and public int[] RollD6(int count);

Generating Floating Point Numbers

You can also select random floating point numbers using the NextDouble method:

double randomNumber = random.NextDouble();

This will give back numbers between 0 and 1. Of course, if you're working with floats, you'll need to cast the result to a float:

float randomNumber = (float)random.NextDouble();

So what if you want numbers besides the range of 0 to 1? What if you want -5 to +5?

If you're using XNA, you can use the MathHelper class to get this done, by using the Lerp (linear interpolation) method:

double randomNumber = random.NextDouble();
float numberInRightRange = MathHelper.Lerp(-5, +5, (float)randomNumber);

The MathHelper class is a part of XNA, so if you aren't using XNA, you'd need to create your own Lerp method (public static float Lerp(float min, float max, float value) { return min + (max - min) * value; }).

Random Number Generator Pitfalls

You don't need to create a new Random object every time you want to pick a new random number. Generally speaking, one Random object is sufficient for a single class, or a single algorithm. For example, when you go to shuffle a simulated deck of cards in your game, you will only need to create one Random object in your Shuffle method, and use it for the entire process. In fact, you could promote your Random object to be an instance variable, or even a static class variable, and only create one, regardless of how many times you call your Shuffle method.

There is a danger to creating too many Random objects too quickly, especially if you're using the parameterless constructor. Check out the following code:

int[] randomNumbers = new int[100];
 
for (int index = 0; index < 100; index++)
{
    randomNumbers[index] = new Random().Next(50) + 1;
    Console.WriteLine(randomNumbers[index]);
}

When you run it, what do you see? It keeps picking the exact same number over and over again! Why is that? It's because when you create a new Random object without supplying a seed, it is automatically seeded with the current time. And also remember that pseudo-random number generation follows a repeatable pattern. If we seed it with the same number, we'll get the same result.

What's happening here is that we're creating Random objects so incredibly fast that they all happen at the same time (or close enough that the computer's clock hasn't changed). They're all seeded with the same value, and so the first number they generate is the exact same.

This could have easily been solved by simply doing this:

int[] randomNumbers = new int[100];
Random random = new Random();
 
for (int index = 0; index < 100; index++)
{
    randomNumbers[index] = random.Next(50) + 1;
    Console.WriteLine(randomNumbers[index]);
}

Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!