EXPLORING SMARTBASIC
RANDOM NUMBERS

I have written on several occasions about random numbers. I will probably continue to do so until such time as we have exhausted all the questions, gripes, and hacks. This expose will explain how and why RANDOM works, and help you get the most out of it.

The RND function returns a random number between 0 and 1. Although the value
is never 1, it is possible for it to equal exactly 0. There are 3 ways to ask for a random number:

RND(1) or RND(2), or any positive number, will extract the NEXT random number from the generator. The argument value is unimportant.

RND(0) will restore the previously generated random number. This might be useful if your program FORGETS what the last random number was and you want to double check its value without affecting anything else

RND(-x) will reset the random seed to a particular value based on the supplied number. This can be useful in GAME debug situations where you may want to recreate an exact condition.

Now what to do with random numbers? What use is a number between 0 and 1. Well quite simply, you multiply it by the number of choices you have to make. If you want to randomly decide whether to go North South East or West, use something like:

move = INT(RND(1)*4+1)

Note that we multiply by 4 (the number of choices) and add 1 before taking the INT. This will give us a random INTEGER which is 1, 2, 3, or 4. This result can be used with an ON GOTO statement to branch to the correct routine.

Random number generation on the ADAM is accomplished by a complex, routine at 4696(1258). It either loads the floating point accumulator with the 4-byte CURRENT random seed, or with 4 bytes representing the user-supplied value if RND(-x) was used. It then points to 4 constants at address 4552(11C8). These values are 45, 230, 64, and 187. You can change those if you wish but they will likely cause your random number generator to fail. Each of these numbers is multiplied in turn to the current value in the FPA. After each recursion, one of the 4 CURRENT RANDOM bytes is updated with the OVERFLOW of the calculation.
The resulting value in the floating point accumulator is then reduced to a number between 0 and 1 to return to the caller.

If all that sounds confusing, it is! But there is more...

NEW and RUN insist on re-initializing the random seed by copying the 4 static bytes FB 40 D2 92 into the random generator at address 16190 (3F3E) and 16192 (3F40). The routine that accomplishes this task is found from 11907 (2E83) to 11918 (2E8E). This is supposed to prevent the random number generator from breaking down but it has the effect of generating the same random numbers ever time a program is run. I have run some quite severe tests on the random generator. I have left it crunching away for a full day and periodically reporting the distribution of numbers. I have seen no evidence of the generator breaking down.

You may have seen some random routines which try to overcome this problem. While there are many approaches, I will outline some of the more common ones, illustrating their concept and their shortcomings.

1) The random keypress

In the beginning, we had little knowledge of ADAM’s workings, but we knew something about RND(-x). So what we needed was a variable (-x). Some of the first efforts just asked you to press any key; its value would be the random seed. Unfortunately, most people press the same key when ANY KEY is asked for. Thus a program using this approach will likely be random between different players rather than different each time the same player plays it:

10 PRINT “Press any key to start”
20 GET keys
30 x=ASC(key$)
40 r=RND(-x)

2) The PDL approach

If a game uses the joystick, then the PDL function is useful to set a random seed. Here, the basic principle is HOW LONG before a button on the keypad is pressed:

10 PRINT “Press any key on the keypad”
20 x=x-1:REM start to set a random seed
30 if not PDL(11) goto 20: REM carry on until key is pressed
40 r=RND(x):REM set the seed

This routine can be defeated quite easily by holding down a button while typing RUN. You can make this more complex by asking for multiple buttons; it is unlikely that buttons will be pressed at the same speed every time:

10 PRINT “Press 1-2-3 on the keypad”
20 GOSUB 100: REM wait for a button
30 if x<>1 goto 20:REM did not press 1
40 GOSUB 100: REM wait for a button
50 if x<>2 goto 40
60 GOSUB 100: REM wait for a button
70 if x<>3 goto 60
80 goto 120
100 x=PDL(13):if x<15 then RETURN:rem a button was pressed
110 y=y-1:GOTO 100:REM set seed and wait

Using this kind of routine was for a long time our only source of randomness. It became annoying when a program used only the keyboard for input but required joystick input to start it.

3) Reading the keyboard

Trying to use the above approach with the keyboard was unsuccessful at first. The GET function would wait forever until a key was pressed. This was partly the reason for something like a random key as outlined in number 1. Finally, we discovered that the last keypress was recorded in memory address 64885. We had also figured out that we could POKE that high by resetting the POKE limit. The following (with a few editorial remarks) was submitted years ago by Bob Tarnowski (unknown origin):

10 POKE 16149,255:POKE 16150,255: REM reset POKE limit
15 POKE 64885,0:REM reset the last keypress
20 PRINT “Press Any Key to Continue”
30 a=a+1:if a>2000 then a=1
40 if PEEK(64885) then r=RND(-a):GOTO 60
50 GOTO 30
60 REM continue with your program

