A faster version of hungry dog where the message moves.
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; 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 stuff org 16514 ; 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 screen call init_frame_buffer ; build the buffer call 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: ld hl,frame_buffer ld de,33 ld b,25 ; leading HALT plus 24 row terminators ld a,$76 init_buffer_row: ld (hl),a add hl,de djnz 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: ld a,(frames) pick_reduce: cp 5 jr c,pick_ready sub 5 jr pick_reduce pick_ready: ld (message_index),a add a,a ld e,a ld d,0 ld hl,message_table add hl,de ld e,(hl) inc hl ld d,(hl) ld (message_ptr),de ret ; 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: ld a,(change_requested) and a ; was message changed? ret z ; no, return xor a ; yes, clear flag ld (change_requested),a call wait_key_release call reset_auto_message ld a,(message_index) ; grab next message inc a cp 5 jr c,next_message_ready xor a ; at end, start over next_message_ready: ; grab message location ld (message_index),a add a,a ld e,a ld d,0 ld hl,message_table add hl,de ld e,(hl) inc hl ld d,(hl) ld (message_ptr),de ret ; 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: ld hl,(auto_message_count) dec hl ld (auto_message_count),hl ld a,h or l ret nz ld a,1 ld (change_requested),a ret ; 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: ld a,(frames) ; get frame counter ld l,a ld h,1 ; 256..511 frames until the next change ld (auto_message_count),hl ; and save to counter ret ; 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: ld hl,(message_ptr) ; load location of message ld a,(position+1) ; integer part of the 8.8 position ld b,a and a jr z,background_phase_ready background_phase: inc hl ld a,(hl) and a jr nz,background_phase_next ld hl,(message_ptr) background_phase_next: djnz background_phase background_phase_ready: ld de,frame_buffer+1 ld c,24 background_row: ld b,32 background_cell: ld a,(hl) and a jr nz,background_write ld hl,(message_ptr) ld a,(hl) background_write: ld (de),a inc hl inc de djnz background_cell inc de dec c jr 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: ld hl,dog_data dog_record: ld a,(hl) ; row inc hl cp $ff ret z ld c,(hl) ; column inc hl ld b,(hl) ; length inc hl ; HL now points to record data push hl ; preserve source pointer push bc ; preserve column and length ld hl,frame_buffer+1 ld de,33 dog_seek_row: and a jr z,dog_seek_col add hl,de dec a jr dog_seek_row dog_seek_col: pop bc ; B=length, C=column ld e,c ld d,0 add hl,de ; HL=display destination ex de,hl ; DE=display destination pop hl ; HL=record data source ld c,b ld b,0 ldir ; HL advances to the next record jr dog_record ; end draw_dog ; --- ; +++ ; Copy to screen copy_to_screen: ld hl,frame_buffer ; set hl to buffer ld de,(d_file) ; set de to display file ld bc,33*24+1 ; set bc to buffer size ldir ; and copy to screen ret ; 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: ld a,(position+1) add a,a ; two bytes per 8.8 speed entry ld e,a ld d,0 ld hl,speed_table add hl,de ld c,(hl) ; BC=8.8 characters per frame inc hl ld b,(hl) ld hl,(position) ld a,(direction) cp 1 jr nz,move_left move_right: add hl,bc ld a,h cp 32 jr nc,turn_left ld (position),hl ret turn_left: ld hl,$1fff ; remain at position 31 while slowing/reversing ld (position),hl ld a,$ff ld (direction),a ret move_left: and a ; clear carry for 16-bit subtraction sbc hl,bc bit 7,h ; underflow crossed the left boundary jr nz,turn_right ld (position),hl ret turn_right: ld hl,0 ld (position),hl ld a,1 ld (direction),a ret ; end move_background ; --- ; +++ ; Break ; ; preserves state, but will exit if SPACE is pressed ; pressing any other key will reset the message 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 ; yes, exit as normal ld bc,(last_k) ; was another key pressed? inc c jr z,check_key_done ; no, then skip ahead ld a,1 ; yes, then change message ld (change_requested),a check_key_done: 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 check_break ; --- ; +++ ; Wait for key release routine wait_key_release: ld bc,(last_k) ; check last character inc c ; check if key being pressed ret z ; no, then return jr 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: ld hl,frame_delay delay_loop: ld (delay_count),hl call check_break ld hl,(delay_count) dec hl ld a,h or l jr nz,delay_loop ret ; end delay ; --- ; ; end routines ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Data ; ; Constants frame_delay: equ 100 ; Variables message_ptr: dw msg_01 ; inital message message_index: db 0 ; index to message position: dw 0 ; 8.8 fixed point: H=character, L=sub-character direction: db 1 ; direction of moveement change_requested: db 0 ; user pressed a key (change message) delay_count: dw 0 ; delay auto_message_count: dw 400 ; 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,$00c0 dw $0100,$0140,$0180,$01c0 dw $0200,$0240,$0280,$02c0 dw $0300,$0340,$0380,$03c0 dw $03c0,$0380,$0340,$0300 dw $02c0,$0280,$0240,$0200 dw $01c0,$0180,$0140,$0100 dw $00c0,$0080,$0060,$0040 ; Messages - these are all inverted characters message_table: dw msg_01, msg_02, msg_03, msg_04, msg_05 msg_01: ; AHUNGRYDOGBELIEVESINNOTHINGBUTMEAT db $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,0 msg_02: ; MERCYISAWORDNOHUNGRYDOGUNDERSTANDS db $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,0 msg_03: ; BEWARETHEDOGTHATHASFORGOTTENITSMASTER db $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,0 msg_04: ; ASTARVINGDOGTRUSTSONLYITSTEETH db $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,0 msg_05: ; HUNGERMAKESAFAITHFULDOGWILD db $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 Image dog_data: ; row - column - length - data db 1, 27, 3, $80,$84,$80 db 2, 24, 6, $80,$80,$85,$80,$80,$80 db 3, 4, 4, $07,$03,$80,$80 db 3, 23, 7, $80,$80,$82,$07,$07,$07,$80 db 4, 4, 5, $80,$04,$02,$84,$80 db 4, 22, 8, $80,$07,$80,$07,$82,$80,$81,$84 db 5, 4, 6, $80,$82,$00,$85,$80,$80 db 5, 15, 16, $80,$80,$80,$80,$80,$80,$80,$82,$07,$07,$05,$80,$80,$80,$84,$80 db 6, 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,$84 db 7, 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,$85 db 8, 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,$81 db 9, 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,$80 db 10, 0, 1, $86 db 10, 5, 23, $04,$82,$80,$80,$80,$81,$80,$07,$00,$06,$04,$00,$80,$80,$05,$80,$80,$07,$84,$80,$80,$04,$80 db 11, 0, 4, $82,$01,$07,$02 db 11, 6, 22, $07,$85,$80,$01,$80,$07,$00,$06,$81,$81,$81,$80,$07,$81,$80,$80,$80,$80,$80,$86,$82,$80 db 12, 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,$80 db 13, 3, 23, $80,$82,$05,$00,$80,$80,$00,$86,$00,$02,$81,$80,$80,$86,$80,$85,$80,$80,$80,$80,$80,$82,$80 db 14, 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,$80 db 15, 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,$80 db 16, 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,$80 db 17, 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,$01 db 18, 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,$86 db 19, 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,$80 db 20, 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,$80 db 21, 0, 21, $01,$00,$00,$00,$05,$00,$00,$00,$01,$00,$06,$06,$04,$87,$81,$80,$80,$80,$07,$01,$83 db 22, 0, 19, $00,$00,$00,$00,$85,$00,$00,$87,$00,$02,$81,$06,$06,$80,$80,$80,$84,$07,$87 db 23, 0, 17, $04,$00,$00,$00,$00,$00,$00,$87,$84,$06,$06,$06,$06,$81,$80,$80,$86 db $ff frame_buffer: defs 33*24+1 ; ; end data ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; 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 ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *