Maelstrom: Split the game over stage into distinct phases

From f4487d0088b193354646efbb3fb564d7f6036989 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 22 Apr 2026 21:19:54 -0700
Subject: [PATCH] Split the game over stage into distinct phases

Fixes https://github.com/libsdl-org/Maelstrom/issues/37
---
 game/gameover.cpp | 126 +++++++++++++++++++++++++++-------------------
 game/gameover.h   |  14 +++++-
 2 files changed, 86 insertions(+), 54 deletions(-)

diff --git a/game/gameover.cpp b/game/gameover.cpp
index ba6f01be..c243ba83 100644
--- a/game/gameover.cpp
+++ b/game/gameover.cpp
@@ -54,6 +54,39 @@ static int cmp_byfrags(const void *pA, const void *pB)
 }
 
 void GameOverPanelDelegate::OnShow()
+{
+	UIElement *image;
+
+	m_panel->HideAll();
+
+	image = m_panel->GetElement<UIElement>("image");
+	if (image) {
+		image->Show();
+	}
+
+	m_handleLabel = nullptr;
+
+	m_state = STATE_SHOWING;
+
+	DelaySound();
+}
+
+void GameOverPanelDelegate::OnHide()
+{
+	if (gReplay.IsRecording()) {
+		// Save this as the last game
+		gReplay.Save(LAST_REPLAY);
+		gLastGameID = gReplay.GetGameInfo().gameID;
+	}
+	gReplay.SetMode(REPLAY_IDLE);
+
+	/* Make sure we clear the game info so we don't crash trying to
+	   update UI in a future replay
+	*/
+	gGameInfo.Reset();
+}
+
+void GameOverPanelDelegate::HandleShown()
 {
 	UIElement *image;
 	UIElement *label;
@@ -73,13 +106,6 @@ void GameOverPanelDelegate::OnShow()
 	else
 		SDL_qsort(final,MAX_PLAYERS,sizeof(struct FinalScore),cmp_byscore);
 
-	m_panel->HideAll();
-
-	image = m_panel->GetElement<UIElement>("image");
-	if (image) {
-		image->Show();
-	}
-
 	/* Show the player ranking */
 	if ( gGameInfo.IsMultiplayer() ) {
 		int nextLabel = 1;
@@ -119,8 +145,6 @@ void GameOverPanelDelegate::OnShow()
 	delete[] final;
 
 	/* -- See if they got a high score */
-	m_showIME = false;
-	m_handleLabel = nullptr;
 	if (gReplay.IsRecording() && !gReplay.HasContinues() &&
 	    !gGameInfo.IsMultiplayer() && !gGameInfo.IsKidMode() &&
 	    (gGameInfo.wave == 1) && (gGameInfo.lives <= DEFAULT_START_LIVES) &&
@@ -133,54 +157,38 @@ void GameOverPanelDelegate::OnShow()
 		}
 	}
 
-	m_showTime = SDL_GetTicks();
+	if (m_state == STATE_SHOWING) {
+		m_state = STATE_FINISHED_NAME;
+		m_readyTime = SDL_GetTicks();
+	}
 }
 
-void GameOverPanelDelegate::OnHide()
+void GameOverPanelDelegate::HandleEnableIME()
 {
-	if (gReplay.IsRecording()) {
-		// Save this as the last game
-		gReplay.Save(LAST_REPLAY);
-		gLastGameID = gReplay.GetGameInfo().gameID;
-	}
-	gReplay.SetMode(REPLAY_IDLE);
-
-	/* Make sure we clear the game info so we don't crash trying to
-	   update UI in a future replay
-	*/
-	gGameInfo.Reset();
+	screen->EnableTextInput(m_handleLabel->X(), m_handleLabel->Y(), m_handleLabel->Width(), m_handleLabel->Height());
+	m_state = STATE_ENTERING_NAME;
 }
 
 void GameOverPanelDelegate::OnTick()
 {
-	if (m_showIME) {
-		// Wait for the sound to complete before bringing up text entry
-		if (sound->Playing()) {
-			return;
-		}
-
-		screen->EnableTextInput(m_handleLabel->X(), m_handleLabel->Y(), m_handleLabel->Width(), m_handleLabel->Height());
-		m_showIME = false;
-	}
-
-	if (m_handleLabel) {
-		return;
-	}
-
-	/* -- Wait for the game over sound */
-	if (sound->Playing()) {
-		return;
-	}
-
-	if (gGameInfo.IsMultiplayer()) { /* Let them watch their ranking */
-		const Uint32 MULTIPLAYER_SHOW_TIME = 3000;
-		if ((SDL_GetTicks() - m_showTime) < MULTIPLAYER_SHOW_TIME) {
-			return;
-		}
+	switch (m_state) {
+	case STATE_SHOWING:
+		HandleShown();
+		break;
+	case STATE_ENABLE_IME:
+		HandleEnableIME();
+		break;
+	case STATE_ENTERING_NAME:
+		// Wait until we're done
+		break;
+	case STATE_FINISHED_NAME:
+		HandleFinishedName();
+		break;
+	case STATE_DONE:
+		// We're done showing the scores
+		ui->ShowPanel(PANEL_MAIN);
+		break;
 	}
-
-	// We're done showing the scores
-	ui->ShowPanel(PANEL_MAIN);
 }
 
 bool GameOverPanelDelegate::HandleEvent(const SDL_Event &event)
@@ -269,7 +277,7 @@ void GameOverPanelDelegate::BeginEnterName()
 	}
 	m_handleSize = (int)SDL_strlen(m_handle);
 
-	m_showIME = true;
+	m_state = STATE_ENABLE_IME;
 }
 
 void GameOverPanelDelegate::FinishEnterName()
@@ -282,9 +290,23 @@ void GameOverPanelDelegate::FinishEnterName()
 		gReplay.Save();
 		LoadScores();
 	}
-	m_showIME = false;
-	m_handleLabel = nullptr;
 
 	sound->HaltSound();
 	sound->PlaySound(gGotPrize, 6);
+	DelaySound();
+
+	m_state = STATE_FINISHED_NAME;
+	m_readyTime = SDL_GetTicks();
+}
+
+void GameOverPanelDelegate::HandleFinishedName()
+{
+	if (gGameInfo.IsMultiplayer()) { /* Let them watch their ranking */
+		const Uint32 MULTIPLAYER_SHOW_TIME = 3000;
+		if ((SDL_GetTicks() - m_readyTime) < MULTIPLAYER_SHOW_TIME) {
+			return;
+		}
+	}
+
+	m_state = STATE_DONE;
 }
diff --git a/game/gameover.h b/game/gameover.h
index 08d74581..5a76b6dc 100644
--- a/game/gameover.h
+++ b/game/gameover.h
@@ -35,15 +35,25 @@ class GameOverPanelDelegate : public UIPanelDelegate
 	virtual bool HandleEvent(const SDL_Event &event);
 
 protected:
+	void HandleShown();
+	void HandleEnableIME();
 	void BeginEnterName();
 	void FinishEnterName();
+	void HandleFinishedName();
 
 protected:
+	enum {
+		STATE_SHOWING,
+		STATE_ENABLE_IME,
+		STATE_ENTERING_NAME,
+		STATE_FINISHED_NAME,
+		STATE_DONE,
+	} m_state;
+
 	UIElement *m_handleLabel;
 	int m_handleSize;
 	char m_handle[MAX_NAMELEN+1];
-	bool m_showIME;
-	Uint64 m_showTime;
+	Uint64 m_readyTime;
 };
 
 #endif // _gameover_h