ZX81 Assembly Listing for mazegen.asm


ZX81 assembly listing for ***MAZEGEN**SLR/2024**

***MAZEGEN**SLR/2024** (mazegen.asm)

MazeGen is a simple square maze generator for the zx81. You can build mazes from 2x2 to 10x10 in size.


ASSEMBLY PROGRAM LISTING

;
; Maze Generator
; Steven Reid (c) 2024
; v1 01/30/2024 - initial build
; v2 02/05/2024 - Minor tweaks to generate maze.
;

; +++
;
; 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,_m_,_a_,_z_,_e_,_g_,_e_,_n_,_as
        db _as,_s_,_l_,_r_,_sl,_2_,_0_,_2_,_4_,_as
        db _as,$76,$76  ; ***MAZE***SLR/2024***

start:
        call slow               ; SLOW is required.

; end header and startup
;
; ---

; +++
;
; Main Program
;

        ld a,5          ; reset maze size
        ld (size),a

main:
        call get_size
        call create_maze
        call print_maze
        call wait_for_key

        jr main

; exit!
ret

;  End of loop!
;

; +++
; Get Size
get_size:
        call cls                ; clear screen / exapnd screen

        ; print title
        ld bc,9+2*256
        call printat
        ld hl,ig_text
        call print

        ; print maze size
print_size:
        ld bc,5+4*256
        call printat
        ld h,1
        ld a,(size)
        call print_binary

        ; adjust size or generate maze
get_key_loop:
        ld hl,$0250             ; longer pause
        call delay
        call get_move
        cp $22
        jr z,shrink_maze
        cp $71
        jr z,shrink_maze
        cp $23
        jr z,grow_maze
        cp $70
        jr z,grow_maze
        cp $2C
        ret z
        cp $76
        ret z
        jr get_key_loop

shrink_maze:
        ld a,(size)
        dec a
        cp 1
        jp z,get_key_loop
        ld (size),a
        jp print_size

grow_maze:
        ld a,(size)
        inc a
        cp 11
        jp z,get_key_loop
        ld (size),a
        jp print_size

; stings
ig_text:
        db _as,_sp,_m_,_a_,_z_,_e_,_g_,_e_,_n_,_sp,_as,$76,$76
        db _s_,_i_,_z_,_e_,_cl,$76,$76
        db _6_,_mi,_s_,_h_,_r_,_i_,_n_,_k_,_cm,_sp,_7_,_mi,_g_,_r_,_o_,_w_,_cm,_sp,_g_,_mi,_g_,_e_,_n_,_e_,_r_,_a_,_t_,_e_,$ff

; end init_game
; ---

; +++
; Wait for key
wait_for_key:
        ld hl,$0250             ; longer pause
        call delay
        call get_move
        or a                    ; key pressed?
        jr z,wait_for_key
        ret
; end wait_for_key
; ---

; +++
; Get key
;
;       Routine won't return until a key is pressed.
;       Screen is upated until that happens.
;
; in:           none
; out:          a       = character code of pressed key
; destroys:     af, bc, hl, de
;
get_move:
        ; did the player make a move?
        ld bc,(last_k)          ; get key information
        inc c                   ; Test if NOKEY pressed
        jr z,get_move           ; not yet then loop
        dec c                   ; restore c

        call findchar           ; yep, grab character pressed
        ld a,(hl)

        ret                     ; return
; end get move
; ---

; +++
; Print Digit String and Copy Digit String
;
; in:           a (binary value)
;               h = 1 if no leading spaces, 0 if leading spaces
; destroys:     af,bc,hl
; preserves:    de
;

;       Prints a binary string to the text window.
print_binary:
        ld c,0
        ld b,1                  ; set skip flag
        ld h,256-100            ; hundreds
        call pb_num1
        ld h,256-10             ; tens
        call pb_num1
        ld b,0                  ; unset skip flag for last digit
        ld h,256-1              ; ones
pb_num1:
        ld l,$1c-1              ; reset l to zx81 0 -1
pb_num2:
        inc l                   ; next digit
        add a,h                 ; sub digit
        jr c,pb_num2            ; until we are too far
        sub h                   ; then add back
        push af                 ; save digit
        ld a,l                  ; set a to digit in l
        djnz printnum           ; is b==0? Then jump to printnum
        cp $1c                  ; at zero?
        jr nz,printnum          ; no, print number
        inc b                   ; reset skip flag
        ld a,c                  ; check if leading spaces
        and a                   ; check if zero
        jr nz,skipnum           ; no, then skip printing
                                ; if yes, a is 0 which is a space!
printnum:
        rst $10                 ; print digit (or leading space)
skipnum:
        pop af                  ; restore digit

        ret
