diff --git a/app/build.gradle b/app/build.gradle
index fece2bd..acd7231 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -19,7 +19,7 @@ android {
buildTypes {
release {
- minifyEnabled false
+ minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@@ -47,6 +47,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
+ implementation "androidx.activity:activity-ktx:1.9.0"
implementation 'androidx.window:window:1.2.0' // For better display support
implementation 'androidx.window:window-java:1.2.0'
implementation 'com.google.code.gson:gson:2.10.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 72386f6..f3d41de 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,6 @@
-
+ // Use actual ID from layout - display only the number
+ binding.scoreText.text = newScore.toString()
+ })
+
+ viewModel.currentLevel.observe(this, Observer { newLevel ->
+ // Use actual ID from layout - display only the number
+ binding.currentLevelText.text = newLevel.toString()
+ })
+
// Load random mode setting
isRandomModeEnabled = getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.preferences", Context.MODE_PRIVATE)
.getBoolean("random_mode_enabled", false)
@@ -348,28 +365,17 @@ class MainActivity : AppCompatActivity(),
}
gameView.onGameOver = { finalScore ->
- // Pause music on game over
+ // Start animation & pause music
+ gameView.startGameOverAnimation()
gameMusic.pause()
- // Update high scores
+ // Calculate final stats, XP, and high score
val timePlayedMs = System.currentTimeMillis() - gameStartTime
- val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
- val currentDate = dateFormat.format(Date())
-
- // Track if this is a high score
- val isHighScore = highScoreManager.isHighScore(finalScore)
-
- // Show game over screen
- showGameOver(finalScore)
-
- // Save player stats to track game history
- statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel)
-
- // Handle progression - XP earned, potential level up
+ statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, viewModel.currentLevel.value ?: 1)
val xpGained = progressionManager.calculateGameXP(
score = finalScore,
linesCleared = gameBoard.lines,
- level = currentLevel,
+ level = viewModel.currentLevel.value ?: 1,
gameTime = timePlayedMs,
tSpins = statsManager.getSessionTSpins(),
combos = statsManager.getSessionMaxCombo(),
@@ -377,13 +383,22 @@ class MainActivity : AppCompatActivity(),
perfectClearCount = statsManager.getSessionPerfectClears()
)
val newRewards = progressionManager.addXP(xpGained)
+ val newHighScore = highScoreManager.isHighScore(finalScore)
+ statsManager.endSession() // End session after calculations
- // Show progression screen if player earned XP
- if (xpGained > 0) {
- // Delay showing progression screen for a moment
+ Log.d(TAG, "Game Over. Score: $finalScore, Level: ${viewModel.currentLevel.value}, Lines: ${gameBoard.lines}, Start Level: $selectedLevel, New High Score: $newHighScore, XP Gained: $xpGained")
+
+ // Show appropriate screen: Progression or Game Over directly
+ if (xpGained > 0 || newHighScore) {
+ // Delay showing progression slightly to let animation play
Handler(Looper.getMainLooper()).postDelayed({
- showProgressionScreen(xpGained, newRewards)
- }, 2000)
+ showProgressionScreen(xpGained, newRewards, newHighScore, finalScore)
+ }, 1500) // Delay can be adjusted
+ } else {
+ // No XP, no high score -> show game over screen directly after animation
+ Handler(Looper.getMainLooper()).postDelayed({
+ showGameOverScreenDirectly(finalScore)
+ }, 1500) // Delay to match progression path
}
}
@@ -515,126 +530,53 @@ class MainActivity : AppCompatActivity(),
binding.linesText.text = lines.toString()
binding.comboText.text = gameBoard.getCombo().toString()
- // Update current level for stats
- currentLevel = level
-
// Force redraw of next piece preview
binding.nextPieceView.invalidate()
}
/**
- * Show game over screen
+ * Shows the final game over screen with stats.
*/
- private fun showGameOver(score: Int) {
- Log.d("MainActivity", "Showing game over screen with score: $score")
- val gameTime = System.currentTimeMillis() - gameStartTime
-
- // Hide all game UI elements
+ private fun showGameOverScreenDirectly(score: Int) {
+ Log.d(TAG, "Showing final game over screen with score: $score")
+ // Ensure game UI is hidden
binding.gameControlsContainer.visibility = View.GONE
binding.holdPieceView.visibility = View.GONE
binding.nextPieceView.visibility = View.GONE
binding.pauseButton.visibility = View.GONE
-
- // Hide landscape panels if they exist
binding.leftControlsPanel?.visibility = View.GONE
binding.rightControlsPanel?.visibility = View.GONE
-
- // Update session stats
- statsManager.updateSessionStats(
- score = score,
- lines = gameBoard.lines,
- pieces = piecesPlaced,
- time = gameTime,
- level = currentLevel
- )
-
- // Calculate XP earned
- val xpGained = progressionManager.calculateGameXP(
- score = statsManager.getSessionScore(),
- linesCleared = statsManager.getSessionLines(),
- level = currentLevel,
- gameTime = gameTime,
- tSpins = statsManager.getSessionTSpins(),
- combos = statsManager.getSessionMaxCombo(),
- quadCount = statsManager.getSessionQuads(),
- perfectClearCount = statsManager.getSessionPerfectClears()
- )
-
- // Add XP and check for rewards
- val newRewards = progressionManager.addXP(xpGained)
-
- // End session and save stats
- statsManager.endSession()
-
- // Update session stats display
+ progressionScreen.visibility = View.GONE // Ensure progression is hidden
+
+ // 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.
binding.sessionScoreText.text = getString(R.string.session_score, score)
binding.sessionLinesText.text = getString(R.string.session_lines, gameBoard.lines)
binding.sessionPiecesText.text = getString(R.string.session_pieces, piecesPlaced)
binding.sessionTimeText.text = getString(R.string.session_time, timeFormat.format(gameTime))
- binding.sessionLevelText.text = getString(R.string.session_level, currentLevel)
+ binding.sessionLevelText.text = getString(R.string.session_level, viewModel.currentLevel.value ?: 1)
- // Update session line clear stats
binding.sessionSinglesText.text = getString(R.string.singles, statsManager.getSessionSingles())
binding.sessionDoublesText.text = getString(R.string.doubles, statsManager.getSessionDoubles())
binding.sessionTriplesText.text = getString(R.string.triples, statsManager.getSessionTriples())
binding.sessionQuadsText.text = getString(R.string.quads, statsManager.getSessionQuads())
- // Flag to track if high score screen will be shown
- var showingHighScore = false
+ // Make the container visible
+ binding.gameOverContainer.visibility = View.VISIBLE
- // Play game over sound and trigger animation
- if (isSoundEnabled) {
+ // Play game over sound if not already played by progression
+ if (isSoundEnabled && progressionScreen.visibility != View.VISIBLE) {
gameMusic.playGameOver()
}
-
- // First trigger the animation in the game view
- Log.d("MainActivity", "Triggering game over animation")
- gameView.startGameOverAnimation()
-
- // Wait a moment before showing progression screen to let animation be visible
- Handler(Looper.getMainLooper()).postDelayed({
- // Show progression screen first with XP animation
- showProgressionScreen(xpGained, newRewards)
-
- // Override the continue button behavior if high score needs to be shown
- val originalOnContinue = progressionScreen.onContinue
-
- progressionScreen.onContinue = {
- // If this is a high score, show high score entry screen
- if (highScoreManager.isHighScore(score)) {
- showingHighScore = true
- showHighScoreEntry(score)
- } else {
- // Just show game over screen normally
- progressionScreen.visibility = View.GONE
- binding.gameOverContainer.visibility = View.VISIBLE
-
- // Keep all game UI elements hidden
- binding.gameControlsContainer.visibility = View.GONE
- binding.holdPieceView.visibility = View.GONE
- binding.nextPieceView.visibility = View.GONE
- binding.pauseButton.visibility = View.GONE
- binding.leftControlsPanel?.visibility = View.GONE
- binding.rightControlsPanel?.visibility = View.GONE
-
- // Get all themes with "Theme" in their name
- val themeRewards = progressionManager.getUnlockedThemes().filter {
- it.contains("Theme", ignoreCase = true)
- }
-
- // Update theme selector if new themes were unlocked
- if (themeRewards.isNotEmpty()) {
- updateThemeSelector()
- }
- }
- }
- }, 2000) // Increased from 1000ms (1 second) to 2000ms (2 seconds)
-
- // Vibrate to indicate game over
- vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK)
+
+ // Vibrate if not already vibrated by progression
+ if (progressionScreen.visibility != View.VISIBLE) {
+ vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK)
+ }
}
/**
@@ -643,7 +585,7 @@ class MainActivity : AppCompatActivity(),
private fun showHighScoreEntry(score: Int) {
val intent = Intent(this, HighScoreEntryActivity::class.java).apply {
putExtra("score", score)
- putExtra("level", currentLevel)
+ putExtra("level", viewModel.currentLevel.value ?: 1) // Read from ViewModel
}
// Use the launcher instead of startActivity
highScoreEntryLauncher.launch(intent)
@@ -930,30 +872,23 @@ class MainActivity : AppCompatActivity(),
* Start a new game
*/
private fun startGame() {
- // Reset pieces placed counter
+ Log.d(TAG, "Starting game at level $selectedLevel")
+ // Reset game state
+ viewModel.resetGame() // Resets score and level in ViewModel
+ viewModel.setLevel(selectedLevel) // Set initial level from selection
piecesPlaced = 0
-
- // Set initial game state
- currentScore = 0
- currentLevel = selectedLevel
- lastLines = 0 // Reset lastLines to 0
- lastLinesGroup = 0 // Reset lastLinesGroup to 0
- lastRandomLevel = 0 // Reset lastRandomLevel to 0
gameStartTime = System.currentTimeMillis()
-
- // Update UI to show initial values
- binding.scoreText.text = "$currentScore"
- binding.currentLevelText.text = "$currentLevel"
- binding.linesText.text = "0"
- binding.comboText.text = "0"
-
+ lastLines = 0
+ lastLinesGroup = 0
+ lastRandomLevel = 0
+ // Observers will update scoreText and currentLevelText
+
// Reset game view and game board
gameBoard.reset()
gameView.reset()
- // Ensure block skin is properly set (helps with random mode)
+ // Ensure block skin is properly set
val selectedSkin = progressionManager.getSelectedBlockSkin()
- Log.d("RandomMode", "Game start: Setting block skin to $selectedSkin")
gameView.setBlockSkin(selectedSkin)
// Update selectors to refresh UI state
@@ -962,9 +897,9 @@ class MainActivity : AppCompatActivity(),
selectedSkin,
progressionManager.getPlayerLevel()
)
-
- // Show game elements
- gameView.visibility = View.VISIBLE
+
+ // Ensure game UI elements are visible and others hidden
+ binding.gameView.visibility = View.VISIBLE
binding.gameControlsContainer.visibility = View.VISIBLE
binding.holdPieceView.visibility = View.VISIBLE
binding.nextPieceView.visibility = View.VISIBLE
@@ -974,7 +909,7 @@ class MainActivity : AppCompatActivity(),
titleScreen.visibility = View.GONE
progressionScreen.visibility = View.GONE
- // Show game UI elements in landscape mode
+ // Show landscape specific controls if needed
if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
binding.leftControlsPanel?.visibility = View.VISIBLE
binding.rightControlsPanel?.visibility = View.VISIBLE
@@ -982,32 +917,22 @@ class MainActivity : AppCompatActivity(),
// Configure callbacks
gameView.onGameStateChanged = { score, level, lines ->
- updateGameStateUI(score, level, lines)
+ // We'll adapt updateGameStateUI later to use ViewModel
+ updateGameStateUI(score, level, lines)
}
gameView.onGameOver = { finalScore ->
- // Pause music on game over
+ // Start animation & pause music
+ gameView.startGameOverAnimation()
gameMusic.pause()
- // Update high scores
+ // Calculate final stats, XP, and high score
val timePlayedMs = System.currentTimeMillis() - gameStartTime
- val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
- val currentDate = dateFormat.format(Date())
-
- // Track if this is a high score
- val isHighScore = highScoreManager.isHighScore(finalScore)
-
- // Show game over screen
- showGameOver(finalScore)
-
- // Save player stats to track game history
- statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel)
-
- // Handle progression - XP earned, potential level up
+ statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, viewModel.currentLevel.value ?: 1)
val xpGained = progressionManager.calculateGameXP(
score = finalScore,
linesCleared = gameBoard.lines,
- level = currentLevel,
+ level = viewModel.currentLevel.value ?: 1,
gameTime = timePlayedMs,
tSpins = statsManager.getSessionTSpins(),
combos = statsManager.getSessionMaxCombo(),
@@ -1015,13 +940,22 @@ class MainActivity : AppCompatActivity(),
perfectClearCount = statsManager.getSessionPerfectClears()
)
val newRewards = progressionManager.addXP(xpGained)
+ val newHighScore = highScoreManager.isHighScore(finalScore)
+ statsManager.endSession() // End session after calculations
- // Show progression screen if player earned XP
- if (xpGained > 0) {
- // Delay showing progression screen for a moment
+ Log.d(TAG, "Game Over. Score: $finalScore, Level: ${viewModel.currentLevel.value}, Lines: ${gameBoard.lines}, Start Level: $selectedLevel, New High Score: $newHighScore, XP Gained: $xpGained")
+
+ // Show appropriate screen: Progression or Game Over directly
+ if (xpGained > 0 || newHighScore) {
+ // Delay showing progression slightly to let animation play
Handler(Looper.getMainLooper()).postDelayed({
- showProgressionScreen(xpGained, newRewards)
- }, 2000)
+ showProgressionScreen(xpGained, newRewards, newHighScore, finalScore)
+ }, 1500) // Delay can be adjusted
+ } else {
+ // No XP, no high score -> show game over screen directly after animation
+ Handler(Looper.getMainLooper()).postDelayed({
+ showGameOverScreenDirectly(finalScore)
+ }, 1500) // Delay to match progression path
}
}
@@ -1423,11 +1357,11 @@ class MainActivity : AppCompatActivity(),
return super.onGenericMotionEvent(event)
}
- private fun showProgressionScreen(xpGained: Long, newRewards: List) {
+ private fun showProgressionScreen(xpGained: Long, newRewards: List, isNewHighScore: Boolean, finalScore: Int) {
// Apply theme before showing the screen
progressionScreen.applyTheme(currentTheme)
- // Hide all game UI elements
+ // Hide game/other UI elements
binding.gameControlsContainer.visibility = View.GONE
binding.holdPieceView.visibility = View.GONE
binding.nextPieceView.visibility = View.GONE
@@ -1443,6 +1377,16 @@ class MainActivity : AppCompatActivity(),
// Display progression data
progressionScreen.showProgress(progressionManager, xpGained, newRewards, currentTheme)
+
+ // Set up the continue action
+ progressionScreen.onContinue = {
+ if (isNewHighScore) {
+ showHighScoreEntry(finalScore)
+ } else {
+ // No high score, just show the final game over screen
+ showGameOverScreenDirectly(finalScore)
+ }
+ }
}
/**
@@ -2045,11 +1989,10 @@ class MainActivity : AppCompatActivity(),
}
private fun updateGameStateUI(score: Int, level: Int, lines: Int) {
- currentScore = score.toLong()
- currentLevel = level
+ viewModel.setScore(score.toLong()) // Use ViewModel setter
+ viewModel.setLevel(level) // Use ViewModel setter
- binding.scoreText.text = "$score"
- binding.currentLevelText.text = "$level"
+ // Update other UI elements not handled by score/level observers
binding.linesText.text = "$lines"
binding.comboText.text = gameBoard.getCombo().toString()
diff --git a/app/src/main/java/com/pixelmintdrop/MainActivityViewModel.kt b/app/src/main/java/com/pixelmintdrop/MainActivityViewModel.kt
new file mode 100644
index 0000000..e62a138
--- /dev/null
+++ b/app/src/main/java/com/pixelmintdrop/MainActivityViewModel.kt
@@ -0,0 +1,40 @@
+package com.pixelmintdrop
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class MainActivityViewModel : ViewModel() {
+
+ // Private MutableLiveData for internal updates
+ private val _currentScore = MutableLiveData(0L)
+ private val _currentLevel = MutableLiveData(1)
+
+ // Public LiveData for observation by the Activity
+ val currentScore: LiveData = _currentScore
+ val currentLevel: LiveData = _currentLevel
+
+ // Example function to update the score (logic would be moved here)
+ fun incrementScore(points: Long) {
+ _currentScore.value = (_currentScore.value ?: 0L) + points
+ // Potentially add logic here to check for level up based on score
+ }
+
+ // Function to set the score directly
+ fun setScore(score: Long) {
+ _currentScore.value = score
+ }
+
+ // Example function to update the level
+ fun setLevel(level: Int) {
+ _currentLevel.value = level
+ }
+
+ fun resetGame() {
+ _currentScore.value = 0L
+ _currentLevel.value = 1
+ // Reset other game state within the ViewModel as needed
+ }
+
+ // Add other state variables and logic related to game state here
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e2847c8..c1d5e01 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME