ZX81 Assembly Listing for aquarium.asm


ZX81 assembly listing for **AQUARIUM*SLR/2024**

**AQUARIUM*SLR/2024** (aquarium.asm)

A remake of Kimmifish in machine code with variable speed and moving bubbles.


ASSEMBLY PROGRAM LISTING

;
; Aquarium
; Steven Reid (c) 2024
; 01/13/2024 - My version of the aquarium screen saver for the ZX81.
; 01/25/2/24 - Complete version with fish and bubbles.
;

; +++
;
; Header and startup
;

        ; start up stuff
        org 16514               ; stored in REM at top (ZX81)
        jr start                ; needed for z80asm

; title and copyright (will show when listed)
copy:
        db _as,_as,_a_,_q_,_u_,_a_,_r_,_i_,_u_,_m_
        db _as,_s_,_l_,_r_,_sl,_2_,_0_,_2_,_4_,_as
        db _as,$76,$76  ; **AQUARIUM*SLR/2024**

start:
        call slow               ; SLOW is required.
        call cls                ; clear screen / expand screen

; end header and startup
;
; ---

; +++
;
; Main Program
;

        call initialize
main_loop:
        call move_back_bubbles
        call move_fish
        call move_front_bubbles
        call copy_to_screen
        jr main_loop            ; rinse and repeat

;  end main
;
; ---

; +++
; Setup initial fish and such
initialize:
        call clear_buffer
        call print_aquarium

        ; determine delay
frame_speed:    equ 5           ; frames to wait (will adjust if NTSC)
        ld hl,margin            ; get margin
        ld a,(hl)
        ld hl,speed+1           ; get speed location (self modifying code)
        sub 55                  ; is it PAL?
        ld (hl),frame_speed     ; set frames to wait (PAL default)
        jr z,done_with_speed    ; skip if PAL
        inc (hl)                ; adjust for NTSC
        done_with_speed:

        ; initialize fish
        ld hl,fish_pos          ; first fish
        ld a,5
        ld (hl),a
        inc hl
        ld (hl),a
        inc hl
        ld a,15
        ld (hl),a
        inc hl
        ld (hl),a
        inc hl
        xor a
        ld (hl),255
        inc hl
        ld (hl),a
        inc hl
        ld (hl),255
        inc hl
        ld (hl),a
        inc hl
        ld (hl),a
        inc hl
        ld (hl),a
        ld b,num_small_bubbles*4+num_big_bubbles*4
clear_bubbles:
        inc hl
        ld (hl),a
        djnz clear_bubbles
        ret
; end initialize
; ---

; +++
; Print aquarium
print_aquarium:
        ; first, print water
        ld bc,1+33*1
        ld d,$09
        ld e,$0a
        call print_random_row
        ; then print ground
        ld bc,1+33*23
        ld d,$80
        ld e,$83

print_random_row:
        ; print a row of random blocks
        ld hl,(d_file)
        add hl,bc
        ld b,32
print_row:
        push hl
        push bc
        ld b,255
        call rnd
        pop bc
        pop hl
        sub 127
prr_block1:
        ld a,d
        jp nc,print_block
prr_block2:
        ld a,e
        print_block:
                ld (hl),a
                inc hl
        djnz print_row
        ret
; end print_aquarium
; ---

; +++
; Move fish
move_fish:

        ; loop through fish
        ; move fish
        ; off screen, get new fish
        ; print fish
        ; repeat

        ; move first fish
        ld hl,fish_speed        ; determine how fast to move
        ld a,(hl)               ; get speed
        inc hl
        add a,(hl)              ; increment sub-position
        ld (hl),a               ; save sub-position
        ld de,(fish_pos)        ; grab coordinates (x,y)
        jr nc,move_first_fish   ; no carry, skip to print
        inc d                   ; move left
