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 ; ---