RandomStream.bas
Britlion's Crazy Random Number generators! (Based completely on the stream generator from Patrik Rak, and much thanks for his work on this)
Boriel has included a better random function in the code; but this passes through floating point numbers, which is potentially fairly slow - and for games we usually require integer numbers anyway!
I've written a few functions that are a possible alternative.
This is the base function that does the hard work of generating a random number from 0-255 in the A register (or as a return value, conveniently enough). This is the same random number generator that Boriel is using, incidentally (based pretty much wholly on Patrik Rak's stream random generator, as posted on the World of Spectrum Forums).
Update: Tweaked for Einar Saukas' optimization, September 2012.
The following function, randomBase, returns a pseudorandom value between 0 and 255 - that is a one byte return. This is the base of Patrik Rak's random stream generator, and is the fastest function here. Other functions will call this one. They will also call it FROM MACHINE CODE - so expect the ASM context label "random" to be there. If you change this label, you will have to also change the calling functions
FUNCTION FASTCALL randomBase () AS UBYTE
ASM
random:
ld de,$A280 ; xz -> yw
ld hl,$C0DE ; yw -> zt
ld (random+1),hl ; x = y, z = w
ld a,l ; w = w ^ ( w << 3 )
add a,a
add a,a
add a,a
xor l
ld l,a
ld a,d ; t = x ^ (x << 1)
add a,a
xor d
ld h,a
rra ; t = t ^ (t >> 1) ^ w
xor h
xor l
ld h,e ; y = z
ld l,a ; w = t
ld (random+4),hl
END ASM
END FUNCTION
This function will update the seed value based on the current frames counter. To improve randomness, get the user to have a human interaction that can take a variable amount of time and then run this.
SUB FASTCALL updateSeed()
REM Updates the random generator seed from the FRAMES system variable.
time()
ASM
LD A,E
EX DE,HL
LD HL,random+2
XOR (HL)
AND A
JR NZ,updateSeedNotZero
INC A
updateSeedNotZero:
LD (HL),A
LD HL,random+4
LD A,E
XOR (HL)
LD (HL),A
INC HL
LD A,D
XOR (HL)
LD (HL),A
END ASM
END SUB
The above function requires the timer function, which simply grabs the time from the frames variable and returns is as a unsigned-long variable, in registers DEHL:
FUNCTION FASTCALL time() as uLong
asm
DI
LD DE,(23674)
LD D,0
LD HL,(23672)
EI
end asm
end function
This function returns a value from zero to the specified limit number (limit <= 255).
You can therefore, for example, roll a dice by calling randomLimit(5) + 1
to get 1-6.
FUNCTION fastcall randomLimit(limit as uByte) as uByte
ASM
AND A
RET Z ; Input zero, output zero.
LD B,A ; Save A
LD C,255
randomBinLoop:
RLA
JR C, randomBinLoopExit
RR C
JR randomBinLoop ; loop back until we find a bit.
randomBinLoopExit:
randomBinRedoCall:
call random
AND C
CP B
RET Z
JR NC, randomBinRedoCall
END ASM
END FUNCTION
It's worth noting that the issue with the above is that it basically rolls a random, and if it's bigger than limit, it rolls another one. This could potentially take a while, and isn't guaranteed to be fast. Though the probability of failing to hit the zone is kept to 50% at worst, so on average it will roll 1.5 random numbers, I think, per call. This should usually be faster than a floating point multiply, I believe.
If you want, in a similar way to sinclair basic and ZX BASIC RND function, a number between 0 and 1, this function provides that, using a FIXED type return. This is usually good enough for most purposes, but is quite a lot faster to process than a full floating point number.
FUNCTION FASTCALL randomFixed() as FIXED
ASM
call random
push AF
call random
ld l,A
POP AF
ld h,a
ld d,0
ld e,d
END ASM
END FUNCTION