; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Hungry Dog 2; Steven Reid (c) 2026; A faster version of Hungry Dog to animate the background.; v1 08/30/2026 - initial build.;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Header and startup;; start up stufforg16514; stored in REM at top (ZX81)jr start ; needed for z80asm; title and copyright (will show when LISTed)copy:db_as,_h_,_u_,_n_,_h_,_r_,_y_,_d_,_o_,_g_db_2_,_as,_s_,_l_,_r_,_sl,_2_,_0_,_2_,_6_db_as,$76; *HUNGRYDOG2*SLR/2026*start:call slow ; SLOW is required.call cls ; clear screen / expand screencall init_frame_buffer ; build the buffercall pick_message ; set starting message;; end header and startup;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Main program;mainloop:call draw_background
call draw_dog
call copy_to_screen
call delay
call tick_auto_message
call change_message_if_requested
call move_background
jp mainloop ; start again!;; end main;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Routines;; +++; Initial frame buffer; Build a valid ZX81 display file in the back buffer.; D_FILE begins with a HALT byte and every 32-character row ends; with another HALT byte.init_frame_buffer:ldhl,frame_buffer
ldde,33ldb,25; leading HALT plus 24 row terminatorslda,$76init_buffer_row:ld (hl),aaddhl,dedjnz init_buffer_row
ret; end; ---; +++; Pick one of the five sayings using the ROM frame counter as a seed.; Note this won't be random when using an emulator.pick_message:lda,(frames)
pick_reduce:cp5jrc,pick_ready
sub5jr pick_reduce
pick_ready:ld (message_index),aadda,alde,aldd,0ldhl,message_table
addhl,delde,(hl)
inchlldd,(hl)
ld (message_ptr),deret; end pick_message; ---; +++; Change message; Any non-SPACE key requests the next saying. The actual change happens here,; outside the delay/check routine, so return addresses are never abandoned.change_message_if_requested:lda,(change_requested)
anda; was message changed?ret z ; no, returnxora; yes, clear flagld (change_requested),acall wait_key_release
call reset_auto_message
lda,(message_index) ; grab next messageincacp5jrc,next_message_ready
xora; at end, start overnext_message_ready:; grab message locationld (message_index),aadda,alde,aldd,0ldhl,message_table
addhl,delde,(hl)
inchlldd,(hl)
ld (message_ptr),deret; end change_message_if_requested; ---; +++; Tick auto message counter; Request a new saying after an occasional interval even when no key is used.tick_auto_message:ldhl,(auto_message_count)
dechlld (auto_message_count),hllda,horlret nz
lda,1ld (change_requested),aret; end tick_auto_message; ---; +++; Reset auto message; The next interval varies from 256 to 511 animation frames, using FRAMES as; a lightweight source of variation.reset_auto_message:lda,(frames) ; get frame counterldl,aldh,1; 256..511 frames until the next changeld (auto_message_count),hl; and save to counterret; end reset_auto_message; ---; +++; Draw background; ; Fill all 24 rows in the back buffer with the appropirate message,; starting at position and skipping any end of row terminators.; A zero byte terminates a saying and wraps the source back to its; beginning.draw_background:ldhl,(message_ptr) ; load location of messagelda,(position+1) ; integer part of the 8.8 positionldb,aandajr z,background_phase_ready
background_phase:inchllda,(hl)
andajr nz,background_phase_next
ldhl,(message_ptr)
background_phase_next:djnz background_phase
background_phase_ready:ldde,frame_buffer+1ldc,24background_row:ldb,32background_cell:lda,(hl)
andajr nz,background_write
ldhl,(message_ptr)
lda,(hl)
background_write:ld (de),ainchlincdedjnz background_cell
incdedeccjr nz,background_row
ret; end draw_background; ---; +++; Draw Dog;; Dog data is stored as row, column, length, bytes. This preserves background; in the gaps between separate fragments on the same row. It is composited; into the back buffer, never directly into the live display.draw_dog:ldhl,dog_data
dog_record:lda,(hl) ; rowinchlcp$ffret z
ldc,(hl) ; columninchlldb,(hl) ; lengthinchl; HL now points to record datapushhl; preserve source pointerpushbc; preserve column and lengthldhl,frame_buffer+1ldde,33dog_seek_row:andajr z,dog_seek_col
addhl,dedecajr dog_seek_row
dog_seek_col:popbc; B=length, C=columnlde,cldd,0addhl,de; HL=display destinationexde,hl; DE=display destinationpophl; HL=record data sourceldc,bldb,0
ldir ; HL advances to the next recordjr dog_record
; end draw_dog; ---; +++; Copy to screencopy_to_screen:ldhl,frame_buffer ; set hl to bufferldde,(d_file) ; set de to display fileldbc,33*24+1; set bc to buffer size
ldir ; and copy to screenret; end copy_to_screen; ---; +++; Move background routine;; Update an 8.8 fixed-point position at a constant frame rate. Speed is a; fraction of one character per frame and varies with position: slow at the; ends, fast in the middle.move_background:lda,(position+1)
adda,a; two bytes per 8.8 speed entrylde,aldd,0ldhl,speed_table
addhl,deldc,(hl) ; BC=8.8 characters per frameinchlldb,(hl)
ldhl,(position)
lda,(direction)
cp1jr nz,move_left
move_right:addhl,bclda,hcp32jr nc,turn_left
ld (position),hlretturn_left:ldhl,$1fff; remain at position 31 while slowing/reversingld (position),hllda,$ffld (direction),aretmove_left:anda; clear carry for 16-bit subtractionsbchl,bcbit7,h; underflow crossed the left boundaryjr nz,turn_right
ld (position),hlretturn_right:ldhl,0ld (position),hllda,1ld (direction),aret; end move_background; ---; +++; Break;; preserves state, but will exit if SPACE is pressed; pressing any other key will reset the messagecheck_break:exx; save register states; did the player press break key (space)?call$0f46; was break pressed? (break-1 ROM routine)jr nc,break ; yes, exit as normalldbc,(last_k) ; was another key pressed?inccjr z,check_key_done ; no, then skip aheadlda,1; yes, then change messageld (change_requested),acheck_key_done:exx; restore registersret; and return; yes, exit the program as normalbreak:rst$0008; call ERROR-1 resetdb$ff; with error code 0 (normal exit); end check_break; ---; +++; Wait for key release routinewait_key_release:ldbc,(last_k) ; check last characterincc; check if key being pressedret z ; no, then returnjr wait_key_release ; yes, wait some more; end wait_key_release; ---; +++; Delay routine; The keyboard is checked throughout the pause rather than only; once per rendered frame.delay:ldhl,frame_delay
delay_loop:ld (delay_count),hlcall check_break
ldhl,(delay_count)
dechllda,horljr nz,delay_loop
ret; end delay; ---;; end routines;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Data;; Constantsframe_delay:equ100; Variablesmessage_ptr:dw msg_01 ; inital messagemessage_index:db0; index to messageposition:dw0; 8.8 fixed point: H=character, L=sub-characterdirection:db1; direction of moveementchange_requested:db0; user pressed a key (change message)delay_count:dw0; delayauto_message_count:dw400; time before next message; Precalculated Speeds; 16-bit 8.8 speeds indexed by integer position. $0040 is 1/4 character; per frame and $03c0 is 3 3/4 characters per frame. Values above $0100; intentionally allow multiple character-block jumps near the middle.speed_table:dw$0040,$0060,$0080,$00c0dw$0100,$0140,$0180,$01c0dw$0200,$0240,$0280,$02c0dw$0300,$0340,$0380,$03c0dw$03c0,$0380,$0340,$0300dw$02c0,$0280,$0240,$0200dw$01c0,$0180,$0140,$0100dw$00c0,$0080,$0060,$0040; Messages - these are all inverted charactersmessage_table:dw msg_01, msg_02, msg_03, msg_04, msg_05
msg_01:; AHUNGRYDOGBELIEVESINNOTHINGBUTMEATdb$a6,$ad,$ba,$b3,$ac,$b7,$be,$a9,$b4,$ac,$a7,$aa,$b1,$ae,$aa,$bb,$aa,$b8,$ae,$b3,$b3,$b4,$b9,$ad,$ae,$b3,$ac,$a7,$ba,$b9,$b2,$aa,$a6,$b9,0msg_02:; MERCYISAWORDNOHUNGRYDOGUNDERSTANDSdb$b2,$aa,$b7,$a8,$be,$ae,$b8,$a6,$bc,$b4,$b7,$a9,$b3,$b4,$ad,$ba,$b3,$ac,$b7,$be,$a9,$b4,$ac,$ba,$b3,$a9,$aa,$b7,$b8,$b9,$a6,$b3,$a9,$b8,0msg_03:; BEWARETHEDOGTHATHASFORGOTTENITSMASTERdb$a7,$aa,$bc,$a6,$b7,$aa,$b9,$ad,$aa,$a9,$b4,$ac,$b9,$ad,$a6,$b9,$ad,$a6,$b8,$ab,$b4,$b7,$ac,$b4,$b9,$b9,$aa,$b3,$ae,$b9,$b8,$b2,$a6,$b8,$b9,$aa,$b7,0msg_04:; ASTARVINGDOGTRUSTSONLYITSTEETHdb$a6,$b8,$b9,$a6,$b7,$bb,$ae,$b3,$ac,$a9,$b4,$ac,$b9,$b7,$ba,$b8,$b9,$b8,$b4,$b3,$b1,$be,$ae,$b9,$b8,$b9,$aa,$aa,$b9,$ad,0msg_05:; HUNGERMAKESAFAITHFULDOGWILDdb$ad,$ba,$b3,$ac,$aa,$b7,$b2,$a6,$b0,$aa,$b8,$a6,$ab,$a6,$ae,$b9,$ad,$ab,$ba,$b1,$a9,$b4,$ac,$bc,$ae,$b1,$a9,0; Dog Imagedog_data:; row - column - length - datadb1, 27, 3, $80,$84,$80db2, 24, 6, $80,$80,$85,$80,$80,$80db3, 4, 4, $07,$03,$80,$80db3, 23, 7, $80,$80,$82,$07,$07,$07,$80db4, 4, 5, $80,$04,$02,$84,$80db4, 22, 8, $80,$07,$80,$07,$82,$80,$81,$84db5, 4, 6, $80,$82,$00,$85,$80,$80db5, 15, 16, $80,$80,$80,$80,$80,$80,$80,$82,$07,$07,$05,$80,$80,$80,$84,$80db6, 4, 26, $80,$80,$05,$02,$80,$84,$80,$80,$07,$07,$80,$80,$07,$83,$81,$80,$80,$80,$07,$81,$01,$80,$80,$80,$80,$84db7, 4, 26, $80,$80,$80,$00,$87,$80,$07,$06,$06,$06,$81,$80,$85,$82,$81,$84,$80,$80,$81,$80,$81,$80,$07,$82,$82,$85db8, 4, 26, $80,$80,$80,$82,$85,$07,$06,$81,$80,$80,$80,$80,$03,$00,$81,$80,$80,$86,$07,$80,$82,$84,$84,$81,$80,$81db9, 4, 25, $80,$07,$00,$84,$81,$07,$81,$80,$80,$80,$03,$00,$00,$81,$80,$80,$86,$80,$80,$82,$84,$80,$82,$80,$80db10, 0, 1, $86db10, 5, 23, $04,$82,$80,$80,$80,$81,$80,$07,$00,$06,$04,$00,$80,$80,$05,$80,$80,$07,$84,$80,$80,$04,$80db11, 0, 4, $82,$01,$07,$02db11, 6, 22, $07,$85,$80,$01,$80,$07,$00,$06,$81,$81,$81,$80,$07,$81,$80,$80,$80,$80,$80,$86,$82,$80db12, 0, 27, $80,$82,$81,$80,$87,$81,$01,$80,$80,$00,$84,$01,$02,$06,$80,$80,$80,$80,$05,$80,$80,$80,$80,$80,$80,$82,$80db13, 3, 23, $80,$82,$05,$00,$80,$80,$00,$86,$00,$02,$81,$80,$80,$86,$80,$85,$80,$80,$80,$80,$80,$82,$80db14, 0, 25, $86,$86,$86,$86,$00,$00,$00,$04,$00,$00,$00,$00,$06,$81,$80,$80,$86,$05,$80,$80,$80,$05,$80,$84,$80db15, 0, 25, $86,$86,$01,$00,$00,$00,$00,$06,$00,$00,$00,$00,$85,$80,$80,$80,$82,$80,$82,$84,$80,$80,$81,$86,$80db16, 0, 25, $86,$86,$86,$00,$00,$00,$06,$06,$00,$00,$00,$87,$86,$84,$80,$80,$80,$07,$84,$80,$80,$80,$06,$80,$80db17, 0, 28, $86,$86,$86,$00,$00,$00,$06,$06,$00,$00,$00,$00,$86,$80,$80,$80,$80,$80,$04,$80,$80,$80,$87,$80,$82,$80,$07,$01db18, 0, 28, $86,$01,$01,$00,$00,$06,$06,$00,$00,$00,$00,$00,$02,$80,$80,$80,$80,$80,$80,$02,$84,$80,$80,$80,$06,$06,$84,$86db19, 0, 27, $86,$01,$00,$00,$00,$00,$00,$00,$00,$87,$02,$04,$02,$80,$80,$80,$80,$80,$80,$80,$83,$03,$84,$84,$84,$80,$80db20, 0, 26, $86,$01,$00,$87,$00,$00,$00,$86,$00,$00,$01,$00,$06,$00,$03,$80,$80,$80,$80,$80,$07,$80,$80,$80,$80,$80db21, 0, 21, $01,$00,$00,$00,$05,$00,$00,$00,$01,$00,$06,$06,$04,$87,$81,$80,$80,$80,$07,$01,$83db22, 0, 19, $00,$00,$00,$00,$85,$00,$00,$87,$00,$02,$81,$06,$06,$80,$80,$80,$84,$07,$87db23, 0, 17, $04,$00,$00,$00,$00,$00,$00,$87,$84,$06,$06,$06,$06,$81,$80,$80,$86db$ffframe_buffer:defs33*24+1;; end data;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *;; Defines;; ZX81 system varsd_file:equ$400cdf_cc:equ16398last_k:equ16421margin:equ16424s_posn:equ16441frames:equ16436; ZX81 ROM functionskscan:equ$02bbfindchar:equ$07bdstop:equ$0cdcslow:equ$0f2bfast:equ$02e7save:equ$02f9printat:equ$08f5pause:equ$0f35cls: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;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *