package com.mintris.model import android.content.Context import android.content.SharedPreferences import com.mintris.R import kotlin.math.pow import kotlin.math.roundToInt /** * Manages player progression, experience points, and unlockable rewards */ class PlayerProgressionManager(context: Context) { private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) // Player level and XP private var playerLevel: Int = 1 private var playerXP: Long = 0 private var totalXPEarned: Long = 0 // Track unlocked rewards private val unlockedThemes = mutableSetOf() private val unlockedBlocks = mutableSetOf() private val unlockedPowers = mutableSetOf() private val unlockedBadges = mutableSetOf() // XP gained in the current session private var sessionXPGained: Long = 0 init { loadProgress() } /** * Load player progression data from shared preferences */ private fun loadProgress() { playerLevel = prefs.getInt(KEY_PLAYER_LEVEL, 1) playerXP = prefs.getLong(KEY_PLAYER_XP, 0) totalXPEarned = prefs.getLong(KEY_TOTAL_XP_EARNED, 0) // Load unlocked rewards val themesSet = prefs.getStringSet(KEY_UNLOCKED_THEMES, setOf()) ?: setOf() val blocksSet = prefs.getStringSet(KEY_UNLOCKED_BLOCKS, setOf()) ?: setOf() val powersSet = prefs.getStringSet(KEY_UNLOCKED_POWERS, setOf()) ?: setOf() val badgesSet = prefs.getStringSet(KEY_UNLOCKED_BADGES, setOf()) ?: setOf() unlockedThemes.addAll(themesSet) unlockedBlocks.addAll(blocksSet) unlockedPowers.addAll(powersSet) unlockedBadges.addAll(badgesSet) // Add default theme if nothing is unlocked if (unlockedThemes.isEmpty()) { unlockedThemes.add(THEME_CLASSIC) } } /** * Save player progression data to shared preferences */ private fun saveProgress() { prefs.edit() .putInt(KEY_PLAYER_LEVEL, playerLevel) .putLong(KEY_PLAYER_XP, playerXP) .putLong(KEY_TOTAL_XP_EARNED, totalXPEarned) .putStringSet(KEY_UNLOCKED_THEMES, unlockedThemes) .putStringSet(KEY_UNLOCKED_BLOCKS, unlockedBlocks) .putStringSet(KEY_UNLOCKED_POWERS, unlockedPowers) .putStringSet(KEY_UNLOCKED_BADGES, unlockedBadges) .apply() } /** * Calculate XP required to reach a specific level */ fun calculateXPForLevel(level: Int): Long { return (BASE_XP * level.toDouble().pow(XP_CURVE_FACTOR)).roundToInt().toLong() } /** * Calculate total XP required to reach a certain level from level 1 */ fun calculateTotalXPForLevel(level: Int): Long { var totalXP = 0L for (lvl in 1 until level) { totalXP += calculateXPForLevel(lvl) } return totalXP } /** * 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 { // Base XP from score with level multiplier val scoreXP = (score * (1 + LEVEL_MULTIPLIER * level)).toLong() // XP from lines cleared val linesXP = lines * XP_PER_LINE // XP from special moves val tetrisBonus = tetrisCount * TETRIS_XP_BONUS val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS // Time bonus (to reward longer gameplay) val timeBonus = (gameTime / 60000) * TIME_XP_PER_MINUTE // XP per minute played // Calculate total XP return scoreXP + linesXP + tetrisBonus + perfectClearBonus + timeBonus } /** * Add XP to the player and handle level-ups * Returns a list of newly unlocked rewards */ fun addXP(xpAmount: Long): List { sessionXPGained = xpAmount playerXP += xpAmount totalXPEarned += xpAmount val newRewards = mutableListOf() val oldLevel = playerLevel // Check for level ups var xpForNextLevel = calculateXPForLevel(playerLevel) while (playerXP >= xpForNextLevel) { playerXP -= xpForNextLevel playerLevel++ // Check for new rewards at this level val levelRewards = checkLevelRewards(playerLevel) newRewards.addAll(levelRewards) // Calculate XP needed for the next level xpForNextLevel = calculateXPForLevel(playerLevel) } // Save progress if there were any changes if (oldLevel != playerLevel || newRewards.isNotEmpty()) { saveProgress() } return newRewards } /** * Check if the player unlocked new rewards at the current level */ private fun checkLevelRewards(level: Int): List { val newRewards = mutableListOf() // Check for theme unlocks when (level) { 5 -> { if (unlockedThemes.add(THEME_NEON)) { newRewards.add("Unlocked Neon Theme!") } } 10 -> { if (unlockedThemes.add(THEME_MONOCHROME)) { newRewards.add("Unlocked Monochrome Theme!") } } 15 -> { if (unlockedThemes.add(THEME_RETRO)) { newRewards.add("Unlocked Retro Arcade Theme!") } } 20 -> { if (unlockedThemes.add(THEME_MINIMALIST)) { newRewards.add("Unlocked Minimalist Theme!") } } 25 -> { if (unlockedThemes.add(THEME_GALAXY)) { newRewards.add("Unlocked Galaxy Theme!") } } } // Check for power unlocks when (level) { 8 -> { if (unlockedPowers.add(POWER_FREEZE_TIME)) { newRewards.add("Unlocked Freeze Time Power!") } } 12 -> { if (unlockedPowers.add(POWER_BLOCK_SWAP)) { newRewards.add("Unlocked Block Swap Power!") } } 18 -> { if (unlockedPowers.add(POWER_SAFE_LANDING)) { newRewards.add("Unlocked Safe Landing Power!") } } 30 -> { if (unlockedPowers.add(POWER_PERFECT_CLEAR)) { newRewards.add("Unlocked Perfect Clear Power!") } } } // Check for block skin unlocks if (level % 7 == 0 && level <= 35) { val blockSkin = "block_skin_${level / 7}" if (unlockedBlocks.add(blockSkin)) { newRewards.add("Unlocked New Block Skin!") } } return newRewards } /** * Start a new progression session */ fun startNewSession() { sessionXPGained = 0 } // Getters fun getPlayerLevel(): Int = playerLevel fun getCurrentXP(): Long = playerXP fun getXPForNextLevel(): Long = calculateXPForLevel(playerLevel) fun getSessionXPGained(): Long = sessionXPGained fun getUnlockedThemes(): Set = unlockedThemes.toSet() fun getUnlockedPowers(): Set = unlockedPowers.toSet() fun getUnlockedBlocks(): Set = unlockedBlocks.toSet() fun getUnlockedBadges(): Set = unlockedBadges.toSet() /** * Check if a specific theme is unlocked */ fun isThemeUnlocked(themeId: String): Boolean { return unlockedThemes.contains(themeId) } /** * Check if a specific power is unlocked */ fun isPowerUnlocked(powerId: String): Boolean { return unlockedPowers.contains(powerId) } /** * Award a badge to the player */ fun awardBadge(badgeId: String): Boolean { val newlyAwarded = unlockedBadges.add(badgeId) if (newlyAwarded) { saveProgress() } return newlyAwarded } /** * Reset all player progression data */ fun resetProgress() { playerLevel = 1 playerXP = 0 totalXPEarned = 0 unlockedThemes.clear() unlockedBlocks.clear() unlockedPowers.clear() unlockedBadges.clear() // Add default theme unlockedThemes.add(THEME_CLASSIC) saveProgress() } companion object { private const val PREFS_NAME = "mintris_progression" private const val KEY_PLAYER_LEVEL = "player_level" private const val KEY_PLAYER_XP = "player_xp" private const val KEY_TOTAL_XP_EARNED = "total_xp_earned" private const val KEY_UNLOCKED_THEMES = "unlocked_themes" private const val KEY_UNLOCKED_BLOCKS = "unlocked_blocks" private const val KEY_UNLOCKED_POWERS = "unlocked_powers" private const val KEY_UNLOCKED_BADGES = "unlocked_badges" // XP curve parameters private const val BASE_XP = 1000.0 // Base XP for level 1 private const val XP_CURVE_FACTOR = 1.5 // Exponential factor for XP curve // XP calculation constants private const val LEVEL_MULTIPLIER = 0.1 // 10% bonus per level private const val XP_PER_LINE = 10L private const val TETRIS_XP_BONUS = 50L private const val PERFECT_CLEAR_XP_BONUS = 200L private const val TIME_XP_PER_MINUTE = 5L // Theme IDs const val THEME_CLASSIC = "theme_classic" const val THEME_NEON = "theme_neon" const val THEME_MONOCHROME = "theme_monochrome" const val THEME_RETRO = "theme_retro" const val THEME_MINIMALIST = "theme_minimalist" const val THEME_GALAXY = "theme_galaxy" // Power IDs const val POWER_FREEZE_TIME = "power_freeze_time" const val POWER_BLOCK_SWAP = "power_block_swap" const val POWER_SAFE_LANDING = "power_safe_landing" const val POWER_PERFECT_CLEAR = "power_perfect_clear" } }