Greed/Z80 Assembly

From Rosetta Code

This code will run on an MSX computer under MSX-DOS. It needs an MSX that supports the 80-column text mode, which pretty much means an MSX2.

;; MSX version of Greed - needs 80 column system and MSX-DOS.
;; Assembles using Sjasm
	;;;	RAM locations
linlen:	equ	0F3AEh		; RAM location for line length
cursor:	equ	0F3DCh		; Terminal cursor location
timer:	equ	0FC9Eh		; System timer
	;;;	MSX ROM calls
calslt:	equ	1Ch		; Interslot call
rom:	equ	0FCC0h		; Main ROM slot
initxt:	equ	6Ch		; Switch to text mode
cls_:	equ	0C3h		; Clear screen
	;;;	MSX DOS calls
dos:	equ	5		; MSX-DOS entry point
gtime:	equ	2Ch		; Get system time
strout: equ	9h		; Print string
dirio:	equ	6h		; Direct console I/O
	;;;	Program is loaded at 100h
	org	100h	
	;;;	Initialize the RNG using the system time
	ld	c,gtime		; Get time (H:M:S in H,L,D)
	call	dos	
	ld	b,h
	ld	c,l
	ld	hl,rnddat+1	; Store time as A,B,C for XABC
	ld	(hl),b
	inc	hl
	ld	(hl),c
	inc	hl
	ld	(hl),d
	call	rand		; Run the RNG once
	;;;	Switch to 80-column text mode
	ld	a,80		; 80 columns
	ld	(linlen),a
	ld	iy,rom		; BIOS call to initialize text mode
	ld	ix,initxt
	call	calslt
	;;;	Store '1'..'9' at 1..9, '@' at 10, and inverted
	;;;	versions at 129..
	;;;	Read the font for 1..9
	di			; Addr. to start loading is 1188h
	xor	a		; (1000h + 8*ord('1'))
	out	(99h),a		; A14,A15,A16 are always 0
	ld	a,14|128	; Write to port 14
	out	(99h),a
	ld	a,88h		; Low byte is 88h
	out	(99h),a
	ld	a,11h		; High byte is 11h, and we're reading
	out	(99h),a
	ld	bc,4898h	; Read 48h (72) bytes from port 98h
	ld	hl,nfont	; Into the font buffer
	push	hl		; We will need it again
	;;;	And read the font for '@', at 1200h
	xor	a
	out	(99h),a
	ld	a,14|128
	out	(99h),a
	xor	a		; Low byte is 00h
	out	(99h),a
	ld	a,12h		; High byte is 12h
	out	(99h),a
	ld	b,8		; Read 8 bytes
	;;;	Then write this starting at address 1008h
	xor	a
	out	(99h),a		; A14,A15,A16 = 0
	ld	a,14|128	; Write to port 14
	out	(99h),a
	ld	a,8h		; Low byte = 8h
	out	(99h),a
	ld	a,64|10h	; High byte = 10h, and we're writing
	out	(99h),a
	ld	bc,5098h	; Write 50h (80) bytes to port 98h
	pop	hl		; Starting at the font
	push	hl		; We will need it again
	;;;	Invert the font data
	ld	hl,nfont	; Starting at the font,	
	ld	b,80		; for 80 bytes (1..9 + @)
invdgt:	ld	a,(hl)		; Get byte,
	cpl			; invert all bits,
	ld	(hl),a		; Store byte back
	inc	hl		; Next byte
	djnz 	invdgt
	;;;	Then write it at 1408h
	xor	a
	out	(99h),a		; A14,A15,A16 are 0
	ld	a,14|128
	out	(99h),a
	ld	a,8h		; Low byte is 8h
	out	(99h),a
	ld	a,64|14h	; High byte is 14h, and we're writing
	out	(99h),a
	ld	bc,5098h	; Write 50h (80) bytes to port 98h
	pop	hl		; Starting at the font
	xor	a		; Set highlights on by default
	ld	(hilite),a	
	;;;	Initialize the game
