diff --git a/README.md b/README.md index 1f7bd6e..d9af2c4 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ A modern Tetris implementation for Android, featuring smooth animations, respons The game features a comprehensive scoring system: #### Base Scores -- Single line: 40 points -- Double: 100 points -- Triple: 300 points -- Tetris (4 lines): 1200 points +- Single line: 100 points +- Double: 300 points +- Triple: 500 points +- Quad (4 lines): 1200 points #### Multipliers @@ -48,15 +48,15 @@ The game features a comprehensive scoring system: - 4 combos: 2.5x - 5+ combos: 3.0x -3. **Back-to-Back Tetris** - - 50% bonus (1.5x) for consecutive Tetris clears - - Resets if a non-Tetris clear is performed +3. **Back-to-Back Quad** + - 50% bonus (1.5x) for consecutive Quad clears + - Resets if a non-Quad clear is performed 4. **Perfect Clear** - 2x for single line - 3x for double - 4x for triple - - 5x for Tetris + - 5x for Quad - Awarded when clearing lines without leaving blocks 5. **All Clear** diff --git a/app/src/main/java/com/pixelmintdrop/MainActivity.kt b/app/src/main/java/com/pixelmintdrop/MainActivity.kt index 5e9841b..f5e39e8 100644 --- a/app/src/main/java/com/pixelmintdrop/MainActivity.kt +++ b/app/src/main/java/com/pixelmintdrop/MainActivity.kt @@ -214,6 +214,7 @@ class MainActivity : AppCompatActivity(), // Set up game view gameView.setGameBoard(gameBoard) gameView.setHaptics(gameHaptics) + gameBoard.setStatsManager(statsManager) // Set up theme selector themeSelector.onThemeSelected = { themeId: String -> @@ -301,7 +302,16 @@ class MainActivity : AppCompatActivity(), statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel) // Handle progression - XP earned, potential level up - val xpGained = progressionManager.calculateGameXP(finalScore, gameBoard.lines, currentLevel, timePlayedMs, statsManager.getSessionTetrises(), 0) + val xpGained = progressionManager.calculateGameXP( + score = finalScore, + linesCleared = gameBoard.lines, + level = currentLevel, + gameTime = timePlayedMs, + tSpins = statsManager.getSessionTSpins(), + combos = statsManager.getSessionMaxCombo(), + quadCount = statsManager.getSessionQuads(), + perfectClearCount = statsManager.getSessionPerfectClears() + ) val newRewards = progressionManager.addXP(xpGained) // Show progression screen if player earned XP @@ -468,12 +478,14 @@ class MainActivity : AppCompatActivity(), // Calculate XP earned val xpGained = progressionManager.calculateGameXP( - score = score, - lines = gameBoard.lines, + score = statsManager.getSessionScore(), + linesCleared = statsManager.getSessionLines(), level = currentLevel, gameTime = gameTime, - tetrisCount = statsManager.getSessionTetrises(), - perfectClearCount = 0 // Implement perfect clear tracking if needed + tSpins = statsManager.getSessionTSpins(), + combos = statsManager.getSessionMaxCombo(), + quadCount = statsManager.getSessionQuads(), + perfectClearCount = statsManager.getSessionPerfectClears() ) // Add XP and check for rewards @@ -496,7 +508,7 @@ class MainActivity : AppCompatActivity(), 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()) + binding.sessionQuadsText.text = getString(R.string.quads, statsManager.getSessionQuads()) // Flag to track if high score screen will be shown var showingHighScore = false @@ -911,7 +923,16 @@ class MainActivity : AppCompatActivity(), statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel) // Handle progression - XP earned, potential level up - val xpGained = progressionManager.calculateGameXP(finalScore, gameBoard.lines, currentLevel, timePlayedMs, statsManager.getSessionTetrises(), 0) + val xpGained = progressionManager.calculateGameXP( + score = finalScore, + linesCleared = gameBoard.lines, + level = currentLevel, + gameTime = timePlayedMs, + tSpins = statsManager.getSessionTSpins(), + combos = statsManager.getSessionMaxCombo(), + quadCount = statsManager.getSessionQuads(), + perfectClearCount = statsManager.getSessionPerfectClears() + ) val newRewards = progressionManager.addXP(xpGained) // Show progression screen if player earned XP diff --git a/app/src/main/java/com/pixelmintdrop/StatsActivity.kt b/app/src/main/java/com/pixelmintdrop/StatsActivity.kt index 09b9b9c..1652365 100644 --- a/app/src/main/java/com/pixelmintdrop/StatsActivity.kt +++ b/app/src/main/java/com/pixelmintdrop/StatsActivity.kt @@ -78,7 +78,7 @@ class StatsActivity : AppCompatActivity() { binding.totalSinglesText.setTextColor(textColor) binding.totalDoublesText.setTextColor(textColor) binding.totalTriplesText.setTextColor(textColor) - binding.totalTetrisesText.setTextColor(textColor) + binding.totalQuadsText.setTextColor(textColor) binding.maxLevelText.setTextColor(textColor) binding.maxScoreText.setTextColor(textColor) binding.maxLinesText.setTextColor(textColor) @@ -116,7 +116,7 @@ class StatsActivity : AppCompatActivity() { 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()) + binding.totalQuadsText.text = getString(R.string.quads, statsManager.getTotalQuads()) // Update best performance stats binding.maxLevelText.text = getString(R.string.max_level, statsManager.getMaxLevel()) diff --git a/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt b/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt index 9f55369..7629065 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt @@ -53,7 +53,7 @@ class GameHaptics(private val context: Context) { 1 -> (50L * multiplier).toLong() // Single line: short vibration 2 -> (80L * multiplier).toLong() // Double line: slightly longer 3 -> (120L * multiplier).toLong() // Triple line: even longer - 4 -> (200L * multiplier).toLong() // Tetris: longest vibration + 4 -> (200L * multiplier).toLong() // Quad: longest vibration else -> (50L * multiplier).toLong() } @@ -61,7 +61,7 @@ class GameHaptics(private val context: Context) { 1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration 2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration 3 -> (180 * multiplier).toInt().coerceAtMost(255) // Triple line: strong vibration - 4 -> 255 // Tetris: maximum vibration + 4 -> 255 // Quad: maximum vibration else -> (80 * multiplier).toInt().coerceAtMost(255) } diff --git a/app/src/main/java/com/pixelmintdrop/game/GameView.kt b/app/src/main/java/com/pixelmintdrop/game/GameView.kt index e7145d5..305e2f8 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GameView.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GameView.kt @@ -1262,11 +1262,11 @@ class GameView @JvmOverloads constructor( // Create new animation pulseAnimator = ValueAnimator.ofFloat(0f, 1f, 0f).apply { duration = when (lineCount) { - 4 -> 2000L // Tetris - longer duration - 3 -> 1600L // Triples - 2 -> 1200L // Doubles - 1 -> 1000L // Singles - else -> 1000L + 1 -> 500L // Single + 2 -> 1000L // Double + 3 -> 1500L // Triple + 4 -> 2000L // Quad - longer duration + else -> 500L } interpolator = LinearInterpolator() addUpdateListener { animation -> diff --git a/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt b/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt index 3c15a92..83718b2 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt @@ -256,7 +256,7 @@ class GamepadController( 1 -> 100 2 -> 150 3 -> 200 - else -> 255 // For tetris (4 lines) + else -> 255 // For quad (4 lines) } vibrateGamepad(RUMBLE_LINE_CLEAR_DURATION_MS, amplitude) } diff --git a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt index bf20b4d..bca6233 100644 --- a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt +++ b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt @@ -1,6 +1,7 @@ package com.pixelmintdrop.model import android.util.Log +import java.util.* /** * Represents the game board (grid) and manages game state @@ -43,7 +44,7 @@ class GameBoard( // Scoring state private var combo = 0 - private var lastClearWasTetris = false + private var lastClearWasQuad = false private var lastClearWasPerfect = false private var lastClearWasAllClear = false private var lastPieceClearedLines = false // Track if the last piece placed cleared lines @@ -69,6 +70,15 @@ class GameBoard( private var pieceSpawnTime = 0L private val spawnGracePeriod = 250L // Changed from 150ms to 250ms + private var lastClearWasTSpin = false + private var lastClearWasMiniTSpin = false + private var currentCombo = 0 + + private var rng: Random = Random(System.currentTimeMillis()) + + // Added for StatsManager + private var statsManager: StatsManager? = null + init { spawnNextPiece() spawnPiece() @@ -541,8 +551,8 @@ class GameBoard( } } else 1.0 - // Calculate back-to-back Tetris bonus - val backToBackMultiplier = if (clearedLines == 4 && lastClearWasTetris) 1.5 else 1.0 + // Calculate back-to-back Quad bonus + val backToBackMultiplier = if ((clearedLines == 4 && lastClearWasQuad) || (isTSpin() && lastClearWasTSpin)) 1.5 else 1.0 // Calculate perfect clear bonus val perfectClearMultiplier = if (isPerfectClear) { @@ -579,7 +589,7 @@ class GameBoard( }.start() // Update line clear state - lastClearWasTetris = clearedLines == 4 + lastClearWasQuad = clearedLines == 4 lastClearWasPerfect = isPerfectClear lastClearWasAllClear = isAllClear @@ -702,7 +712,7 @@ class GameBoard( // Reset scoring state combo = 0 - lastClearWasTetris = false + lastClearWasQuad = false lastClearWasPerfect = false lastClearWasAllClear = false lastPieceClearedLines = false @@ -762,4 +772,11 @@ class GameBoard( /** * Update the game level */ + + /** + * Set the StatsManager instance for recording stats + */ + fun setStatsManager(manager: StatsManager) { + statsManager = manager + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelmintdrop/model/PlayerProgressionManager.kt b/app/src/main/java/com/pixelmintdrop/model/PlayerProgressionManager.kt index 3e0a568..457afa0 100644 --- a/app/src/main/java/com/pixelmintdrop/model/PlayerProgressionManager.kt +++ b/app/src/main/java/com/pixelmintdrop/model/PlayerProgressionManager.kt @@ -93,24 +93,26 @@ class PlayerProgressionManager(context: Context) { /** * Calculate XP from a game session based on score, lines, level, etc. */ - fun calculateGameXP(score: Int, lines: Int, level: Int, gameTime: Long, - tetrisCount: Int, perfectClearCount: Int): Long { + fun calculateGameXP(score: Int, linesCleared: Int, level: Int, gameTime: Long, + tSpins: Int, combos: Int, quadCount: Int, perfectClearCount: Int): Long { // Base XP from score with level multiplier (capped at level 10) val cappedLevel = min(level, 10) val scoreXP = (score * (1 + LEVEL_MULTIPLIER * cappedLevel)).toLong() // XP from lines cleared (reduced for higher levels) - val linesXP = lines * XP_PER_LINE * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) + val linesXP = linesCleared * XP_PER_LINE * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) // XP from special moves (reduced for higher levels) - val tetrisBonus = tetrisCount * TETRIS_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) - val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) + val tSpinXP = tSpins * T_SPIN_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) + val combosXP = combos * COMBO_XP_BONUS + val quadBonus = quadCount * QUAD_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5) + val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS // Time bonus (reduced for longer games) val timeBonus = (gameTime / 60000) * TIME_XP_PER_MINUTE * (1 - (gameTime / 3600000) * 0.1).coerceAtLeast(0.5) // Calculate total XP - return (scoreXP + linesXP + tetrisBonus + perfectClearBonus + timeBonus).toLong() + return (scoreXP + linesXP + tSpinXP + combosXP + quadBonus + perfectClearBonus + timeBonus).toLong() } /** @@ -306,8 +308,10 @@ class PlayerProgressionManager(context: Context) { private const val XP_CURVE_FACTOR = 2.0 private const val LEVEL_MULTIPLIER = 0.03 private const val XP_PER_LINE = 40L - private const val TETRIS_XP_BONUS = 150L - private const val PERFECT_CLEAR_XP_BONUS = 300L + private const val COMBO_XP_BONUS = 5L + private const val PERFECT_CLEAR_XP_BONUS = 200L + private const val T_SPIN_XP_BONUS = 80L + private const val QUAD_XP_BONUS = 150L private const val TIME_XP_PER_MINUTE = 20L // Theme constants diff --git a/app/src/main/java/com/pixelmintdrop/model/StatsManager.kt b/app/src/main/java/com/pixelmintdrop/model/StatsManager.kt index 89de6a0..5da4e4e 100644 --- a/app/src/main/java/com/pixelmintdrop/model/StatsManager.kt +++ b/app/src/main/java/com/pixelmintdrop/model/StatsManager.kt @@ -16,11 +16,15 @@ class StatsManager(context: Context) { private var maxScore: Int = 0 private var maxLines: Int = 0 + // Special move stats (lifetime) + private var totalTSpins: Int = 0 + private var totalPerfectClears: 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 + private var totalQuads: Int = 0 // Session stats private var sessionScore: Int = 0 @@ -29,11 +33,16 @@ class StatsManager(context: Context) { private var sessionTime: Long = 0 private var sessionLevel: Int = 0 + // Special move stats (session) + private var sessionTSpins: Int = 0 + private var sessionPerfectClears: Int = 0 + private var sessionMaxCombo: 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 + private var sessionQuads: Int = 0 init { loadStats() @@ -49,11 +58,15 @@ class StatsManager(context: Context) { maxScore = prefs.getInt(KEY_MAX_SCORE, 0) maxLines = prefs.getInt(KEY_MAX_LINES, 0) + // Load special move stats + totalTSpins = prefs.getInt(KEY_TOTAL_T_SPINS, 0) + totalPerfectClears = prefs.getInt(KEY_TOTAL_PERFECT_CLEARS, 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) + totalQuads = prefs.getInt(KEY_TOTAL_QUADS, 0) } private fun saveStats() { @@ -67,9 +80,11 @@ class StatsManager(context: Context) { .putInt(KEY_MAX_SCORE, maxScore) .putInt(KEY_MAX_LINES, maxLines) .putInt(KEY_TOTAL_SINGLES, totalSingles) + .putInt(KEY_TOTAL_T_SPINS, totalTSpins) + .putInt(KEY_TOTAL_PERFECT_CLEARS, totalPerfectClears) .putInt(KEY_TOTAL_DOUBLES, totalDoubles) .putInt(KEY_TOTAL_TRIPLES, totalTriples) - .putInt(KEY_TOTAL_TETRISES, totalTetrises) + .putInt(KEY_TOTAL_QUADS, totalQuads) .apply() } @@ -82,7 +97,10 @@ class StatsManager(context: Context) { sessionSingles = 0 sessionDoubles = 0 sessionTriples = 0 - sessionTetrises = 0 + sessionQuads = 0 + sessionTSpins = 0 + sessionPerfectClears = 0 + sessionMaxCombo = 0 } fun updateSessionStats(score: Int, lines: Int, pieces: Int, time: Long, level: Int) { @@ -108,8 +126,8 @@ class StatsManager(context: Context) { totalTriples++ } 4 -> { - sessionTetrises++ - totalTetrises++ + sessionQuads++ + totalQuads++ } } } @@ -142,7 +160,11 @@ class StatsManager(context: Context) { fun getTotalSingles(): Int = totalSingles fun getTotalDoubles(): Int = totalDoubles fun getTotalTriples(): Int = totalTriples - fun getTotalTetrises(): Int = totalTetrises + fun getTotalQuads(): Int = totalQuads + + // Getters for special move stats (lifetime) + fun getTotalTSpins(): Int = totalTSpins + fun getTotalPerfectClears(): Int = totalPerfectClears // Getters for session stats fun getSessionScore(): Int = sessionScore @@ -150,12 +172,17 @@ class StatsManager(context: Context) { fun getSessionPieces(): Int = sessionPieces fun getSessionTime(): Long = sessionTime fun getSessionLevel(): Int = sessionLevel + fun getSessionTriples(): Int = sessionTriples + fun getSessionQuads(): Int = sessionQuads - // Getters for line clear stats (session) + // Add missing getters for session line clears fun getSessionSingles(): Int = sessionSingles fun getSessionDoubles(): Int = sessionDoubles - fun getSessionTriples(): Int = sessionTriples - fun getSessionTetrises(): Int = sessionTetrises + + // Getters for special move stats (session) + fun getSessionTSpins(): Int = sessionTSpins + fun getSessionPerfectClears(): Int = sessionPerfectClears + fun getSessionMaxCombo(): Int = sessionMaxCombo fun resetStats() { // Reset all lifetime stats @@ -172,7 +199,11 @@ class StatsManager(context: Context) { totalSingles = 0 totalDoubles = 0 totalTriples = 0 - totalTetrises = 0 + totalQuads = 0 + + // Reset special move stats + totalTSpins = 0 + totalPerfectClears = 0 // Save the reset stats saveStats() @@ -189,8 +220,10 @@ class StatsManager(context: Context) { 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_T_SPINS = "total_t_spins" + private const val KEY_TOTAL_PERFECT_CLEARS = "total_perfect_clears" private const val KEY_TOTAL_DOUBLES = "total_doubles" private const val KEY_TOTAL_TRIPLES = "total_triples" - private const val KEY_TOTAL_TETRISES = "total_tetrises" + private const val KEY_TOTAL_QUADS = "total_quads" } } \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 582437e..eb8a729 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -592,7 +592,7 @@ android:fontFamily="sans-serif" /> + android:fontFamily="sans-serif" + android:text="T-Spins: 0" />