My maze generator for the ZX81. This one uses a different display format.
;
; Maze Generator
; Steven Reid (c) 2024
; v1 01/30/2024 - initial build
; v2 02/05/2024 - Minor tweaks to generate maze.
; v3 02/08/2024 - Updated with different map.
;
; +++
;
; 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
ld a,$87 ; print left dot
rst $10
pm_top_row: ; and loop through
ld a,(hl)
inc hl
dec a
ld a,$83 ; set to bar
jr nz,pm_print_bar
ld a,_sp ; print space
rst $10
ld a,$87
rst $10
jr pm_done_top
pm_print_bar:
rst $10 ; print bar
rst $10
pm_done_top:
djnz pm_top_row
ld a,$76 ; return
rst $10
ld hl,maze
ld a,(size)
ld b,a
pm_print_row:
push bc
ld a,$85 ; print left wall
rst $10
; first row of cells
ld a,(size)
ld b,a
push hl ; save first row
pm_print_cell:
ld a,_sp
rst $10
bit 0,(hl) ; is right wall removed?
jr nz,pm_drop_right_wall
ld a,$85
pm_drop_right_wall:
rst $10
inc hl
djnz pm_print_cell
pop hl ; restore first row
ld a,$76 ; return
rst $10
ld a,$85 ; print left wall
rst $10
; second row of cells
ld a,(size)
ld b,a
pm_print_bottom_cell:
bit 1,(hl) ; is bottom wall removed?
ld a,$83
jr z,pm_keep_bottom_wall
ld a,_sp
pm_keep_bottom_wall:
rst $10
ld a,(hl)
push hl
ld hl,corner
ld d,0
ld e,a
add hl,de
ld a,(hl)
pop hl
rst $10
inc hl
djnz pm_print_bottom_cell
ld a,$76 ; return
rst $10
pop bc
djnz pm_print_row
ret
corner:
db $81, $83, $85, $87
; 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)
; Fast RND
;
; An 8-bit pseudo-random number generator,
; using a similar method to the Spectrum ROM,
; - without the overhead of the Spectrum ROM.
;
; R = random number seed
; an integer in the range [1, 256]
;
; R -> (33*R) mod 257
;
; S = R - 1
; an 8-bit unsigned integer
ld a,1
rrca ; multiply by 32
rrca
rrca
xor 0x1f
add a, d
sbc a, 255 ; carry
ld (rnd+1), a
; 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
; ---