; end print binary
; ---

; +++
; Create a new maze!
create_maze:

        ; initialize graphic
        ld a,1          ; reset counter
        ld (timer),a
        ld (cursor),a
        call print_icon

        ;
        ; initialize maze
        ;

        ; get max size
        ld hl,size
        xor a
        ld b,(hl)
cm_add_size:
        add a,(hl)
        djnz cm_add_size

        ld l,a
        ld h,0          ; save value (will use below)
        inc a
        ld (max),a              ; save max elements

        ; clear maze
        ld b,h
        ld c,l
        ld hl,maze
        ld de,visit
cm_clear_maze:
        ld (hl),0
        inc hl
        ex de,hl
        ld (hl),0
        inc hl
        ex de,hl
        dec bc
        ld a,b
        or c
        jr nz,cm_clear_maze

        ; get end position
        ld a,(size)             ; grab maze width
        ld d,a                  ; set in d (range)
        call rnd                ; get new value in a
        ld c,a                  ; set x to random location
        ld b,d                  ; set y to bottom row
        push de                 ; save d (range)

        ; set end of maze
        ld a,2                  ; remove bottom wall
        ld hl,maze              ; set visit position
        call set_pos            ; set position to value in a

        ; get start position
        pop de                  ; restore d (range)
        call rnd                ; get new value in a
        ld c,a                  ; set x to random location
        ld a,1                  ; set a to 1
        ld b,a                  ; set y to first row
        ld (yx_pos),bc          ; save start
        ld (count),a            ; and count is 1
        ld (cellat),a           ; and cell at is 1

        ; set start of maze
        ld hl,visit             ; set visit position
        ; a is set to count (1)
        call set_pos            ; set position to value in a

; A) GET LIST OF UNVISITED NEIGHBORS OF THE CURRENT CELL
cm_move_loop:

        call print_icon

        ; okay, get list of unvisited cells
        xor a
        ld (avail),a            ; zero out available cells

cm_check_top:
        ld bc,(yx_pos)          ; grab position
        dec b                   ; at top? (y=0)
        jp z, cm_check_right    ; yes, move to next
        ld hl,visit             ; check visit
        call get_pos            ; get position
        or a                    ; is it zero?
        jp nz,cm_check_right    ; no, skip ahead
        ld hl,avail
        set 1,(hl)              ; set bit 1

cm_check_right:
        ld bc,(yx_pos)          ; grab position
        ld a,(size)             ; get size
        cp c                    ; at width (x=size)
        jp z, cm_check_down     ; yes, move to next
        inc c                   ; no, move right
        ld hl,visit             ; check visit
        call get_pos            ; get position
        or a                    ; is it zero?
        jp nz,cm_check_down     ; no, skip ahead
        ld hl,avail
        set 2,(hl)              ; set bit 2

cm_check_down:
        ld bc,(yx_pos)          ; grab position
        ld a,(size)             ; get size
        cp b                    ; at height (y=size)
        jp z, cm_check_left     ; yes, move to next
        inc b                   ; no, move down
        ld hl,visit             ; check visit
        call get_pos            ; get position
        or a                    ; is it zero?
        jp nz,cm_check_left     ; no, skip ahead
        ld hl,avail
        set 3,(hl)              ; set bit 3

cm_check_left:
        ld bc,(yx_pos)          ; grab position
        dec c                   ; at left? (x=0)
        jp z, cm_got_list       ; yes, move to next
        ld hl,visit             ; check visit
        call get_pos            ; get position
        or a                    ; is it zero?
        jp nz,cm_got_list       ; no, skip ahead
        ld hl,avail
        set 4,(hl)              ; set bit 4

cm_got_list:

; B)IF NO UNVISITED NEIGHBORS, BACKTRACK TO PREVIOIUS CELL
        ld a,(avail)            ; grab available cells
        or a                    ; any available cells?
        jp nz,cm_move_random    ; yes! Move to one!

        ; nothing available, backtrack

        ld hl,cellat            ; get cell at
        dec (hl)                ; go to previous
        ld a,(size)
        ld b,a                  ; set y to size of maze
cm_get_y_loop:
        ld a,(size)
        ld c,a                  ; set x to size of maze
cm_get_x_loop:
        push bc                 ; save position
        ld hl,visit             ; get visit
        call get_pos            ; get position
;        ld (avail),a            ; save a (so I can see it)
        ld hl,cellat            ; get cell at
        pop bc                  ; restore pos
        cp a,(hl)               ; at position?
;        ld (yx_pos),bc          ; mave it current cell
        jr z,cm_found_pos       ; yes, skip ahead
        dec c
        jr nz,cm_get_x_loop      ; move left until at end
        djnz cm_get_y_loop      ; move up until at end
        ; error! shouldn't get here
        ret

