Rear Gunner

It’s been a while… I finally completed my first program in assembly. Well, sort of. Bob, from Bob’s 8-bit blog, decided to translate a program from Basic to Assembly. I ended up modifying it to add levels and a timer. Here it is:

START:
ld d,0
ld e,0
call FUNC_GET_DFILE_ADDRESS // returns print position in hl
ld de,SCORETXT
LBL1:
ld a,(de)
cp $FF
jr z, LBL2
ld (hl),a
inc hl
inc de
jp LBL1

; 44 PRINT AT 0,13;”LIVES=” // lives at 0,19
LBL2:
ld d,0
ld e,13
call FUNC_GET_DFILE_ADDRESS // returns print position in hl
ld de,LIVESTXT
LBL3:
ld a,(de)
cp $FF
jr z, LBL4
ld (hl),a
inc hl
inc de
jp LBL3

; 46 PRINT AT 0,24;”LEVEL=” // level at 0,30
LBL4:
ld d,0
ld e,24
call FUNC_GET_DFILE_ADDRESS // returns print position in hl
ld de,LEVELTXT
LBL5:
ld a,(de)
cp $FF
jr z, START_AGAIN
ld (hl),a
inc hl
inc de
jp LBL5

START_AGAIN:
; 70 PRINT AT 0,6;SCORE

ld de,SCORE_STR
ld a,(SCORE)
ld h,0
ld l,a
call FUNC_8BIT_TO_STRING
ld d,0
ld e,6
call FUNC_GET_DFILE_ADDRESS
ld a,(SCORE_STR)
ld (hl),a
inc hl
ld a,(SCORE_STR+1)
ld (hl),a
inc hl
ld a,(SCORE_STR+2)
ld (hl),a

; 80 PRINT AT 0,19;LIVES
ld d,0
ld e,19
call FUNC_GET_DFILE_ADDRESS
ld a,(LIVES)
add a,28 // digit char value is value+28
ld (hl),a

; 85 PRINT AT 0,30;LEVEL
ld d,0
ld e,30
call FUNC_GET_DFILE_ADDRESS
ld a,(LEVEL)
add a,28 // digit char value is value+28
ld (hl),a

; 90 IF LIVES = 0 THEN STOP
ld a,(LIVES)
cp 0
ret z
; 100 PAUSE 200
ld b, 20 // wait for 20 * 1/10 = 2 seconds
call FUNC_WAIT

; 110 LET A=2+INT (RND*20) // A is gunsight’s line
ld b,20
call FUNC_RAND_NUM
add a,2
ld (VAR01),a
; 120 LET B=2+INT (RND*29) // B is gunsight’s column
ld b,29
call FUNC_RAND_NUM
add a,2
ld (VAR02),a
; 150 LET C=2+INT (RND*29) // C is target’s column
ld b,29
call FUNC_RAND_NUM
add a,2
ld (VAR03),a

; 160 LET D=2+INT (RND*20) // D is target’s line
ld b,20
call FUNC_RAND_NUM
add a,2
ld (VAR04),a

; 170 IF (A=D) AND (B=C) THEN GOTO 10
ld a,(VAR01)
ld b,a
ld a,(VAR03)
cp b
jp z,START
ld a,(VAR02)
ld b,a
ld a,(VAR04)
cp b
jp z,START

; 180 LET STARTTIME = PEEK 16436 + 256 * PEEK 16437
ld de,(FRAMES)
ld (STARTTIME),de

MAIN_LOOP:

; 300 PRINT AT D,C;CHR$(172) // print target(reverse G)
ld a,(VAR04)
ld d,a
ld a,(VAR03)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$ac

ld b,1 // wait for 1* 1/10 = 1/10 second
call FUNC_WAIT

; 340 PRINT AT A,B;”+” // print gunsight at new position
ld a,(VAR01)
ld d,a
ld a,(VAR02)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$15 // print +

ld b,1 // wait for 1* 1/10 = 1/10 second
call FUNC_WAIT

; 350 LET E=A
ld a,(VAR01)
ld (VAR08),a

; 360 LET F=B
ld a,(VAR02)
ld (VAR09),a
;
; 370 IF INKEY$ =”5″ THEN GOSUB 600 // Going left
READ_KEY_UP:
ld a, $F7
in a, ($FE)
bit 4,a
jp nz, READ_KEY_DOWN
; 600 LET B=B-1
ld a,(VAR02)
dec a
ld (VAR02),a
; 610 PRINT AT E,F;” ” // clear gunsight at previous position
ld a,(VAR08)
ld d,a
ld a,(VAR09)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
; 620 IF B=0 THEN LET B=30
ld a,(VAR02)
ld b, 0
cp b
jp nz, READ_KEY_DOWN
ld a,30
ld (VAR02),a

