ZX81 Assembly Listing for hdog2.asm


ZX81 assembly listing for hdog2

hdog2 (hdog2.asm)


ASSEMBLY PROGRAM LISTING

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