Seeds
We just created a program to generate pseudorandom numbers, but every time we run it we get the same results. We know that these numbers are the results of complex equations and algorithms, so why are they the same? It's because each time we run the program, we're starting with the same seed.
Defining seeds
A seed provides a starting point for an algorithm. So, in the previous example, yes we're using complex algorithms to generate numbers, but we're kicking off the algorithm at the same point each time. No matter how complex the algorithm is, if you start at the same point, and perform the same operations, you're going to get the same results.
Imagine that we have three people, and each person is about to walk the same path by 5 steps. If they all start from the same square, they will end at the same square:
Now, in the next diagram, we give these three people unique starting positions. Even though they are doing the same actions as before, and are on the same path, their results are different because they started from different locations:
In this analogy, the path is the algorithm, and the starting square is the seed. By changing the seed we can get different results from the same actions.
You will have most likely used seeds before and not even known it. Games that procedurally generate worlds, such as Minecraft and Lego Worlds, give you the option to set a seed manually before generating a world. If your friend generates a world that looks great, they can grab their seed and give it to you. When you input that seed yourself, you kick off the algorithm at the same place that your friends did and you end up with the same worlds.
Using seeds
Now that we know what seeds are, let's fix the previous example so that we don't keep generating the same numbers. To do this, we will use the std::srand()
function. It's similar to std::rand()
, but it takes an argument. This argument is used to set the seed for an algorithm. We'll add the call to std::srand()
before we enter the while loop.
Tip
You only need to set the seed once per run of the application. Once std::srand()
has been called, all the subsequent calls to std::rand()
will be based upon the updated initial seed.
The updated code should look like this:
// Random number generation // This program will generate a random number each time we press enter. #include <iostream> using namespace std; int main() { // Here we will call srand() to set the seed for future rand() calls. srand(100); while (true) { cout << "Press enter to generate a random number:"; cin.get(); // Generate a random integer. int randomInteger = rand() % 201 + 50; cout << randomInteger << endl << endl; } return 0; }
Now when we run this code we get different results! I got 214, 60, 239, 71, and 233. Don't worry if your numbers don't match mine exactly; they are both CPU- and vendor-specific. So, what will happen if we run the program again? We changed the seed. So we should get different numbers again, right?
Not quite. We called std::srand()
and set a new seed, but each time we run the program we're setting the same seed again. We're kicking the algorithm off at the same position each time, so we're seeing the same results. What we really want to do is randomly generate a seed during runtime so that the algorithm always starts at a new position.
Generating random seeds during the runtime
There are many ways to achieve this, and your use case will determine which method is suitable. For us, as game developers, something relatively trivial such as the current system time will usually suffice.
This does mean that if you run the program at the exact same time you'll get the same results, but that's almost never going to be a problem for our use. C++ provides us with a nice function to get the current time, time()
, which is located in <ctime>
.
Let's update the program one last time and pass time()
as a parameter in std::srand()
so that we generate unique numbers with every run:
// Here we will call srand() to set the seed for future rand() calls.
//srand(100);
srand(time(nullptr));
Now, every time we run the program, we get unique numbers! You may have noticed that if you run the program multiple times in succession, the first number is always very similar to the last run. That's because between the runs time doesn't change a lot. This means that the starting points are close to each other and the results reflect this.
Controlled randomness is the key to generating random numbers
The process of generating random numbers is a huge component in creating systems that procedurally generate game content. There are lots of ways in which random data is generated, such as noise maps and other external systems, but in this book, we'll stick to these simple C++ functions.
We want systems that are predictable enough to give us control over them as developers, but they should be dynamic enough to create variations for the player. This balance can be hard to achieve, and sometimes games get it wrong. Later in this chapter, we'll look at some of the things that you have to watch out for when incorporating procedural generation into a game project to avoid this.