From aa44c389bab4d3ef95abee1ea76b8bf921ed24df Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Wed, 15 Jul 2026 23:00:52 +0200 Subject: [PATCH] multiplayer support --- README.md | 10 ++++-- common.asm | 53 +++++++++++++++++++++++++--- over.asm | 8 ++--- play.asm | 80 ++++++++++++++++++++++++++++++++++++++++++ title.asm | 101 ++++++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 232 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a200a4e..7b04624 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,18 @@ that passes in any other lane is a **point**. Three misses end the round. The spawn rate and the rabbits' speed ramp up with your score, just like in the original. +In **multiplayer** the rabbits still spawn on their own, but player 2 +steers the closest one left and right, trying to run the scooter down. + ## Controls | Input | Action | |-------|--------| -| Joystick port 2 left/right, or `A` / `D` | change lane (auto-repeats while held) | -| Fire (or `SPACE`) | start / retry | +| Menu: joystick up/down, or the `S` / `M` keys | pick single player / multiplayer | +| Menu: fire (or `SPACE`); the `1` / `2` keys pick and start at once | start the picked mode | +| P1: joystick port 2 left/right, or `A` / `D` | move the scooter (auto-repeats while held) | +| P2: joystick port 1 left/right, or `J` / `L` | steer the closest rabbit (multiplayer) | +| Fire (or `SPACE`) | start; on the game over screen: back to the menu | ## How it works diff --git a/common.asm b/common.asm index 05fccd1..2acf053 100644 --- a/common.asm +++ b/common.asm @@ -206,11 +206,49 @@ rj_noright: lda joyb rts -; fire or space held? returns A != 0 when pressed +; read joystick port 1 merged with the j/l keys (player 2), +; return active-high bits in A and joyb2 +read_joy2: + lda #$ff ; no column selected: port B = joystick 1 + sta CIA1_PA + lda CIA1_PB + eor #$ff + and #$1f + sta joyb2 + and #$0f ; a held direction grounds the same port B + bne rj2_done ; lines the key rows use: skip the scan + lda #$ef ; 'j' = left: keyboard column 4, row bit 2 + sta CIA1_PA + lda CIA1_PB + and #$04 + bne rj2_noleft + lda joyb2 + ora #JOY_LEFT + sta joyb2 +rj2_noleft: + lda #$df ; 'l' = right: keyboard column 5, row bit 2 + sta CIA1_PA + lda CIA1_PB + and #$04 + bne rj2_done + lda joyb2 + ora #JOY_RIGHT + sta joyb2 +rj2_done: + lda joyb2 + rts + +; fire on either stick or space held? returns A != 0 when pressed start_pressed: jsr read_joy and #JOY_FIRE bne sp_done + lda #$ff ; deselect the columns: port B = joystick 1 + sta CIA1_PA + lda CIA1_PB + eor #$ff + and #JOY_FIRE + bne sp_done lda #$7f ; space: keyboard column 7, row bit 4 sta CIA1_PA lda CIA1_PB @@ -546,12 +584,14 @@ txt_score: !scr "score" txt_miss: !scr "miss" txt_rabbit: !scr "rabbit" txt_scooter: !scr "scooter" -txt_press: !scr "fire = start game" -txt_info: !scr "joystick 2 or a/d moves" +txt_1p: !scr "single player" +txt_2p: !scr "multiplayer" +txt_info: !scr "p1: joystick 2 or a/d" +txt_info2: !scr "p2: joystick 1 or j/l" txt_ttgname: !scr "teletype games" txt_presents:!scr "presents" txt_over: !scr "game over" -txt_retry: !scr "fire = retry" +txt_return: !scr "fire = return" txt_blank: !fill 19, $20 washtbl: ; gold glow ramp (16 entries, cyclic) @@ -565,7 +605,10 @@ bg_top: !byte COL_SKY ; background color above / below the bg_bot: !byte COL_GRASS ; horizon raster split seed: !byte $a7 ; rand8 LFSR state fheld: !byte 0 ; fire/space edge detection state -joyb: !byte 0 ; joystick bits from read_joy (active high) +joyb: !byte 0 ; player 1 input bits from read_joy +joyb2: !byte 0 ; player 2 input bits from read_joy2 +mp_mode: !byte 0 ; 0 = single player, 1 = multiplayer +k12held: !byte 0 ; menu '1'/'2' key edge detection state en_sh: !byte 0 ; per-frame sprite register shadows msb_sh: !byte 0 xex_sh: !byte 0 diff --git a/over.asm b/over.asm index d00086c..408662a 100644 --- a/over.asm +++ b/over.asm @@ -32,17 +32,17 @@ over_init: rts over_update: - ; blink the retry prompt + ; blink the return prompt lda framectr and #32 bne ou_hide - +prtext 16, 14, YELLOW, txt_retry, 12 + +prtext 16, 13, YELLOW, txt_return, 13 jmp ou_start ou_hide: - +prtext 16, 14, YELLOW, txt_blank, 12 + +prtext 16, 13, YELLOW, txt_blank, 13 ou_start: jsr start_edge beq ou_done - jmp play_init ; its rts returns to the main loop + jmp title_init ; back to the menu, mode can be re-picked ou_done: rts diff --git a/play.asm b/play.asm index 95cd58e..eefeed9 100644 --- a/play.asm +++ b/play.asm @@ -27,6 +27,8 @@ play_init: sta spawnt sta lcnt sta rcnt + sta p2lcnt + sta p2rcnt jsr draw_score lda #0 ; draw_score leaves a digit code in A @@ -113,6 +115,52 @@ pu_rrel: 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 @@ -197,6 +245,36 @@ pu_rnext: pu_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 @@ -298,6 +376,8 @@ flash: !byte 0 ; frames left of the post-hit blink spawnt: !byte 0 ; frames since the last spawn 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 ; ---- rabbit slots (hardware sprites 1-7) ---- r_act: !fill NRABBITS, 0 diff --git a/title.asm b/title.asm index b674d59..c727f60 100644 --- a/title.asm +++ b/title.asm @@ -1,7 +1,7 @@ ; ----------------------------------------------------------- ; title.asm - full-screen menu card: big RABBIT / SCOOTER -; logo flanked by two hopping rabbits, the scooter waiting at -; the bottom, blinking "fire = start game" +; logo flanked by two hopping rabbits, single/multiplayer +; picker (joystick up/down + fire, or the 1 / 2 keys) ; ----------------------------------------------------------- title_init: @@ -12,7 +12,8 @@ title_init: +prbig 2, 8, RED, txt_rabbit, 6 +prbig 8, 6, WHITE, txt_scooter, 7 - +prtext 18, 8, WHITE, txt_info, 23 + +prtext 18, 9, LGREY, txt_info, 21 + +prtext 19, 9, LGREY, txt_info2, 21 ; decoration: white rabbits flank the logo (sprites 1-2, ; double-sized), the red scooter waits at the bottom @@ -65,17 +66,99 @@ title_update: adc #SPRP_BIG sta SPRPTR+2 - ; blink the start line + ; joystick up/down picks the mode + jsr read_joy + lda joyb + and #1 ; up + beq tu_noup + lda #0 + sta mp_mode +tu_noup: + lda joyb + and #2 ; down + beq tu_nodown + lda #1 + sta mp_mode +tu_nodown: + + ; the 's' / 'm' keys pick the mode (fire still starts) + lda #$fd ; 's': keyboard column 1, row bit 5 + sta CIA1_PA + lda CIA1_PB + and #$20 + bne tu_nos + lda #0 + sta mp_mode +tu_nos: + lda #$ef ; 'm': keyboard column 4, row bit 4 + sta CIA1_PA + lda CIA1_PB + and #$10 + bne tu_nom + lda #1 + sta mp_mode +tu_nom: + + ; the 1 / 2 keys pick and start right away (edge detected) + lda #$7f ; keyboard column 7: '1' = bit 0, '2' = bit 3 + sta CIA1_PA + lda CIA1_PB + and #$09 + cmp #$09 + beq tu_keyup ; neither key held + ldx k12held + bne tu_menu ; still held from an earlier frame + inx + stx k12held + and #$01 + bne tu_key2 + lda #0 + sta mp_mode + jmp title_go +tu_key2: + lda #1 + sta mp_mode + jmp title_go +tu_keyup: + lda #0 + sta k12held +tu_menu: + + ; the two options: the picked line is yellow + lda mp_mode + bne tm_multi + +prtext 14, 14, YELLOW, txt_1p, 13 + +prtext 16, 14, WHITE, txt_2p, 11 + jmp tm_cursor +tm_multi: + +prtext 14, 14, WHITE, txt_1p, 13 + +prtext 16, 14, YELLOW, txt_2p, 11 +tm_cursor: + ; blinking '>' cursor in front of the picked line + lda #$20 + sta SCREEN+14*40+12 + sta SCREEN+16*40+12 lda framectr - and #32 - bne tu_hide - +prtext 15, 11, YELLOW, txt_press, 17 + and #16 + bne tu_start + lda mp_mode + bne tm_cur2 + lda #62 ; '>' + sta SCREEN+14*40+12 + lda #YELLOW + sta COLRAM+14*40+12 jmp tu_start -tu_hide: - +prtext 15, 11, YELLOW, txt_blank, 17 +tm_cur2: + lda #62 + sta SCREEN+16*40+12 + lda #YELLOW + sta COLRAM+16*40+12 + tu_start: + ; fire or space starts the picked mode jsr start_edge beq tu_done +title_go: jmp play_init ; its rts returns to the main loop tu_done: rts