cm_found_pos:
        ld (yx_pos),bc          ; mave it current cell

        jp cm_move_loop         ; and start again

; C) CHOOSE A RANDOM UNVISTED NEIGHBOR
cm_move_random:
        ld d,4                  ; set raange to four
        call rnd                ; get random move
        ld hl,avail             ; get list of available cells
cm_move_up:                     ; can we move up?
        cp 1                    ; test up?
        jr nz,cm_move_right     ; no, skip ahead
        bit 1,(hl)              ; can we move there?
        jr z,cm_move_right      ; no, skip ahead
        ; move up
        ld bc,(yx_pos)          ; grab position
        dec b                   ; move up
        ld (yx_pos),bc          ; mave it current cell
        ld hl,maze              ; grab maze
        ld a,2                  ; remove bottom wall
        call set_pos
        jp set_current          ; and set to current

cm_move_right:                  ; can we move right?
        cp 2                    ; test right?
        jr nz,cm_move_down      ; no, skip ahead
        bit 2,(hl)              ; can we move there?
        jr z,cm_move_down       ; no, skip ahead
        ; move right
        ld bc,(yx_pos)          ; grab position
        inc c                   ; move right
        ld (yx_pos),bc          ; mave it current cell
        dec c                   ; move back
        ld hl,maze              ; grab maze
        ld a,1                  ; remove right wall
        call set_pos
        jp set_current          ; and set to current

cm_move_down:                   ; can we move down?
        cp 3                    ; test down?
        jr nz,cm_move_left      ; no, skip ahead
        bit 3,(hl)              ; can we move there?
        jr z,cm_move_left       ; no, skip ahead
        ; move down
        ld bc,(yx_pos)          ; grab position
        inc b                   ; move up
        ld (yx_pos),bc          ; mave it current cell
        dec b                   ; move back
        ld hl,maze              ; grab maze
        ld a,2                  ; remove bottom wall
        call set_pos
        jp set_current          ; and set to current

cm_move_left:                   ; can we move left?
        cp 4                    ; test left?
        jr nz,cm_no_move        ; no, skip ahead
        bit 4,(hl)              ; can we move there?
        jr z,cm_no_move         ; no, skip ahead
        ; move left
        ld bc,(yx_pos)          ; grab position
        dec c                   ; move left
        ld (yx_pos),bc          ; mave it current cell
        ld hl,maze              ; grab maze
        ld a,1                  ; remove right wall
        call set_pos
        jp set_current          ; and set to current

cm_no_move:                     ; can't move, start over
        jr cm_move_random       ; nope, try again...

; C.1) AND REMOVE THE WALL BETWEEN IT AND THE CURRENT CELL
; C.2) MARK THE NEIGHBOR AS VISTED AND MAKE IT THE CURRENT CELL
set_current:
        ld a,(count)            ; get count
        inc a                   ; add 1
        ld (count),a            ; set count to a
        ld (cellat),a           ; set cell at to a
        ld bc,(yx_pos)          ; grab position
        ld hl,visit             ; grab visited
        call set_pos            ; and set to current pos

        ld hl,max
        cp (hl)                 ; are we at end of maze?
        jp nz,cm_move_loop      ; no, keep moving

        ; yes, return!
        ret

; get's position of of x,y in the maze at hl
; and adds it with the value in a
set_pos:
        push af
        call add_pos
        pop af
        add a,(hl)      ; add to a
        ld (hl),a       ; and store it
        ret

; get's position of of x,y in the maze at hl
; and returns the value in a
get_pos:
        call add_pos
        ld a,(hl)
        ret

; calculates value in array from x,y
add_pos:
        dec c           ; (x-1)
        ld d,0          ; set d to 0
        ld e,c          ; value of x
        add hl,de       ; add x to hl
        dec b           ; (y-1)
        ret z           ; return if at row 1
        ld a,(size)     ; get maze width
        ld e,a          ; set to width
sp_loop:
        add hl,de       ; add x
        djnz sp_loop    ; (x-1)*y
        ret

; print waiting icon
print_icon:
        ; update counter (slows counter down)
        ld hl,timer     ; get timer
        dec (hl)        ; decrement it
        ret nz          ; return if not zero
        ld a,20         ; reset counter
        ld (hl),a
        ; print wait icon
        ld bc,10+4*256
        call printat
        ld hl,wait
        call print      ; print wait location
        ld hl,cursor    ; grab cusor
        inc (hl)        ; move to new position
        ld a,(hl)       ; get cursor position
        cp 4
        jr nz,pi_cont   ; continue if not 4
        xor a           ; otherwise reset a
        ld (hl),a       ; save current position
