WelcomeWhat's NewsORLibraryDownloadsLinksWebMaster@OnyxRing.com

This site
produced
656719
pageviews since
8/19/2004

Today's date:
11/20/2017






Author: Jim Fisher
Title: Randomizing Random

Creation Date: 8/6/2004 10:38:26 PM
Last Updated: 8/6/2004 10:38:26 PM


Randomizing Random

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.)

   Include "Parser"; Include "VerbLib"; !library includes 
   Object    SimpleRoom "Simple Room" !single room 
    with   description "This is a simple room"
    has    light
   ;
   [Initialise;
    random(0);   !Z-code standard says 
             !that this command is
             !SUPPOSED to initialize
             !the random number generator
    location=SimpleRoom;
   ];
   Include "Grammar"; !library include
   Verb "rand" !!! an example rand command
    * -> rand
   ;
   [randsub t; !implementation of new verb
    for(t=1:t<10:t++) 
    print random(100),"^";
   ];
   end;
This is a pretty straightforward example. One room and a grammar rule the prints ten random numbers. Compile this, and run the resulting .Z5 file under Frotz. At the command prompt enter in the command "RAND" and you'll see the ten random numbers printed. Looks good so far.

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.

The Fix...

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.
   
   REPLACE KeyboardPrimitive;
   global randseed=0;
   [KeyboardPrimitive a_buffer a_table t;
    if(randseed>0) !Already randomized,  
                !so use library code
       read a_buffer a_table;
    else !otherwise, get and randomize
    {
       @aread a_buffer a_table 1 procrandseed t; 
       for(t=0:t<randseed:t++) random(1); 
    }
   ];
   [procrandseed;
    randseed++;
    return false;
   ];

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.



Copyright 2004 - 2017 : Jim Fisher
OnyxRing.com has been given the potentially non-exclusive right to display the content of this article. However the original author retains all rights. Permission to reproduce this article -- either in part or in whole -- is left strictly to the discretion of the original author.

Table of ContentsAuthorsSearchIndex
Would you
recommend this
article to
someone else?
You bet I would! Heck No!