06-18-2012, 11:07 PM
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).
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.
The above function depends on my timer function, which gets the value in FRAMES:
This function returns a value from zero to the specified limit number (limit <= 255)
it's worth noting that the issue with this 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%, 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 do want a number a random number between 0 and 1, using FIXED is quite a lot faster than float. This routine uses randombase to do that:
Timings:
20,000 loops took:
variable=rnd*128 => 1180 frames.
variable=randomFixed()*128 => 613 Frames (1.92 x faster)
variable=randomBin(128) => 74 frames. (this example is 16 x faster) (worst case averages 13x faster; best case averages 22x faster)
(This does vary by limit, but is never > 89 frames for any limit chosen. Can be as low as about 52. Average is about 64.)
All three, of course, return an integer from 0 to 127 (the last 0-128; this chosen because it's a worse case scenario for timing).
For scale, 20,000 loops calling randomBase takes 37 frames. Of course, this returns a number from 0-255 regardless, with no option to set the upper bound.
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).
Code:
FUNCTION FASTCALL randomBase () as uByte
ASM
random: ld hl,$A280 ; xz -> yw
ld de,$C0DE ; yw -> zt
ld (random+1),de ; x = y, z = w
ld a,e ; w = w ^ ( w << 3 )
add a,a
add a,a
add a,a
xor e
ld e,a
ld a,h ; t = x ^ (x << 1)
add a,a
xor h
ld d,a
rra ; t = t ^ (t >> 1) ^ w
xor d
xor e
ld h,l ; y = z
ld l,a ; w = t
ld (random+4),hl
;ret (Not necessary, since the compiler will add ret to the end)
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.
Code:
SUB FASTCALL updateSeed()
REM Updates the random generator seed from the FRAMES system variable.
t()
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
Code:
FUNCTION FASTCALL t() 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)
Code:
FUNCTION fastcall randomBin(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
If you do want a number a random number between 0 and 1, using FIXED is quite a lot faster than float. This routine uses randombase to do that:
Code:
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
Timings:
Code:
dim time as uLong
DIM n as uInteger
DIM variable as uByte
time=t()
for n=0 to 20000
REM One line of the following three:
'variable=rnd*128
'variable=randomFixed()*128
'variable=randomBin(128)
next n
print t()-time
20,000 loops took:
variable=rnd*128 => 1180 frames.
variable=randomFixed()*128 => 613 Frames (1.92 x faster)
variable=randomBin(128) => 74 frames. (this example is 16 x faster) (worst case averages 13x faster; best case averages 22x faster)
(This does vary by limit, but is never > 89 frames for any limit chosen. Can be as low as about 52. Average is about 64.)
All three, of course, return an integer from 0 to 127 (the last 0-128; this chosen because it's a worse case scenario for timing).
For scale, 20,000 loops calling randomBase takes 37 frames. Of course, this returns a number from 0-255 regardless, with no option to set the upper bound.