; 380 IF INKEY$ =”8″ THEN GOSUB 650 // Going right
READ_KEY_DOWN:
ld a, $EF
in a, ($FE)
bit 2,a
jp nz, READ_KEY_LEFT
; 650 LET B=B+1
ld a,(VAR02)
inc a
ld (VAR02),a
; 660 PRINT AT E,F;” ” // clear gunsight at previous position
ld a,(VAR08)
ld d,a
ld a,(VAR09)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
; 670 IF B=31 THEN LET B=1
ld a,(VAR02)
ld b,31
cp b
jp nz, READ_KEY_LEFT
ld a,1
ld (VAR02),a

; 390 IF INKEY$ =”7″ THEN GOSUB 700 // Going up
READ_KEY_LEFT:
ld a, $EF
in a, ($FE)
bit 3,a
jp nz, READ_KEY_RIGHT
; 700 LET A = A-1
ld a,(VAR01)
dec a
ld (VAR01),a
; 710 PRINT AT E,F;” ” // clear gunsight at previous position
ld a,(VAR08)
ld d,a
ld a,(VAR09)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
; 720 IF A=0 THEN LET A=22
ld a,(VAR01)
ld b,0
cp b
jp nz, READ_KEY_RIGHT
ld a,22
ld (VAR01),a

; 400 IF INKEY$ =”6″ THEN GOSUB 750 // Going down
READ_KEY_RIGHT:
ld a, $EF
in a, ($FE)
bit 4,a
jp nz, READ_KEY_SHOOT
; 750 LET A = A+1
ld a,(VAR01)
inc a
ld (VAR01),a
; 760 PRINT AT E,F;” ” // clear gunsight at previous position
ld a,(VAR08)
ld d,a
ld a,(VAR09)
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
; 770 IF A=23 THEN LET A=1
ld a,(VAR01)
ld b,23
cp b
jp nz, READ_KEY_SHOOT
ld a,1
ld (VAR01),a

; 410 IF INKEY$ “0” THEN GOTO 300 // No Shooting, loop back
READ_KEY_SHOOT:
ld a, $EF
in a, ($FE)
bit 0,a
jp nz, MAIN_LOOP

; 420 IF DA THEN GOTO 300 // Not same column, loop back
ld a,(VAR04)
ld b,a
ld a,(VAR01)
cp b
jp nz, MAIN_LOOP

; 430 IF CB THEN GOTO 300 // Not same line, loop back
ld a,(VAR03)
ld b,a
ld a,(VAR02)
cp b
jp nz, MAIN_LOOP

; 440 LET ENDTIME = PEEK 16436 + 256 * PEEK 16437
ld de,(FRAMES)
ld (ENDTIME),de

; 450 PRINT AT D-1,C-1; CHR$(190) + ” ” + CHR$(185) // print a 3×3 drawing
ld a,(VAR04)
dec a
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$be // inverse Y
inc hl
ld (hl),$00
inc hl
ld (hl),$B9 // inverse T

; 460 PRINT AT D,C-1;” ” + CHR$(128) + ” ” // to represent target blowing
ld a,(VAR04)
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
inc hl
ld (hl),$80 // black square
inc hl
ld (hl),$00

; 470 PRINT AT D+1,C-1;CHR$(185) + ” ” + CHR$(190)
ld a,(VAR04)
inc a
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$b9 // inverse T
inc hl
ld (hl),$00
inc hl
ld (hl),$be // inverse G

;// SUBROUTINE UPDATE LIVES AND SCORE
; 800 LET ELAPSED =(STARTTIME-ENDTIME)/50
ld hl,(STARTTIME)
ld de,(ENDTIME)
sbc hl,de
ld d,50
call DIV_HL_D
ld (ELAPSED),hl

; 810 IF ELAPSED > TIMER THEN GOTO 850
ld a,(TIMER)
ld d,0
ld e,a
sbc hl,de
jp nc,LOSE_LIFE

; 820 LET SCORE = SCORE + 1
ld a,(SCORE)
inc a
ld (SCORE),a

; 822 IF SCORE/5 = INT (SCORE/5) THEN GOSUB 900
ld a,(SCORE)
ld h,0
ld l,a
ld d,5
call DIV_HL_D
cp 0
jp nz, CLEAR_BLOWING
ld a,(LEVEL)
inc a
ld (LEVEL),a
ld a,(TIMER)
dec a
dec a
ld (TIMER),a
jp CLEAR_BLOWING

