Files
rabbitc64/play.asm
T
2026-07-18 15:24:50 +02:00

582 lines
11 KiB
NASM

; -----------------------------------------------------------
; play.asm - the game: lane movement with key-repeat, rabbit
; spawning with a score-driven interval, arrivals scored or
; counted as a miss, HUD updates
; -----------------------------------------------------------
; ---- start (or restart) a round ----
play_init:
lda #COL_SKY ; in case we come from a black screen
sta bg_top
lda #COL_GRASS
sta bg_bot
lda #$d8 ; text multicolor on: road + tree cells
sta XCTRL
jsr clear_screen
jsr draw_road
; Copy static tree positions into dynamic arrays
ldx #0
ldy #0
pti_loop:
lda tree_tbl,y
sta t_row,x
iny
lda tree_tbl,y
sta t_col,x
iny
inx
cpx #NTREES
bne pti_loop
; HUD: score on the left, miss slots on the right
+prtext 0, 1, BLACK, txt_score, 5
+prtext 0, 30, BLACK, txt_miss, 4
lda #0
sta score8
sta sc_d0
sta sc_d1
sta sc_d2
sta miss
sta flash
sta spawnt
sta dif8
sta tree_timer
sta lcnt
sta rcnt
sta p2lcnt
sta p2rcnt
jsr draw_score
lda #0 ; draw_score leaves a digit code in A
ldx #NRABBITS-1
pi_clr:
sta r_act,x
dex
bpl pi_clr
lda #2 ; start in the middle lane
sta player_lane
; sprite colors back to red scooter + white rabbits
; (the title borrows sprites 1-4 as red scooters)
ldx #7
pi_col:
lda #WHITE
sta SPR_COL,x
dex
bne pi_col
lda #RED
sta SPR_COL
; player sprite: scooter, fixed row, double-sized;
; rabbits come per frame
lda #SPRP_SCOOT
sta SPRPTR
lda #PLAYER_SY
sta SPRPOS+1
lda #1
sta SPR_EN
sta SPR_XEX
sta SPR_YEX
+setupd play_update
rts
; ---- one frame of gameplay ----
play_update:
jsr read_joy
; -- left: move on press, then auto-repeat while held --
lda joyb
and #JOY_LEFT
beq pu_lrel
inc lcnt
lda lcnt
cmp #1
beq pu_lmove
cmp #REP_HOLD+REP_PERIOD
bne pu_lend
lda #REP_HOLD
sta lcnt
pu_lmove:
lda player_lane
beq pu_loff ; leftmost lane: that's the grass
dec player_lane
jmp pu_lend
pu_loff:
lda lcnt ; only a fresh press costs a miss,
cmp #1 ; a held direction doesn't chain them
bne pu_lend
jsr add_miss
jmp pu_lend
pu_lrel:
lda #0
sta lcnt
pu_lend:
; -- right, same dance --
lda joyb
and #JOY_RIGHT
beq pu_rrel
inc rcnt
lda rcnt
cmp #1
beq pu_rmove
cmp #REP_HOLD+REP_PERIOD
bne pu_rend
lda #REP_HOLD
sta rcnt
pu_rmove:
lda player_lane
cmp #LANES-1
beq pu_roff ; rightmost lane: grass on this side too
inc player_lane
jmp pu_rend
pu_roff:
lda rcnt
cmp #1
bne pu_rend
jsr add_miss
jmp pu_rend
pu_rrel:
lda #0
sta rcnt
pu_rend:
; -- multiplayer: player 2 steers the closest rabbit --
lda mp_mode
beq pu_nop2
jsr read_joy2
lda joyb2
and #JOY_LEFT
beq pu_p2lrel
inc p2lcnt
lda p2lcnt
cmp #1
beq pu_p2lmove
cmp #REP_HOLD+REP_PERIOD
bne pu_p2lend
lda #REP_HOLD
sta p2lcnt
pu_p2lmove:
lda #$ff ; one lane to the left
sta rtmp2
jsr steer_closest
jmp pu_p2lend
pu_p2lrel:
lda #0
sta p2lcnt
pu_p2lend:
lda joyb2
and #JOY_RIGHT
beq pu_p2rrel
inc p2rcnt
lda p2rcnt
cmp #1
beq pu_p2rmove
cmp #REP_HOLD+REP_PERIOD
bne pu_p2rend
lda #REP_HOLD
sta p2rcnt
pu_p2rmove:
lda #1 ; one lane to the right
sta rtmp2
jsr steer_closest
jmp pu_p2rend
pu_p2rrel:
lda #0
sta p2rcnt
pu_p2rend:
pu_nop2:
; -- shadow init: player sprite bit + its x MSB --
ldx player_lane
lda pxlo,x
sta SPRPOS ; sprite 0 x
lda pxhi,x
sta msb_sh ; lane 4 sits past x=255
lda #1 ; the scooter rides double-sized
sta xex_sh
sta yex_sh
sta en_sh
; -- a hit blinks the scooter for a dozen frames --
lda flash
beq pu_noflash
dec flash
lda framectr
and #4
bne pu_noflash
lda #0
sta en_sh
pu_noflash:
; -- difficulty clock: +1 every 64 frames (~1.3 s), capped --
lda framectr
and #63
bne pu_noclk
lda dif8
cmp #$ff
beq pu_noclk
inc dif8
pu_noclk:
; -- spawn: interval = 70 - time/2, floor 22; the clock, not
; the score, drives it, so the pace always keeps climbing --
inc spawnt
lda dif8
cmp #96
bcc pu_ivcalc
lda #22
jmp pu_ivgot
pu_ivcalc:
lsr
sta rtmp
lda #70
sec
sbc rtmp
pu_ivgot:
cmp spawnt
bcs pu_nospawn
lda #0
sta spawnt
jsr spawn_rabbit
pu_nospawn:
; -- advance every live rabbit --
ldx #NRABBITS-1
pu_rab:
lda r_act,x
beq pu_rnext
lda r_tlo,x ; t += speed (8.8 fixed point)
clc
adc r_splo,x
sta r_tlo,x
lda r_thi,x
adc r_sphi,x
sta r_thi,x
bcc pu_ralive
; arrived at the player line
lda #0
sta r_act,x
lda r_lane,x
cmp player_lane
beq pu_rhit
jsr add_score ; dodged: one point
jmp pu_rnext
pu_rhit:
jsr add_miss ; ran into you
jmp pu_rnext
pu_ralive:
jsr rab_steer
jsr rab_pos
pu_rnext:
dex
bpl pu_rab
jsr write_shadows
jsr animate_trees
; -- third miss ends the round --
lda miss
cmp #MAX_MISS
bcc pu_done
lda #SFX_OVER
jsr sfx_play
jmp over_init ; its rts returns to the main loop
pu_done:
rts
; ---- single player: rabbit X sometimes hops a lane, mostly
; toward the scooter but sometimes away, so a dodge can work;
; hops stop on the last quarter of the run (no unfair hits) ----
rab_steer:
lda mp_mode
bne rst_done ; multiplayer: player 2 does the steering
lda r_thi,x
cmp #$c0
bcs rst_done ; too close to the player line already
jsr rand8
and #31 ; hop roughly every 32nd frame
bne rst_done
lda r_lane,x
cmp player_lane
beq rst_rand ; sharing the scooter's lane: random side
bcc rst_right ; rabbit is on the left: toward = right
lda #$ff
bne rst_bias
rst_right:
lda #1
rst_bias:
sta rtmp2
jsr rand8
cmp #64 ; 1 in 4 hops go the other way
bcs rst_move
lda rtmp2
eor #$fe ; $01 <-> $ff
sta rtmp2
jmp rst_move
rst_rand:
jsr rand8
lsr
lda #1
bcc rst_store
lda #$ff
rst_store:
sta rtmp2
rst_move:
lda r_lane,x
clc
adc rtmp2
bmi rst_done ; already on the leftmost lane
cmp #LANES
bcs rst_done ; ... or the rightmost one
sta r_lane,x
rst_done:
rts
; ---- shift the closest live rabbit's lane by rtmp2 (+/-1) ----
steer_closest:
ldx #$ff ; best slot so far
lda #0
sta rtmp ; best t so far
ldy #NRABBITS-1
stc_find:
lda r_act,y
beq stc_next
lda r_thi,y
cmp rtmp
bcc stc_next
sta rtmp
tya
tax
stc_next:
dey
bpl stc_find
cpx #$ff
beq stc_done ; no rabbit on the road
lda r_lane,x
clc
adc rtmp2
bmi stc_done ; already on the leftmost lane
cmp #LANES
bcs stc_done ; ... or the rightmost one
sta r_lane,x
stc_done:
rts
; ---- claim a free rabbit slot and roll its lane + speed ----
spawn_rabbit:
ldx #NRABBITS-1
sr_find:
lda r_act,x
beq sr_got
dex
bpl sr_find
rts ; every slot busy: skip this spawn
sr_got:
lda #1
sta r_act,x
jsr rand5
sta r_lane,x
lda #0
sta r_tlo,x
sta r_thi,x
; speed = $0260 base + random byte + score ramp (tops out at 64)
jsr rand8
clc
adc #$60
sta rtmp
lda #2
adc #0
sta rtmp2
lda score8
cmp #64
bcc sr_ramp
lda #64
sr_ramp:
tay
lda rtmp
clc
adc sptab_lo,y
sta r_splo,x
lda rtmp2
adc sptab_hi,y
sta r_sphi,x
rts
; ---- animate roadside trees: scroll down + fan outward ----
animate_trees:
inc tree_timer
lda tree_timer
cmp #TREE_SPEED
bcs at_go
rts
at_go:
lda #0
sta tree_timer
; Phase 1: erase all trees at current positions
ldx #NTREES-1
at_erase:
stx at_idx
lda t_row,x
sta trow
lda t_col,x
sta tcol
jsr erase_tree
ldx at_idx
dex
bpl at_erase
; Phase 2: update all positions
ldx #NTREES-1
at_move:
inc t_row,x
lda t_col,x
cmp #20
bcs at_mright
; left side: move one column left
lda t_col,x
beq at_mresp
dec t_col,x
jmp at_mcheck
at_mright:
; right side: move one column right
lda t_col,x
cmp #37
bcs at_mresp
inc t_col,x
at_mcheck:
lda t_row,x
cmp #23
bcc at_mnext
at_mresp:
; respawn near the horizon
lda t_col,x
cmp #20
bcs at_mrespr
; left side: random col 10-13
jsr rand8
and #3
clc
adc #10
sta t_col,x
jmp at_mresprow
at_mrespr:
; right side: random col 24-27
jsr rand8
and #3
clc
adc #24
sta t_col,x
at_mresprow:
jsr rand8
and #1
clc
adc #7
sta t_row,x
at_mnext:
dex
bpl at_move
; Phase 3: redraw all trees at new positions
ldx #NTREES-1
at_draw:
stx at_idx
lda t_row,x
sta trow
lda t_col,x
sta tcol
jsr draw_tree
ldx at_idx
dex
bpl at_draw
at_done:
rts
; ---- score: bump the decimal digits and the difficulty byte ----
add_score:
lda #SFX_SCORE
jsr sfx_play
lda score8
cmp #$ff
beq as_digits
inc score8
as_digits:
inc sc_d2
lda sc_d2
cmp #10
bne draw_score
lda #0
sta sc_d2
inc sc_d1
lda sc_d1
cmp #10
bne draw_score
lda #0
sta sc_d1
inc sc_d0
lda sc_d0
cmp #10
bne draw_score
lda #0
sta sc_d0
; falls through: repaint the three HUD digits
draw_score:
lda sc_d0
ora #48 ; digit screen codes start at 48
sta SCREEN+7
lda sc_d1
ora #48
sta SCREEN+8
lda sc_d2
ora #48
sta SCREEN+9
rts
; ---- miss: X mark on the HUD + blink the scooter ----
add_miss:
lda #SFX_MISS
jsr sfx_play
inc miss
ldy miss ; Y: the rabbit loop still needs X
lda #24 ; 'x'
sta SCREEN+34,y ; marks land on columns 35-37
lda #RED
sta COLRAM+34,y
lda #12
sta flash
rts
; ---- gameplay variables ----
player_lane: !byte 2
score8: !byte 0 ; score capped at 255, drives the difficulty
sc_d0: !byte 0 ; score digits: hundreds, tens, ones
sc_d1: !byte 0
sc_d2: !byte 0
miss: !byte 0
flash: !byte 0 ; frames left of the post-hit blink
spawnt: !byte 0 ; frames since the last spawn
dif8: !byte 0 ; elapsed-time difficulty, +1 per 64 frames
lcnt: !byte 0 ; held-direction counters for the auto-repeat
rcnt: !byte 0
p2lcnt: !byte 0 ; the same for player 2's steering
p2rcnt: !byte 0
; ---- dynamic tree state ----
t_row: !fill NTREES, 0 ; current row of each tree
t_col: !fill NTREES, 0 ; current column of each tree
tree_timer: !byte 0 ; frames since last tree scroll
at_idx: !byte 0 ; animate_trees loop index
; ---- rabbit slots (hardware sprites 1-7) ----
r_act: !fill NRABBITS, 0
r_lane: !fill NRABBITS, 0
r_tlo: !fill NRABBITS, 0 ; distance along the lane, 8.8 fixed point
r_thi: !fill NRABBITS, 0
r_splo: !fill NRABBITS, 0 ; per-rabbit speed, 8.8 fixed point
r_sphi: !fill NRABBITS, 0