pi_cont:
        ld b,0          ; calculate wait icon
        ld c,a
        ld hl,icon
        add hl,bc
        ld a,(hl)
        rst $10         ; and print it!
        ret

; Examples:
; .--.--.--.
; | 1  2| 0|
; .--.  .--.
; | 8| 3  4|
; .  .--.  .
; | 7  6  5|
; .--.--.--.
; FIRST CELL IS START (OPENING)
; HIGHEST # END CELL IS Exit
; bit 0 = remove right
; bit 1 = remove bottom
; bit 2 is visited

; variables
max_size:       equ 10  ; max size
size:   db 10            ; maze size (will get bigger)
max:    db 0            ; max size of maze (used as test)
yx_pos:                 ; if pulling into 16 bits
x_pos:  db 0            ; x and y position
y_pos:  db 0
cellat: db 0            ; current cell
count:  db 0            ; max cell (used in unvisited cells)
avail:  db 0            ; list of available moves
maze:   defs max_size*max_size  ; maze information (max size)
visit:  defs max_size*max_size  ; maze information (max size)
cursor: db 0            ; holds cursor
timer:  db 0            ; holds timer before updating cursor
wait:   db _b_,_u_,_i_,_l_,_d_,_i_,_n_,_g_,$ff
icon:   db $07, $84, $81, $82

; end create_maze
; ---

; +++
print_maze:

        call cls                ; clear screen / exapnd screen

        ; print top
        ld hl,visit
        ld a,(size)
        ld b,a
pm_top_row:
        ld a,_pr
        rst $10
        ld a,(hl)
        inc hl
        dec a
        ld a,_mi
        jr nz,pm_print_bar
        ld a,_sp
pm_print_bar:
        rst $10
        rst $10
        djnz pm_top_row
        ld a,_pr
        rst $10
        ld a,$76        ; return
        rst $10

        ld hl,maze
        ld a,(size)
        ld b,a
pm_print_row:
        push bc

        ld a,_i_
        rst $10

        ld a,(size)
        ld b,a
        push hl         ; save first row
pm_print_cell:
        ld a,_sp
        rst $10
        rst $10
        bit 0,(hl)      ; is right wall removed?
        ld a,_i_
        jr z,pm_keep_right_wall
        ld a,_sp
pm_keep_right_wall:
        rst $10
        inc hl
        djnz pm_print_cell
        pop hl          ; restore first row

        ld a,$76        ; return
        rst $10

        ld a,(size)
        ld b,a
pm_print_bottom_cell:
        ld a,_pr
        rst $10
        bit 1,(hl)      ; is bottom wall removed?
        ld a,_mi
        jr z,pm_keep_bottom_wall
        ld a,_sp
pm_keep_bottom_wall:
        rst $10
        rst $10
        inc hl
        djnz pm_print_bottom_cell

        ld a,_pr
        rst $10
        ld a,$76        ; return
        rst $10

        pop bc
        djnz pm_print_row

        ret
; end print_maze
; ---

; +++
; Print string at HL
; stops when reaches $FF
print:
        ld a,(hl)               ; load character
        inc hl                  ; increment memory
        cp $ff                  ; last character?
        ret z                   ; yep, return
        rst $10                 ; nope, print it!
        jr print                ; loop
; end print
; ---

; +++
; Break
;
; preserves state, but will exit if SPACE is pushed

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

; +++
; Delay
;
; set bc to speed
; uses check_break to exit

delay_count: dw $0000
delay:
        ld (delay_count),hl     ; save delay

        call check_break

        ; check if done
        ld hl,(delay_count)     ; grab what to test
        dec hl                  ; subtract 1
        ld a,h                  ; check if done
        or l
        jr nz,delay             ; not zero, keep going!

        ret                     ; pause is done!

; end delay
; ---

; +++
;
; Random
;
;       Returns a random number between 1 and range value (a)
;
; in:           d       = range value (high)
; out:          a       = a number between 1 and range value
; preserves:    de, bc (d is range)
; destroys:     af, hl
;
rnd:    ; random routine with refresh (doesn't use bc)
        ld hl,0
        ld a,r
        add a,l
        ld l,a
        ld a,r
        add a,h
        and $0f
        ld h,a                  ; set pointer back within ROM
        ld (rnd+1),hl           ; save current pointer (selfmodifying code!!!)

	ld a,(hl)		; get value in ROM
rrange:	sub d			; we need 0-rval only
	jr nc,rrange            ; repeat unitl within range of value
	adc a,d		        ; undo last subtraction, range 1-rval
	ret			; back to mainprogram


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