Greed/Z80 Assembly

From Rosetta Code
Revision as of 18:39, 8 June 2020 by Not a robot (talk | contribs) (Z80 assembly version of Greed)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Z80 Assembly

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.

<lang z80>;; 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 </lang>