; ----------------------------------------------------------- ; play.asm - the game: eight levels of pyramid maze, each one ; cell bigger than the last (4x4 up to 11x11, always square, ; centered on screen); a fresh maze carves itself at the ; start of every level (recursive backtracker, a few steps a ; frame), player 1 (joystick 2) is the trapped worker running ; for the exit, player 2 (joystick 1) is ra: the cursor rides ; the wall corners and fire spins the four wall pieces around ; it by 90 degrees; scarabs roam the corridors on the ; worker's life, and the scorpions also spit venom stings ; down them ; ; the walls, posts and the portal are chars (the screen stays ; the wall data structure), but every actor - worker, cursor, ; bullet, enemies, sting - 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 ; opt_relic - numbered relics are scattered over the cells; ; the worker must pick them up in order before ; the portal will take him, one more relic each ; level (2 on level 1 up to 9 on level 8) ; ; screen layout: status on row 0 (over the colored raster ; band), the centered lattice on rows 1-23, hints on row 24 ; ----------------------------------------------------------- play_init: jsr clear_screen +prtext 0, 0, WHITE, txt_level, 5 lda mzsize ; level number 1-8 next to the label sec sbc #MZMIN-1 ora #$30 sta SCREEN+6 lda #YELLOW sta COLRAM+6 lda opt_relic ; relic mode keeps its own counter here bne pi_goal +prtext 0, 34, LGREY, txt_goal, txt_goale-txt_goal pi_goal: +prtext 24, 4, DGREY, txt_hint, txt_hinte-txt_hint lda mzsize ; this level's lattice: a square sta mzcw sta mzch jsr set_maze lda mzsize ; one more enemy each level, capped sec sbc #MZMIN-1 cmp #NENEMIES bcc pi_ne lda #NENEMIES pi_ne: sta nenem 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 mzch bcs ps_exit asl ; cell row -> screen row (offy + 2*cy + 1) sec adc offy sta exrow tax jsr row_ptr ldy excol lda #CH_EXIT sta (scrlo),y lda #COL_EXIT sta (collo),y ; the worker starts in the top-left cell, facing right lda offx clc adc #1 sta px lda offy clc adc #1 sta py lda #1 sta fdx lda #0 sta fdy lda px sta tgc lda py sta tgr jsr set_target ldx #SP_RUN jsr spr_jump ; ra's cursor starts on the center post lda mzcw lsr asl clc adc offx sta kx lda mzch lsr asl clc adc offy 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_SHOT lda #COL_HILITE ; ra wears the sun's color sta SPRCOL+SP_CUR lda #COL_STING sta SPRCOL+SP_STING jsr place_relics ; first: the enemies then avoid their cells 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 es_on sta cool sta rotflash lda #1 sta cfheld sta rfheld +setupd play_update jmp upd_sprites ; positions, shapes and enables now ; ============================================================ ; maze geometry - the lattice is sized and placed from the ; cell counts in mzcw / mzch (the levels are squares, the ; menu backdrop is the full 19x11) ; ============================================================ ; from mzcw/mzch: screen offsets, the exit column, the ; visited-index multiply table, ra's post bounds and the ; enemies' spawn half set_maze: lda #19 ; offx = (39 - (2*cw+1)) / 2 = 19 - cw sec sbc mzcw sta offx lda #12 ; rows 1-23: offy = 12 - ch sec sbc mzch sta offy lda #0 ; multab[i] = i * mzcw tax sm_mul: sta multab,x clc adc mzcw inx cpx #MZMAX bne sm_mul lda mzcw ; the right border column holds the exit asl clc adc offx sta excol sec ; interior posts: offx+2 .. offx+2*cw-2 sbc #2 sta kmaxx lda offx clc adc #2 sta kminx lda mzch asl clc adc offy sec sbc #2 sta kmaxy lda offy clc adc #2 sta kminy lda mzcw ; enemies spawn on the right half lsr sta enhalf rts ; fill the lattice with sandstone in latwcol, the pivot posts ; (even local column and row) in latpcol draw_lattice: lda offy sta ptmp dl_fill: ldx ptmp jsr row_ptr lda mzcw asl sta lcnt inc lcnt ; 2*cw+1 chars per lattice row ldy offx dl_col: lda #CH_WALL sta (scrlo),y lda latwcol sta (collo),y iny dec lcnt bne dl_col inc ptmp lda mzch asl clc adc offy ; the bottom lattice row cmp ptmp bcs dl_fill lda offy ; post rows: offy, offy+2, ... sta ptmp dl_prow: ldx ptmp jsr row_ptr lda mzcw asl clc adc offx tay ; post columns from the right edge down lda mzcw sta lcnt inc lcnt ; cw+1 posts per post row dl_pcol: lda #CH_POST sta (scrlo),y lda latpcol sta (collo),y dey dey dec lcnt bne dl_pcol inc ptmp inc ptmp lda mzch asl clc adc offy cmp ptmp bcs dl_prow rts ; ============================================================ ; maze generation - recursive backtracker over the cells; ; the screen itself is the wall data structure ; ============================================================ ; 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 mzcw ; unsigned: -1 wraps to $ff and fails too bcs gm_next ldx gtd lda gcy clc adc dytab,x sta gny cmp mzch bcs gm_next ldx gny lda multab,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 * mzcw + gcx] = 1 ldx gcy lda multab,x clc adc gcx tax lda #1 sta visited,x rts carve_cell: ; open the cell at (offx+2*gcx+1, offy+2*gcy+1) lda gcy asl sec adc offy tax jsr row_ptr lda gcx asl sec adc offx 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 sec adc offy clc adc dytab,x tax jsr row_ptr ldx gtd lda gcx asl sec adc offx 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 ; ---- enemies: even index = scarab, odd = scorpion; random ; right-half cells, away from the worker's start ---- spawn_enemies: lda #0 sta ei sn_all: jsr spawn_one inc ei lda ei cmp nenem bne sn_all rts spawn_one: ; place enemy ei on a random free cell sn_rx: jsr rand8 and #$0f cmp mzcw bcs sn_rx cmp enhalf ; keep to the right half bcc sn_rx asl sec adc offx ; cell column -> screen column sta ttx sn_ry: jsr rand8 and #$0f cmp mzch bcs sn_ry asl sec adc offy 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 worker 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 lda ei and #1 ; the type wears its own color tay lda etypecol,y 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 etypecol: !byte COL_SCARAB, COL_SCORP ; ============================================================ ; the relics (relic order option) - numbered treasures on the ; maze cells; the worker must collect them in order before ; the portal will let him out ; ============================================================ ; ---- scatter this level's relics over random cells: one ; more each level, 2 on level 1 up to 9 on level 8 ---- place_relics: lda #0 sta rnext sta rcount sta ri lda opt_relic beq pl_done lda mzsize ; level 1-8 -> 2-9 relics sec sbc #MZMIN-2 sta rcount pl_one: jsr rand8 and #$0f cmp mzcw bcs pl_one asl ; cell column -> screen column sec adc offx sta ttx pl_ry: jsr rand8 and #$0f cmp mzch bcs pl_ry asl sec adc offy sta tty lda ttx ; never on the worker's own starting cell... cmp px bne pl_free lda tty cmp py beq pl_one pl_free: jsr relic_at ; ...nor on a cell already holding one bcs pl_one ldx ri lda ttx sta rlx,x lda tty sta rly,x jsr draw_relic inc ri lda ri cmp rcount bne pl_one pl_done: rts ; ---- stamp relic X on (ttx, tty): its order is its digit ---- draw_relic: txa clc adc #CH_RELIC pha ldx tty jsr row_ptr ldy ttx pla sta (scrlo),y lda #COL_RELIC sta (collo),y rts ; ---- C set when one of the ri placed relics sits on ; (ttx, tty) - the placement loop's duplicate check ---- relic_at: ldx ri beq rlk_no rlk_loop: dex lda rlx,x cmp ttx bne rlk_next lda rly,x cmp tty beq rlk_yes rlk_next: cpx #0 bne rlk_loop rlk_no: clc rts rlk_yes: sec rts ; ---- the worker stepped onto a cell: if it holds the relic ; that is next in the order, it goes into his hands ---- take_relic: lda opt_relic beq tk_done ldx rnext cpx rcount bcs tk_done ; all of them already in hand lda rlx,x cmp px bne tk_done lda rly,x cmp py bne tk_done ldx py ; the cell goes back to plain corridor jsr row_ptr ldy px lda #CH_FLOOR sta (scrlo),y lda #BLACK sta (collo),y inc rnext lda #4 ; a white border blip marks the find sta bflash lda #WHITE sta bcolor lda #SFX_SELECT jmp sfx_play tk_done: rts ; ---- C set when the portal will take the worker: always in ; the classic game, once every relic is his in relic mode ---- exit_open: lda opt_relic beq eo_yes lda rnext cmp rcount bcc eo_no eo_yes: sec rts eo_no: clc rts ; ---- C set when the char in A can be walked or flown ; through: the corridor floor and the relics lying on it ---- open_ch: cmp #CH_FLOOR beq oc_yes cmp #CH_RELIC ; the relic digits sit on open floor bcc oc_no cmp #CH_RELIC+NRELIC bcs oc_no oc_yes: sec rts oc_no: clc rts ; ============================================================ ; one frame of play ; ============================================================ play_update: inc tframe ; the run clock: 50 frames = one second lda tframe cmp #50 bne pu_clock lda #0 sta tframe inc tsec lda tsec cmp #60 bne pu_clock lda #0 sta tsec inc tmin lda tmin cmp #100 ; the clock pegs at 99:59 bne pu_clock lda #99 sta tmin pu_clock: 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_sting lda endflag bne pu_end jsr upd_cursor jsr upd_flash jsr upd_status jsr upd_goal jsr upd_clock 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 lda mzsize ; the last level won: the blessing is earned cmp #MZMAX bcs pw_final inc mzsize ; otherwise the next maze is one cell bigger +fadeto play_init pw_final: +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_exit jsr open_ch bcc ur_stop ; a wall or a post: blocked jsr enemy_at ; walking into an enemy still hurts bcs ur_dead lda es_on ; and so does the venom sting beq ur_go lda ttx cmp esx bne ur_go lda tty cmp esy beq ur_dead ur_go: lda ttx sta px lda tty sta py jmp take_relic ; the cell may hold the next relic ur_stop: rts ur_exit: jsr exit_open bcc ur_sealed lda #1 sta endflag rts ur_sealed: ; relics still out there: the portal is shut jmp deny_blip 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 jsr open_ch bcs 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 nenem bne ea_loop clc rts ; ---- player 2: ra's 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 kmaxx ; right: the interior posts end here bcs uc_fire inx inx jmp uc_go uc_up: cpy kminy bcc uc_fire beq uc_fire dey dey jmp uc_go uc_down: cpy kmaxy bcs uc_fire iny iny jmp uc_go uc_left: cpx kminx bcc uc_fire beq 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 deny_blip ; 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 ; ---- red border blip + buzz: a denied spin, or the worker ; knocking on a portal the relics have not opened yet ---- deny_blip: lda #6 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_nosht lda sx cmp ttx bne so_nosht lda sy cmp tty beq so_yes so_nosht: lda es_on ; the sting blocks a spin too beq so_no lda esx cmp ttx bne so_no lda esy 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 jsr try_sting ue_next: inc ei lda ei cmp nenem 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 worker: the level 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 jsr open_ch bcs 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 ; ---- a scorpion (odd index) spits a venom sting when it ; lines up with the worker; one sting flies at a time ---- try_sting: lda ei and #1 beq ts_no ; scarabs do not shoot lda es_on bne ts_no jsr rand8 ; menacing, not machine-gun constant and #3 bne ts_no ldx ei lda ex,x cmp px beq ts_col lda ey,x cmp py beq ts_row ts_no: rts ts_col: ; same column: the sting flies vertically lda #0 sta esdx ldy #1 lda py cmp ey,x bcs ts_cgo ldy #$ff ts_cgo: sty esdy jmp ts_fire ts_row: ; same row: horizontally lda #0 sta esdy ldy #1 lda px cmp ex,x bcs ts_rgo ldy #$ff ts_rgo: sty esdx ts_fire: lda ex,x sta esx sta tgc lda ey,x sta esy sta tgr jsr set_target ; the sprite pops in on the scorpion, ldx #SP_STING ; not gliding over from the last sting jsr spr_jump lda #1 sta es_on lda #SFX_SHOT jmp sfx_play ; ---- the sting flies a char every other frame; a wall ; swallows it, the worker dies on it ---- upd_sting: lda es_on beq ust_done lda framectr and #1 bne ust_done lda esx clc adc esdx sta ttx lda esy clc adc esdy sta tty lda px ; onto the worker: the level is over cmp ttx bne ust_norun lda py cmp tty bne ust_norun lda #2 sta endflag rts ust_norun: ldx tty jsr row_ptr ldy ttx lda (scrlo),y jsr open_ch bcs ust_fly lda #0 ; a wall swallows it sta es_on rts ust_fly: lda ttx sta esx lda tty sta esy ust_done: 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 excol jsr exit_open bcc uf_exlock lda framectr and #8 bne uf_exw lda #COL_EXIT jmp uf_exset uf_exw: lda #WHITE jmp uf_exset uf_exlock: lda #DGREY ; sealed while a relic is still missing 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 #COL_HILITE uf_cset: sta SPRCOL+SP_CUR ; fall through: the relic he is after next pulses white relic_blink: lda opt_relic beq rb_done ldx rnext cpx rcount bcs rb_done lda rlx,x sta ptmp lda rly,x tax jsr row_ptr ldy ptmp lda framectr and #8 bne rb_white lda #COL_RELIC jmp rb_set rb_white: lda #WHITE rb_set: sta (collo),y rb_done: 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, 17, 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+23,x ; bar cells: row 0, columns 23-32 lda #LRED sta COLRAM+23,x inx cpx #10 bne us_bar rts us_ready: +prtext 0, 17, LGREEN, txt_ready, 5 lda #$20 ; clear the bar area ldx #9 us_clr: sta SCREEN+23,x dex bpl us_clr rts ; ---- relic mode's readout in the goal corner: relics in ; hand / relics on the level, next to the portal glyph; green ; once the last one is his ---- upd_goal: lda opt_relic bne ug_show rts ug_show: lda rnext ora #$30 sta SCREEN+34 lda #$2f ; '/' sta SCREEN+35 lda rcount ora #$30 sta SCREEN+36 lda #$20 sta SCREEN+37 lda #CH_EXIT sta SCREEN+38 jsr exit_open lda #LRED bcc ug_col lda #LGREEN ug_col: ldx #4 ug_loop: sta COLRAM+34,x dex bpl ug_loop rts ; ---- the run clock, live on the status row ---- upd_clock: lda tmin jsr two_digits stx SCREEN+11 ; clock cells: row 0, columns 11-15 sta SCREEN+12 lda #$3a ; ':' sta SCREEN+13 lda tsec jsr two_digits stx SCREEN+14 sta SCREEN+15 ldx #4 ucl_col: lda #LGREY sta COLRAM+11,x dex bpl ucl_col rts ; ---- drive the eight sprites from the grid state ---- upd_sprites: lda framectr lsr lsr lsr sta frame8 ; the worker 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 sting glides 4: a char every other frame, smoothly lda esx sta tgc lda esy sta tgr jsr set_target lda #4 sta gspd ldx #SP_STING jsr spr_glide ; 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 nenem bne us_en ; shapes: the worker 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 lda #SPR_STING sta SPRPTR+SP_STING ldx #0 us_shape: txa clc adc frame8 and #1 sta rtmp txa and #1 ; even index = scarab, odd = scorpion beq us_scar lda #SPR_ENB bne us_seta ; always taken us_scar: lda #SPR_ENA us_seta: clc adc rtmp sta SPRPTR+SP_EN,x inx cpx nenem bne us_shape ; enable: worker + cursor + bullet, sting, live enemies lda #%00000011 ldy s_on beq us_nosht ora #%00000100 us_nosht: ldy es_on beq us_nostg ora #%10000000 us_nostg: sta rtmp ldx #0 us_alive: lda edead,x bne us_dead lda rtmp ora ebit,x sta rtmp us_dead: inx cpx nenem bne us_alive lda rtmp sta SPR_EN jmp spr_commit ebit: !byte 8, 16, 32, 64 ; ---- play texts ---- txt_level: !scr "level" txt_goal: !scr "exit " !byte CH_EXIT txt_goale: txt_hint: !scr "worker: joy2/wasd ra: joy1/ijkl" txt_hinte: txt_wait: !scr "wait" txt_ready: !scr "ready" ; ---- play variables ---- mzsize: !byte MZMIN ; this level's cells per side (4-11) mzcw: !byte 0 ; lattice cells across / down mzch: !byte 0 offx: !byte 0 ; screen offset of the lattice offy: !byte 0 excol: !byte 0 ; screen column of the right border wall enhalf: !byte 0 ; enemies spawn from this cell column nenem: !byte 0 ; live enemy slots this level (1-4) kminx: !byte 0 ; ra's interior post bounds kmaxx: !byte 0 kminy: !byte 0 kmaxy: !byte 0 px: !byte 0 ; worker position (screen col / row) py: !byte 0 fdx: !byte 0 ; worker 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 rcount: !byte 0 ; relics on this level (relic order option) rnext: !byte 0 ; the one he must pick up next ri: !byte 0 ; relic placement loop index s_on: !byte 0 ; bullet in flight? sx: !byte 0 ; bullet position and heading sy: !byte 0 sdx: !byte 0 sdy: !byte 0 es_on: !byte 0 ; venom sting in flight? esx: !byte 0 ; sting position and heading esy: !byte 0 esdx: !byte 0 esdy: !byte 0 rheld: !byte 0 ; worker direction repeat state rtimer: !byte 0 rdir: !byte 0 rfheld: !byte 0 ; worker 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 lcnt: !byte 0 ; draw_lattice column counter 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 tframe: !byte 0 ; run clock: frame, second, minute tsec: !byte 0 tmin: !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 multab: !fill MZMAX, 0 ; row * mzcw for the visited index ex: !fill NENEMIES, 0 ; enemy positions and headings ey: !fill NENEMIES, 0 edir: !fill NENEMIES, 0 edead: !fill NENEMIES, 0 ; respawn countdown, 0 = alive rlx: !fill NRELIC, 0 ; relic positions (screen col / row) rly: !fill NRELIC, 0 ; ---- maze generator buffers ---- visited: !fill NCELLS, 0 stackx: !fill NCELLS, 0 stacky: !fill NCELLS, 0