A remake of Kimmifish in machine code with variable speed and moving bubbles.
;
; 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
; ---