From cdd80e5796980ee1f5089d0e9457d595f9103a20 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Thu, 27 Mar 2025 17:06:39 -0400 Subject: [PATCH] Add stats system with lifetime and session statistics, including line clear tracking --- app/src/main/java/com/mintris/MainActivity.kt | 55 +++++- .../main/java/com/mintris/StatsActivity.kt | 54 ++++++ .../java/com/mintris/model/StatsManager.kt | 175 ++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 96 +++++++++- app/src/main/res/layout/activity_stats.xml | 147 +++++++++++++++ app/src/main/res/values/strings.xml | 24 +++ 6 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/mintris/StatsActivity.kt create mode 100644 app/src/main/java/com/mintris/model/StatsManager.kt create mode 100644 app/src/main/res/layout/activity_stats.xml diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index d6eac7e..fd49615 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -21,6 +21,9 @@ import com.mintris.model.GameBoard import com.mintris.audio.GameMusic import com.mintris.model.HighScoreManager import android.content.Intent +import com.mintris.model.StatsManager +import java.text.SimpleDateFormat +import java.util.* class MainActivity : AppCompatActivity() { @@ -32,6 +35,7 @@ class MainActivity : AppCompatActivity() { private lateinit var gameMusic: GameMusic private lateinit var titleScreen: TitleScreen private lateinit var highScoreManager: HighScoreManager + private lateinit var statsManager: StatsManager // Game state private var isSoundEnabled = true @@ -40,6 +44,8 @@ class MainActivity : AppCompatActivity() { private val maxLevel = 20 private var currentScore = 0 private var currentLevel = 1 + private var gameStartTime: Long = 0 + private var piecesPlaced: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -53,6 +59,7 @@ class MainActivity : AppCompatActivity() { titleScreen = binding.titleScreen gameMusic = GameMusic(this) highScoreManager = HighScoreManager(this) + statsManager = StatsManager(this) // Set up game view gameView.setGameBoard(gameBoard) @@ -117,6 +124,8 @@ class MainActivity : AppCompatActivity() { } else { android.util.Log.d("MainActivity", "Sound is disabled, skipping haptic feedback") } + // Record line clear in stats + statsManager.recordLineClear(lineCount) } // Add callbacks for piece movement and locking @@ -130,6 +139,7 @@ class MainActivity : AppCompatActivity() { if (isSoundEnabled) { gameHaptics.vibrateForPieceLock() } + piecesPlaced++ } // Set up button click listeners with haptic feedback @@ -187,6 +197,13 @@ class MainActivity : AppCompatActivity() { } } + // Set up stats button + binding.statsButton.setOnClickListener { + gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY) + val intent = Intent(this, StatsActivity::class.java) + startActivity(intent) + } + // Initialize level selector updateLevelSelector() @@ -205,6 +222,9 @@ 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() } @@ -213,7 +233,35 @@ class MainActivity : AppCompatActivity() { * Show game over screen */ private fun showGameOver(score: Int) { - binding.finalScoreText.text = getString(R.string.score) + ": " + score + val gameTime = System.currentTimeMillis() - gameStartTime + + // Update session stats + statsManager.updateSessionStats( + score = score, + lines = gameBoard.lines, + pieces = piecesPlaced, + time = gameTime, + level = currentLevel + ) + + // End session and save stats + statsManager.endSession() + + // Update session stats display + val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + timeFormat.timeZone = TimeZone.getTimeZone("UTC") + + 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) + + // 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.sessionTetrisesText.text = getString(R.string.tetrises, statsManager.getSessionTetrises()) // Check if this is a high score if (highScoreManager.isHighScore(score)) { @@ -291,10 +339,13 @@ class MainActivity : AppCompatActivity() { private fun startGame() { gameView.start() - gameMusic.setEnabled(isMusicEnabled) // Explicitly set enabled state + gameMusic.setEnabled(isMusicEnabled) if (isMusicEnabled) { gameMusic.start() } + gameStartTime = System.currentTimeMillis() + piecesPlaced = 0 + statsManager.startNewSession() } private fun restartGame() { diff --git a/app/src/main/java/com/mintris/StatsActivity.kt b/app/src/main/java/com/mintris/StatsActivity.kt new file mode 100644 index 0000000..269adb0 --- /dev/null +++ b/app/src/main/java/com/mintris/StatsActivity.kt @@ -0,0 +1,54 @@ +package com.mintris + +import android.os.Bundle +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.mintris.databinding.ActivityStatsBinding +import com.mintris.model.StatsManager +import java.text.SimpleDateFormat +import java.util.* + +class StatsActivity : AppCompatActivity() { + private lateinit var binding: ActivityStatsBinding + private lateinit var statsManager: StatsManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityStatsBinding.inflate(layoutInflater) + setContentView(binding.root) + + statsManager = StatsManager(this) + + // Set up back button + binding.backButton.setOnClickListener { + finish() + } + + updateStats() + } + + private fun updateStats() { + // Format time duration + val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + timeFormat.timeZone = TimeZone.getTimeZone("UTC") + + // Update lifetime stats + binding.totalGamesText.text = getString(R.string.total_games, statsManager.getTotalGames()) + binding.totalScoreText.text = getString(R.string.total_score, statsManager.getTotalScore()) + binding.totalLinesText.text = getString(R.string.total_lines, statsManager.getTotalLines()) + binding.totalPiecesText.text = getString(R.string.total_pieces, statsManager.getTotalPieces()) + binding.totalTimeText.text = getString(R.string.total_time, timeFormat.format(statsManager.getTotalTime())) + + // Update line clear stats + binding.totalSinglesText.text = getString(R.string.singles, statsManager.getTotalSingles()) + binding.totalDoublesText.text = getString(R.string.doubles, statsManager.getTotalDoubles()) + binding.totalTriplesText.text = getString(R.string.triples, statsManager.getTotalTriples()) + binding.totalTetrisesText.text = getString(R.string.tetrises, statsManager.getTotalTetrises()) + + // Update best performance stats + binding.maxLevelText.text = getString(R.string.max_level, statsManager.getMaxLevel()) + binding.maxScoreText.text = getString(R.string.max_score, statsManager.getMaxScore()) + binding.maxLinesText.text = getString(R.string.max_lines, statsManager.getMaxLines()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mintris/model/StatsManager.kt b/app/src/main/java/com/mintris/model/StatsManager.kt new file mode 100644 index 0000000..3a3eae7 --- /dev/null +++ b/app/src/main/java/com/mintris/model/StatsManager.kt @@ -0,0 +1,175 @@ +package com.mintris.model + +import android.content.Context +import android.content.SharedPreferences + +class StatsManager(context: Context) { + private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // Lifetime stats + private var totalGames: Int = 0 + private var totalScore: Long = 0 + private var totalLines: Int = 0 + private var totalPieces: Int = 0 + private var totalTime: Long = 0 + private var maxLevel: Int = 0 + private var maxScore: Int = 0 + private var maxLines: Int = 0 + + // Line clear stats (lifetime) + private var totalSingles: Int = 0 + private var totalDoubles: Int = 0 + private var totalTriples: Int = 0 + private var totalTetrises: Int = 0 + + // Session stats + private var sessionScore: Int = 0 + private var sessionLines: Int = 0 + private var sessionPieces: Int = 0 + private var sessionTime: Long = 0 + private var sessionLevel: Int = 0 + + // Line clear stats (session) + private var sessionSingles: Int = 0 + private var sessionDoubles: Int = 0 + private var sessionTriples: Int = 0 + private var sessionTetrises: Int = 0 + + init { + loadStats() + } + + private fun loadStats() { + totalGames = prefs.getInt(KEY_TOTAL_GAMES, 0) + totalScore = prefs.getLong(KEY_TOTAL_SCORE, 0) + totalLines = prefs.getInt(KEY_TOTAL_LINES, 0) + totalPieces = prefs.getInt(KEY_TOTAL_PIECES, 0) + totalTime = prefs.getLong(KEY_TOTAL_TIME, 0) + maxLevel = prefs.getInt(KEY_MAX_LEVEL, 0) + maxScore = prefs.getInt(KEY_MAX_SCORE, 0) + maxLines = prefs.getInt(KEY_MAX_LINES, 0) + + // Load line clear stats + totalSingles = prefs.getInt(KEY_TOTAL_SINGLES, 0) + totalDoubles = prefs.getInt(KEY_TOTAL_DOUBLES, 0) + totalTriples = prefs.getInt(KEY_TOTAL_TRIPLES, 0) + totalTetrises = prefs.getInt(KEY_TOTAL_TETRISES, 0) + } + + private fun saveStats() { + prefs.edit() + .putInt(KEY_TOTAL_GAMES, totalGames) + .putLong(KEY_TOTAL_SCORE, totalScore) + .putInt(KEY_TOTAL_LINES, totalLines) + .putInt(KEY_TOTAL_PIECES, totalPieces) + .putLong(KEY_TOTAL_TIME, totalTime) + .putInt(KEY_MAX_LEVEL, maxLevel) + .putInt(KEY_MAX_SCORE, maxScore) + .putInt(KEY_MAX_LINES, maxLines) + .putInt(KEY_TOTAL_SINGLES, totalSingles) + .putInt(KEY_TOTAL_DOUBLES, totalDoubles) + .putInt(KEY_TOTAL_TRIPLES, totalTriples) + .putInt(KEY_TOTAL_TETRISES, totalTetrises) + .apply() + } + + fun startNewSession() { + sessionScore = 0 + sessionLines = 0 + sessionPieces = 0 + sessionTime = 0 + sessionLevel = 0 + sessionSingles = 0 + sessionDoubles = 0 + sessionTriples = 0 + sessionTetrises = 0 + } + + fun updateSessionStats(score: Int, lines: Int, pieces: Int, time: Long, level: Int) { + sessionScore = score + sessionLines = lines + sessionPieces = pieces + sessionTime = time + sessionLevel = level + } + + fun recordLineClear(lineCount: Int) { + when (lineCount) { + 1 -> { + sessionSingles++ + totalSingles++ + } + 2 -> { + sessionDoubles++ + totalDoubles++ + } + 3 -> { + sessionTriples++ + totalTriples++ + } + 4 -> { + sessionTetrises++ + totalTetrises++ + } + } + } + + fun endSession() { + totalGames++ + totalScore += sessionScore + totalLines += sessionLines + totalPieces += sessionPieces + totalTime += sessionTime + + if (sessionLevel > maxLevel) maxLevel = sessionLevel + if (sessionScore > maxScore) maxScore = sessionScore + if (sessionLines > maxLines) maxLines = sessionLines + + saveStats() + } + + // Getters for lifetime stats + fun getTotalGames(): Int = totalGames + fun getTotalScore(): Long = totalScore + fun getTotalLines(): Int = totalLines + fun getTotalPieces(): Int = totalPieces + fun getTotalTime(): Long = totalTime + fun getMaxLevel(): Int = maxLevel + fun getMaxScore(): Int = maxScore + fun getMaxLines(): Int = maxLines + + // Getters for line clear stats (lifetime) + fun getTotalSingles(): Int = totalSingles + fun getTotalDoubles(): Int = totalDoubles + fun getTotalTriples(): Int = totalTriples + fun getTotalTetrises(): Int = totalTetrises + + // Getters for session stats + fun getSessionScore(): Int = sessionScore + fun getSessionLines(): Int = sessionLines + fun getSessionPieces(): Int = sessionPieces + fun getSessionTime(): Long = sessionTime + fun getSessionLevel(): Int = sessionLevel + + // Getters for line clear stats (session) + fun getSessionSingles(): Int = sessionSingles + fun getSessionDoubles(): Int = sessionDoubles + fun getSessionTriples(): Int = sessionTriples + fun getSessionTetrises(): Int = sessionTetrises + + companion object { + private const val PREFS_NAME = "mintris_stats" + private const val KEY_TOTAL_GAMES = "total_games" + private const val KEY_TOTAL_SCORE = "total_score" + private const val KEY_TOTAL_LINES = "total_lines" + private const val KEY_TOTAL_PIECES = "total_pieces" + private const val KEY_TOTAL_TIME = "total_time" + private const val KEY_MAX_LEVEL = "max_level" + private const val KEY_MAX_SCORE = "max_score" + private const val KEY_MAX_LINES = "max_lines" + private const val KEY_TOTAL_SINGLES = "total_singles" + private const val KEY_TOTAL_DOUBLES = "total_doubles" + private const val KEY_TOTAL_TRIPLES = "total_triples" + private const val KEY_TOTAL_TETRISES = "total_tetrises" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7271d89..ab63a51 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -135,10 +135,92 @@ android:textStyle="bold" /> + + + + + + + + + + + + + + + + + + + + @@ -209,6 +291,16 @@ android:text="@string/high_scores" android:textColor="@color/white" android:textSize="18sp" /> + +