22.18 A randomizer module

22.18 A randomizer module

We are going to be randomizing our parameters a lot in this book. In most of our programs we'll want to have a bunch of different objects, and we will want to use randomization to keep the objects from all being the same. In our more advanced programs we may have our objects use randomization so as to give themselves an appearance of interesting behavior.

Even when there's only one object using a given set of parameters, it's not always clear which values to use. It may be that there are no 'best' values, and it's more interesting to look at a wide range of possibilities. In other cases, there may be a 'best' set of values, but the only way to try and find these values is to feel around the parameter space by trying lots of random possibilities.

The C library of standard functions supplies three relevant functions that are of significance here.

  long int time(NULL)   srand(unsigned int seed)   short int rand()  

Randomizing with the C library

Each call to rand() returns a 15-bit positive integer that lies between 0 and +32K. The way rand works is that the C library maintains a hidden 32-bit integer variable holdrand . When your program starts, holdrand has a default value of 1. Every time you call rand() , holdrand is replaced by holdrand * 214013 + 2531011 , and rand returns the low 15 bits of the high word of holdrand . (That is, the return value is (holdrand >> 16) & 0x7FFF .)

If rand starts with the same value of holdrand , it's always going to give you the same sequence of values. So what we usually do is to use the srand function to initialize the randomizer to a fresh state. What srand(seed) does is simply sets the hidden holdrand variable equal to the seed argument.

But how can our deterministic program come up with a 'random' seed to feed into srand ? What we usually do is to use the time(NULL) function. This function returns the number of seconds that have elapsed since midnight, January 1, 1970. For whatever reason, this is the official 'birth instant' of our computer era, a little like the anomalous moment when the Western calendar went from BC to 1AD.

The cRandomizer class

To make it easier to randomize things, we wrap the randomizing functions up inside a class called cRandomizer . Instead of having to use the % operator to bring numbers into range, the cRandomizer has a method random(N) that will give you a number between 0 and N “ 1, a method random(lo, hi) that will give you a number from lo to hi inclusive, a randomReal(loreal, hireal) method to give you a real number between loreal and hireal , a randomColor() method, and so on. The randomBOOL(double truthweight) is a cute function that gives lets you specify a truthweight between 0.0 and 1.0. The truthweight is the probability of the randomBOOL returning a TRUE . So randomBOOL(0.75) would return TRUE three fourths of the time and would return FALSE one fourth of the time.

The default cRandomizer constructor seeds it with the time function. You also have the option of feeding a seed number into the constructor so as initialize your cRandomizer into some specific state. When you're debugging a program it's often a good idea to have the cRandomizer always set to the same state so that it's easier for you to reproduce the exact same behavior over and over.

 #ifndef RANDOMIZER_H  #define RANDOMIZER_H  /* This is a set of 32-bit-based randomizing functions. The functions  use standard C library techniques. The code is written to be portable,  although there is one line you need to comment in or out at the head  of Randomizer.cpp according to whether or not you use Microsoft MFC.      These randomizing functions are based on a modular scheme derived  from the Microsoft implementation of the C library randomizer. In the  Microsoft implementation, the C Library int rand() function works by  maintaining a _holdrand variable and iterating _holdrand * 214013 +  2531011. rand() returns (_holdrand >> 16) & 07FFF, which is a 15 bit  positive short integer. We use the same scheme, but tailor it to  return a 32 bit unsigned long integer. Returning _holdrand gives too  much correlation, so we actually execute the _holdrand update twice,  and use the high words of the two successive _holdrands as the upper  and lower words of the value we return.      The Randomizer.cpp file includes a historical note at the end about  an unsuccessful attempt to base the randomizer on Wolfram's CA Rule 30.  */  #include "realnumber.h"      //For the Real typedef, which is double or float  class cRandomizer  {  private:      static cRandomizer * _pinstancesingleton;      unsigned long _seed; //Start value of _shiftregister      unsigned long _shiftregister;          //Used internally for the running compute process.      unsigned long _thirtytwobits();          // The internal pseudorandom function used.      cRandomizer();          //Uses the C Library randomizer and seeds it with the time.      cRandomizer(unsigned long seednumber);          //C Library randomizer seeded by seednumber.  public:      static cRandomizer * _pinstance();      static void delete singleton ();      unsigned long getSeed(){return _seed;}      void setSeed(unsigned long seednumber);          //Start off in a specific state      unsigned long randomizeSeed(void);          //Seed with the time in seconds      unsigned long random(); //Return an unsigned long int      unsigned long random(unsigned long n);          //Return an int between 0 and n  1      long random(long lon, long hin);          //int between lon and hin inclusive      BOOL randomBOOL(Real truthweight = 0.5);          // Return TRUE truthweight often.      unsigned char randomByte(void); //Return a byte between 0 and 255      unsigned short randomShort(unsigned short n);          // Short between 0 and n-1      Real randomReal(void); //A real between 0.0 and 1.0      Real randomSignedReal(void); //A real between -1.0 and 1.0      Real randomReal(Real lo, Real hi); //A real between lo and hi      Real mutate(Real base, Real lo, Real hi, Real percent);          //Mutate base by percent of size.      int mutate(int base, int lo, int hi, Real percent);          //Mutate base by percent of size.      unsigned long mutateColor(unsigned long base, Real percent);          //Mutate a color.      Real randomSign(void); //1.0 or -1.0      void randomUnitDiskPair(Real *x, Real *y);          // Makes (x,y) a random point with distance <= 1 from (0,0)      void randomUnitPair(Real *x, Real *y);          // Makes (x,y) a random point with distance 1 from (0,0)      unsigned long randomColor(); //A Windows COLORREF number.  };  #endif RANDOMIZER_H 

