I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Greed/Z80 Assembly

From Rosetta Code

Z80 Assembly[edit]

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
inir
;;; 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
inir
;;; 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
otir
ei
;;; 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
di
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
otir
ei
xor a ; Set highlights on by default
cpl
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
otir
inc h ; Next line
ld l,0 ; Beginning of next line
dec d ; Done yet?
jp nz,.line
ei
;;; 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
rlca
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,msg.help ; 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)
cpl
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?
scf
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
ret
;;; 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
ret
;;; 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
di
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
ei
pop bc
pop de
pop hl
ret
;;; "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
ret
;;; 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