! ==============================================================================
! random.h -- a portable and predictable replacement for the standard system
!             'random' function.
!
! Version 1.3 (Feb06) by Roger Firth ([email protected])
!                     based on an algorithm by George Marsaglia translated
!                     into Inform by Richard Bos ([email protected]).
!
! Compatibility: for Inform 6.3 (Z-code and Glulx).
!
! Dependencies: none.
!
! License: The Creative Commons Attribution-ShareAlike License published at
! http://creativecommons.org/licenses/by-sa/2.5/ applies to this software.
! In summary: you must credit the original author(s); if you alter, transform,
! or build upon this software, you may distribute the SOURCE FORM of the
! resulting work only under a license identical to this one. Note that the
! ShareAlike clause does not affect the way in which you distribute the COMPILED
! FORM of works built upon this software.
! Copyright remains with the original author(s), from whom you must seek
! permission if you wish to vary any of the terms of this license.
! The author(s) would also welcome bug reports and enhancement suggestions.
!
! ------------------------------------------------------------------------------
! INSTALLATION
!
! Place this line before Parser.h:
!
!   Replace random;
!
! Place this line after Grammar.h:
!
!   Include "random";
!
! ------------------------------------------------------------------------------
! USAGE
!
! The library replaces the 'random' function which is built into the standard
! Inform system, and provides similar capabilities:
!
!   random(N)               returns a random number in the range 1..N.
!   random(a1,a2, ... aN)   returns a random choice from the given selection.
!
!   random(0)               initialises the seed unpredictably, and returns zero.
!
!   random(-S)              initialises the seed repeatably, and returns zero;
!                           this is needed only to avoid unpredictable random
!                           numbers (the default). As suggested in Section 2.4
!                           of the Z-machine Standard, the algorithm is:
!                           - if the absolute value of S is 1..999, the 'random'
!                           sequence is 1,2,3, ... S,1,2,3, ... S,1,2,3, ...
!                           and each random(N) returns the next entry in this
!                           sequence, modulo N.
!                           - if the absolute value of S is 1000..32767, each
!                           random(N) returns the next entry in the random
!                           sequence starting at S, modulo N.
!
! In DEBUG mode, the verbs RANDOM and RANDOM R[EPEATABLE] issue the call
! random(-1000) to seed a repeatable random sequence. The verb RANDOM S[ERIAL]
! issues the call random(-100) to seed a repeating rising sequence.
! The verb RANDOM U[NPREDICTABLE] reverts to unpredictable random numbers.
!
! ------------------------------------------------------------------------------
! COMPATIBILITY
!
! 1.    In the standard function, the RANDOM verb has only one option: to seed a
!       repeating rising sequence _or_ a repeatable random sequence (depending
!       on the interpreter being used). Here, RANDOM _always_ seeds a repeatable
!       random sequence, and the player must use RANDOM S[ERIAL] to obtain a
!       repeating rising sequence.
!
! 2.    In the standard function, the number of arguments to random(a1,a2, ... aN)
!       is unspecified. Here, the limit is seven.
!
! 3.    In the standard function, the arguments to random(a1,a2, ... aN) must
!       all be constants. Here, they can also be variables.
!
! 4.    In the standard function, the final argument to random(a1,a2, ... aN)
!       can be a zero. Here, the final argument must be non-zero. Thus, change
!       random(6,4,2,0) to random(6,4,0,2).
!
! 5.    In the standard function, the call random() is not permitted. Here, it
!       is identical to random(0), setting the random seed to an unpredictable
!       value.
!
! 6.    The use of the @log_shift instruction means that this code won't compile
!       to Z-code Version 3. To get round this, you could replace the calls to
!       shiftL() and shiftR() by multiplication and division statements.
!
! ------------------------------------------------------------------------------
! NOTES
!
! 1.    Normally, the system function performs adequately. You will probably
!       need this replacement only in exceptional circumstances, especially as
!       it is likely to be significantly slower than the original (though in
!       typical usage, you probably won't notice the difference).
!
! 2.    The generation of random numbers is a function provided by the
!       Z-machine and Glulx interpreters. If you discover an interpreter with
!       a poor generator, you may wish to replace it with this one, which
!       provides a good random spread of numbers.
!
! 3.    Because each interpreter's generator is different, the same game will
!       obtain different random numbers when played on different interpreters.
!       For test purposes, or to support a walkthrough of a demonstration game,
!       you may wish to obtain exactly the same random behaviour, irrespective
!       of the platform on which the interpreter is running (and also
!       irrespective of whether you're using the Z-machine or Glulx).
!
! 4.    Before Including this file, you can define:
!
!           Constant RANDOM_SERIAL_DEFAULT   = 100;
!           Constant RANDOM_SERIAL_THRESHOLD = 1000;
!
!       to override the standard values. If you set RANDOM_SERIAL_THRESHOLD
!       to 1, the RANDOM S[ERIAL] repeating rising sequence is disabled.
!
! ------------------------------------------------------------------------------
! HISTORY
!
! Version 1.3: (Feb06) Changed RANDOM default to be 'repeatable'.
! Version 1.2: (Feb06) Improved comments suggested by Richard Bos.
! Version 1.1: (Jan06) Incorporated 'rising sequence' feature.
! Version 1.0: (Jan06) Original by Roger Firth.
!
! ==============================================================================
! Replacement for system 'random' routine

System_file;

Array  randomArgs --> 8;                ! randomArgs-->0 not used

[ random a1 a2 a3 a4 a5 a6 a7
   n;                                  ! number of arguments to routine
   randomArgs-->1 = a1; randomArgs-->2 = a2; randomArgs-->3 = a3;
   randomArgs-->4 = a4; randomArgs-->5 = a5; randomArgs-->6 = a6;
   randomArgs-->7 = a7;
   for (n=7 : n>0 : n--) if (randomArgs-->n) break;
   if (n > 1) return randomArgs-->rnd(n);
   return rnd(a1);
];

! ------------------------------------------------------------------------------
! Random Number Generator

Default RANDOM_SERIAL_DEFAULT   100;
Default RANDOM_SERIAL_THRESHOLD 1000;

Global randomHiSeed;                    ! High 15 bits
Global randomMdSeed;                    ! Middle 2 bits
Global randomLoSeed;                    ! Low 15 bits
Global randomSerial;                    ! true: generate rising sequence

! The 32-bit seed (previous random number) is held as three Inform integers.
! There is nothing magical about the above division, except that it makes both
! the middle right-shift and the return statement simpler. This is a bit of
! serendipity, because the right-shift is by 17 bits, which just happens to be
! the 32-bit complement of the 15 bits needed for an Inform integer.
!
! George Marsaglia calls this a 3-shift shift register RNG, and says in
! message id [email protected]
! (look this up at, for example, http://groups.google.com/advanced_search)
!
! Given an initial 32-bit integer y, randomly selected from the set of
! unsigned values S={1,2,...,2**32-1}, the application of
!     y = y ^ (y<<13);  y = y ^ (y>>17);  y = y ^ (y<<5);
! (where '^' is the bitwise exclusive OR operator, '<<' is the logical left
! shift operator and '>>' is the logical right shift operator) will produce
! a sequence of 2**32-1 different random integers, each uniformly distributed
! over S. They will not be independent uniform choices from S, but will seem
! to behave as though they were, satisfying all but the most demanding tests
! of randomness, much better than do the more commonly used congruential RNGs.

[ rnd value
   tmp;

   if (value < 0) {                    ! seed a serial/predictable sequence
       value = -value;
       randomHiSeed = value; randomMdSeed = 0; randomLoSeed = value;
       if (value < RANDOM_SERIAL_THRESHOLD) randomSerial = true;
       else                                 randomSerial = false;
       rfalse;
   }

   if (value == 0) {                   ! seed an unpredictable sequence
       randomiseSeed();
       rfalse;
   }

   if (randomSerial) {                 ! generate next serial value
       if (++randomLoSeed >= randomHiSeed) randomLoSeed = 0;
   }
   else {                              ! generate next random value
       if (randomHiSeed|randomMdSeed|randomLoSeed == 0) randomiseSeed();

       ! Left-shift by 13 places
       ! Seed: 0ABCDEFGHIJKLMNO 00000000000000Pa 0bcdefghijklmnop
       ! Mask: 0NOPabcdefghijkl 00000000000000mn 0op0000000000000

       tmp = shiftL(randomHiSeed&$0003,13) | shiftL(randomMdSeed&$0003,11) | shiftR(randomLoSeed&$7FF0,4);
       randomHiSeed = xor(randomHiSeed, tmp);
       randomMdSeed = xor(randomMdSeed, shiftR(randomLoSeed&$000C,2));
       randomLoSeed = xor(randomLoSeed, shiftL(randomLoSeed&$0003,13));

       ! Right-shift by 17 places
       ! Seed: 0ABCDEFGHIJKLMNO 00000000000000Pa 0bcdefghijklmnop
       ! Mask: 0000000000000000 0000000000000000 0ABCDEFGHIJKLMNO

       !randomHiSeed = xor(randomHiSeed, 0);
       !randomMdSeed = xor(randomMdSeed, 0);
       randomLoSeed = xor(randomLoSeed, randomHiSeed);

       ! Left-shift by 5 places
       ! Seed: 0ABCDEFGHIJKLMNO 00000000000000Pa 0bcdefghijklmnop
       ! Mask: 0FGHIJKLMNOPabcd 00000000000000ef 0ghijklmnop00000

       tmp = shiftL(randomHiSeed&$03FF,5) | shiftL(randomMdSeed&$0003,3) | shiftR(randomLoSeed&$7000,12);
       randomHiSeed = xor(randomHiSeed, tmp);
       randomMdSeed = xor(randomMdSeed, shiftR(randomLoSeed&$0C00,10));
       randomLoSeed = xor(randomLoSeed, shiftL(randomLoSeed&$03FF,5));
   }

   ! Return 15-bit value based on randomLoSeed

   return (randomLoSeed % value) + 1;
];

! ------------------------------------------------------------------------------
! Low-level utility functions

#Ifdef TARGET_ZCODE;

[ randomiseSeed;
   @random 32767 randomHiSeed;
   @random 3     randomMdSeed;
   @random 32767 randomLoSeed;
   randomSerial = false;
];

[ shiftL a n r; @log_shift a n r; return r; ];

[ shiftR a n r; n = -n; @log_shift a n r; return r; ];

[ xor a b;      return (a|b) & (~(a&b)); ];

#Ifnot; ! TARGET_GLULX

[ randomiseSeed;
   @random 32767 randomHiSeed; randomHiSeed++;
   @random 3     randomMdSeed; randomMdSeed++;
   @random 32767 randomLoSeed; randomLoSeed++;
   randomSerial = false;
];

[ shiftL a n r; @shiftl a n r;  return r; ];

[ shiftR a n r; @ushiftr a n r; return r; ];

[ xor a b r;    @bitxor a b r;  return r; ];

#Endif; ! TARGET_

! ------------------------------------------------------------------------------
! Enhanced RANDOM verb

#Ifdef DEBUG;

[ RandomUnpredictableSub;
   noun = 0;
   RandomSeedSub();
];

[ RandomSerialSub;
   noun = RANDOM_SERIAL_DEFAULT;
   RandomSeedSub();
];

[ RandomRepeatableSub;
   noun = RANDOM_SERIAL_THRESHOLD;
   RandomSeedSub();
];

[ RandomSeedSub;
   if (noun < 0) noun = -noun;
   random(-noun);
   switch (noun) {
     0:
       "[Random number generator now unpredictable.]";
     1 to RANDOM_SERIAL_THRESHOLD-1:
       "[Random number generator now serial.]";
     default:
       "[Random number generator now repeatable.]";
   }
];

Extend 'random' replace
   *                                   -> RandomRepeatable
   * 'serial'/'s//'                    -> RandomSerial
   * 'repeatable'/'r//'                -> RandomRepeatable
   * 'unpredictable'/'u//'             -> RandomUnpredictable
   * number                            -> RandomSeed;

#Endif; ! DEBUG

! ==============================================================================