rom time to time, while reading RAIF postings, the subject of repeating random numbers comes up. For all appearances, this seems to be some sort of bug in one of the most popular and widely used Z-Code interpreters available today: Frotz. Reports seem to indicate that it affects various versions of Frotz. While researching this issue and developing a work around, I was able to confirm the behavior with WinFrotz version 2.32 release 5.3.
As an IF player, one might be tempted to simply change to a different interpreter, but a developer doesn't have that option. One of the beauties of the Z-Code standard is that it allows the players to choose their own interpreter. And to be perfectly frank: Frotz is too darn popular to ignore.
Show me the bug!
Before delving into the explanation of why this behavior occurs and how to fix it, let me include a code snip to demonstrate the bug (note: the standard library isn't really needed here to demonstrate this bug, but I prefer that sample code resemble real-world scenarios as closely as possible.)
Now write down these ten numbers and enter the command "RESTART". At the new command prompt enter "RAND" again. If your interpreter suffers from this bug, then the new list of ten numbers will match the first list exactly.
Now this method of checking for the behavior will always demonstrate it if it exists. Period. But the behavior will often pop up in less predictable ways. For instance, it is possible to:
1) start Frotz 2) run this example 3) record the ten "random numbers" 4) exit Frotz entirely 5) start Frotz again 6) run the example again...
...And still get the same random sequence. Because of the way random numbers work, this method will not always be the same. But often they will.
But why? Aren't random numbers random?
No, actually they are not. All random numbers generated by modern computers are actually pseudo-random numbers, which are the result of a mathematical formula that is applied against the previously produced number.
If you didn't catch that don't worry about it. Think of it like this: That list of ten numbers that was generated in the example program above is associated with another number called a "seed" value. Behind the scenes, the interpreter has the job of setting this seed value. This called "seeding the random number generator." Seeding the generator with the same value will always produce the same list. That doesn't make for a very random program. Traditionally, to achieve a non-repeating sequence every run, the random number generator is seeded with the current time represented in seconds elapsed since 1980.
In Inform, this is not possible since 1) there does not appear to be a way to seed the generator even if we had a seed value, and 2) there is not a readily available method that will return a non-predictable result to use as a seed value. According to the Z-code standard, calling random(0); is supposed to accomplish both of these, but as is the case with WinFrotz, not all interpreters have this implemented.
Luckily, there is a way of accomplishing the first task's desired effect without "seeding" the generator at all. Specifically, cycle through the random-number generator a random number of times. Given a single static list of randomly generated numbers, a non-predicatable sequence can be achieved simply by starting at a random location in the list. Think of this as pseudo-seeding the pseudo-random number generator.
"What's that?" you say. "Random numbers are what we are unable to get here. How do you get a random number to use for iterating through the generator with?" You don't. Instead you choose a number that will almost always be different every time you run. Some programmers opt to use a value equal to the current number of game moves elapsed once the player performs some arbitrary task (like entering a room). But that still leaves the game predictable, just less so.
The solution shown below measures the only thing that is bound to be different every game: the length of time it takes a user to enter text and press return in milliseconds!
This solution also has the advantages of being both reusable and transparent. It is reusable in that it is not game specific. It isn’t tied to a specific event or action. It works with any game without modification. It is transparent in that the developer has to do nothing other than include the text. No special reference needs to be made to it. No methods need to be called. The block of code can be implemented at the top of any game source file and work. It's just drop and go code.
Just cut and paste this code to the top of the example code we worked with earlier. Recompile and run the tests again to see the behavior corrected.