
**********************************************************************
files: Random.TXT, Random.TAP
       Random.BAS, RNDFun.ASM, RandPlot.ASM
**********************************************************************


** Constraining a list of RND numbers
   ==================================

The general Spectrum rule for selecting any random integer from a range is:
A.	LET a=s+INT(RND*n)
where s is the lowest value, n is the number of values and s+n-1 is the highest value.

For selecting a random number from a non-sequential range the rule is:
B.	LET a=s+INT(RND*n)*g
where s is the lowest value, n the number of values and g the gap between the values; eg.
	LET a=4+INT(RND*4)*5
gives one of 4,9,14,19 as it adds 0,5,10,15 to 4.

If the numbers in the required set are not evenly spaced then, if there are not too many possible values, just put them in an array or string and use RND to calculate a pointer.

As can be seen, if the gap 'g' is one then A = B. With no modifiers the expression INT(RND*n) will return an integer between 0 and n-1.

As an example, generating a list of numbers in the range 1-49. Because of the limit to the precision of numbers the Spectrum can store, if RND returns .99999999 then RND*49 gives 49 instead of 48.99999951, so then adding one will give 50.

Use "1+INT ((RND+1e-5)*(49-1e-5))"; leave this test loop running for a while to prove it.

[code]
 300 DIM b(49):
     FOR p=0 TO 0 STEP 0:
       LET b=1+INT ((RND+1e-5)*(49-1e-5)): PRINT b;":";:
       LET b(b)=b(b)+1: POKE 23692,255:
     NEXT p
[/code]

It would quit with "3 Subscript wrong" if it got a number outside of 1-49. It won't quit. It generates a fairly even spread across all 49 numbers, as can be seen from listing the array contents afterwards.

That can be used as a Lotto numbers generator, pressing Break with your eyes closed and taking the last six numbers printed. It can generate the same number twice in six, of course, so here's an enhanced version which stops that.

[code]
 300 DIM b(49,2): LET n=0:
     FOR p=0 TO 0 STEP 0:
       LET b=1+INT ((RND+1e-5)*(49-1e-5)): LET n=n+1:
       PRINT STR$ b+":" AND b(b,2)<n-5;:
       LET b(b,1)=b(b,1)+1: LET b(b,2)=n: POKE 23692,255:
     NEXT p
[/code]

   ==================================

Then there's this ...

[code]
  10 REM MARK RANDOM NUMBERS IN A ARRAY
  30 FOR m=1 TO 21
  40   DIM A(49)
  70   FOR n=49 TO 44 STEP -1
  80     LET x=1+INT (RND*n)
  85     IF a(x) THEN GO TO 80
  90     LET a(x)=50-n
 110   NEXT n
 112   REM PRINT RANDOM NUMBERS IN SETS OF 6
 115   FOR o=1 TO 49
 116     IF a(o) THEN PRINT " " AND o<10;o;"  ";
 120   NEXT o:
       PRINT :
     NEXT m


 9  25  26  29  31  34
21  24  27  28  34  44
 1   8  18  19  23  41
 3  14  17  23  31  43
 3   6   8  19  22  27
 9  21  33  34  42  43
 8  24  39  42  44  45
 6  14  18  25  36  41
 4   7  14  15  23  36
 4   9  18  22  23  28
 2  12  21  23  42  46
 7   8   9  13  19  39
 6   8   9  12  25  47
 1   2  13  18  28  33
 5   7  10  27  28  34
 1   6  19  33  37  43
 1   9  12  29  30  37
12  16  20  23  35  44
 8  10  21  26  30  40
 1  16  25  34  39  45
 2   4   7   9  18  37
[/code]

   ==================================

Alternatively, here's an example of using a recursive function to do the duplicate number check, avoiding any extra use of GO TO, FOR...NEXT or IF...THEN. Line 80 does the looping within itself via the FN call.

[code]
   3 DEF FN a()=FN r(1+INT (RND*49)):
     DEF FN r(x)=VAL (("FN a()" AND a(x))+("x" AND NOT a(x)))
  10 REM MARK RANDOM NUMBERS IN A ARRAY
  30 FOR m=1 TO 21
  40   DIM A(49)
  70   FOR n=1 TO 6
  80     LET a(FN a())=n
 110   NEXT n
 112   REM PRINT RANDOM NUMBERS IN SETS OF 6
 115   FOR o=1 TO 49
 116     IF a(o) THEN PRINT " " AND o<10;o;"  ";
 120   NEXT o:
       PRINT :
     NEXT m
[/code]

It's possible to print the six numbers on a line without the IF...THEN at line 116, then the whole routine can be written in one line of BASIC, but it slows things down quite a lot. Add line 114 and change line 116 thus:

[code]
 114 LET s$=CHR$ 22+CHR$ 0+CHR$0
...
 116 PRINT #2 AND a(o);s$ AND NOT a(o);" " AND o<10;o;"  ";
[/code]

Alternatively, discarding line 114 and changing line 116 to:

[code]
 116 PRINT ((" " AND o<10)+STR$ o+"  ") AND a(o);
[/code]

dispenses with the final IF...THEN .


**********************************************************************


** RND Commands
   ============

RANDOMIZE takes an INT value, therefore (as RND is always <1):

RANDOMIZE RND = RANDOMIZE INT RND = RANDOMIZE 0 = RANDOMIZE

which uses the first two bytes of FRAMES as the next SEED value.

