; =============================================================================
;
;                           Litos8 random generator
;
; =============================================================================

; General algorithm (new_seed = old_seed*17405 + 10395331) for the random
; generator is taken from interpreted BASIC and is described in Microsoft
; Knowledgebase article Q28150. Other possible coefficients are:
; 214013/2531011, 17405/10395331, 214013/13737667, (214013/10395331,) 69069/1
;
; Random numbers in interval are usually calculated with modulo operation.
; Such random numbers are not evenly spreaded out, their density is lower at
; end of the interval. Therefore in this generator repeated generation is used 
; of random numbers until suitable number is found, such numbers are spreaded
; evenly in whole interval.

; -----------------------------------------------------------------------------
; Exported user functions (17):
;	RandInit - initialize random generator
;	RandShift - shift random generator with an event
;	RandByte - get random byte
;	RandWord - get random word
;	RandDWord - get random dword
;	RandByteMax - get random byte with max value
;	RandWordMax - get random word with max value
;	RandDWordMax - get random dword with max value
;	RandSByteMax - get random signed byte with max value
;	RandSWordMax - get random signed word with max value
;	RandSDWordMax - get random signed dword with max value
;	RandByteInt - get random byte in interval
;	RandWordInt - get random word in interval
;	RandDWordInt - get random dword in interval
;	RandSByteInt - get random signed byte in interval
;	RandSWordInt - get random signed word in interval
;	RandSDWordInt - get random signed dword in interval
; -----------------------------------------------------------------------------

RNDCOEFF1	EQU	17405		; random coefficient 1 (multiple)
RNDCOEFF2	EQU	10395331	; random coefficient 2 (add)
RNDCOEFF2H	EQU	9Eh		; random coefficient 2 HIGH (add)
RNDCOEFF2L	EQU	9EC3h		; random coefficient 2 LOW (add)

		CODE_SECTION

; -----------------------------------------------------------------------------
;                   Initialize random generator
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = new random generator seed
;		BL = flag, 0 = use pseudorandom sequence, 1 = enable events
; -----------------------------------------------------------------------------

; ------------- set new seed value

RandInit:	mov	[RandSeed],ax	; set seed LOW
		mov	[RandSeed+2],dx	; set seed HIGH
		mov	[RandEventEnable],bl ; set flag, enable events

; ------------- shuffle random generator

		push	ax		; push AX
		push	cx		; push CX
		mov	cx,20		; CX <- number of shuffles
RandInit2:	call	RandNext	; next random generator value
		loop	RandInit2	; next value
		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                   Shift random generator with an event
; -----------------------------------------------------------------------------
; INPUT:	AX,BX,CX = shift random value
; -----------------------------------------------------------------------------

RandShift:	cmp	byte [RandEventEnable],0 ; enabled events?
		je	short RandShift2 ; events not enabled

		add	[RandSeed+1],ah
		adc	[RandSeed+2],al
		adc	[RandSeed],bl
		adc	[RandSeed+3],bh
		adc	[RandSeed+1],cl
		adc	[RandSeed+2],ch
RandShift2:	ret

; -----------------------------------------------------------------------------
;                Next random generator value, get random word
; -----------------------------------------------------------------------------
; OUTPUT:	AX = random word (0 to 0ffffh)
; -----------------------------------------------------------------------------

; ------------- push registers

RandWord:
RandNext:	push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI
		push	di		; push DI

; ------------- prepare accumulator -> DI:SI:CX:BX

		xor	di,di		; DI <- 0
		xor	si,si		; SI <- 0
		mov	cx,RNDCOEFF2H	; CX <- coefficient 2 HIGH
		mov	bx,RNDCOEFF2L	; BX <- coefficient 2 LOW

; ------------- multiple seed with COEF1 and add to accumulator

		mov	ax,RNDCOEFF1	; AX <- coefficient 1
		mov	dx,[RandSeed]	; DX <- seed WORD 1
		mul	dx		; DX:AX <- multiple 1
		add	bx,ax		; BX <- add result to accumulator 1
		adc	cx,dx		; CX <- add result to accumulator 2
		adc	si,byte 0	; SI <- carry 3
		adc	di,byte 0	; DI <- carry 4

		mov	ax,RNDCOEFF1	; AX <- coefficient 1
		mov	dx,[RandSeed+2]	; DX <- seed WORD 2
		mul	dx		; DX:AX <- multiple 2
		add	cx,ax		; CX <- add result to accumulator 2
		adc	si,dx		; SI <- add result to accumulator 3
		adc	di,byte 0	; DI <- carry 4

		mov	ax,RNDCOEFF1	; AX <- coefficient 1
		mov	dx,[RandSeed+4]	; DX <- seed WORD 3
		mul	dx		; DX:AX <- multiple 3
		add	si,ax		; SI <- add result to accumulator 3
		adc	di,dx		; DI <- add result to accumulator 4

		mov	ax,RNDCOEFF1	; AX <- coefficient 1
		mov	dx,[RandSeed+6]	; DX <- seed WORD 4
		mul	dx		; DX:AX <- multiple 4
		add	di,ax		; DI <- add result to accumulator 4

