From 04b87e8f1927f32ee14f480ca5f770942f896626 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 17:34:40 -0400 Subject: [PATCH] Fix: Resolve crash on startup due to ProGuard/Gson TypeToken issue - Added necessary ProGuard rules to keep HighScore class, generic signatures, and annotations. - Fixed bug where music started on title screen instead of game start. - Fixed bug where session stats were blank on game over screen by ensuring stats are displayed correctly after high score entry. - Fixed bug where game music did not restart after playing again. --- ...otlin-compiler-16266712807184755328.salive | 0 app/proguard-rules.pro | 35 +++++- .../java/com/pixelmintdrop/MainActivity.kt | 106 +++++++++++++++--- .../java/com/pixelmintdrop/audio/GameMusic.kt | 24 +++- 4 files changed, 145 insertions(+), 20 deletions(-) delete mode 100644 .kotlin/sessions/kotlin-compiler-16266712807184755328.salive diff --git a/.kotlin/sessions/kotlin-compiler-16266712807184755328.salive b/.kotlin/sessions/kotlin-compiler-16266712807184755328.salive deleted file mode 100644 index e69de29..0000000 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index bdf5303..da50729 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,10 +1,41 @@ # Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# By default, the flags in this file are appended to flags specified +# in /Users/coreyclark/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number table for debugging exceptions. +#-keepattributes SourceFile,LineNumberTable + +# Keep rule for Gson serialization of HighScore data class +-keep class com.pixelmintdrop.model.HighScore { *; } +-keepclassmembers class com.pixelmintdrop.model.HighScore { *; } + +# Keep generic signatures for Gson TypeToken +-keepattributes Signature + +# Keep annotations used by Gson (e.g., @SerializedName, @Expose) +-keepattributes *Annotation* + +# Keep the anonymous inner class generated for the TypeToken in HighScoreManager +-keep class com.pixelmintdrop.model.HighScoreManager$* { *; } + +# Keep Gson TypeToken related classes +-keep class com.google.gson.reflect.TypeToken { *; } +-keep class com.google.gson.reflect.** { *; } + # Keep models intact -keep class com.pixelmintdrop.model.** { *; } diff --git a/app/src/main/java/com/pixelmintdrop/MainActivity.kt b/app/src/main/java/com/pixelmintdrop/MainActivity.kt index e837a91..da12577 100644 --- a/app/src/main/java/com/pixelmintdrop/MainActivity.kt +++ b/app/src/main/java/com/pixelmintdrop/MainActivity.kt @@ -122,12 +122,27 @@ class MainActivity : AppCompatActivity(), // Add these new properties at the class level private var currentCustomizationMenuSelection = 0 + // CAPTURE session stats needed for display into MainActivity member variables + private var lastSessionScore = 0 + private var lastSessionLines = 0 + private var lastSessionPieces = 0 + private var lastSessionTime = 0L + private var lastSessionLevel = 0 + private var lastSessionSingles = 0 + private var lastSessionDoubles = 0 + private var lastSessionTriples = 0 + private var lastSessionQuads = 0 + override fun onCreate(savedInstanceState: Bundle?) { // Register activity result launcher for high score entry highScoreEntryLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - // No matter what the result is, we just show the game over container + // When returning from HighScoreEntryActivity: + // 1. Hide progression screen progressionScreen.visibility = View.GONE - binding.gameOverContainer.visibility = View.VISIBLE + // 2. Make game over container visible (it's already laid out) + binding.gameOverContainer.visibility = View.VISIBLE + // 3. *** Call showGameOverScreenDirectly to populate the stats *** + showGameOverScreenDirectly(lastSessionScore) // Use the captured score // Keep all game UI elements hidden binding.gameControlsContainer.visibility = View.GONE @@ -406,7 +421,33 @@ class MainActivity : AppCompatActivity(), val newHighScore = highScoreManager.isHighScore(finalScore) statsManager.endSession() // End session after calculations - Log.d(TAG, "Game Over. Score: $finalScore, Level: ${viewModel.currentLevel.value}, Lines: ${gameBoard.lines}, Start Level: ${viewModel.selectedLevel.value}, New High Score: $newHighScore, XP Gained: $xpGained") + // CAPTURE session stats needed for display into MainActivity member variables + lastSessionScore = statsManager.getSessionScore() + lastSessionLines = statsManager.getSessionLines() + lastSessionPieces = statsManager.getSessionPieces() + lastSessionTime = statsManager.getSessionTime() + lastSessionLevel = statsManager.getSessionLevel() + lastSessionSingles = statsManager.getSessionSingles() + lastSessionDoubles = statsManager.getSessionDoubles() + lastSessionTriples = statsManager.getSessionTriples() + lastSessionQuads = statsManager.getSessionQuads() + + // *** Add detailed logging here *** + Log.d(TAG, "[GameOverDebug] Captured Stats:") + Log.d(TAG, "[GameOverDebug] Score: $lastSessionScore (from manager: ${statsManager.getSessionScore()})") + Log.d(TAG, "[GameOverDebug] Lines: $lastSessionLines (from manager: ${statsManager.getSessionLines()})") + Log.d(TAG, "[GameOverDebug] Pieces: $lastSessionPieces (from manager: ${statsManager.getSessionPieces()})") + Log.d(TAG, "[GameOverDebug] Time: $lastSessionTime (from manager: ${statsManager.getSessionTime()})") + Log.d(TAG, "[GameOverDebug] Level: $lastSessionLevel (from manager: ${statsManager.getSessionLevel()})") + Log.d(TAG, "[GameOverDebug] Singles: $lastSessionSingles (from manager: ${statsManager.getSessionSingles()})") + Log.d(TAG, "[GameOverDebug] Doubles: $lastSessionDoubles (from manager: ${statsManager.getSessionDoubles()})") + Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})") + Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})") + + // End the session (updates lifetime stats) + statsManager.endSession() + + Log.d(TAG, "Game Over. Captured Score: $lastSessionScore, Level: $lastSessionLevel, Lines: $lastSessionLines, Start Level: ${viewModel.selectedLevel.value}, New High Score: $newHighScore, XP Gained: $xpGained") // Show appropriate screen: Progression or Game Over directly if (xpGained > 0 || newHighScore) { @@ -570,8 +611,8 @@ class MainActivity : AppCompatActivity(), /** * Shows the final game over screen with stats. */ - private fun showGameOverScreenDirectly(score: Int) { - Log.d(TAG, "Showing final game over screen with score: $score") + private fun showGameOverScreenDirectly(score: Int) { // Keep score param for logging if needed elsewhere + Log.d(TAG, "Showing final game over screen with score from param: $score, from StatsManager: ${statsManager.getSessionScore()}") // Ensure game UI is hidden binding.gameControlsContainer.visibility = View.GONE binding.holdPieceView.visibility = View.GONE @@ -584,20 +625,20 @@ class MainActivity : AppCompatActivity(), // Update session stats display in the gameOverContainer val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) timeFormat.timeZone = TimeZone.getTimeZone("UTC") - val gameTime = System.currentTimeMillis() - gameStartTime // Recalculate or pass from onGameOver? - // Let's recalculate for simplicity here. + // val gameTime = System.currentTimeMillis() - gameStartTime // No longer needed here - // Set text directly using interpolation (workaround attempt) - binding.sessionScoreText.text = "Score: $score" - binding.sessionLinesText.text = "Lines: ${gameBoard.lines}" - binding.sessionPiecesText.text = "Pieces: $piecesPlaced" - binding.sessionTimeText.text = "Time: ${timeFormat.format(gameTime)}" - binding.sessionLevelText.text = "Level: ${viewModel.currentLevel.value ?: 1}" + // --- Consistently use StatsManager session getters --- + binding.sessionScoreText.text = "Score: ${statsManager.getSessionScore()}" + binding.sessionLinesText.text = "Lines: ${statsManager.getSessionLines()}" + binding.sessionPiecesText.text = "Pieces: ${statsManager.getSessionPieces()}" + binding.sessionTimeText.text = "Time: ${timeFormat.format(statsManager.getSessionTime())}" + binding.sessionLevelText.text = "Level: ${statsManager.getSessionLevel()}" binding.sessionSinglesText.text = "Singles: ${statsManager.getSessionSingles()}" binding.sessionDoublesText.text = "Doubles: ${statsManager.getSessionDoubles()}" binding.sessionTriplesText.text = "Triples: ${statsManager.getSessionTriples()}" binding.sessionQuadsText.text = "Quads: ${statsManager.getSessionQuads()}" + // --- End StatsManager usage --- // Make the container visible binding.gameOverContainer.visibility = View.VISIBLE @@ -900,6 +941,9 @@ class MainActivity : AppCompatActivity(), */ private fun startGame() { Log.d(TAG, "Starting game at level ${viewModel.selectedLevel.value}") + // Reset session stats FIRST + statsManager.startNewSession() + // Reset game state viewModel.resetGame() // Resets score and level in ViewModel viewModel.setLevel(viewModel.selectedLevel.value ?: 1) // Set initial level from selection @@ -973,7 +1017,33 @@ class MainActivity : AppCompatActivity(), val newHighScore = highScoreManager.isHighScore(finalScore) statsManager.endSession() // End session after calculations - Log.d(TAG, "Game Over. Score: $finalScore, Level: ${viewModel.currentLevel.value}, Lines: ${gameBoard.lines}, Start Level: ${viewModel.selectedLevel.value}, New High Score: $newHighScore, XP Gained: $xpGained") + // CAPTURE session stats needed for display into MainActivity member variables + lastSessionScore = statsManager.getSessionScore() + lastSessionLines = statsManager.getSessionLines() + lastSessionPieces = statsManager.getSessionPieces() + lastSessionTime = statsManager.getSessionTime() + lastSessionLevel = statsManager.getSessionLevel() + lastSessionSingles = statsManager.getSessionSingles() + lastSessionDoubles = statsManager.getSessionDoubles() + lastSessionTriples = statsManager.getSessionTriples() + lastSessionQuads = statsManager.getSessionQuads() + + // *** Add detailed logging here *** + Log.d(TAG, "[GameOverDebug] Captured Stats:") + Log.d(TAG, "[GameOverDebug] Score: $lastSessionScore (from manager: ${statsManager.getSessionScore()})") + Log.d(TAG, "[GameOverDebug] Lines: $lastSessionLines (from manager: ${statsManager.getSessionLines()})") + Log.d(TAG, "[GameOverDebug] Pieces: $lastSessionPieces (from manager: ${statsManager.getSessionPieces()})") + Log.d(TAG, "[GameOverDebug] Time: $lastSessionTime (from manager: ${statsManager.getSessionTime()})") + Log.d(TAG, "[GameOverDebug] Level: $lastSessionLevel (from manager: ${statsManager.getSessionLevel()})") + Log.d(TAG, "[GameOverDebug] Singles: $lastSessionSingles (from manager: ${statsManager.getSessionSingles()})") + Log.d(TAG, "[GameOverDebug] Doubles: $lastSessionDoubles (from manager: ${statsManager.getSessionDoubles()})") + Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})") + Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})") + + // End the session (updates lifetime stats) + statsManager.endSession() + + Log.d(TAG, "Game Over. Captured Score: $lastSessionScore, Level: $lastSessionLevel, Lines: $lastSessionLines, Start Level: ${viewModel.selectedLevel.value}, New High Score: $newHighScore, XP Gained: $xpGained") // Show appropriate screen: Progression or Game Over directly if (xpGained > 0 || newHighScore) { @@ -1007,6 +1077,7 @@ class MainActivity : AppCompatActivity(), // Start background music if enabled if (viewModel.isMusicEnabled.value == true) { // Read from ViewModel + gameMusic.prepareMusic() // Ensure player is ready gameMusic.start() } @@ -1014,8 +1085,8 @@ class MainActivity : AppCompatActivity(), gameView.start() // Observer ensures gameMusic is enabled/disabled correctly via gameMusic.setEnabled() - // Reset session stats - statsManager.startNewSession() + // Reset session stats - MOVED TO TOP + // statsManager.startNewSession() progressionManager.startNewSession() gameBoard.updateLevel(viewModel.selectedLevel.value ?: 1) } @@ -1222,6 +1293,9 @@ class MainActivity : AppCompatActivity(), } else if (binding.pauseContainer.visibility == View.VISIBLE) { // If pause menu is showing, handle as a resume resumeGame() + } else if (binding.customizationContainer.visibility == View.VISIBLE) { + // If customization menu is showing, hide it + hideCustomizationMenu() } else if (binding.gameOverContainer.visibility == View.VISIBLE) { // If game over is showing, go back to title hideGameOver() diff --git a/app/src/main/java/com/pixelmintdrop/audio/GameMusic.kt b/app/src/main/java/com/pixelmintdrop/audio/GameMusic.kt index 3a4fb53..57f53d6 100644 --- a/app/src/main/java/com/pixelmintdrop/audio/GameMusic.kt +++ b/app/src/main/java/com/pixelmintdrop/audio/GameMusic.kt @@ -141,8 +141,6 @@ class GameMusic(private val context: Context) { if (!enabled && mediaPlayer?.isPlaying == true) { pause() - } else if (enabled && mediaPlayer != null && isPrepared) { - start() } } @@ -160,4 +158,26 @@ class GameMusic(private val context: Context) { Log.e("GameMusic", "Error releasing music: ${e.message}") } } + + /** + * Ensures the main media player is prepared for playback. + */ + fun prepareMusic() { + if (mediaPlayer != null && !isPrepared) { + try { + mediaPlayer?.prepare() + isPrepared = true + Log.d("GameMusic", "MediaPlayer prepared successfully in prepareMusic()") + } catch (e: IllegalStateException) { + Log.e("GameMusic", "Error preparing MediaPlayer (already prepared?): ${e.message}") + // Assume it's prepared if IllegalStateException is thrown + isPrepared = true + } catch (e: Exception) { + Log.e("GameMusic", "Error preparing MediaPlayer", e) + isPrepared = false + } + } else if (mediaPlayer != null && isPrepared) { + Log.d("GameMusic", "MediaPlayer already prepared in prepareMusic()") + } + } } \ No newline at end of file