[/code]


**********************************************************************


** Using RND in machine code
   =========================

RNDFun.ASM [code]
RNDFUN: ld   b,$1D           ;B= calculator code for VAL
        rst  $28             ;FP_CALC
        defb $34,$B0         ;stk_data (short string)
        defw $023F           ;address of "RND" code in key table
        defb $01             ;length
        defb $3B             ;execute_B

        defb $34,$40,$B0,$00 ;stk_data (small integer)
        defb $04             ;4
        defb $04             ;multiply (RND*4)
        defb $27             ;int (INT (RND*4))
        defb $A1             ;const_one
        defb $0F             ;add (1+INT (RND*4))
        defb $34,$40,$B0,$00 ;stk_data (small integer)
        defb $05             ;5
        defb $04             ;multiply (1+INT (RND*4))*5
        defb $A1             ;const_one
        defb $03             ;subtract (1+INT (RND*4))*5-1

        defb $38             ;end_calc
        call $2da2           ;FP_TO_BC
        ret
[/code]

The first part puts the next RND value on the calculator stack; the remainder does the rest of the (1+INT (RND*4))*5-1 expression. Running two loops starting with the same seed, one using "PRINT (1+INT (RND*4))*5-1" and one using "PRINT USR rndfun" will give the same sequences of numbers.


**********************************************************************


** Some alternatives to RND
   ========================

Jon Ritman's routine from Head over Heels was published as Star Tip 3 in Your Sinclair August 1987.

http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8708pp.zip

"This is the 32-bit random number generator routine I used in Batman and Head Over Heels. The routine is quite fast and returns a reasonable random number. This version returns with HL holding a 16-bit random number. However, if you need all 32 bits, simply add the instruction LD DE,(SEED+2) just before  the RET at the end. The 32-bit seed may be changed to any value of your choice."

   ========================

Toni Baker published a routine as part of her "Machine Code Calculator" series in ZX Computing July-November 1986.

http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/mccalc.zip

"As with the BASIC RND function it suffers from the disadvantage that
it is incredibly slow. For this reason Figure Two contains two other
programs, labelled RANDOM and RNDQ respectively. The purpose of RANDOM
is to assign (SEED) with the next value in the pseudo- random
sequence. On return HL will contain the new value of (SEED), being a
random word between 0000 and FFFF; also, the A register will contain
the high part of this number, and will therefore be a random byte
between 00 and FF.

The RNDQ subroutine (which relies on the existence of the RANDOM
subroutine) will do exactly the same job as RND - ie. will leave a
random number between 0 and 1 at the top of the calculator stack - but
will do it much, much faster."

   ========================

Selecting each & every item in a pre-defined list once only in a random order can be done with the "Fisher-Yates shuffle" technique or variations thereof. Refer to this Wikipedia page for examples:

https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

Here's a simple example which erases the screen in an irregular manner:

[code]
 10 RANDOMIZE: DIM p(768)
 30 FOR f=1 TO 768
 40   LET p(f)=f
 50 NEXT f
 60 FOR f=768 TO 1 STEP -1
 70   LET i=INT(RND*f)+1: POKE 22527+p(i),0
 90   LET a=p(i): LET p(i)=p(f): LET p(f)=a
100 NEXT f
[/code]

   ========================

The Linear Feedback Shift Register uses a self-referential loop to take as its input the previous number output and can generate long irregular sequences without repetition. Refer to this Wikipedia page for examples:

https://en.wikipedia.org/wiki/Linear_feedback_shift_register

A simple routine which will generate every integer from 1-65535, using the 16-bit Fibonacci LFSR example from that page, with taps at 11,13,14,16:

[code]
        ld   hl,[seed value]
        [loop]
        ld a,l : rla : rla : xor l
                       rla : xor l
                 rla : rla : xor l
                 rla : rla : rla
        rr h : rr l
        [/loop]
[/code]

   ========================

Some other alternatives:

http://map.tni.nl/sources/external/z80bits.html


**********************************************************************


** Random point screen fill
   ========================

Here's a bit of assembler to do the same thing. It doesn't stop (ie. get a duplicate point) until all of the upper screen is black. The call to SET_WORK is to reset WORKSP, because the RND call from FP_CALC adds two bytes (RND + ENTER) but doesn't remove them when it's finished.

RandPlot.ASM [code]
RNDFUN: ld   b,$1D           ;B= calculator code for VAL
        rst  $28             ;FP_CALC
        defb $34,$B0         ;stk_data (short string)
        defw $023F           ;address of "RND" code in key table
        defb $01             ;length
        defb $3B             ;execute_B
        defb $34,$00,$41,$00 ;stk_data 65536
        defb $04             ;multiply
        defb $38             ;end_calc
        call $2da2           ;FP_TO_BC = PLOT coordinates
        ld   hl,$afff
        sbc  hl,bc
        jr   c,RNDFUN
        ld   (23677),bc      ;(COORDS)
        call $22aa           ;PIXEL_ADD
        ld   b,a
        inc  b
        ld   a,$fe
LOOP1:  rrca
        djnz LOOP1
        cpl
        ld   b,a
        and  (hl)            ;check if bit is set
        ret  nz
        ld   a,b
        or   (hl)
        ld   (hl),a          ;update display byte
        call $16bf           ;SET_WORK
        jr   RNDFUN
[/code]


**********************************************************************