; 850 LET LIVES = LIVES – 1
LOSE_LIFE:
ld a,(LIVES)
dec a
ld (LIVES),a

CLEAR_BLOWING:

ld b,10 // wait for 10 * 1/10 = 1 second
call FUNC_WAIT

; 500 PRINT AT D-1,C-1;” ”
ld a,(VAR04)
dec a
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
inc hl
ld (hl),$00
inc hl
ld (hl),$00

; 510 PRINT AT D,C-1;” ”
ld a,(VAR04)
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
inc hl
ld (hl),$00
inc hl
ld (hl),$00

; 520 PRINT AT D+1,C-1;” ”
ld a,(VAR04)
inc a
ld d,a
ld a,(VAR03)
dec a
ld e,a
call FUNC_GET_DFILE_ADDRESS
ld (hl),$00
inc hl
ld (hl),$00
inc hl
ld (hl),$00

; 530 GOTO 70 // restart program
jp START_AGAIN

FUNC_WAIT: ;
; Input: B = number of 1/10 seconds
; Each innerloop takes 21 cycles, that is 21 * 0,3077 microseconds = 6.4617 microseconds
; repeated 2500 times gives approximately 1/10 sec
ld hl,2500
WAIT_LOOP2:
dec hl ; 6 cycles
ld a,h ; 4 cycles
or l ; 4 cycles
jr nz,WAIT_LOOP2 ; 12 cycles if z=0, 7 otherwise

dec b
jr nz, FUNC_WAIT
ret

;FUNC_WAIT:
; wait for hl frames (50 frames = 1 sec)
; set 7,h
; ld (FRAMES),hl
;WAIT_LOOP:
; ld hl,(FRAMES)
; ld a,h
; and %01111111
; or l
; jp nz, WAIT_LOOP
; ret

FUNC_GETRANDOM:
; Calculate a new ‘random’ number from the last one and r-register
ld a, r
ld hl, LMATH_GR_SEED
add a, (hl)
rrca
ld (hl), a
ret
LMATH_GR_SEED:
db 91

FUNC_RAND_NUM:
; Get a random 8-bit number within a range
; Call with B as the maximum value + 1
call FUNC_GETRANDOM
RAND_NUM_LOOP:
cp b
ret c
sub b
jp RAND_NUM_LOOP

FUNC_GET_DFILE_ADDRESS:
; return the adress of a position in the display memory
; in: line in register d, column in register e
; out: the address in register hl
ld h, 0
ld l, d
add hl, hl // x2
add hl, hl // x4
add hl, hl // x8
add hl, hl // x16
add hl, hl // x32
ld b, 0
ld c, d
add hl, bc // x33
; Add the column
ld c, e
add hl, bc
; Add to the base of the display memory (+1)
ld de, (D_FILE)
inc de
add hl, de
ret

; from http://z80-heaven.wikidot.com/advanced-math#toc3
DIV_HL_D: ; HL = HL / D, A = remainder
XOR A ; Clear upper eight bits of A
LD B, 16 ; Sixteen bits in dividend
_loop:
ADD HL, HL ; Do a SLA HL. If the upper bit was 1, the c flag is set
RLA ; This moves the upper bits of the dividend into A
JR C, _overflow
CP D ; Check if we can subtract the divisor
JR C, _skip ; Carry means D > A
_overflow:
SUB D ; Do subtraction for real this time
INC L ; Set the next bit of the quotient (currently bit 0)
_skip:
DJNZ _loop
RET

FUNC_8BIT_TO_STRING:
;Input: HL = number to convert, DE = location of string
;Output: string at (DE)
ld bc,-100
call Num1
ld c,-10
call Num1
ld c,b
Num1:
ld a,$1C-1 ; chr$($1C) = ‘0’
Num2:
inc a
add hl,bc
jr c,Num2
sbc hl,bc

ld (de),a
inc de
ret

VAR01: // gunsight line coordinate A
db $00
VAR02: // gunsight column coordinate B
db $00
VAR03: // target column coordinate C
db $00
VAR04: db $00 // target line coordinate B
TIMER: db $0A
LIVES: db $05
LEVEL: db $00
SCORE: db $00
VAR08: db $00
VAR09: db $00
STARTTIME: dw $00,$00
ENDTIME: dw $00,$00
ELAPSED: dw $00,$00
SCORE_STR: dbzx ‘000’
SCORETXT: dbzx ‘SCORE=’
db $FF
LEVELTXT: dbzx ‘LEVEL=’
db $FF
LIVESTXT: dbzx ‘LIVES=’
db $FF
END _asm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s