Files
blessingofra/play.asm
T
2026-07-21 16:46:09 +02:00

1263 lines
23 KiB
NASM

; -----------------------------------------------------------
; play.asm - the game: a fresh maze carves itself on screen
; every round (recursive backtracker, a few steps a frame),
; player 1 (joystick 2) runs the corridors toward the exit
; portal, player 2 (joystick 1) rides the wall corners and
; fire spins the four wall pieces around the cursor by 90
; degrees; the roaming enemies end the round on touch
;
; the walls, posts and the portal are chars (the screen stays
; the wall data structure), but every actor - runner, cursor,
; bullet, enemies - is a hardware sprite gliding between the
; cells; actor collisions are grid coordinate checks
;
; optional extras (see options.asm):
; opt_shoot - player 1's fire shoots a bullet in the last
; direction faced; a hit enemy respawns on a
; random cell after RESPAWN_TIME frames
; opt_cool - a spin starts a COOL_TIME recharge, shown as
; a draining 1/8-step bar on the status row
;
; screen layout: status on row 0 (over the colored raster
; band), the 39x23 maze lattice on rows 1-23, hints on row 24
; -----------------------------------------------------------
play_init:
jsr clear_screen
+prtext 0, 0, WHITE, txt_t1, 7
+prtext 0, 7, CYAN, txt_t2, 3
+prtext 0, 28, LGREY, txt_goal, txt_goale-txt_goal
+prtext 24, 6, DGREY, txt_hint, txt_hinte-txt_hint
lda #COL_WALL
sta latwcol
lda #COL_POST
sta latpcol
jsr draw_lattice
jsr gen_init
lda #0 ; the actors arrive when the maze is done
sta SPR_EN
lda #STATCOL ; the status row gets its color band
sta statbg
+setupd play_build
rts
; ---- the maze carves itself, BUILD_STEPS steps a frame ----
play_build:
lda #BUILD_STEPS
sta ptmp
pb_loop:
jsr gm_step
bcs play_start
dec ptmp
bne pb_loop
rts
; ---- the maze is done: exit, actors, sprites, input ----
play_start:
; the exit: a portal in the right border wall
ps_exit:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs ps_exit
asl ; cell row -> screen row
clc
adc #2
sta exrow
tax
jsr row_ptr
ldy #38
lda #CH_EXIT
sta (scrlo),y
lda #COL_EXIT
sta (collo),y
; the runner starts in the top-left cell, facing right
lda #1
sta px
sta fdx
lda #2
sta py
lda #0
sta fdy
lda px
sta tgc
lda py
sta tgr
jsr set_target
ldx #SP_RUN
jsr spr_jump
; the corner cursor starts on the center post
lda #18
sta kx
lda #11
sta ky
lda #COL_HILITE
jsr mark_slots
lda kx
sta tgc
lda ky
sta tgr
jsr set_target
ldx #SP_CUR
jsr spr_jump
lda #WHITE
sta SPRCOL+SP_RUN
sta SPRCOL+SP_CUR
lda #YELLOW
sta SPRCOL+SP_SHOT
jsr spawn_enemies
; input state: fire still held from the previous screen
; must be released before it can spin or shoot
lda #0
sta rheld
sta cheld
sta endflag
sta s_on
sta sx
sta sy
sta cool
sta rotflash
lda #1
sta cfheld
sta rfheld
+setupd play_update
jmp upd_sprites ; positions, shapes and enables now
; ============================================================
; maze generation - recursive backtracker over the 19x11
; cells; the screen itself is the wall data structure
; ============================================================
; fill rows 1-23 of the lattice: brick walls in latwcol, the
; pivot posts (even col, odd row) in latpcol
draw_lattice:
ldx #1
dl_fill:
stx ptmp
jsr row_ptr
ldy #38
dl_col:
lda #CH_WALL
sta (scrlo),y
lda latwcol
sta (collo),y
dey
bpl dl_col
ldx ptmp
inx
cpx #24
bne dl_fill
ldx #1 ; post rows 1, 3, ... 23
dl_prow:
stx ptmp
jsr row_ptr
ldy #38 ; post columns 38, 36, ... 0
dl_pcol:
lda #CH_POST
sta (scrlo),y
lda latpcol
sta (collo),y
dey
dey
bpl dl_pcol
ldx ptmp
inx
inx
cpx #25
bcc dl_prow
rts
; reset the generator: nothing visited, start on cell (0,0)
gen_init:
ldx #0
lda #0
gi_clr:
sta visited,x
inx
cpx #NCELLS
bne gi_clr
lda #0
sta gcx
sta gcy
sta gsp
jsr gv_mark
jmp carve_cell
; one generator step: carve toward a random unvisited
; neighbor, or back up one; C set when the maze is finished
gm_step:
; collect the unvisited neighbor directions
lda #0
sta gn
ldx #0
gm_scan:
stx gtd
lda gcx
clc
adc dxtab,x
sta gnx
cmp #MAZE_CW ; unsigned: -1 wraps to $ff and fails too
bcs gm_next
ldx gtd
lda gcy
clc
adc dytab,x
sta gny
cmp #MAZE_CH
bcs gm_next
ldx gny
lda mul19,x
clc
adc gnx
tax
lda visited,x
bne gm_next
ldx gn
lda gtd
sta gdlist,x
inc gn
gm_next:
ldx gtd
inx
cpx #4
bne gm_scan
lda gn
beq gm_pop
; pick a random one of them
gm_pick:
jsr rand8
and #3
cmp gn
bcs gm_pick
tax
lda gdlist,x
sta gtd
; push the current cell, open the wall, step through it
ldx gsp
lda gcx
sta stackx,x
lda gcy
sta stacky,x
inc gsp
jsr carve_wall
ldx gtd
lda gcx
clc
adc dxtab,x
sta gcx
ldx gtd
lda gcy
clc
adc dytab,x
sta gcy
jsr gv_mark
jsr carve_cell
clc
rts
gm_pop:
; dead end: back up; empty stack = the maze is done
lda gsp
beq gm_done
dec gsp
ldx gsp
lda stackx,x
sta gcx
lda stacky,x
sta gcy
clc
rts
gm_done:
sec
rts
gen_maze_full: ; the menu backdrop wants it all at once
jsr gm_step
bcc gen_maze_full
rts
gv_mark: ; visited[gcy * 19 + gcx] = 1
ldx gcy
lda mul19,x
clc
adc gcx
tax
lda #1
sta visited,x
rts
carve_cell: ; open the cell (2*gcx+1, 2*gcy+2)
lda gcy
asl
clc
adc #2
tax
jsr row_ptr
lda gcx
asl
clc
adc #1
tay
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
rts
carve_wall: ; open the wall slot toward direction gtd
ldx gtd
lda gcy
asl
clc
adc #2
clc
adc dytab,x
tax
jsr row_ptr
ldx gtd
lda gcx
asl
clc
adc #1
clc
adc dxtab,x
tay
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
rts
dxtab: !byte 0, 0, $ff, 1 ; up, down, left, right
dytab: !byte $ff, 1, 0, 0
mul19: !byte 0, 19, 38, 57, 76, 95, 114, 133, 152, 171, 190
; ---- enemies: random colors, random right-half cells ----
spawn_enemies:
lda #0
sta ei
sn_all:
jsr spawn_one
inc ei
lda ei
cmp #NENEMIES
bne sn_all
rts
spawn_one: ; place enemy ei on a random free cell
sn_rx:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs sn_rx
clc
adc #MAZE_CW-MAZE_CH ; cell column 8-18: away from the start
asl
clc
adc #1
sta ttx ; candidate screen column
sn_ry:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs sn_ry
asl
clc
adc #2
sta tty ; candidate screen row
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR ; only a carved cell will do...
bne sn_rx
lda ttx ; ...without the runner on it...
cmp px
bne sn_free
lda tty
cmp py
beq sn_rx
sn_free:
jsr enemy_at ; ...or another live enemy
bcs sn_rx
ldx ei
lda ttx
sta ex,x
lda tty
sta ey,x
jsr rand8
and #7
tay
lda ecoltab,y
sta ecol,x
sta SPRCOL+SP_EN,x
jsr rand8
and #3
sta edir,x
lda #0
sta edead,x
lda ttx ; the sprite pops in on the cell
sta tgc
lda tty
sta tgr
jsr set_target
lda ei
clc
adc #SP_EN
tax
jmp spr_jump
ecoltab: ; anything but the white runner / grey walls
!byte RED, CYAN, PURPLE, YELLOW, ORANGE, LRED, LBLUE, BLUE
; ============================================================
; one frame of play
; ============================================================
play_update:
lda cool ; the spin recharge ticks down
beq pu_nocool
dec cool
pu_nocool:
jsr upd_runner
lda endflag
bne pu_end
jsr upd_shot
jsr upd_enemies
lda endflag
bne pu_end
jsr upd_cursor
jsr upd_flash
jsr upd_status
jmp upd_sprites
pu_end:
cmp #1
beq pu_win
lda #SFX_LOSE
jsr sfx_play
+fadeto lose_init
pu_win:
lda #SFX_WIN
jsr sfx_play
+fadeto win_init
; ---- player 1: walk the corridors, one cell per step ----
upd_runner:
jsr read_joy
and #$0f
sta rdir
bne ur_held
lda #0
sta rheld
rts
ur_held:
lda rheld
beq ur_first
dec rtimer
beq ur_rep
rts
ur_rep:
lda #REP_PERIOD
sta rtimer
jmp ur_move
ur_first:
lda #1
sta rheld
lda #REP_HOLD
sta rtimer
ur_move:
; decode the direction; it also becomes the gun's facing
ldx px
ldy py
lda rdir
lsr
bcs ur_up
lsr
bcs ur_down
lsr
bcs ur_left
inx ; right
lda #1
sta fdx
lda #0
sta fdy
jmp ur_try
ur_up:
dey
lda #0
sta fdx
lda #$ff
sta fdy
jmp ur_try
ur_down:
iny
lda #0
sta fdx
lda #1
sta fdy
jmp ur_try
ur_left:
dex
lda #$ff
sta fdx
lda #0
sta fdy
ur_try:
stx ttx
sty tty
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_EXIT
beq ur_win
cmp #CH_FLOOR
bne ur_stop ; a wall or a post: blocked
jsr enemy_at ; walking into an enemy still hurts
bcs ur_dead
lda ttx
sta px
lda tty
sta py
ur_stop:
rts
ur_win:
lda #1
sta endflag
rts
ur_dead:
lda #2
sta endflag
rts
; ---- the optional gun: one straight-flying bullet sprite ----
upd_shot:
lda s_on
beq sh_spawn
lda sx ; one step along its heading
clc
adc sdx
sta ttx
lda sy
clc
adc sdy
sta tty
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq sh_open
lda #0 ; a wall (or the portal) stops it
sta s_on
jmp sh_spawn
sh_open:
jsr enemy_at
bcc sh_fly
jsr kill_idx ; X = the victim
lda #0
sta s_on
jmp sh_spawn
sh_fly:
lda ttx
sta sx
lda tty
sta sy
sh_spawn:
lda opt_shoot ; fire spawns a bullet (edge detected)
beq sh_done
lda joyb ; read_joy ran in upd_runner this frame
and #JOY_FIRE
beq sh_frel
lda rfheld
bne sh_done
lda #1
sta rfheld
lda s_on
bne sh_done ; one bullet at a time
lda px
sta sx
lda py
sta sy
lda fdx
sta sdx
lda fdy
sta sdy
lda #1
sta s_on
lda #SFX_SHOT
jmp sfx_play
sh_frel:
lda #0
sta rfheld
sh_done:
rts
; ---- enemy X was hit: it starts its respawn timer ----
kill_idx:
lda #RESPAWN_TIME
sta edead,x
lda #4 ; white border blip
sta bflash
lda #WHITE
sta bcolor
lda #SFX_HIT
jmp sfx_play
; ---- is a live enemy standing on (ttx, tty)?
; returns C set + its index in X when one is ----
enemy_at:
ldx #0
ea_loop:
lda edead,x
bne ea_next
lda ex,x
cmp ttx
bne ea_next
lda ey,x
cmp tty
bne ea_next
sec
rts
ea_next:
inx
cpx #NENEMIES
bne ea_loop
clc
rts
; ---- player 2: the corner cursor + the wall spin ----
upd_cursor:
jsr read_joy2
sta j2bits
and #$0f
sta cdir
bne uc_held
lda #0
sta cheld
jmp uc_fire
uc_held:
lda cheld
beq uc_first
dec ctimer
beq uc_rep
jmp uc_fire
uc_rep:
lda #CREP_PERIOD
sta ctimer
jmp uc_move
uc_first:
lda #1
sta cheld
lda #CREP_HOLD
sta ctimer
uc_move:
ldx kx
ldy ky
lda cdir
lsr
bcs uc_up
lsr
bcs uc_down
lsr
bcs uc_left
cpx #36 ; right: interior posts end at column 36
bcs uc_fire
inx
inx
jmp uc_go
uc_up:
cpy #4 ; top interior post row is 3
bcc uc_fire
dey
dey
jmp uc_go
uc_down:
cpy #21 ; bottom interior post row is 21
bcs uc_fire
iny
iny
jmp uc_go
uc_left:
cpx #3 ; left interior post column is 2
bcc uc_fire
dex
dex
uc_go:
stx ttx
sty tty
lda #COL_WALL ; the old corner's walls lose the highlight
jsr mark_slots
lda #0 ; a spin flash does not follow the cursor
sta rotflash
lda ttx
sta kx
lda tty
sta ky
lda #COL_HILITE ; show the walls a spin here would move
jsr mark_slots
lda #SFX_CURSOR
jsr sfx_play
uc_fire:
lda j2bits
and #JOY_FIRE
beq uc_frel
lda cfheld
bne uc_done
lda #1
sta cfheld
jmp do_rotate ; its rts returns to play_update's caller
uc_frel:
lda #0
sta cfheld
uc_done:
rts
; ---- spin the four wall slots around post (kx, ky) by 90
; degrees clockwise; denied while anyone stands in them or
; while the optional cooldown is still recharging ----
do_rotate:
lda opt_cool
beq dr_ready
lda cool
beq dr_ready
dr_deny:
jmp rot_deny ; still recharging / a slot is taken
dr_ready:
lda kx ; nobody may stand in the four slots
sta ttx
ldx ky
dex
stx tty
jsr slot_occ
bcs dr_deny
ldx ky
inx
stx tty
jsr slot_occ
bcs dr_deny
lda ky
sta tty
ldx kx
dex
stx ttx
jsr slot_occ
bcs dr_deny
ldx kx
inx
stx ttx
jsr slot_occ
bcs dr_deny
ldx ky ; gather the four slot chars
dex
jsr row_ptr
ldy kx
lda (scrlo),y
sta nch
ldx ky
inx
jsr row_ptr
ldy kx
lda (scrlo),y
sta sch
ldx ky
jsr row_ptr
ldy kx
dey
lda (scrlo),y
sta wch
iny
iny
lda (scrlo),y
sta ech
; clockwise: north<-west, east<-north, south<-east, west<-south
ldx ky
dex
jsr row_ptr
ldy kx
lda wch
jsr put_slot
ldx ky
inx
jsr row_ptr
ldy kx
lda ech
jsr put_slot
ldx ky
jsr row_ptr
ldy kx
dey
lda sch
jsr put_slot
iny
iny
lda nch
jsr put_slot
lda #10 ; the moved pieces flash white
sta rotflash
lda opt_cool ; the spin starts the recharge
beq dr_nocool
lda #COOL_TIME
sta cool
dr_nocool:
lda #SFX_ROTATE
jmp sfx_play
rot_deny:
lda #6 ; red border blip on a denied spin
sta bflash
lda #RED
sta bcolor
lda #SFX_DENY
jmp sfx_play
slot_occ: ; C set when someone stands on (ttx, tty)
lda px
cmp ttx
bne so_norun
lda py
cmp tty
beq so_yes
so_norun:
jsr enemy_at
bcs so_yes
lda s_on
beq so_no
lda sx
cmp ttx
bne so_no
lda sy
cmp tty
beq so_yes
so_no:
clc
rts
so_yes:
sec
rts
put_slot: ; write char A at (scrlo),y + matching color
sta (scrlo),y
cmp #CH_WALL
beq ps_wall
lda #BLACK
sta (collo),y
rts
ps_wall:
lda #COL_WALL
sta (collo),y
rts
; ---- paint the four slots around the cursor post with the
; color in A - only wall blocks change, so the highlight marks
; exactly the pieces a spin would move (floors stay black) ----
mark_slots:
sta hlcol
ldx ky ; north
dex
jsr row_ptr
ldy kx
jsr mark_one
ldx ky ; south
inx
jsr row_ptr
ldy kx
jsr mark_one
ldx ky ; west
jsr row_ptr
ldy kx
dey
jsr mark_one
iny ; east: falls through to mark it
iny
mark_one:
lda (scrlo),y
cmp #CH_WALL
bne mo_skip
lda hlcol
sta (collo),y
mo_skip:
rts
; ---- the enemies: staggered random walkers ----
upd_enemies:
lda #0
sta ei
ue_loop:
ldx ei
lda edead,x
beq ue_alive
dec edead,x ; shot down: waiting to respawn
bne ue_next
jsr spawn_one ; the timer ran out: back on a fresh cell
jmp ue_next
ue_alive:
lda framectr
clc
adc estag,x
and #7 ; each enemy steps every 8th frame
bne ue_next
jsr enemy_step
lda endflag
bne ue_out
ue_next:
inc ei
lda ei
cmp #NENEMIES
bne ue_loop
ue_out:
rts
estag: !byte 0, 2, 4, 6
enemy_step:
jsr rand8 ; 1 in 4: wander off in a new direction
and #3
beq es_newdir
ldx ei
lda edir,x
jsr etry_dir
bcc es_done
es_newdir:
lda #8
sta etries
es_pick:
jsr rand8
and #3
jsr etry_dir
bcc es_done
dec etries ; walled in on all sides: sit this one out
bne es_pick
es_done:
rts
etry_dir: ; A = direction; C clear when it moved (or won)
sta etd
tax
ldy ei
lda ex,y
clc
adc dxtab,x
sta ttx
lda ey,y
clc
adc dytab,x
sta tty
lda px ; onto the runner: the round is over
cmp ttx
bne en_norun
lda py
cmp tty
bne en_norun
lda #2
sta endflag
clc
rts
en_norun:
ldx tty ; anything but open floor blocks it
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq en_open
sec
rts
en_open:
jsr enemy_at ; another enemy blocks it
bcc en_nofoe
sec
rts
en_nofoe:
lda s_on ; walking into the bullet hurts too
beq en_move
lda sx
cmp ttx
bne en_move
lda sy
cmp tty
bne en_move
lda #0
sta s_on
ldx ei
jsr kill_idx
clc
rts
en_move:
ldx ei
lda ttx
sta ex,x
lda tty
sta ey,x
lda etd
sta edir,x
clc
rts
; ---- per-frame flashes: the portal spins and blinks, the
; freshly spun walls flash white, the cursor shows the
; recharge state on its own color ----
upd_flash:
jsr upd_portal ; cycle the portal glyph
ldx exrow
jsr row_ptr
ldy #38
lda framectr
and #8
bne uf_exw
lda #COL_EXIT
jmp uf_exset
uf_exw:
lda #WHITE
uf_exset:
sta (collo),y
lda rotflash ; the spun walls flash, then re-highlight
beq uf_norf
dec rotflash
bne uf_white
lda #COL_HILITE
jmp uf_mark
uf_white:
lda #WHITE
uf_mark:
jsr mark_slots
uf_norf:
lda opt_cool ; the cursor dims red while recharging
beq uf_hot
lda cool
beq uf_hot
lda #RED
jmp uf_cset
uf_hot:
lda #WHITE
uf_cset:
sta SPRCOL+SP_CUR
rts
; ---- status row: the spin recharge readout (cooldown option)
; "wait" + a bar draining pixel by pixel, "ready" after ----
upd_status:
lda opt_cool
bne us_show
rts
us_show:
lda cool
beq us_ready
+prtext 0, 12, LRED, txt_wait, 4
lda cool ; pixels left: cool / 2 = 0-80
lsr
sta ptmp
ldx #0
us_bar:
lda ptmp
beq us_empty
cmp #8
bcc us_part
sec ; a full cell, and onward
sbc #8
sta ptmp
lda #$a0
jmp us_put
us_part:
clc ; 1-7 pixels: one of the bar chars
adc #CH_BAR-1
ldy #0
sty ptmp
jmp us_put
us_empty:
lda #$20
us_put:
sta SCREEN+18,x ; bar cells: row 0, columns 18-27
lda #LRED
sta COLRAM+18,x
inx
cpx #10
bne us_bar
rts
us_ready:
+prtext 0, 12, LGREEN, txt_ready, 5
lda #$20 ; clear the bar area
ldx #9
us_clr:
sta SCREEN+18,x
dex
bpl us_clr
rts
; ---- drive the eight sprites from the grid state ----
upd_sprites:
lda framectr
lsr
lsr
lsr
sta frame8
; the runner glides 2 pixels a frame
lda px
sta tgc
lda py
sta tgr
jsr set_target
lda #2
sta gspd
ldx #SP_RUN
jsr spr_glide
; the cursor glides 4 (its step is two cells)
lda kx
sta tgc
lda ky
sta tgr
jsr set_target
lda #4
sta gspd
ldx #SP_CUR
jsr spr_glide
; the bullet snaps cell to cell
lda sx
sta tgc
lda sy
sta tgr
jsr set_target
ldx #SP_SHOT
jsr spr_jump
; the enemies glide 1
lda #1
sta gspd
ldx #0
us_en:
stx ptmp
lda ex,x
sta tgc
lda ey,x
sta tgr
jsr set_target
lda ptmp
clc
adc #SP_EN
tax
jsr spr_glide
ldx ptmp
inx
cpx #NENEMIES
bne us_en
; shapes: the runner bobs, each enemy waves on its own beat
lda frame8
and #1
clc
adc #SPR_RUN
sta SPRPTR+SP_RUN
lda #SPR_CUR
sta SPRPTR+SP_CUR
lda #SPR_SHOT
sta SPRPTR+SP_SHOT
ldx #0
us_shape:
txa
clc
adc frame8
and #1
sta rtmp
lda #SPR_ENA ; enemies 0-1 are ghosts, 2-3 crawlers
cpx #2
bcc us_seta
lda #SPR_ENB
us_seta:
clc
adc rtmp
sta SPRPTR+SP_EN,x
inx
cpx #NENEMIES
bne us_shape
; enable: runner + cursor + the bullet and the live enemies
lda #%00000011
ldy s_on
beq us_nosht
ora #%00000100
us_nosht:
sta rtmp
ldx #0
us_alive:
lda edead,x
bne us_dead
lda rtmp
ora ebit,x
sta rtmp
us_dead:
inx
cpx #NENEMIES
bne us_alive
lda rtmp
sta SPR_EN
jmp spr_commit
ebit: !byte 8, 16, 32, 64
; ---- play texts ----
txt_goal: !scr "reach the "
!byte CH_EXIT
txt_goale:
txt_hint: !scr "p1: joy2/wasd p2: joy1/ijkl"
txt_hinte:
txt_wait: !scr "wait"
txt_ready: !scr "ready"
; ---- play variables ----
px: !byte 0 ; runner position (screen col / row)
py: !byte 0
fdx: !byte 0 ; runner facing: the gun aims this way
fdy: !byte 0
kx: !byte 0 ; corner cursor position (post col / row)
ky: !byte 0
exrow: !byte 0 ; screen row of the exit portal
endflag: !byte 0 ; 0 = playing, 1 = escaped, 2 = caught
cool: !byte 0 ; spin recharge frames left (cooldown option)
rotflash:!byte 0 ; frames the freshly spun walls stay white
s_on: !byte 0 ; bullet in flight?
sx: !byte 0 ; bullet position and heading
sy: !byte 0
sdx: !byte 0
sdy: !byte 0
rheld: !byte 0 ; runner direction repeat state
rtimer: !byte 0
rdir: !byte 0
rfheld: !byte 0 ; runner fire edge detection state
cheld: !byte 0 ; cursor direction repeat state
ctimer: !byte 0
cdir: !byte 0
cfheld: !byte 0 ; cursor fire edge detection state
j2bits: !byte 0
ptmp: !byte 0 ; scratch
ttx: !byte 0 ; move target (col / row)
tty: !byte 0
nch: !byte 0 ; the four slot chars around the cursor
ech: !byte 0
sch: !byte 0
wch: !byte 0
hlcol: !byte 0 ; mark_slots color parameter
latwcol: !byte 0 ; draw_lattice colors: walls, posts
latpcol: !byte 0
frame8: !byte 0 ; framectr / 8: the animation beat
ei: !byte 0 ; enemy loop index
etries: !byte 0
etd: !byte 0
gcx: !byte 0 ; maze generator state
gcy: !byte 0
gsp: !byte 0
gn: !byte 0
gtd: !byte 0
gnx: !byte 0
gny: !byte 0
gdlist: !fill 4, 0
ex: !fill NENEMIES, 0 ; enemy positions, headings, colors
ey: !fill NENEMIES, 0
edir: !fill NENEMIES, 0
ecol: !fill NENEMIES, 0
edead: !fill NENEMIES, 0 ; respawn countdown, 0 = alive
; ---- maze generator buffers ----
visited: !fill NCELLS, 0
stackx: !fill NCELLS, 0
stacky: !fill NCELLS, 0