This routine was quite advanced at its time. Bob had figured out that large RND(-x) numbers did not effectively create a random seed. You can illustrate this quite easily by trying the following program:

10 INPUT “Large number please “;x
20 r=RND(-x)
30 for x=1 to 10:PRINT INT(RND(1)*10);” “;:NEXT
40 PRINT: x=x+l: GOTO 20

This program will show the difference in random seeds from something like 100000, 100001, 100002, etc. With numbers of that size, everything appears normal. Now try 100,000,000 or any number in that vicinity. You will see that the random numbers don’t appear to be as random any more. Loosely, that can be equated to the INFINITY+1=INFINITY paradox; numbers of that size can’t distin-guish themselves from another that is just one bigger.

Rather than use the RND(-x) approach, why not just DEAL random numbers off the top of the deck until the contestant yells STOP! This is the way magicians perform random card tricks is it not? Since we already suspect that the random number generator does not deteriorate, there is no problem if hundreds (or thousands) of numbers are dealt.

So now you can go back over the previous examples and substitute the INCREMENT value with something like R=RND(1). Thus each time a PASS is made, another random number is dealt from the top of the deck. Don’t forget to also remove the RND(-X) line in the routines.

4) The Random Word

This one is similar to the PDL function one using multiple keypresses. In this case, we ask the player to type his/her name. The random seed is set, not by the letters themselves, but by the time taken to type them in. I would suggest the use of this type of routine only if the player’s name will actually be used somewhere in the program:

100 POKE 16149,255:POKE 16150,255: REM POKE limit
110 PRINT “Enter your Name:”
120 POKE 64985,0
130 p=PEEK(64885):if p=0 then r=RND(1):goto 130: REM wait
140 if p=13 goto 200: REM must be end of name
150 n$=n$+CHR$(p):GOTO 120: REM build name one letter at a time
200 REM program starts here

Particularly if a player’s name is very long, this routine could yield thousands of different starting positions.

5) High tech approach

Machine language programmers know that the Z-80 has a refresh register. This register will contain a random value between 0 and 127. It is the trigger that is used to REFRESH memory. Enter this submission, also from an unknown source:

100 for i=0 to 5:READ d:POKE 1056+i,d:NEXT
110 DATA 237,95,50,38,4,201
120 CALL 1056:x=RND(-PEEK(1062))

The first two lines POKE in a routine which gets the refresh value and POKE it into address 1062. We then extract that value and use it to create a -x type seed. Unfortunately, this method only gives us 128 unique starting positions. Still, it is better than nothing and it cannot be cheated since there is no way the PLAYER can know what state the refresh register is in. We will come back to this one in a moment.

But why should BASIC reset the seed and force me to do all this extra work? NO REASON. As a matter of fact, you can POKE zeroes into addresses 11907 to 11911 to disable the re-setting of the random seed. I have experienced no ill effects from this approach.

Furthermore, for those using my zero page clock, you can make your random generator use the Hours, Minutes, Seconds, and Jiffies as a random seed with the following:

10 DATA 42,84,0,34,62,63,42,86,0,34,64,63
20 FOR x=11907 to 11918: READ y: POKE x,y: NEXT

Add this routine to your HELLO file and you won’t have to worry about randoms any more. Remember, however, that the clock is turned off when in GRAPHICS modes. A game that terminates in graphics mode will restart with the same random seed if RUN is typed from the graphics mode. Accordingly, those games should end with a TEXT command to restart the clock.

6) Higher tech

If we go back to example number 4, we can combine a few things we have learned. Rather than having to CALL a routine that gets the REFRESH register, why not incorporate it into the routine that would normally have RESET the seed every time a program is run..... follow losely:

110 DATA 237,95 :REM this gets the REFRESH into register A
120 DATA 111 :REM this puts it in register L
130 for i=0 to 2:READ d:POKE 11907+i,d:POKE 11913+i,d:NEXT

The last line replaces the two routines which load the HL register with a static value with one that inserts a DYNAMIC one. This true random fix can be inserted into your HELLO file and run only once as it is a permanent fix. This routine will work if you are in GR HGR or TEXT mode. Its only disadvantage is that it creates only about 256 starting random seeds. As it is always functioning for you automatically, you could supplement it with a simple keypress routine as in the first three examples. Although this approach cannot be MY personal favorite
since I did NOT invent it, it is the one that I recommend the most because of its simplicity and apparently permanent nature.

7) Using the interrupts