start:	call	cls		; Clear the screen
	ld	hl,200Ch	; In the middle of the screen,
	ld	(cursor),hl
	ld	de,msg.gen	; Write "generating field"
	ld	c,strout
	call	dos	
	ld	hl,field
	ld	c,22		; 22 lines,
.line:	ld	b,79		; 79 times,	
.rget:	call	rand		; Get random hex digit
	and	15		; 0..15
	cp	9		; 0..8
	jp	nc,.rget	; Invalid = try again
	inc	a		; 1..9
	ld	(hl),a		; Write digit
	inc	hl		; Next field
	djnz	.rget
	ld	(hl),0		; Write a 0 in column 80
	inc	h
	ld	l,0
	dec	c		; Next line
	jr	nz,.line
xcoord:	call	rand		; Generate player X coordinate
	and	127
	cp 	79		; 0..78
	jr	nc,xcoord
	ld	l,a
ycoord:	call	rand		; Generate player Y coordinate
	and	31
	cp	22		; 0..21
	jr	nc,ycoord
	ld	h,a
	ld	(player),hl	; Store coordinates
	ld	a,high field
	add	h
	ld	h,a
	ld	(hl),0		; Zero out start coordinate
	ld	hl,0		; Set the score to 0
	ld	(score),hl
	;;;	Draw screen
screen:	call	cls		; Clear the screen 
	ld	hl,0118h	; Write "Score:" at bottom of screen
	ld	(cursor),hl
	ld	de,msg.score
	ld	c,strout
	call	dos
	ld	de,msg.hlp	; Write "press ? for help"
	call	statln
.field:	di			; Set VDP address to 0 and to write
	xor	a		; Highest bits = 0
	out	(99h),a
	ld	a,14|128
	out	(99h),a
	xor	a		; Low byte = 0
	out	(99h),a
	ld	a,64		; High bits = 0 but write bit on
	out	(99h),a
	ld	d,22		; 22 times,
	ld	hl,field	; Starting at the field
	ld	c,98h		; To I/O port 98h,
.line:	ld	b,80		; Write 80 characters
	inc	h		; Next line
	ld	l,0 		; Beginning of next line
	dec	d		; Done yet?
	jp	nz,.line
	;;;	Print the score
prscr:	ld	hl,scrtxt.last	; Pointer to end of digit string
	push	hl
	ld	hl,(score)	; Get score
.dgt:	ld	bc,-10		; Divisor is -10
	ld	de,-1		; Quotient
.div:	inc	de		; Increment quotient
	add	hl,bc		; Subtract 10 from dividend
	jr	c,.div		; Until it goes negative
	ex	de,hl		; Quotient is new dividend
	ld	a,'0'+10	; Make digit
	add	e
	pop	bc		; Get pointer to string
	dec	bc		; Store digit
	ld	(bc),a
	push	bc 		; Save pointer to string
	ld	a,h		; See if dividend is now 0
	or	l
	jr	nz,.dgt		; If not, do next digit
	ld	hl,0818h	; Set cursor location
	ld	(cursor),hl
	pop	de		; Get pointer to string
	ld	c,strout	; Print it
	call	dos
	;;;	Player move
	;;;	Check possible moves
moves:	ld	hl,dirs		; stack: current direction pointer
	push	hl
	ld	hl,steps	; and current step pointer
	push	hl
.dir:	pop	hl		; Load current step in (de) and advance ptr
	ld	e,(hl)		; X coord
	inc	hl
	ld	d,(hl)		; Y coord
	inc	hl
	push	hl
	ld	a,e		; (0,0) = done
	or	d
	jr	z,.mdone
	ld	hl,(player)	; Get player coordinate
	call	step
	call	valid		; Is it valid?
	jr 	nc,.inval	
	ld	c,1		; C = step counter
	dec	a		; It is. Subtract 1 from counter
	jr	z,.valid 	; If it is 0 now, it is valid
	ld	b,a		; If not, scan 'a' places ahead