Since we're going to use the cRandomizer a lot, you might wonder if, as time goes by, the cRandomizer will accumulate more and more functions. Later, for instance, we might have some cMyClass with parameters that we like to randomize, so will it make sense to add a void cRandomizer::randomizeMyClass(cMyClass &myclassobject) ; method to the cRandomizer class?

No, this would be a bad idea, because then we'd always be coming back and changing the Randomizer.h and Randomizer.cpp files. For sanity 's sake, it's much better to try and get your basic, general-purpose files working once and for all and not be continually going back and changing them. This is one of the reasons why we don't use COLORREF as a type in Randomizer.h (instead we use the equivalent unsigned long ).

So how then can we write a method for randomizing a cMyClass with a cRandomizer ? We'll put a randomizing method that's a member of cMyClass , that is, we'll have a void cMyClass::randomize() . And then inside the implementation for this method, we'll use standard cRandomizer calls to randomize the fields of cMyClass .

The static cRandomizer::Randomizer object

So as not keep having to create new cRandomizer objects to randomize things, we give the cRandomizer class a static cRandomizer member. This means that anywhere in our Pop program we can call, say, cRandomizer::RANDOMIZER.randomReal() to get a random real number. This is very much a Java-style thing to do; Java has lots of special members and methods that are statics of its standard classes. This is an example of what's called the 'Singleton pattern.'

In C++ a static such as RANDOMIZER has to be declared in a *.h file and it has to actually be instantiated , or 'live' as an instance, inside a *.cpp file. We put the instantiation inside randomizer.cpp .

 cRandomizer* cRandomizer::pinstance()/* pinstance() allocates      _pinstancesingleton if it's NULL, then returns it. */  {      if (cRandomizer::_pinstancesingleton == NULL)          //First time pinstance() is called  #ifndef _DEBUG //not _DEBUG means Release build          cRandomizer::_pinstancesingleton = new cRandomizer();      /* In Release build, the default constructor seeds          with the time for variety. */  #else // _DEBUG means Debug build          cRandomizer::_pinstancesingleton = new cRandomizer(1);      // In Debug build, use a fixed seed to help replicate bugs.  #endif //End the _DEBUG switch      return cRandomizer::_pinstancesingleton;  } 

In understanding this code, you need to know that Visual Studio has a line #define DEBUG that gets preprocessed if and only if you are making the Debug build and not the Release build. Thus in our code, the compiler chooses between the two declarations depending on whether you are doing a Debug or a Release build.

We don't want the randomizer to be so random in Debug, because there we like to be able to start up a session over and over and keep seeing the same bug in the same spot.

Software Engineering and Computer Games
Software Engineering and Computer Games
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net