The following routine, with a few more remarks thrown in, is also from an unknown source. It makes use of the Z-80 non-maskable-interrupt (or NMI). BASIC uses the NMI as an automatic/timed interrupt to run the FLASH mode. It does not matter whether or not FLASH has been activated, the routine at 66(hex) or 102(decimal) is executed 60 times per second. The following routine must be executed in the exact order shown below as timing is critical when POKEing around in a routine that gets executed 60 times per second... there is no room
for error:

100 DATA 229,42,64,63,35,34,64,63,225,201
120 for x=172 to 181:read ml:poke x,ml:next
130 poke 171,0: REM let the end of the routine fall through
140 poke 11907,201: REM disable the random RESET routine

Line 120 POKES in a routine which increments two of the 4-byte random seed number. Such a small change, effected at 60 times per second is enough to truly randomize not only the first number you get, but all the others that follow. This means that even if by chance, you start at a predetermined position in the DECK, your chances of getting the same number on the second pass are very remote. The routine itself is POKEd in AFTER the RETURN from the NMI routine at 102 (it ends at 171). Thus while we are POKEing data, nothing is happening. Once
all the data is POKEd in, we open the door at line 103. The final line disables the routine that puts those nasty static numbers every time a program is RUN. While it is an ingenious way of creating truly random numbers, this routine places some extra work on the CPU. The only way you might notice a step down in speed is when you have a very intensive loop such as in a sort routine. Note also that this routine will have no effect on the seed while in GR or HGR mode.

WARNING!!!! if you are using my zero page clock, this routine is incompatible as it uses some of the same memory areas.

8) Low tech but effective

The following is my favorite in simplicity and safety -- it is a hard one to understand let alone CHEAT. You can prelude this one with a preset from the refresh register or by disabling the RUN reset altogether if you want a bit more flexibility. Basically, it remembers what key was pressed at the time the program started. It then stalls until a different key is pressed. Thus if you
type “RUN” followed by “N”, the program will not start until you press a key other than “N” and then press “N” again. It is possible to cheat this one as well but I won’t tell you how. Many of my previous games used something along those lines so I don’t want to give away all my secrets.

100 REM ask for instructions and set random seed
110 REM can be included at any program start
120 REM Instructions or help question is a convenient way
130 REM of getting a key press
150 PRINT “Do You Want Instructions (Y/N)?”
160 p=PEEK(64885): REM record current keypress value
165 REM stay here until a different key is pressed
170 if p=PEEK(64885) then r=RND(1):goto 170:REM deal out a number
180 p=PEEK(64885):REM get current key
190 REM now check for y Y n N
200 REM if none of the above branch back to 170 until different

While not fool proof, this method will give the hackers a hard time. But look at it this way: If I play a game it’s no fun if it is the same all the time. Those who want to play THE SAME GAME will find a way so don’t bother with them

9) The last word

Finally, with special thanks to Bob Currie EAUG (Edmonton) and the ANN network which published his article on random numbers, I have another addition to the randomness of random numbers. The following is an extract from his article:

“If we scan all of the accessible memory locations in the ADAM, we find that there are three addresses at which one does not always get the same number. At 17003(dec) we get a zero seven times out of ten and a one three times out of ten. At 65220(dec) we get a 4 seven times out of ten and a 140 three times out of ten. At 17011(dec) we get the numbers from 1 to 12 with a pretty much even chance of getting any one of them.”

Bob goes on to say that you can add the 3 values at these addresses to form the basis for a RND(-x) routine. I have come upon a more ingenious method. Firstly, adding the numbers together gives results ranging from 5 to 17 or 141 to 153 or 26 unique combinations. If we instead use the value at 17003 as a multiplier, we can increase the range to 48 unique combinations:

r=PEEK(65220)+PEEK(17011) + 12*PEEK(17003)

The first half of the equation yields 5 to 16 or 141 to 152. Adding 12 times the value at 17003 expands this range to 5 to 28 or 141 to 164. But now what do we do with this number? Well we could always use a RND(-x) function (as Bob suggests), but that would limit us to ONLY 48 unique combinations. Just POKE it into one of the RANDOM SEED values prior to scanning the keyboard. This will effect some change in the seed number while not always having the same effect:

100 REM ask for instructions and set random seed
110 REM can be included at any program start
120 REM Instructions or help question is a convenient way
130 REM of getting a key press
140 REM
145 POKE 16191,PEEK(65220)+PEEK(17011)+12*PEEK(17003)
150 PRINT “Do You Want Instructions (Y/N)?”
160 p=PEEK(64885): REM record current keypress value
165 REM stay here until a different key is pressed
170 if p=PEEK(64885) then r=RND(1):goto 170
180 p=PEEK(64885):REM get current key
190 REM now check for y Y n N
200 REM if none of the above branch back to 170 until different


Guy Cousineau
1059 Hindley Street
OTTAWA Canada
K2B 5L9

 

Back to Top