Files
blessingofra/play.asm
T
2026-07-18 18:34:28 +02:00

784 lines
14 KiB
NASM

; -----------------------------------------------------------
; play.asm - the game: a fresh maze is carved with a
; recursive backtracker every round, player 1 (joystick 2)
; runs the corridors toward the exit diamond, player 2
; (joystick 1) rides the wall corners and fire spins the
; four wall pieces around the cursor by 90 degrees; the
; roaming colored enemies end the round on touch
;
; screen layout: status on row 0, the 39x23 maze lattice on
; rows 1-23 (cells = odd col / even row, posts = even col /
; odd row, wall slots = the rest), a hint line on row 24
; -----------------------------------------------------------
play_init:
jsr clear_screen
+prtext 0, 0, WHITE, txt_title, 9
+prtext 0, 28, LGREY, txt_goal, txt_goale-txt_goal
+prtext 24, 6, DGREY, txt_hint, txt_hinte-txt_hint
; fill the whole lattice with wall blocks
ldx #1
pi_fillrow:
stx ptmp
jsr row_ptr
ldy #38
pi_fillcol:
lda #CH_WALL
sta (scrlo),y
lda #COL_WALL
sta (collo),y
dey
bpl pi_fillcol
ldx ptmp
inx
cpx #24
bne pi_fillrow
; the pivot posts get their own darker shade
ldx #1 ; post rows 1, 3, ... 23
pi_postrow:
stx ptmp
jsr row_ptr
ldy #38 ; post columns 38, 36, ... 0
pi_postcol:
lda #COL_POST
sta (collo),y
dey
dey
bpl pi_postcol
ldx ptmp
inx
inx
cpx #25
bcc pi_postrow
; carve the maze
ldx #0
lda #0
pi_clrv:
sta visited,x
inx
cpx #NCELLS
bne pi_clrv
jsr gen_maze
; the exit: a green diamond in the right border wall
pi_exit:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs pi_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
lda #1
sta px
lda #2
sta py
jsr draw_player
; the corner cursor starts on the center post
lda #18
sta kx
lda #11
sta ky
jsr spawn_enemies
; input state: a fire still held from the previous screen
; must be released before it can spin walls
lda #0
sta rheld
sta cheld
sta endflag
lda #1
sta cfheld
+setupd play_update
rts
; ============================================================
; maze generation - recursive backtracker over the 19x11
; cells; the screen itself is the wall data structure
; ============================================================
gen_maze:
lda #0
sta gcx
sta gcy
sta gsp
jsr gv_mark
jsr carve_cell
gm_loop:
; 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
jmp gm_loop
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
jmp gm_loop
gm_done:
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_loop:
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 runner
asl
clc
adc #1
sta gnx ; candidate screen column
sn_ry:
jsr rand8
and #$0f
cmp #MAZE_CH
bcs sn_ry
asl
clc
adc #2
sta gny ; candidate screen row
ldx gny
jsr row_ptr
ldy gnx
lda (scrlo),y
cmp #CH_FLOOR ; only an empty cell will do
bne sn_rx
lda #CH_ENEMY
sta (scrlo),y
jsr rand8
and #7
tax
lda ecoltab,x
sta (collo),y
ldx ei
sta ecol,x
lda gnx
sta ex,x
lda gny
sta ey,x
jsr rand8
and #3
sta edir,x
inc ei
lda ei
cmp #NENEMIES
bne sn_loop
rts
ecoltab: ; anything but the white runner / grey walls
!byte RED, CYAN, PURPLE, YELLOW, ORANGE, LRED, LBLUE, BLUE
; ============================================================
; one frame of play
; ============================================================
play_update:
jsr upd_runner
lda endflag
bne pu_end
jsr upd_enemies
lda endflag
bne pu_end
jsr upd_cursor
jsr upd_flash
rts
pu_end:
cmp #1
beq pu_win
lda #SFX_LOSE
jsr sfx_play
jmp lose_init ; its rts returns to the main loop
pu_win:
lda #SFX_WIN
jsr sfx_play
jmp win_init
; ---- player 1: walk the corridors, one char 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:
ldx px
ldy py
lda rdir
lsr
bcs ur_up
lsr
bcs ur_down
lsr
bcs ur_left
inx ; right
jmp ur_try
ur_up:
dey
jmp ur_try
ur_down:
iny
jmp ur_try
ur_left:
dex
ur_try:
stx ttx
sty tty
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq ur_go
cmp #CH_EXIT
beq ur_win
cmp #CH_ENEMY
beq ur_dead
rts ; a wall: blocked
ur_go:
ldx py
jsr row_ptr
ldy px
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
lda ttx
sta px
lda tty
sta py
jmp draw_player
ur_win:
lda #1
sta endflag
rts
ur_dead:
lda #2
sta endflag
rts
draw_player:
ldx py
jsr row_ptr
ldy px
lda #CH_PLAYER
sta (scrlo),y
lda #WHITE
sta (collo),y
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
ldx ky ; the old post gets its color back
jsr row_ptr
ldy kx
lda #COL_POST
sta (collo),y
lda ttx
sta kx
lda tty
sta ky
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 ----
do_rotate:
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
; every slot must be plain wall or floor
lda nch
jsr rot_ok
bcs rot_deny
lda sch
jsr rot_ok
bcs rot_deny
lda wch
jsr rot_ok
bcs rot_deny
lda ech
jsr rot_ok
bcs rot_deny
; 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 #SFX_ROTATE
jmp sfx_play
rot_deny:
lda #SFX_DENY
jmp sfx_play
rot_ok: ; C clear when A is spinnable (wall or floor)
cmp #CH_WALL
beq ro_yes
cmp #CH_FLOOR
beq ro_yes
sec
rts
ro_yes:
clc
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
; ---- the enemies: staggered random walkers ----
upd_enemies:
lda #0
sta ei
ue_loop:
ldx ei
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
ldx tty
jsr row_ptr
ldy ttx
lda (scrlo),y
cmp #CH_FLOOR
beq ed_move
cmp #CH_PLAYER
beq ed_kill
sec ; wall, exit or another enemy: blocked
rts
ed_kill:
lda #2
sta endflag
clc
rts
ed_move:
ldx ei
lda ey,x
sta gny ; old position
lda ex,x
sta gnx
lda ttx
sta ex,x
lda tty
sta ey,x
lda etd
sta edir,x
ldx gny ; erase the old cell
jsr row_ptr
ldy gnx
lda #CH_FLOOR
sta (scrlo),y
lda #BLACK
sta (collo),y
ldx tty ; draw at the new one
jsr row_ptr
ldy ttx
lda #CH_ENEMY
sta (scrlo),y
ldx ei
lda ecol,x
sta (collo),y
clc
rts
; ---- per-frame flashes: the exit blinks, the cursor pulses ----
upd_flash:
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
ldx ky
jsr row_ptr
ldy kx
lda framectr
and #4
bne uf_cred
lda #YELLOW
jmp uf_cset
uf_cred:
lda #RED
uf_cset:
sta (collo),y
rts
; ---- play texts ----
txt_goal: !scr "reach the "
!byte CH_EXIT
txt_goale:
txt_hint: !scr "p1: joy2/wasd p2: joy1/ijkl"
txt_hinte:
; ---- play variables ----
px: !byte 0 ; runner position (screen col / row)
py: !byte 0
kx: !byte 0 ; corner cursor position (post col / row)
ky: !byte 0
exrow: !byte 0 ; screen row of the exit diamond
endflag: !byte 0 ; 0 = playing, 1 = escaped, 2 = caught
rheld: !byte 0 ; runner direction repeat state
rtimer: !byte 0
rdir: !byte 0
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
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
; ---- maze generator buffers ----
visited: !fill NCELLS, 0
stackx: !fill NCELLS, 0
stacky: !fill NCELLS, 0