; ------------- save new seed value (in mixed order - to shuffle value)

		mov	[RandSeed],si	; new value 1 <- old value 3
		mov	[RandSeed+2],di	; new value 2 <- old value 4
		mov	[RandSeed+4],cx	; new value 3 <- old value 2
		mov	[RandSeed+6],bx	; new value 4 <- olf value 1

; ------------- pop registers

		xchg	ax,cx		; AX <- new value 3, old value 2
		pop	di		; pop DI
		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                            Get random byte
; -----------------------------------------------------------------------------
; OUTPUT:	AL = random byte (0 to 0ffh)
; -----------------------------------------------------------------------------

RandByte:	push	dx		; push DX
		push	ax		; push AX

		call	RandNext	; next global random seed (-> AX)
		xchg	ax,dx		; DX <- save random word

		pop	ax		; pop AX
		mov	al,dl		; AL <- random byte
		pop	dx		; pop DX
		ret

; -----------------------------------------------------------------------------
;                         Get random double word
; -----------------------------------------------------------------------------
; OUTPUT:	DX:AX = random double word (0 to 0ffffffffh)
; -----------------------------------------------------------------------------

RandDWord:	call	RandWord	; get random word (-> AX)
		xchg	ax,dx		; DX <- first random word
		call	RandWord	; get random word (-> AX)
		ret

