Back in Chapter 2, we learned that randomization takes a couple forms in game development. The first and most common is randomization where we don’t explicitly control the seed used. That’s what we used to randomize the behavior of the nodes in that chapter.
The second form of randomization is where we know what the seed is, and we can control it to some extent. Minecraft is a great example of this, because each level starts with an input where you can specify a seed.
Seeding can be a bit confusing, but it’s a way to start the randomization algorithm at an unguessable point. That’s because most randomization isn’t true randomization but rather pseudo-randomization.
Computers cannot produce random numbers without observing some outside stimulus; so instead, they offer a predictable kind of randomization progression.
Imagine an infinite sequence of random numbers. Seeding is a way to start at some point in the sequence and continue it:
Seeded randomization
If we pick a different seed, then the starting point is different, so the sequence of random numbers will be different. If someone using the same algorithm picks the seed of 90, they will get the same sequence of numbers.
Minecraft picks a random seed, seeded from details like the date and time and details of the computer’s hardware and configuration.
That seed is then used as the starting point for the pseudo-random number generation to create what appears to be a completely random world.
A New Experiment
We’ll become more familiar with these concepts as we see them in action. Let’s create a new experiment where we study the effects of pseudo-randomization and create a way for starting seeds to be derived and changed.
Let’s call it the SeedExperiment. We can add the following component tree, so we can edit the seed value and see a sample of random numbers that are generated with that seed:
Setting up SeedExperiment
You can make these UI controls as small or as big as you like. We can't anchor them to the screen because their parent is a Node2D, so it has no implicit size. We can set up a couple methods to pick a new random number and to update the sample of random numbers (the labels):
Here, we can see both popular forms of randomization. The first is in pick_random_number, where we’re not defining a seed to start from. When the game starts, the randomize() function is automatically called. This is much the same way as Minecraft does to generate the seed, which it then allows the player to edit.
The second kind of randomization happens in update_random_sample, where we define the seed for the RandomNumberGenerator so that it starts at a predictable point.
We can tie these methods together by adding some signals to the LineEdit and Button, and we can also make the randomization process happen once on load:
When the experiment starts, it will update the LineEdit with a random number between 0 and 99. This is used to update all the labels to use this as the seed.
Press the randomize button a few times to see the different samples. Then, edit the value of the LineEdit so that you switch back and forth between two known seeds. You’ll see that each time you put in the same seed number, the same sample set of random numbers appears.
That’s the power of seeded randomization. You can share the same experiences with your friends, even in a random system, if they use the same seed as you.
Generating Easier Seeds
A random number or sequence of random characters is difficult to remember. Random number generators tend to use longer seeds so that the seed is harder to guess.
Why force your players to remember a sequence of numbers when you can show them with something much easier, like two or three words? Those are much easier to memorize.
Search on Google or GitHub for a word list file in text format. I’m using one that is about 23KB big. I forget where exactly I found it, but I used it for a game I made a couple years ago.
Once you’ve found one, put it into the experiment project so that we can load it into this experiment. Then, we can use code like this to load all the words and allow us to select the desired number of words for our seed:
Downloading a words file
The RandomNumberGenerator’s seed property must be an integer, which we can get using the hash method on strings:
Now, all we need to do is replace the numeric seeds we were generating with these new methods:
It seems a bit silly that we’re getting a PackedStringArray of words from get_all_words, getting a PackedStringArray from get_words, joining them together, and splitting them into another PackedStringArray to get the hash. It’s because we want to show the words to the user, and LineEdit’s text property can only be a string.
This makes the seeds a lot easier to remember and share, because it’s a small number of words to remember.
The hash method’s documentation is careful to point out that identical strings can generate identical hashes to each other, but that the reverse isn’t always true. The hash value is a 32-bit integer, which means larger values are limited to 32-bit representations. Different values can generate identical hashes, because of that loss of specificity. This is called a collision, and it’s common to talk about the possibility of collisions in cryptography.
In fact, seeded randomization has many of the same underpinnings as encryption. Reversible encryption values are only as strong as the encryption algorithm and the secrecy of the key used to seed them. Sound familiar? If someone knows the randomization algorithm and seed, they can reproduce the same sequence of random values.
Summary
In this chapter, we learned all about generating seeds so that we can control the randomization that can occur in our games. We’re going to use this knowledge in the next chapter, as we build a new game.
Take some time to think about how to integrate this code into a larger project. How would you structure this code if it wasn’t on the “play” screen? How can you call into it to get the words and use them to randomize the behavior of worlds and NPCs?