move_first_fish:
        ld a,d
        cp 33+8                 ; off edge?
        jp nz,print_first_fish  ; no, move fish
        ; yes, get new y pos
        call get_fish_speed     ; get new speed
        ld (fish_speed),a
        ld de,(fish_pos+2)      ; get other fish
        call get_fish_row       ; and get a new row
        ld e,a                  ; save y
        ld d,256-8              ; reset x
print_first_fish:
        ld (fish_pos),de        ; save position
        ld hl,fish_frame        ; flip frame
        ld a,1
        xor (hl)
        ld (hl),a
        ld hl,fish_right_frame_0 ; load graphics
        or a
        jr z,skip_frame
        ld hl,fish_right_frame_1 ; load graphics
skip_frame:
        call print_fish

        ; move second fish
        ld hl,fish_speed+2        ; determine how fast to move
        ld a,(hl)               ; get speed
        inc hl
        add a,(hl)              ; increment sub-position
        ld (hl),a               ; save sub-position
        ld de,(fish_pos+2)      ; grab coordinates (x,y)
        jr nc,move_second_fish  ; no carry, skip to print
        dec d                   ; move right
move_second_fish:
        ld a,d
        cp -10                  ; off edge?
        jr nz,print_second_fish ; no, move fish
        ; yes, get new y pos
        call get_fish_speed     ; get new speed
        ld (fish_speed+2),a
        ld de,(fish_pos)        ; get other fish
        ld d,a
        call get_fish_row       ; and get a new row
        ld e,a                  ; save y
        ld d,33+8               ; reset x
print_second_fish:
        ld (fish_pos+2),de      ; save position
        ld hl,fish_frame+1      ; flip frame
        ld a,1
        xor (hl)
        ld (hl),a
        ld hl,fish_left_frame_0 ; load graphics
        or a
        jr z,skip_left_frame
        ld hl,fish_left_frame_1 ; load graphics
skip_left_frame:
        call print_fish


        ret
; end move_fish
; ---

; +++
; Get new fish speed
; returns speed in a
get_fish_speed:
        ld b,127        ; from 1-127
        call rnd
        add 128         ; from 128 to 255
        ret
; end get_fish_speed
; ---

; +++
; Get new fish position
; e = position to avoid
; returns position in a
get_fish_row:
        ld b,buffer_size-2
        call rnd        ; from 1 to buffer_size-2
        dec a           ; make from 0 to buffer_size-3
        ld b,a          ; save new pos
        ; check if other+2 < top (top>e+2 or e+2 < top)
        ld a,e
        add a,2
        ld d,a
        cp b
        jr c,good_to_go
        ; check if top+2 < othertop
        ld a,b
        add a,2
        cp e
        jr nc,get_fish_row
        ; we are good!
good_to_go:
        ld a,b          ; restore position
        ret

; end get_fish_row
; ---

; +++
; Print Fish
print_fish:
        ; de = x,y coords
        ; hl = fish graphics

        ld c,3
pf_y_loop:
        ld b,6

        ld a,d          ; save x pos
        push af         ; save af

pf_x_loop:
        push bc         ; save loop

        ld a,(hl)       ; char to print, save to pb_char+1
        ld (pb_char+1),a
        push hl         ; save hl
        call buffer_print
        pop hl          ; restore hl, de is presvered (bc destroyed)

        inc d           ; next pos (increment x)
        inc hl          ; next char

        pop bc         ; restore loop

        djnz pf_x_loop

        pop af          ; restore af
        ld d,a          ; restore x position
        inc e           ; next row (increment y)

        dec c
        jr nz,pf_y_loop

        ret
; end print_fish
; ---

; +++
; Buffer Print
        ; set pb_char with what to print
        ; de = x,y coords (passed)
        ; x can be 0 to 31
        ; y can be 0 to buffer_size
        ; 