; -----------------------------------------------------------------------------
;                Get random unsigned byte with max value
; -----------------------------------------------------------------------------
; INPUT:	AL = maximal unsigned value (0 to 0ffh)
; OUTPUT:	AL = random byte (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandByteMax:	or	al,al		; zero maximal value?
		jz	short RandByteMax8 ; maximal value is 0

; ------------- Push registers

		push	cx		; push CX
		push	dx		; push DX

; ------------- preset mask value -> DL

		mov	dl,0ffh		; DL <- preset mask 0ffh
		cmp	al,0fh		; check max. value
		ja	short RandByteMax2 ; max. value is greater
		mov	dl,0fh		; DL <- preset mask 0fh

; ------------- Prepare mask of maximal value -> CL

RandByteMax2:	mov	cl,dl		; CL <- mask
		shr	dl,1		; DL <- mask/2
		cmp	dl,al		; check mask
		jae	short RandByteMax2 ; new mask is ok, use it

; ------------- Find required value -> AL

		xchg	ax,dx		; DL <- required maximum value
RandByteMax4:	call	RandByte	; get random byte (-> AL)
		and	al,cl		; mask result
		cmp	al,dl		; is this value allowed?
		ja	short RandByteMax4 ; invalid value, next attempt

; ------------- Pop registers

		pop	dx		; pop DX
		pop	cx		; pop CX
RandByteMax8:	ret

; -----------------------------------------------------------------------------
;                 Get random signed byte with max value
; -----------------------------------------------------------------------------
; INPUT:	AL = maximal signed value (-128 to +127)
; OUTPUT:	AL = random byte (0 to maximal value)
; -----------------------------------------------------------------------------

RandSByteMax:	or	al,al		; is it unsigned value?
		jns	short RandByteMax ; it is unsigned value or zero
		neg	al		; correct maximal value
		call	RandByteMax	; generate random byte
		neg	al		; correct result
		ret

; -----------------------------------------------------------------------------
;               Get random unsigned word with max value
; -----------------------------------------------------------------------------
; INPUT:	AX = maximal unsigned value (0 to 0ffffh)
; OUTPUT:	AX = random word (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandWordMax:	or	ax,ax		; zero maximal value?
		jz	short RandWordMax8 ; maximal value is 0

; ------------- Push registers

		push	cx		; push CX
		push	dx		; push DX

; ------------- preset mask value -> DX

		mov	dx,0ffffh	; DX <- preset mask 0ffffh
		cmp	ax,0ffh		; check max. value
		ja	short RandWordMax2 ; max. value is greater
		mov	dx,0ffh		; DX <- preset mask 0ffh

; ------------- Prepare mask of maximal value -> CX

RandWordMax2:	mov	cx,dx		; CX <- mask
		shr	dx,1		; DX <- mask/2
		cmp	dx,ax		; check mask
		jae	short RandWordMax2 ; new mask is ok, use it

; ------------- Find required value -> AX

		xchg	ax,dx		; DX <- required maximum value
RandWordMax4:	call	RandWord	; get random word (-> AX)
		and	ax,cx		; mask result
		cmp	ax,dx		; is this value allowed?
		ja	short RandWordMax4 ; invalid value, next attempt

; ------------- Pop registers

		pop	dx		; pop DX
		pop	cx		; pop CX
RandWordMax8:	ret

; -----------------------------------------------------------------------------
;                Get random signed word with max value
; -----------------------------------------------------------------------------
; INPUT:	AX = maximal signed value (-32768 to +32767)
; OUTPUT:	AX = random word (0 to maximal value)
; -----------------------------------------------------------------------------

RandSWordMax:	or	ax,ax		; is it unsigned value?
		jns	short RandWordMax ; it is unsigned value or zero
		neg	ax		; correct maximal value
		call	RandWordMax	; generate random word
		neg	ax		; correct result
		ret

; -----------------------------------------------------------------------------
;             Get random unsigned double word with max value
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = maximal unsigned value (0 to 0ffffffffh)
; OUTPUT:	DX:AX = random double word (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandDWordMax:	or	ax,ax		; zero maximal value?
		jnz	short RandDWordMax2 ; not zero value
		or	dx,dx		; zero maximal value?
		jz	short RandDWordMax8 ; maximal value is 0

; ------------- Push registers

RandDWordMax2:	push	cx		; push CX
		push	si		; push SI
		push	di		; push DI
		push	bp		; push BP

; ------------- preset mask value -> BP:DI

		xor	bp,bp		; BP <- 0
		dec	bp		; BP <- 0ffffh
		mov	di,bp		; DI <- 0ffffh
		or	dx,dx		; check max. value HIGH
		jnz	short RandDWordMax4 ; greater than 0ffffh
		xor	bp,bp		; BP <- 0

; ------------- Prepare mask of maximal value -> SI:CX

RandDWordMax4:	mov	cx,di		; CX <- mask LOW
		mov	si,bp		; SI <- mask HIGH
		shr	bp,1		; BP <- mask/2 HIGH
		rcr	cx,1		; CX <- mask/2 LOW
		cmp	bp,dx		; check mask HIGH
		jb	short RandDWordMax5 ; mask found OK
		ja	short RandDWordMax4 ; use new mask
		cmp	di,ax		; check mask LOW
		jae	short RandDWordMax4 ; use new mask

; ------------- Find required value

RandDWordMax5:	xchg	ax,di		; DI <- max. value LOW
		mov	bp,dx		; BP <- max. value HIGH
RandDWordMax6:	call	RandDWord	; get random double word (-> DX:AX)
		and	ax,cx		; mask result LOW
		and	dx,si		; mask result HIGH
		cmp	dx,bp		; check result HIGH
		jb	short RandDWordMax7 ; result is OK
		ja	short RandDWordMax6 ; try next value
		cmp	ax,di		; check result LOW
		ja	short RandDWordMax6 ; try next value

; ------------- Pop registers

RandDWordMax7:	pop	bp		; pop BP
		pop	di		; pop DI
		pop	si		; pop SI
		pop	cx		; pop CX
RandDWordMax8:	ret

; -----------------------------------------------------------------------------
;             Get random signed double word with max value
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = maximal signed value (-80000000h to +7fffffffh)
; OUTPUT:	DX:AX = random double word (0 to maximal value)
; -----------------------------------------------------------------------------

RandSDWordMax:	or	dx,dx		; is it unsigned value?
		jns	short RandDWordMax ; it is unsigned value or zero
		neg	ax		; correct maximal value LOW
		adc	dx,byte 0	; carry
		neg	dx		; correct maximal value HIGH
		call	RandDWordMax	; generate random double word
		neg	ax		; correct result LOW
		adc	dx,byte 0	; carry
		neg	dx		; correct result HIGH
		ret

; -----------------------------------------------------------------------------
;                       Get random byte in interval
; -----------------------------------------------------------------------------
; INPUT:	AL = maximal value (signed or unsigned)
;		AH = minimal value (signed or unsigned)
; OUTPUT:	AL = random byte (min to max)
; -----------------------------------------------------------------------------

RandByteInt:
RandSByteInt:	sub	al,ah		; AL <- new maximal value
		call	RandByteMax	; generate random byte
		add	al,ah		; AL <- add minimal value
		ret

; -----------------------------------------------------------------------------
;                     Get random word in interval
; -----------------------------------------------------------------------------
; INPUT:	AX = maximal value (signed or unsigned)
;		DX = minimal value (signed or unsigned)
; OUTPUT:	AX = random word (min to max)
; -----------------------------------------------------------------------------

RandWordInt:
RandSWordInt:	sub	ax,dx		; AX <- new maximal value
		call	RandWordMax	; generate random word
		add	ax,dx		; AX <- add minimal value
		ret

; -----------------------------------------------------------------------------
;                   Get random double word in interval
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = maximal value (signed or unsigned)
;		BX:CX = minimal value (signed or unsigned)
; OUTPUT:	DX:AX = random double word (min to max)
; -----------------------------------------------------------------------------

RandDWordInt:
RandSDWordInt:	sub	ax,cx		; AX <- new maximal value LOW
		sbb	dx,bx		; BX <- new maximal value HIGH
		call	RandDWordMax	; generate random double word
		add	ax,cx		; AX <- add minimal value LOW
		adc	dx,bx		; DX <- add minimal value HIGH
		ret

; -----------------------------------------------------------------------------
;                            Uninitialised data
; -----------------------------------------------------------------------------

		DATA_SECTION

; ------------- Seed for random generator

		align	4, resb 1
RandSeed:	resb	8		; seed for random generator
RandEventEnable:resb	1		; flag 0: pseudo, not 0: enable events
