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 in 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.
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 run 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 the 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?
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.
One of the things about these pseudo-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.
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.
Random
ClassThe 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);
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 do 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();
andpublic int[] RollD6(int count);
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 float
s, you’ll need to cast the result to a float:
float randomNumber = (float)random.NextDouble();
So what if you want numbers outside 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; }
).
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]);
}