buffer_print:
        ; work out what square on the screen the character appears at
        ld a,d          ; ld a with x
        bit 7,a
        ret nz          ; out of bounds
        sub 31
        ret p           ; out of bounds

        ld a,e          ; ld a with y
        bit 7,a
        ret nz          ; out of bounds
        sub buffer_size
        ret p           ; out of bounds

        ;now find the correct buffer position
        ld hl,buffer
        ld a,e          ; load a wity y
        or a            ; cp 0
        jr z,findx      ; already at y,skip
        ld bc,33
findy:
        add hl,bc
        dec a
        jr nz, findy

findx:
        ld b,0
        ld c,d          ; load e with x
        add hl,bc
        ; at correct screen pos
        ; print character!
pb_char:
        ld (hl),0       ; what to load

        ret
; end buffer_print
; ---

; +++
; Move back bubbles (behind fish)
move_back_bubbles:
        ld hl,small_bubbles
        ld b,num_small_bubbles
mb_bubbles:
        push bc
        push hl

        ; check if we do anything
        ld a,(hl)               ; get speed
        or a                    ; is speed 0?
        jr nz,mb_start          ; no, start to move bubble
        ; okay, is zero. Should I start a new bubble?
        ld b,255
        call rnd
        cp 200
        jr c,mb_done           ; no, skip adding bubble

        ; load a new bubble!
        ld b,127                ; first, get a bubble speed
        call rnd
        add 32                  ; small bubbles move slower
        ld d,a                  ; save speed
        ld b,32                 ; 1-32
        call rnd
        dec a                   ; 0-31
        pop hl                  ; grab hl
        push hl                 ; and save it
        ld (hl),d               ; save speed
        inc hl
        inc hl                  ; skip over sub-pos (doesn't really matter)
        ld (hl),22              ; save y (22 or below ground)
        inc hl
        ld (hl),a               ; save x (from above)
        jr mb_done              ; don't do anything first run

mb_start:
        inc hl
        add a,(hl)              ; increment sub-position
        ld (hl),a               ; save sub-position
        inc hl
        ld e,(hl)               ; grab y
        inc hl
        ld d,(hl)               ; grab x
        dec hl
        jr nc,mb_print          ; no carry, skip to print
        dec e                   ; move up
mb_print:
        ld (hl),e
        inc hl
        inc hl
        ld a,e                  ; are we at top?
        cp -2
        jr nz,mb_print_bubble
        ; yes, clear this bubble
        ld bc,-4
        add hl,bc
        ld (hl),0
        jr mb_done              ; all done, skip ahead...

mb_print_bubble:
        ; let's print this puppy!
        ; de should hold coordinates
        push hl         ; save hl
        ld a,_pr
        ld (pb_char+1),a
        call buffer_print
        inc e
        ld a,_sp
        ld (pb_char+1),a
        call buffer_print
        pop hl          ; restore hl, de is presvered (bc destroyed)

mb_done:
        pop hl
        ld bc,4
        add hl,bc
        pop bc
        djnz mb_bubbles

        ret
; end move_back_bubbles
; ---

; +++
; Move front bubbles (in front of fish)
move_front_bubbles:
        ld hl,big_bubbles
        ld b,num_big_bubbles
mf_bubbles:
        push bc
        push hl

        ; check if we do anything
        ld a,(hl)               ; get speed
        or a                    ; is speed 0?
        jr nz,mf_start          ; no, start to move bubble
        ; okay, is zero. Should I start a new bubble?
        ld b,255
        call rnd
        cp 175
        jr c,mf_done           ; no, skip adding bubble

        ; load a new bubble!
        ld b,127                ; first, get a bubble speed
        call rnd
        add 128                  ; big bubbles move faster
        ld d,a                  ; save speed
        ld b,128                ; 1-32
        call rnd
        dec a                   ; 0-31
        pop hl                  ; grab hl
        push hl                 ; and save it
        ld (hl),d               ; save speed
        inc hl
        inc hl                  ; skip over sub-pos (doesn't really matter)
        ld (hl),22              ; save y (22 or below ground)
        inc hl
        ld (hl),a               ; save x (from above)
        jr mf_done              ; don't do anything first run

mf_start:
        inc hl
        add a,(hl)              ; increment sub-position
        ld (hl),a               ; save sub-position
        inc hl
        ld e,(hl)               ; grab y
        inc hl
        ld d,(hl)               ; grab x
        dec hl
        jr nc,mf_print          ; no carry, skip to print
        dec e                   ; move up
mf_print:
        ld (hl),e
        inc hl
        inc hl
        ld a,e                  ; are we at top?
        cp -2
        jr nz,mf_print_bubble
        ; yes, clear this bubble
        ld bc,-4
        add hl,bc
        ld (hl),0
        jr mf_done              ; all done, skip ahead...

mf_print_bubble:
        ; let's print this puppy!
        ; de should hold coordinates
        push hl         ; save hl
        ld a,_o_
        ld (pb_char+1),a
        call buffer_print
        inc e
        ld a,_sp
        ld (pb_char+1),a
        call buffer_print
        pop hl          ; restore hl, de is presvered (bc destroyed)

mf_done:
        pop hl
        ld bc,4
        add hl,bc
        pop bc
        djnz mf_bubbles

        ret
; end move_front_bubbles
; ---

; +++
; Copy to screen
copy_to_screen:

        ; grab frames now
        ld hl,frames    ; make hl point to the timecounter
        ld a,(hl)       ; get the timecounter in A
        push af         ; save current frame values
        push hl         ; save location (faster than reloading later)

        ; copy buffer to screen
        ld hl,(d_file)  ; get display
        ld bc,1+33*2    ; skip past water
        add hl,bc
        ex de,hl        ; put display (to) into de
        ld hl,buffer    ; get bufer
        ld bc,33*buffer_size
        ldir            ; load buffer on screen!

        ; now delay - part of wait was printing screen
        pop hl
        pop af
speed:                  ; will update the variable below (self modifying code)
        sub 9           ; the frame we want to test
wfr:    cp (hl)         ; test if frames match
        jr nz,wfr       ; nope, loop until they do

        call check_break

        ret
; end copy_to_screen
; ---

; +++
; Print Borders
print_borders:
        ld hl,(d_file)
        inc hl
        ld a,$80        ; black

        ld c,15         ; rows
pb_y_loop:
        ld b,27         ; columns
pb_x_loop:
        ld (hl),a       ; clear character
        inc hl
        djnz pb_x_loop

        ; skip ahead
        ld de,6
        add hl,de

        dec c
        jr nz,pb_y_loop


        ret
; end print borders
; ---

; +++
; Clear buffer from display
;
clear_buffer:
        ld de,buffer    ; load buffer (to) into de
        ld hl,(d_file)  ; load display (from) into hl
        inc hl          ; skip to first character
        ld bc,33*buffer_size 
        ldir            ; clear buffer from screen
        ret
; end clear buffer
; ---

; +++
; Break
;
; preserves state, but will exit if SPACE is pressed

check_break:
        exx                     ; save register states

        ; did the player press break key (space)?
        call $0f46              ; was break pressed? (break-1 ROM routine)
        jr nc,break             ; no, exit as normal

        exx                     ; restore registers
        ret                     ; and return

        ; yes, exit the program as normal
break:
        rst $0008               ; call ERROR-1 reset
        db $ff                  ; with error code 0 (normal exit)

; end break
; ---

; +++
;
; Random
;
;       Returns a random number between 1 and range value (a)
;       TODO - replace a with b or c for the range value
;
; in:           a       = range value (high)
; out:          a       = a number between 1 and range value
; preserves:    de
; destroys:     af, bc, hl
;
rnda:   ; loads b from a
        ld b,a

rnd:    ; random routine with refresh
        ld hl,0                 ; seed value
        ld a,r                  ; grab the refresh register
        add a,l                 ; add msb of seed (l)
        ld l,a                  ; and save it as the new value
        ld a,r                  ; grab the refresh register again
        add a,h                 ; add lsb of seed (h)
        and $1f                 ; mask it to stay in ZX81 ROM (lower 8K)
        ld h,a                  ; set pointer back within ROM
        ld (rnd+1),hl           ; save current pointer (self modifying code!!!)

        ld a,(hl)		; get value in ROM
rrange:	sub b			; we need 0-rval only
        jr nc,rrange            ; repeat unitl within range of value
        adc a,b		        ; undo last subtraction, range 1-rval
        ret			; back to mainprogram
; end random
;
; ---


; +++
; Variables

num_fish:       equ 2
fish_pos:       defs 2*num_fish         ; x,y for no. of fish
fish_speed:     defs 2*num_fish         ; speed and sub-pos
fish_frame:     defs num_fish           ; frame to display

num_small_bubbles:      equ 10
small_bubbles:  defs 4*num_small_bubbles        ; x,y, speed, sub-pos for small bubbles

num_big_bubbles:        equ 5
big_bubbles:    defs 4*num_big_bubbles          ; x,y, speed, sub-pos for big bubbles

buffer_size:    equ 21
buffer:         defs 33*buffer_size
; end variables
; ---

;+++
; Fish graphics

fish_left_frame_0:
        ; fish 0 left
        db 135, 003, 003, 004, 131, 000
        db 005, 001, 004, 133, 000, 000
        db 002, 131, 131, 001, 003, 000
fish_left_frame_1:
        ; fish 1 left
        db 135, 003, 003, 004, 006, 000
        db 005, 001, 129, 133, 000, 000
        db 002, 131, 131, 001, 134, 000

fish_right_frame_0:
        ; fish 0 right
        db 000, 131, 135, 003, 003, 004
        db 000, 000, 005, 135, 002, 133
        db 000, 003, 002, 131, 131, 001
fish_right_frame_1:
        ; fish 1 right
        db 000, 134, 135, 003, 003, 004
        db 000, 000, 005, 130, 002, 133
        db 000, 006, 002, 131, 131, 001

; end fish graphics
; ---

; +++
;
; Data and Defines
;

; ZX81 system vars
d_file:         equ $400c
df_cc:          equ 16398
last_k:         equ 16421
margin:         equ 16424
s_posn:         equ 16441
frames:         equ 16436

; ZX81 ROM functions
kscan:          equ $02bb
findchar:       equ $07bd
stop:           equ $0cdc
slow:           equ $0f2b
fast:           equ $02e7
save:           equ $02f9
printat:        equ $08f5
pause:          equ $0f35
cls:            equ $0a2a

; ZX81 Characters (not ASCII)
_sp:            equ $00
_qu:            equ $0b
_lb:            equ $0c
_dl:            equ $0d
_cl:            equ $0e
_lp:            equ $10
_rp:            equ $11
_gt:            equ $12
_lt:            equ $13
_eq:            equ $14
_pl:            equ $15
_mi:            equ $16
_as:            equ $17
_sl:            equ $18
_sc:            equ $19
_cm:            equ $1a
_pr:            equ $1b
_0_:            equ $1c
_1_:            equ $1d
_2_:            equ $1e
_3_:            equ $1f
_4_:            equ $20
_5_:            equ $21
_6_:            equ $22
_7_:            equ $23
_8_:            equ $24
_9_:            equ $25
_a_:            equ $26
_b_:            equ $27
_c_:            equ $28
_d_:            equ $29
_e_:            equ $2a
_f_:            equ $2b
_g_:            equ $2c
_h_:            equ $2d
_i_:            equ $2e
_j_:            equ $2f
_k_:            equ $30
_l_:            equ $31
_m_:            equ $32
_n_:            equ $33
_o_:            equ $34
_p_:            equ $35
_q_:            equ $36
_r_:            equ $37
_s_:            equ $38
_t_:            equ $39
_u_:            equ $3a
_v_:            equ $3b
_w_:            equ $3c
_x_:            equ $3d
_y_:            equ $3e
_z_:            equ $3f

; end defines
; ---