From 11847f52aed5bda1966b7d28e009430dc58d2561 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Thu, 5 Mar 2026 18:47:08 +0100 Subject: Add the Game Over screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is still missing the support for player 2, but I've left traces about it. Signed-off-by: Miquel Sabaté Solà --- src/driver.s | 13 +++- src/jetpac.s | 32 +++++++++- src/over.s | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/player.s | 18 +++--- 4 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 src/over.s (limited to 'src') diff --git a/src/driver.s b/src/driver.s index b90cabe..c4bd8e7 100644 --- a/src/driver.s +++ b/src/driver.s @@ -236,7 +236,18 @@ lda Explosions::zp_active bne @sprite_cycling - ;; Nope! Then set the player's timer. + ;; After all the explosions have been done, do we have any life left? + lda Player::zp_lifes + bne @reset_timer + + ;; No! Toggle the game over bit. + ;; TODO: missing the coin game over. + lda Globals::zp_flags + ora #%00000010 + sta Globals::zp_flags + + @reset_timer: + ;; Reset the player's timer to enter the game screen again. lda #PLAYER_TIMER_VALUE sta zp_player_timer diff --git a/src/jetpac.s b/src/jetpac.s index 2e55771..5c1c099 100644 --- a/src/jetpac.s +++ b/src/jetpac.s @@ -46,6 +46,7 @@ .include "enemies.s" .include "bullets.s" .include "title.s" +.include "over.s" .include "driver.s" .include "interrupts.s" @@ -109,8 +110,10 @@ __fallthrough__ main .endproc - .proc main + ;; TODO: score initialization has to happen here. + +@init: ;; Disable the PPU and zero out variables which shadow PPU registers. lda #0 sta PPU::m_mask @@ -140,6 +143,9 @@ ;; Initialize the assets for the game. jsr Assets::init + ;; Initialize some variables from the "Game Over" side of the game. + jsr Over::init + ;; Initialize some PAL-specific constants. .ifdef PAL lda #0 @@ -209,8 +215,28 @@ jmp @main_game_loop @over: - ;; TODO: allow to start over, reset flags, control register, etc. - jmp @over + ;; Display the "Game over" screen if it hasn't been displayed yet. After + ;; that, if we detect that the user wants to go back to the title screen we + ;; go back to @init to initialize everything again except for the score + ;; which should be preserved. Otherwise we go back to the main game loop, + ;; which effectively means to just sit and wait until for the right player + ;; input. + jsr Over::handle + sta Globals::zp_tmp0 + + ;; Wait for the PPU to render the screen. +@set_flags_over: + lda #%10000000 + ora Globals::zp_flags + sta Globals::zp_flags +@wait_for_render_over: + bit Globals::zp_flags + bmi @wait_for_render_over + + ;; Did the user want to start over? + lda Globals::zp_tmp0 + beq @main_game_loop + jmp @init .endproc .segment "VECTORS" diff --git a/src/over.s b/src/over.s new file mode 100644 index 0000000..a2a0f9f --- /dev/null +++ b/src/over.s @@ -0,0 +1,196 @@ +.segment "CODE" + +.scope Over + ;; Has the "Game over" screen been displayed yet? + zp_displayed = $10 + + ;; Timer set whenever the "Game over" screen has been displayed. Whenever it + ;; times out, then the player is redirected to the title screen. + zp_timer = $11 + + ;; Amount of time the player has to wait for the title screen to appear + ;; again. + TIMER_VALUE = HZ * 3 + + ;; Initialize all variables for the "Game over" screens. + .proc init + lda #0 + sta Over::zp_displayed + sta Over::zp_timer + + rts + .endproc + + ;; Handle a "Game over" screen. It has two phases: + ;; 1. Render a "Game over" message. + ;; 2. Wait for a timer to time out. + ;; It will set 1 to the 'a' register if the timer has run out, signaling + ;; that the game can start over. Otherwise it sets 0 to the 'a' register. + .proc handle + ldy #0 + + ;; Has the "Game over" screen been displayed? If not do it now. + lda Over::zp_displayed + bne @do_handle + jsr Over::render + jmp @end + + @do_handle: + lda Over::zp_timer + bne @dec_timer + iny + beq @end + @dec_timer: + dec Over::zp_timer + + @end: + tya + rts + .endproc + + ;; Render the "Game over" message to the screen. This is done in two + ;; phases. We first ensure to disable the PPU, and in the second phase we do + ;; the actual writing. + .proc render + ;; Is PPU disabled? If it is then jump into rendering the screen + ;; directly. + lda PPU::zp_mask + beq @do_render + + ;; Nope! Force the PPU to be disabled and quit. + lda Globals::zp_flags + ora #%01000000 + sta Globals::zp_flags + lda #$00 + sta PPU::zp_mask + rts + + @do_render: + jsr Over::clear_out_screen + ;; TODO: coin game over. + jsr Over::render_regular_game_over + + ;; Enable back the PPU (only background). + lda #%00001110 + sta PPU::zp_mask + + ;; Update PPU registers. + lda #%01000000 + ora Globals::zp_flags + sta Globals::zp_flags + + ;; Set the "Game over" message as displayed and fire up the timer. + lda #1 + sta Over::zp_displayed + lda #Over::TIMER_VALUE + sta Over::zp_timer + + rts + .endproc + + ;; Remove all platforms and the ground. + .proc clear_out_screen + ;; Remove left platform. + bit PPU::m_status + ldx #$29 + stx PPU::m_address + ldx #$83 + stx PPU::m_address + lda #$00 + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + + ;; Remove center platform. + bit PPU::m_status + ldx #$29 + stx PPU::m_address + ldx #$EF + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + + ;; Remove right platform. + bit PPU::m_status + ldx #$29 + stx PPU::m_address + ldx #$38 + stx PPU::m_address + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + + ;; Ground + bit PPU::m_status + ldx #$2B + stx PPU::m_address + ldx #$20 + stx PPU::m_address + + ldx #$20 + @clear_ground_loop: + sta PPU::m_data + dex + bne @clear_ground_loop + + rts + .endproc + + ;; Render the regular "Game over player X" screen. + ;; + ;; TODO: multiplayer support. + .proc render_regular_game_over + ;; Set the position. + bit PPU::m_status + ldx #$29 + stx PPU::m_address + ldx #$67 + stx PPU::m_address + + ;; And just iterate over the "message" until we reach the end of string + ;; $FF character. + ldx #0 + @message_loop: + lda message, x + cmp #$FF + beq @out + sta PPU::m_data + inx + bne @message_loop + + @out: + ;; Reset attributes for the end of the message. + bit PPU::m_status + ldx #$2B + stx PPU::m_address + ldx #$D5 + stx PPU::m_address + lda #0 + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + + rts + + message: + ;; "GAME " + .byte $21, $1B, $27, $1F, $00 + ;; "OVER " + .byte $29, $30, $1F, $2C, $00 + ;; "PLAYER " + .byte $2A, $26, $1B, $33, $1F, $2C, $00 + ;; "1" + .byte $11, $FF + .endproc +.endscope diff --git a/src/player.s b/src/player.s index 73ff20e..7e215a5 100644 --- a/src/player.s +++ b/src/player.s @@ -834,14 +834,19 @@ ;; That's just german for "the Bart, the". .proc die_bart_die - ;; Decrement the life. + ;; Decrement the life. If we reach zero, then there's no point on + ;; signaling the NMI code to render this change. + ;; ;; TODO: this is just considering the first player only!. dec Player::zp_lifes - beq @over + beq @skip_life_update + + ;; Notify NMI code to render lifes again, as they have changed. lda Player::zp_state ora #%00001000 sta Player::zp_state + @skip_life_update: ;; Move the player's sprites out of the screen. ldx #0 lda #$FF @@ -863,14 +868,5 @@ lda Player::zp_screen_x sta Globals::zp_arg3 JAL Explosions::create - - @over: - ;; Set the proper flag for game over. - ;; TODO: game over (coin) - lda Globals::zp_flags - ora #%00000010 - sta Globals::zp_flags - - rts .endproc .endscope -- cgit v1.2.3