MazeGen is a simple square maze generator for the zx81. You can build mazes from 2x2 to 10x10 in size.
; ; 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 ; ---