.check:	inc	c		; One extra step
	call	step
	call	valid		; Is this one valid too?
	jr	nc,.inval	; If not, this move is not valid
	djnz	.check
	;;;	Valid move in current direction
.valid: ld	a,(hilite)	; Do we need to highlight valid moves?
	and	a
	jr	z,.nohi		; If zero, don't highlight
	ld	b,c		; B = step counter
.hilit:	push	hl		; Keep coordinate
	ld	a,high field	; Get value of field
	add	h
	ld	h,a
	ld	a,(hl)
	pop	hl		; Retrieve coordinate
	or	128		; Set high bit (highlight on)
	call	outahl		; Write highlighted value to screen
	ld	a,h		; One step back
	sub	d
	ld 	h,a
	ld	a,l
	sub	e
	ld	l,a
	djnz	.hilit		; Highlight next position
.nohi:	pop	bc		; Step pointer
	pop	hl		; Direction valid pointer
	ld	(hl),1		; It is valid
	jr	.next
.inval:	;;;	Invalid move in current direction
	pop	bc		; Step pointer
	pop	hl		; Direction valid pointer
	ld	(hl),0		; It is not valid
.next:	inc	hl
	push	hl
	push	bc
	jr	.dir
.mdone:	pop	hl		; Remove the pointers from the stack
	pop	hl		; (HL) = whether moves are possible
	xor	a		; Check if game is over
	ld	b,8		; 8 directions to check
.over:	dec	hl		; Valid move in this direction?
	or	(hl)
	djnz	.over
	or	a
	jp	z,over		; Game over if no valid move left
	;;;	Flash the cursor
flash:	ld	a,(timer)	; Get bit 5 of system timer
	and	32		; (To use for flashing '@')
	rlca			; Move to bit 7
	ld	hl,(player)	; And draw the player
	add	a,10		; Selected '@'
	call	outahl
	;;;	Handle input
key:	ld	c,dirio		; Is a key available?
	ld	e,0FFh
	call	dos
	and	a
	jr	z,flash		; If not, check again
	;;;	Check command keys
	cp	27		; Is it escape?
	jp	z,quit		; Then quit
	cp	'?'		; ? = help
	jp	z,help
	or	32		; Lowercase
	cp	'q'		; Q also quits
	jp	z,quit
	cp	'p'		; P = toggle highlighting moves
	jp	z,mvtogl
	cp	'w'		; W = restart game
	jp	z,start
	;;;	Check direction keys
	ld	hl,dirkey
	ld	d,3		; Run thrice
dir:	ld	bc,800h		; B=8 keys, C=direction 0
.key:	cp	(hl)		; This direction?
	jp	z,move
	inc	c
	inc	hl
	djnz	dir.key
	dec	d
	jr	nz,dir
	jr	flash		; Other key = check again
	;;;	Try to move in direction C
move:	ld	h,0		; Valid move in direction C?
	ld	l,c
	ld	de,dirs
	add	hl,de
	ld	a,(hl)
	and	a
	jr	nz,.valid
	ld	de,msg.bad	; Invalid: give error message
	call	statln
	jr	flash
.valid:	push	bc		; Save C
	ld	de,msg.hlp	; Remove error message if it was still there
	call	statln
	pop	bc		; Restore C
	ld	a,c		; C*=2 for index into array of words
	add	c
	ld	h,0
	ld	l,a		; HL = index
	ld	de,steps
	add	hl,de		; Get step from array
	ld	e,(hl)
	inc	hl
	ld	d,(hl) 
	ld	hl,(player)	; Get player location
	call	step		; Do one step (to get the number)
	call	valid		; Always valid, but returns A=step counter
	ld	c,a		; C = counter
	ld	b,0		; BC = counter, to add it to the score
	push	hl		; Save HL
	ld	hl,(score)	; Get score
	add	hl,bc		; Add current amount of steprs
	ld	(score),hl	; Update score
	pop	hl		; Restore HL 
.zero:	push	hl
	ld	a,high field	; Zero out the current position
	add	h
	ld	h,a
	ld	(hl),0
	pop	hl
	dec	c
	jr	z,.done
	call	step		; Go to next position
	jp	.zero
.done:	ld	(player),hl	; This is the player's new position
	jp	screen.field	; Redraw the screen (it's fast enough)
	;;;	Show help screen
help:	ld	de,	; Print help text
	ld	c,strout
	call	dos
.clear:	ld	e,0FFh		; Wait until no key is pressed
	ld	c,dirio
	call	dos
	and	a
	jr	nz,.clear
.wait:	ld	e,0FFh		; Then wait until a key is pressed
	ld	c,dirio
	call	dos
	and	a
	jr	z,.wait
	jp	screen.field	; Then redraw the screen
	;;;	Toggle highligting of valid moves
mvtogl:	ld	hl,hilite	
	ld	a,(hl)
	ld	(hl),a
	jp	screen.field	
	;;;	Game over
over:	ld	hl,(player)	; Write a '*' on the player's position
	ld	a,'*'
	call 	outahl
	ld	de,msg.over	; Ask user if he wants another game
	call	yesno
	jr	nc,quit.yes	; If not, quit.
	jp	start		; If so, new game	
	;;;	Ask if user wants to quit
quit:	ld	de,msg.quit	; Ask user
	call	yesno
	jr 	c,.yes		; If yes, quit.
	ld	de,msg.hlp	; Otherwise, put help message back
	call	statln
	jp	flash
.yes:	ld	de,msg.bye	; Say goodbye
	ld	c,strout
	jp	dos		; And quit
	;;;	Yes/no prompt; message in DE; carry set if 'yes'
yesno:	call	statln
.key:	ld	c,dirio		; Get key
	ld	e,0FFh
	call	dos
	and	a		; No key?
	jr	z,.key		; Try again
	or	32
	cp	'n'		; Pressed no?
	ret	z		; Then return w/carry clear
	cp	'y'		; Pressed yes?
	ret	z		; Then return w/carry set
	jr	.key		; Otherwise, try again
	;;;	Advance the coordinates in HL in the direction DE
step:	ld	a,h		
	add	d
	ld	h,a
	ld	a,l
	add	e
	ld	l,a
	;;;	Check if H=Y, L=X form a valid coordinate
	;;;	(On the field and not zero)
	;;;	Carry flag set if valid, clear if invalid
	;;;	If valid, A = field value
valid:	ld	a,l		; 0 <= L < 79?
	cp	79
	ret	nc
	ld	a,h		; 0 <= H < 22?
	cp	22
	ret	nc
	push	hl		; Keep coordinates
	add	a,high field	; Look up in field
	ld	h,a
	ld	a,(hl)
	pop	hl		; Restore coordinates
	and	a		; Was it zero?
	ret	z		; Then return with carry clear
	scf			; Otherwise, set carry
	;;;	Write status message / question
statln:	ld	hl,2918h	
	ld	(cursor),hl
	ld	c,strout
	jp	dos
	;;;	Write character in A to the screen at position
	;;;	(H=Y, L=X).
outahl:	push	hl		; Save registers 
	push	de
	push	bc
	;;;	Calculate address (H*80+L)
	ld	b,l		; Store L (X)
	ld	l,h		; HL = Y
	ld	h,0
	ld	d,h		; DE = Y
	ld	e,l
	add	hl,hl		; Y *= 2
	add 	hl,hl		; Y *= 4
	add	hl,de		; Y *= 5
	add 	hl,hl 		; Y *= 10
	add	hl,hl		; Y *= 20
	add 	hl,hl		; Y *= 40
	add 	hl,hl		; Y *= 80
	ld	e,b		; DE = X
	add 	hl,de		; Y += X
	ld	b,a		; B = character
	xor	a		; High VDP bits = 0
	out	(99h),a
	ld	a,14|128	; VDP port 14
	out	(99h),a
	ld	a,l		; Low byte = L
	out	(99h),a		
	ld	a,h		; High bits = H
	or	64		; Write bit on
	out	(99h),a
	ld	a,b		; Character
	out	(98h),a
	pop	bc
	pop	de
	pop	hl
	;;;	"X ABC" 8-bit random number generator
rand:	push	hl
	ld	hl,rnddat
	inc	(hl)		; X++
	ld	a,(hl)		; X,
	inc	hl
	xor	(hl)		; ^ C,
	inc	hl
	xor	(hl)		; ^ A
	ld	(hl),a		; -> A
	inc	hl
	add	a,(hl)		; + B,
	ld	(hl),a		; -> B
	rrca			; >> 1
	dec	hl
	xor	(hl)		; ^ A,
	dec	hl
	add	a,(hl)		; + C
	ld	(hl),a		; -> C
	pop	hl
	;;;	Clear the screen
cls:	xor	a
	ld	iy,rom
	ld	ix,cls_
	jp	calslt
msg:	;;;	Messages
.gen:	db	27,120,53,'Generating field$'
.score:	db	'Score: $'
.hlp:	db	27,'KMSX Greed - press ? for help$'
.quit:	db	27,'KReally quit? Y/N$'
.bye:	db	27,'EGoodbye!$'
.bad:	db	27,'KBad move!$'
.over:	db	27,'KGame over! Again? Y/N$'
.help:	db	27,89,32+1,32,27,'K'
	db	10,27,'K',9,9,9,9,8,8,'Welcome to MSX Greed'
	db	13,10,27,'K',9,9,9,'after the original by Matthew Day'
	db	13,10,27,'K'
	db	10,27,'K',9,' The object of the game is to clear as much of'
	db	' the screen as',13,10,27,'K',9,'possible by moving around'
	db	' in a field of numbers. You may move',13,10,27,'K',9
	db	'  in eight directions using the number pad, the arrow keys,'
	db	13,10,27,'K',9,'  or the letters H, J, K, L, Y, U, B, and N'
	db	' (in `vi',39,' style.)',13,10,27,'K',9,'When you'
	db	' move in a direction, the number closest to you shows'
	db	13,10,27,'K',9,' how many numbers you will clear. You cannot'
	db	' move offscreen',13,10,27,'K',9,'or onto or through a space'
	db	' that has already been cleared. When',13,10,27,'K',9,'you'
	db	' cannot make a valid move from your position, the game ends.'
	db	13,10,27,'K',9,'  Your current position is always marked with'
	db	' the `@',39,' sign.',13,10,27,'K',10,27,'K',9,'Other '
	db	'controls are:',13,10,27,'K',9,9,'P = toggle highlighting of'
	db	' valid moves',13,10,27,'K',9,9,'Q (or ESC) = quit',13,10,27
	db	'K',9,9,'W = start new game',13,10,27,'K$'
scrtxt:	db	'0000'
.last:	db	'/1737$'
dirkey:	;;;	Direction keys
.hjkl:	db	'kulnjbhy'	; VI-ish directions
.num:	db	'89632147'	; Number pad directions
.arrow:	db	62,0,60,0,63,0,61,0	; Arrow keys (up/down/left/right only)
steps:	db	 0,-1		; North
	db	 1,-1		; Northeast
	db	 1, 0		; East
	db	 1, 1		; Southeast
	db	 0, 1 		; South
	db	-1, 1		; Southwest
	db	-1, 0 		; West
	db	-1,-1		; Northwest
	db	 0, 0		; End
rnddat:	equ	$		; RNG state
nfont:	equ	rnddat+4	; Temporary font data
player:	equ	nfont		; Player index stored there afterwards
hilite:	equ	player+2	; Set if fields should be highlighted
dirs:	equ	hilite+1	; 8 bytes; N NE E SE S SW W NW 
score:	equ	dirs+8		; Score, 2-byte integer
field:	equ	1000h		; Field location