• Please review our updated Terms and Rules here

RAND() in Assembler

neilobremski

Experienced Member
Joined
Oct 9, 2016
Messages
55
Location
Seattle, USA
The puzzles of Magenta's Maze are generated using the barely sufficient rand() function supplied by MSC 5.1. There are some issues with this, most notably that the highest bit of the 16-bit word output is always zero. The way we C programmers were taught to use this function was to modulus the result by the maximum amount desired thus providing 0 to N - 1 (where N is the maximum). This is unfortunately how Magenta's Maze uses it to place objects within the map and rotate the magic circles.

However, it also uses a function called "coinflip" which returns 1 or 0 randomly. This function calls rand() and stores the result in a static variable:

Code:
char coinflip(void)
{
	static int bitsleft = 0;
	static int data = 0;

	if (!bitsleft) {
		data = rand();
		bitsleft = 15;
		return (char)(data & 1);
	}

	bitsleft--;
	data >>= 1;

	return (char)(data & 1);
}

There are two problems with this with the first being the aforementioned zero high bit, meaning that every 16th coin flip will always be FALSE! Argh! The second and more subtle problem is the use of a static function variable for the random bits which do not get reset when the player finishes (or quits) a map and starts a new one without restarting the game.

I decided for the assembler version to keep the first bug so that the generated levels are the same as the first version of a freshly started Magenta's Maze. However, I fixed the second bug which means that subsequent maps of the still-loaded game will be different. This was a hard decision to make, honestly, because I wanted direct compatibility ... but I cannot ignore that that bug really is a bug!

Finally, here is the code for rand(), srand(), and coinflip() in assembler.

Code:
a 300
; ----------------------------------------------------------------------------
; rand ()							:RAND
;
; Psuedo-random number generator that returns a (positive) 16-bit value in AX.
; NOTE: this is intended to return identical values to the rand() in MSC 5.1.
;
; CALLER MUST make DS = CS.
;
MOV	AX, 43FD; 300 
MOV	DX, 0003; 303 
PUSH	DX	; 306 
PUSH	AX	; 307 
PUSH WORD [035C]; 308 
PUSH WORD [035A]; 30C 
CALL	0326	; 310 
ADD	AX, 9EC3; 313 
ADC	DX, 26	; 316 
MOV [035A], AX	; 319 
MOV [035C], DX	; 31C 
MOV	AX, DX	; 320 
AND	AH, 7F	; 322 
RET		; 325
		;
PUSH	BP	; 326 
MOV	BP, SP	; 327 
MOV AX, [BP+06]	; 329 
MOV BX, [BP+0A]	; 32C 
OR	BX, AX	; 32F 
MOV BX, [BP+08]	; 331 
JNZ	0341	; 334 
		;
MOV AX, [BP+04]	; 336 
MUL	BX	; 339 
MOV	SP, BP	; 33B 
POP	BP	; 33D 
RET	0008	; 33E 
		;
MUL	BX	; 341 
MOV	CX, AX	; 343 
MOV AX, [BP+04]	; 345 
MUL WORD [BP+0A]; 348 
ADD	CX, AX	; 34B 
MOV AX, [BP+04]	; 34D 
MUL	BX	; 350 
ADD	DX, CX	; 352 
MOV	SP, BP	; 354 
POP	BP	; 356 
RET	0008	; 357 

; random number seed plus overflow/remainder
e 035A 01 00 00 00
a 360
; ----------------------------------------------------------------------------
; CoinFlip ()							:COINFLIP
;
; Uses RAND for a psuedo-random bit comparison (use like TEST/JE|JNE).
;
; CALLER MUST make DS = CS.
;
PUSH	SI	; 360
MOV	SI, 037C; 361
DEC BYTE[SI+2]	; 364
JS	0371	; 367 >COINFLIP_RAND
SHR  WORD[SI], 1; 369
TEST WORD[SI], 1; 36B						:COINFLIP_TEST
POP	SI	; 36F
RET		; 370
CALL	0300	; 371 >RAND					:COINFLIP_RAND
MOV	[SI], AX; 374
MOV BYTE[SI+2],F; 376
JMP	036B	; 37A >COINFLIP_TEST

; coinflip data
e 037C 00 00 00 00
a 380
; ----------------------------------------------------------------------------
; srand ()							:SRAND
;
; Seed random number generator with AX and reset coin flip data.
;
MOV [035A], AX	; 380
XOR	AX, AX	; 383
MOV [035C], AX	; 385
MOV [037C], AX	; 388
MOV [037E], AX	; 38B
RET		; 38E
 
Back
Top