;
; rand.s
;
; Generating random numbers
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Sapphire library.
;
; Sapphire is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; Sapphire is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Sapphire.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

;----- External dependencies ------------------------------------------------

		GET	sapphire:divide
		GET	sapphire:sapphire

;----- Main code ------------------------------------------------------------

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- rand ---
;
; On entry:	--
;
; On exit:	R0 == a pseudorandom number between 0 and &7FFFFFFF
;
; Use:		Returns a pseudorandom number.  The algorithm used is the
;		additive generator found in Knuth.  The table is generated
;		from the current monotonic time using a linear congruential
;		generator.  Randomness is fairly good, and it's very quick.

		EXPORT	rand
rand		ROUT

		STMFD	R13!,{R1-R3,R12,R14}	;Save some registers
		WSPACE	rand__wSpace		;Find my workspace address
		LDMIB	R12,{R1,R2}		;Load the table indices
		ADR	R3,rand__table		;Find the ring buffer
		LDR	R0,[R3,R1,LSL #2]	;Load the value x[n-24]
		LDR	R14,[R3,R2,LSL #2]	;Load the value x[n-55]
		ADD	R0,R0,R14		;And form the new number
		STR	R0,[R3,R2,LSL #2]	;Store new number in buffer
		SUBS	R1,R1,#1		;Decrement x-24 index
		MOVLT	R1,#54			;If too small, wrap it round
		SUBS	R2,R2,#1		;Decrement x-55 index
		MOVLT	R2,#54			;If too small, wrap it round
		STMIB	R12,{R1,R2}		;Save the indices back again
		BIC	R0,R0,#&80000000	;Clear the top bit (positive)
		LDMFD	R13!,{R1-R3,R12,PC}^	;Return to caller

		LTORG

; --- rand_setSeed ---
;
; On entry:	R0 == a pseudorandom seed
;
; On exit:	--
;
; Use:		Sets up the random number generator (rand) to the given start
;		position.  The table of values is initialised from the seed
;		in a psuedorandom manner, using a linear congruential
;		generator.

		EXPORT	rand_setSeed
rand_setSeed	ROUT

		STMFD	R13!,{R0-R2,R12,R14}	;Save some registers
		WSPACE	rand__wSpace		;Find my workspace address

		; --- Reset the indices ---

		MOV	R1,#23			;Start 24-index at 24-1
		MOV	R2,#54			;Start 55-index at 55-1
		STMIB	R12,{R1,R2}		;Save them away

		; --- Now start populating the table ---

		ADR	R1,rand__table		;Point to the ring buffer
		MOV	R2,#55			;Counter for the items

00rand_setSeed	ADD	R14,R0,R0,LSR #16	;Mangle the seed a little
		STR	R14,[R1],#4		;Save it in the table

		ADD	R14,R0,R0,LSL #2	;Do multiply op in generator
		RSB	R14,R14,R14,LSL #4
		RSB	R0,R0,R14,LSL #2
		RSB	R0,R0,R0,LSL #3
		ADD	R0,R0,R0,LSL #5

		ADD	R0,R0,#&66000000	;Do add op in generator
		ADD	R0,R0,#&00D60000
		ADD	R0,R0,#&00001900
		ADD	R0,R0,#&000000E1

		SUBS	R2,R2,#1		;Decrement the counter
		BGT	%00rand_setSeed		;If not finished, go again

		LDMFD	R13!,{R0-R2,R12,PC}^	;Return to caller

		LTORG

; --- rnd ---
;
; On entry:	R0 == start value (inclusive)
;		R1 == end value (inclusive)
;
; On exit:	R0 == random number between the boundaries given
;
; Use:		Returns a random integer between the boundaries given.
;		The distribution is slightly skewed towards lower numbers,
;		but there's not a lot I can do about this, folks.

		EXPORT	rnd
rnd		ROUT

		STMFD	R13!,{R1,R2,R14}	;Save a bunch of registers
		MOV	R2,R0			;Look after the start value
		SUB	R1,R1,R0		;Subtract the initial value
		ADD	R1,R1,#1		;Make it inclusive nicely
		BL	rand			;Get a random number
		BL	divide			;Force it to be in range
		ADD	R0,R1,R2		;And add the start value
		LDMFD	R13!,{R1,R2,PC}^	;Return to caller

		LTORG

; --- rand_init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Initialise the random number table.

		EXPORT	rand_init
rand_init	ROUT

		STMFD	R13!,{R12,R14}		;Save a register or two
		WSPACE	rand__wSpace		;Find my workspace
		LDR	R14,rand__flags		;Load my flags word
		TST	R14,#rFlag__inited	;Am I initialised yet?
		LDMNEFD	R13!,{R12,PC}^		;Yes -- don't do it again

		ORR	R14,R14,#rFlag__inited	;Set the flag then
		STR	R14,rand__flags		;And save back the new flags
		STMFD	R13!,{R0}		;Save another register
		SWI	OS_ReadMonotonicTime	;Load the current time
		BL	rand_setSeed		;Set up the random table
		LDMFD	R13!,{R0,R12,PC}^	;And return to caller

		LTORG

rand__wSpace	DCD	0

;------ Workspace -----------------------------------------------------------

		^	0,R12
rand__wStart	#	0

rand__flags	#	4			;Various flaglike objects

rand__24	#	4			;Offset to item 24 in buffer
rand__55	#	4			;Offset to item 55 in buffer

rand__table	#	55*4			;The random number table

rand__wSize	EQU	{VAR}-rand__wStart

rFlag__inited	EQU	(1<<0)			;Am I initialised yet?

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	rand__wSize
		DCD	rand__wSpace
		DCD	0
		DCD	rand_init

;----- That's all, folks ----------------------------------------------------

		END
