Compare commits

..

11 commits

Author SHA1 Message Date
cmclark00
103a21d9b7 fix: update text styles and sizes across app to match mintris theme 2025-03-29 02:21:15 -04:00
cmclark00
68e8cb160f fix: theme colors and UI improvements 2025-03-29 01:59:14 -04:00
cmclark00
42b9bcfab4 fix: progression screen theme colors and XP bar styling 2025-03-29 01:58:53 -04:00
cmclark00
1980f15a46 Adjust XP progression and improve animations: - Increased XP requirements and curve for slower leveling - Enhanced XP gain animations with smoother transitions - Improved visual feedback for level progression 2025-03-29 00:04:44 -04:00
cmclark00
c6a4339931 Fix: Update theme loading to use PlayerProgressionManager consistently across all activities 2025-03-28 20:47:37 -04:00
cmclark00
83935d35a8 Fix: Replace minOf with min in PlayerProgressionManager 2025-03-28 20:43:40 -04:00
cmclark00
d0700202b7 Update game mechanics and haptic feedback implementation 2025-03-28 20:41:03 -04:00
cmclark00
af0082a6db refactor: modernize codebase - Use KTX extension functions for system services - Update performClick handling in touch events - Modernize back gesture handling with KTX - Improve vibrator service initialization 2025-03-28 20:21:25 -04:00
cmclark00
ebff618fa4 fix: balance progression system - Increased base XP requirements and curve - Added diminishing returns for higher levels - Reduced XP rewards for special moves - Capped level multiplier at level 10 - Added time-based XP reduction 2025-03-28 20:19:17 -04:00
cmclark00
7cdc9988cb fix: enhance block skins to match their respective themes - Classic: Clean white blocks with subtle glow - Neon: Strong glow effects with magenta colors - Retro: Pixelated look with highlights and shadows - Minimalist: Clean black blocks with subtle borders - Galaxy: Cosmic effects with gradient and sparkles 2025-03-28 20:17:44 -04:00
cmclark00
2774703df5 fix: persist block skin selection through app restarts 2025-03-28 19:36:14 -04:00
14 changed files with 525 additions and 310 deletions

View file

@ -66,8 +66,7 @@ class HighScoreEntryActivity : AppCompatActivity() {
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
return progressionManager.getSelectedTheme()
}
private fun applyTheme(themeId: String) {

View file

@ -58,8 +58,7 @@ class HighScoresActivity : AppCompatActivity() {
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
return progressionManager.getSelectedTheme()
}
private fun applyTheme(themeId: String) {

View file

@ -91,17 +91,6 @@ class MainActivity : AppCompatActivity() {
themeSelector = binding.themeSelector
blockSkinSelector = binding.blockSkinSelector
// Load and apply theme preference
currentTheme = progressionManager.getSelectedTheme()
applyTheme(currentTheme)
// Load and apply block skin preference
gameView.setBlockSkin(progressionManager.getSelectedBlockSkin())
// Set up game view
gameView.setGameBoard(gameBoard)
gameView.setHaptics(gameHaptics)
// Set up progression screen
progressionScreen = binding.progressionScreen
progressionScreen.visibility = View.GONE
@ -110,6 +99,24 @@ class MainActivity : AppCompatActivity() {
binding.gameOverContainer.visibility = View.VISIBLE
}
// Load and apply theme preference
currentTheme = progressionManager.getSelectedTheme()
applyTheme(currentTheme)
// Load and apply block skin preference
gameView.setBlockSkin(progressionManager.getSelectedBlockSkin())
// Update block skin selector with current selection
blockSkinSelector.updateBlockSkins(
progressionManager.getUnlockedBlocks(),
gameView.getCurrentBlockSkin(),
progressionManager.getPlayerLevel()
)
// Set up game view
gameView.setGameBoard(gameBoard)
gameView.setHaptics(gameHaptics)
// Set up theme selector
themeSelector.onThemeSelected = { themeId: String ->
// Apply the new theme
@ -351,10 +358,7 @@ class MainActivity : AppCompatActivity() {
var showingHighScore = false
// Show progression screen first with XP animation
binding.gameOverContainer.visibility = View.GONE
progressionScreen.visibility = View.VISIBLE
progressionScreen.applyTheme(currentTheme)
progressionScreen.showProgress(progressionManager, xpGained, newRewards, currentTheme)
showProgressionScreen(xpGained, newRewards)
// Override the continue button behavior if high score needs to be shown
val originalOnContinue = progressionScreen.onContinue
@ -586,63 +590,55 @@ class MainActivity : AppCompatActivity() {
* Apply a theme to the game
*/
private fun applyTheme(themeId: String) {
// Only apply if the theme is unlocked
if (!progressionManager.isThemeUnlocked(themeId)) return
// Save the selected theme
currentTheme = themeId
progressionManager.setSelectedTheme(themeId)
// Apply theme to title screen if it's visible
if (titleScreen.visibility == View.VISIBLE) {
titleScreen.applyTheme(themeId)
val themeColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.WHITE
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#FF00FF")
PlayerProgressionManager.THEME_MONOCHROME -> Color.LTGRAY
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#FF5A5F")
PlayerProgressionManager.THEME_MINIMALIST -> Color.BLACK
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
// Apply theme colors based on theme ID
when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> {
// Default black theme
binding.root.setBackgroundColor(Color.BLACK)
}
PlayerProgressionManager.THEME_NEON -> {
// Neon theme with dark purple background
binding.root.setBackgroundColor(Color.parseColor("#0D0221"))
}
PlayerProgressionManager.THEME_MONOCHROME -> {
// Monochrome dark gray
binding.root.setBackgroundColor(Color.parseColor("#1A1A1A"))
}
PlayerProgressionManager.THEME_RETRO -> {
// Retro arcade theme
binding.root.setBackgroundColor(Color.parseColor("#3F2832"))
}
PlayerProgressionManager.THEME_MINIMALIST -> {
// Minimalist white theme
binding.root.setBackgroundColor(Color.WHITE)
// Update text colors for visibility
binding.scoreText.setTextColor(Color.BLACK)
binding.currentLevelText.setTextColor(Color.BLACK)
binding.linesText.setTextColor(Color.BLACK)
binding.comboText.setTextColor(Color.BLACK)
}
PlayerProgressionManager.THEME_GALAXY -> {
// Galaxy dark blue theme
binding.root.setBackgroundColor(Color.parseColor("#0B0C10"))
}
// Get background color for the theme
val backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
// Apply theme to progression screen if it's visible and initialized
if (::progressionScreen.isInitialized && progressionScreen.visibility == View.VISIBLE) {
// Apply background color to root view
binding.root.setBackgroundColor(backgroundColor)
// Apply theme color to title screen
titleScreen.setThemeColor(themeColor)
titleScreen.setBackgroundColor(backgroundColor)
// Apply theme color to game over screen
binding.gameOverContainer.setBackgroundColor(backgroundColor)
binding.gameOverText.setTextColor(themeColor)
binding.scoreText.setTextColor(themeColor)
binding.currentLevelText.setTextColor(themeColor)
binding.linesText.setTextColor(themeColor)
binding.comboText.setTextColor(themeColor)
binding.playAgainButton.setTextColor(themeColor)
binding.playAgainButton.setBackgroundResource(android.R.color.transparent)
binding.playAgainButton.setTextSize(24f)
// Apply theme to progression screen (it will handle its own colors)
progressionScreen.applyTheme(themeId)
}
// Apply theme color to the stats button
val textColor = getThemeColor(currentTheme)
binding.statsButton.setTextColor(textColor)
// Apply theme color to game view
gameView.setThemeColor(themeColor)
gameView.setBackgroundColor(backgroundColor)
// Update the game view to apply theme
gameView.invalidate()
// Save theme preference
progressionManager.setSelectedTheme(themeId)
}
/**
@ -738,4 +734,16 @@ class MainActivity : AppCompatActivity() {
}
return super.onKeyDown(keyCode, event)
}
private fun showProgressionScreen(xpGained: Long, newRewards: List<String>) {
// Apply theme before showing the screen
progressionScreen.applyTheme(currentTheme)
// Show the progression screen
binding.gameOverContainer.visibility = View.GONE
progressionScreen.visibility = View.VISIBLE
// Display progression data
progressionScreen.showProgress(progressionManager, xpGained, newRewards, currentTheme)
}
}

View file

@ -44,8 +44,7 @@ class StatsActivity : AppCompatActivity() {
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
return progressionManager.getSelectedTheme()
}
private fun applyTheme(themeId: String) {

View file

@ -2,12 +2,14 @@ package com.mintris.game
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.BlurMaskFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.BlurMaskFilter
import android.graphics.Shader
import android.os.Build
import android.os.Handler
import android.os.Looper
@ -151,6 +153,7 @@ class GameView @JvmOverloads constructor(
// Block skin
private var currentBlockSkin: String = "block_skin_1"
private val blockSkinPaints = mutableMapOf<String, Paint>()
private var currentThemeColor = Color.WHITE
private enum class Direction {
HORIZONTAL, VERTICAL
@ -173,6 +176,10 @@ class GameView @JvmOverloads constructor(
// Start with paused state
pause()
// Load saved block skin
val prefs = context.getSharedPreferences("mintris_progression", Context.MODE_PRIVATE)
currentBlockSkin = prefs.getString("selected_block_skin", "block_skin_1") ?: "block_skin_1"
// Connect our callbacks to the GameBoard
gameBoard.onPieceMove = { onPieceMove?.invoke() }
gameBoard.onPieceLock = {
@ -232,6 +239,7 @@ class GameView @JvmOverloads constructor(
blockSkinPaints["block_skin_1"] = Paint().apply {
color = Color.WHITE
isAntiAlias = true
style = Paint.Style.FILL
}
// Neon skin
@ -244,9 +252,8 @@ class GameView @JvmOverloads constructor(
// Retro skin
blockSkinPaints["block_skin_3"] = Paint().apply {
color = Color.parseColor("#FF5A5F")
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = 2f
isAntiAlias = false // Pixelated look
style = Paint.Style.FILL
}
// Minimalist skin
@ -269,6 +276,9 @@ class GameView @JvmOverloads constructor(
*/
fun setBlockSkin(skinId: String) {
currentBlockSkin = skinId
// Save the selection to SharedPreferences
val prefs = context.getSharedPreferences("mintris_progression", Context.MODE_PRIVATE)
prefs.edit().putString("selected_block_skin", skinId).commit()
invalidate()
}
@ -594,8 +604,23 @@ class GameView @JvmOverloads constructor(
// Create a clone of the paint to avoid modifying the original
val blockPaint = Paint(paint)
// Special handling for neon skin
if (currentBlockSkin == "block_skin_2") {
// Draw block based on current skin
when (currentBlockSkin) {
"block_skin_1" -> { // Classic
// Draw outer glow
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
canvas.drawRect(left - 2f, top - 2f, right + 2f, bottom + 2f, blockGlowPaint)
// Draw block
blockPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
blockPaint.alpha = if (isGhost) 30 else 255
canvas.drawRect(left, top, right, bottom, blockPaint)
// Draw inner glow
glowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
canvas.drawRect(left + 1f, top + 1f, right - 1f, bottom - 1f, glowPaint)
}
"block_skin_2" -> { // Neon
// Stronger outer glow for neon skin
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 0, 255) else Color.parseColor("#FF00FF")
blockGlowPaint.maskFilter = BlurMaskFilter(16f, BlurMaskFilter.Blur.OUTER)
@ -628,27 +653,90 @@ class GameView @JvmOverloads constructor(
}
canvas.drawRect(left, top, right, bottom, borderPaint)
// Inner glow for neon blocks - brighter than before
// Inner glow for neon blocks
glowPaint.color = if (isGhost) Color.argb(10, 255, 0, 255) else Color.parseColor("#FF00FF")
glowPaint.alpha = if (isGhost) 10 else 100 // More visible inner glow
glowPaint.alpha = if (isGhost) 10 else 100
glowPaint.style = Paint.Style.STROKE
glowPaint.strokeWidth = 2f
glowPaint.maskFilter = BlurMaskFilter(4f, BlurMaskFilter.Blur.NORMAL)
canvas.drawRect(left + 4f, top + 4f, right - 4f, bottom - 4f, glowPaint)
} else {
// Standard rendering for other skins
// Draw outer glow
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
canvas.drawRect(left - 2f, top - 2f, right + 2f, bottom + 2f, blockGlowPaint)
// Draw block with current skin
blockPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else blockPaint.color
}
"block_skin_3" -> { // Retro
// Draw pixelated block with retro effect
blockPaint.color = if (isGhost) Color.argb(30, 255, 90, 95) else Color.parseColor("#FF5A5F")
blockPaint.alpha = if (isGhost) 30 else 255
// Draw main block
canvas.drawRect(left, top, right, bottom, blockPaint)
// Draw inner glow
glowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
canvas.drawRect(left + 1f, top + 1f, right - 1f, bottom - 1f, glowPaint)
// Draw pixelated highlights
val highlightPaint = Paint().apply {
color = Color.parseColor("#FF8A8F")
isAntiAlias = false
style = Paint.Style.FILL
}
// Top and left highlights
canvas.drawRect(left, top, right - 2f, top + 2f, highlightPaint)
canvas.drawRect(left, top, left + 2f, bottom - 2f, highlightPaint)
// Draw pixelated shadows
val shadowPaint = Paint().apply {
color = Color.parseColor("#CC4A4F")
isAntiAlias = false
style = Paint.Style.FILL
}
// Bottom and right shadows
canvas.drawRect(left + 2f, bottom - 2f, right, bottom, shadowPaint)
canvas.drawRect(right - 2f, top + 2f, right, bottom - 2f, shadowPaint)
}
"block_skin_4" -> { // Minimalist
// Draw clean, simple block with subtle border
blockPaint.color = if (isGhost) Color.argb(30, 0, 0, 0) else Color.BLACK
blockPaint.alpha = if (isGhost) 30 else 255
blockPaint.style = Paint.Style.FILL
canvas.drawRect(left, top, right, bottom, blockPaint)
// Draw subtle border
val borderPaint = Paint().apply {
color = Color.parseColor("#333333")
style = Paint.Style.STROKE
strokeWidth = 1f
isAntiAlias = true
}
canvas.drawRect(left, top, right, bottom, borderPaint)
}
"block_skin_5" -> { // Galaxy
// Draw cosmic glow effect
blockGlowPaint.color = if (isGhost) Color.argb(30, 102, 252, 241) else Color.parseColor("#66FCF1")
blockGlowPaint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.OUTER)
canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, blockGlowPaint)
// Draw main block with gradient
val gradient = LinearGradient(
left, top, right, bottom,
Color.parseColor("#66FCF1"),
Color.parseColor("#45B7AF"),
Shader.TileMode.CLAMP
)
blockPaint.shader = gradient
blockPaint.color = if (isGhost) Color.argb(30, 102, 252, 241) else Color.parseColor("#66FCF1")
blockPaint.alpha = if (isGhost) 30 else 255
blockPaint.style = Paint.Style.FILL
canvas.drawRect(left, top, right, bottom, blockPaint)
// Draw star-like sparkles
if (!isGhost) {
val sparklePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.FILL
isAntiAlias = true
maskFilter = BlurMaskFilter(4f, BlurMaskFilter.Blur.NORMAL)
}
// Add small white dots for sparkle effect
canvas.drawCircle(left + 4f, top + 4f, 1f, sparklePaint)
canvas.drawCircle(right - 4f, bottom - 4f, 1f, sparklePaint)
}
}
}
// Draw pulse effect if animation is active and this is a pulsing line
@ -963,4 +1051,26 @@ class GameView @JvmOverloads constructor(
}
pulseAnimator?.start()
}
/**
* Set the theme color for the game view
*/
fun setThemeColor(color: Int) {
currentThemeColor = color
blockPaint.color = color
ghostBlockPaint.color = color
glowPaint.color = color
blockGlowPaint.color = color
borderGlowPaint.color = color
pulsePaint.color = color
invalidate()
}
/**
* Set the background color for the game view
*/
override fun setBackgroundColor(color: Int) {
super.setBackgroundColor(color)
invalidate()
}
}

View file

@ -46,8 +46,9 @@ class TitleScreen @JvmOverloads constructor(
// Callback for when the user touches the screen
var onStartGame: (() -> Unit)? = null
// Theme color
// Theme color and background color
private var themeColor = Color.WHITE
private var backgroundColor = Color.BLACK
// Define tetromino shapes (I, O, T, S, Z, J, L)
private val tetrominoShapes = arrayOf(
@ -110,7 +111,7 @@ class TitleScreen @JvmOverloads constructor(
init {
// Title text settings
titlePaint.apply {
color = Color.WHITE
color = themeColor
textSize = 120f
textAlign = Paint.Align.CENTER
typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
@ -119,7 +120,7 @@ class TitleScreen @JvmOverloads constructor(
// "Touch to start" text settings
promptPaint.apply {
color = Color.WHITE
color = themeColor
textSize = 50f
textAlign = Paint.Align.CENTER
typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL)
@ -129,7 +130,7 @@ class TitleScreen @JvmOverloads constructor(
// High scores text settings
highScorePaint.apply {
color = Color.WHITE
color = themeColor
textSize = 70f
textAlign = Paint.Align.LEFT // Changed to LEFT alignment
typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL) // Changed to monospace
@ -137,16 +138,16 @@ class TitleScreen @JvmOverloads constructor(
alpha = 200
}
// General paint settings for tetrominos (white)
// General paint settings for tetrominos
paint.apply {
color = Color.WHITE
color = themeColor
style = Paint.Style.FILL
isAntiAlias = true
}
// Glow paint settings for tetrominos
glowPaint.apply {
color = Color.WHITE
color = themeColor
style = Paint.Style.FILL
isAntiAlias = true
alpha = 60
@ -184,8 +185,8 @@ class TitleScreen @JvmOverloads constructor(
try {
super.onDraw(canvas)
// Draw background
canvas.drawColor(Color.BLACK)
// Draw background using the current background color
canvas.drawColor(backgroundColor)
// Add any pending tetrominos
tetrominos.addAll(tetrominosToAdd)
@ -340,7 +341,7 @@ class TitleScreen @JvmOverloads constructor(
glowPaint.color = themeColor
// Update background color
setBackgroundColor(when (themeId) {
backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
@ -348,8 +349,29 @@ class TitleScreen @JvmOverloads constructor(
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
})
}
invalidate()
}
/**
* Set the theme color for the title screen
*/
fun setThemeColor(color: Int) {
themeColor = color
titlePaint.color = color
promptPaint.color = color
highScorePaint.color = color
paint.color = color
glowPaint.color = color
invalidate()
}
/**
* Set the background color for the title screen
*/
override fun setBackgroundColor(color: Int) {
backgroundColor = color
invalidate()
}
}

View file

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import com.mintris.R
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.min
/**
* Manages player progression, experience points, and unlockable rewards
@ -94,21 +95,22 @@ class PlayerProgressionManager(context: Context) {
*/
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()
// 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
val linesXP = lines * XP_PER_LINE
// XP from lines cleared (reduced for higher levels)
val linesXP = lines * XP_PER_LINE * (1 - (level - 1) * 0.05).coerceAtLeast(0.5)
// XP from special moves
val tetrisBonus = tetrisCount * TETRIS_XP_BONUS
val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS
// 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)
// Time bonus (to reward longer gameplay)
val timeBonus = (gameTime / 60000) * TIME_XP_PER_MINUTE // XP per minute played
// 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
return (scoreXP + linesXP + tetrisBonus + perfectClearBonus + timeBonus).toLong()
}
/**
@ -281,21 +283,19 @@ class PlayerProgressionManager(context: Context) {
private const val KEY_UNLOCKED_THEMES = "unlocked_themes"
private const val KEY_UNLOCKED_BLOCKS = "unlocked_blocks"
private const val KEY_UNLOCKED_BADGES = "unlocked_badges"
private const val KEY_SELECTED_BLOCK_SKIN = "selected_block_skin"
private const val KEY_SELECTED_THEME = "selected_theme"
private const val KEY_SELECTED_BLOCK_SKIN = "selected_block_skin"
// XP curve parameters
private const val BASE_XP = 4000.0 // Base XP for level 1 (reduced from 5000)
private const val XP_CURVE_FACTOR = 1.9 // Exponential factor for XP curve (reduced from 2.2)
// XP constants
private const val BASE_XP = 3000L
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 TIME_XP_PER_MINUTE = 20L
// XP calculation constants
private const val LEVEL_MULTIPLIER = 0.15 // 15% bonus per level (increased from 10%)
private const val XP_PER_LINE = 15L // Increased from 10
private const val TETRIS_XP_BONUS = 75L // Increased from 50
private const val PERFECT_CLEAR_XP_BONUS = 250L // Increased from 200
private const val TIME_XP_PER_MINUTE = 8L // Increased from 5
// Theme IDs with required levels
// Theme constants
const val THEME_CLASSIC = "theme_classic"
const val THEME_NEON = "theme_neon"
const val THEME_MONOCHROME = "theme_monochrome"
@ -326,7 +326,7 @@ class PlayerProgressionManager(context: Context) {
*/
fun setSelectedBlockSkin(skinId: String) {
if (unlockedBlocks.contains(skinId)) {
prefs.edit().putString(KEY_SELECTED_BLOCK_SKIN, skinId).apply()
prefs.edit().putString(KEY_SELECTED_BLOCK_SKIN, skinId).commit()
}
}

View file

@ -31,6 +31,9 @@ class ProgressionScreen @JvmOverloads constructor(
private val rewardsContainer: LinearLayout
private val continueButton: TextView
// Current theme
private var currentTheme: String = PlayerProgressionManager.THEME_CLASSIC
// Callback for when the player dismisses the screen
var onContinue: (() -> Unit)? = null
@ -62,6 +65,9 @@ class ProgressionScreen @JvmOverloads constructor(
newRewards: List<String>,
themeId: String = PlayerProgressionManager.THEME_CLASSIC
) {
// Update current theme
currentTheme = themeId
// Hide rewards container initially if there are no new rewards
rewardsContainer.visibility = if (newRewards.isEmpty()) View.GONE else View.INVISIBLE
@ -74,20 +80,39 @@ class ProgressionScreen @JvmOverloads constructor(
playerLevelText.text = "Player Level: $playerLevel"
xpGainText.text = "+$xpGained XP"
// Begin animation sequence
xpProgressBar.setXPValues(playerLevel, currentXP, xpForNextLevel)
// Update level up text visibility
val progressionTitle = findViewById<TextView>(R.id.progression_title)
progressionTitle.visibility = if (newRewards.any { it.contains("Level") }) View.VISIBLE else View.GONE
// Animate XP gain text entrance
// Start with initial animations
AnimatorSet().apply {
// Fade in the XP gain text
val xpTextAnimator = ObjectAnimator.ofFloat(xpGainText, "alpha", 0f, 1f).apply {
duration = 500
duration = 800
interpolator = AccelerateDecelerateInterpolator()
}
// Schedule animation for the XP bar after text appears
// Set up the XP progress bar animation sequence
val xpBarAnimator = ObjectAnimator.ofFloat(xpProgressBar, "alpha", 0f, 1f).apply {
duration = 800
interpolator = AccelerateDecelerateInterpolator()
}
// Play animations in sequence
play(xpTextAnimator)
play(xpBarAnimator).after(xpTextAnimator)
start()
}
// Set initial progress bar state
xpProgressBar.setXPValues(playerLevel, currentXP - xpGained, xpForNextLevel)
// Animate the XP gain after a short delay
postDelayed({
xpProgressBar.animateXPGain(xpGained, playerLevel, currentXP, xpForNextLevel)
}, 600)
}, 1000) // Increased delay to 1 second for better visual flow
// If there are new rewards, show them with animation
// If there are new rewards, show them with animation after XP bar animation
if (newRewards.isNotEmpty()) {
// Create reward cards
rewardsContainer.removeAllViews()
@ -113,18 +138,12 @@ class ProgressionScreen @JvmOverloads constructor(
card.animate()
.alpha(1f)
.translationY(0f)
.setDuration(400)
.setStartDelay((i * 150).toLong())
.setDuration(600) // Increased duration for smoother animation
.setStartDelay((i * 200).toLong()) // Increased delay between cards
.setInterpolator(OvershootInterpolator())
.start()
}
}, 2000) // Wait for XP bar animation to finish
}
// Start with initial animations
AnimatorSet().apply {
play(xpTextAnimator)
start()
}, 2500) // Increased delay to wait for XP bar animation to finish
}
}
@ -137,8 +156,17 @@ class ProgressionScreen @JvmOverloads constructor(
cardElevation = 4f
useCompatPadding = true
// Default background color - will be adjusted based on theme
setCardBackgroundColor(Color.BLACK)
// Set background color based on current theme
val backgroundColor = when (currentTheme) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
setCardBackgroundColor(backgroundColor)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
@ -167,6 +195,8 @@ class ProgressionScreen @JvmOverloads constructor(
* Apply the current theme to the progression screen
*/
fun applyTheme(themeId: String) {
currentTheme = themeId
// Get reference to the title text
val progressionTitle = findViewById<TextView>(R.id.progression_title)
val rewardsTitle = findViewById<TextView>(R.id.rewards_title)
@ -248,10 +278,10 @@ class ProgressionScreen @JvmOverloads constructor(
}
}
// Set theme color on XP progress bar
// Update XP progress bar theme color
xpProgressBar.setThemeColor(xpThemeColor)
// Update card colors for any existing reward cards
// Update reward card colors
updateRewardCardColors(themeId)
}
@ -259,8 +289,7 @@ class ProgressionScreen @JvmOverloads constructor(
* Update colors of existing reward cards to match the theme
*/
private fun updateRewardCardColors(themeId: String) {
// Color for card backgrounds based on theme
val cardBackgroundColor = when (themeId) {
val backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
@ -270,28 +299,9 @@ class ProgressionScreen @JvmOverloads constructor(
else -> Color.BLACK
}
// Text color for rewards based on theme
val rewardTextColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.WHITE
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#FF00FF")
PlayerProgressionManager.THEME_MONOCHROME -> Color.LTGRAY
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#FF5A5F")
PlayerProgressionManager.THEME_MINIMALIST -> Color.BLACK
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
// Update each card in the rewards container
for (i in 0 until rewardsContainer.childCount) {
val card = rewardsContainer.getChildAt(i) as? CardView
card?.let {
it.setCardBackgroundColor(cardBackgroundColor)
// Update text color in the card
if (it.childCount > 0 && it.getChildAt(0) is TextView) {
(it.getChildAt(0) as TextView).setTextColor(rewardTextColor)
}
}
card?.setCardBackgroundColor(backgroundColor)
}
}
}

View file

@ -99,6 +99,8 @@ class XPProgressBar @JvmOverloads constructor(
*/
fun setThemeColor(color: Int) {
themeColor = color
progressPaint.color = color
textPaint.color = color
levelBadgePaint.color = color
invalidate()
}

View file

@ -62,36 +62,40 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="24sp"
android:fontFamily="sans-serif-light"
tools:text="Score: 0" />
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
tools:text="score: 0" />
<TextView
android:id="@+id/currentLevelText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="24sp"
android:fontFamily="sans-serif-light"
tools:text="Level: 1" />
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
tools:text="level: 1" />
<TextView
android:id="@+id/linesText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="24sp"
android:fontFamily="sans-serif-light"
tools:text="Lines: 0" />
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
tools:text="lines: 0" />
<TextView
android:id="@+id/comboText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="24sp"
android:fontFamily="sans-serif-light"
tools:text="Combo: 0" />
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
tools:text="combo: 0" />
</LinearLayout>
<!-- Next Piece Preview -->
@ -131,16 +135,18 @@
android:layout_height="wrap_content"
android:text="@string/game_over"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold" />
android:textSize="36sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/session_stats"
android:textColor="@color/white"
android:textSize="20sp"
android:textSize="28sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:layout_marginTop="24dp" />
<TextView
@ -149,7 +155,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionLinesText"
@ -157,7 +165,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionPiecesText"
@ -165,7 +175,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionTimeText"
@ -173,7 +185,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionLevelText"
@ -181,15 +195,18 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/line_clears"
android:textColor="@color/white"
android:textSize="20sp"
android:textSize="28sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:layout_marginTop="16dp" />
<TextView
@ -198,7 +215,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionDoublesText"
@ -206,7 +225,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionTriplesText"
@ -214,7 +235,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/sessionTetrisesText"
@ -222,7 +245,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<Button
android:id="@+id/playAgainButton"
@ -231,7 +256,10 @@
android:layout_marginTop="32dp"
android:background="@color/transparent"
android:text="@string/play"
android:textColor="@color/white" />
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
</LinearLayout>
<!-- Player Progression Screen -->
@ -270,7 +298,9 @@
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginEnd="16dp" />
android:fontFamily="sans-serif"
android:layout_marginEnd="16dp"
android:textAllCaps="false" />
<com.mintris.ui.LevelBadge
android:id="@+id/pauseLevelBadge"
@ -299,7 +329,10 @@
android:background="@color/transparent"
android:text="@string/start"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<Button
android:id="@+id/resumeButton"
@ -309,7 +342,10 @@
android:background="@color/transparent"
android:text="@string/resume"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<Button
android:id="@+id/pauseRestartButton"
@ -319,7 +355,10 @@
android:background="@color/transparent"
android:text="@string/restart"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<Button
android:id="@+id/highScoresButton"
@ -329,7 +368,10 @@
android:background="@color/transparent"
android:text="@string/high_scores"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<Button
android:id="@+id/statsButton"
@ -339,7 +381,10 @@
android:background="@color/transparent"
android:text="@string/stats"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<LinearLayout
android:id="@+id/levelSelectorContainer"
@ -355,8 +400,10 @@
android:layout_height="wrap_content"
android:text="@string/select_level"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="wrap_content"
@ -371,7 +418,9 @@
android:background="@color/transparent"
android:text=""
android:textColor="@color/white"
android:textSize="24sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView
android:id="@+id/pauseLevelText"
@ -381,7 +430,8 @@
android:text="1"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold" />
android:textStyle="bold"
android:fontFamily="sans-serif" />
<Button
android:id="@+id/pauseLevelUpButton"
@ -390,7 +440,9 @@
android:background="@color/transparent"
android:text="+"
android:textColor="@color/white"
android:textSize="24sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
</LinearLayout>
</LinearLayout>
@ -418,7 +470,10 @@
android:background="@color/transparent"
android:text="@string/sound_on"
android:textColor="@color/white"
android:textSize="18sp" />
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false" />
<LinearLayout
android:layout_width="wrap_content"
@ -433,7 +488,10 @@
android:layout_height="wrap_content"
android:text="@string/music"
android:textColor="@color/white"
android:textSize="18sp"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textAllCaps="false"
android:layout_marginEnd="16dp" />
<ImageButton

View file

@ -9,10 +9,11 @@
android:id="@+id/available_skins_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BLOCK SKINS"
android:text="block skins"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:gravity="center"
android:layout_marginBottom="16dp" />

View file

@ -3,16 +3,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="@color/black">
android:padding="16dp">
<TextView
android:id="@+id/progression_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="LEVEL UP"
android:textSize="24sp"
android:text="level up"
android:textSize="48sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textColor="@color/white"
android:gravity="center"
android:layout_marginTop="16dp"
@ -22,8 +22,10 @@
android:id="@+id/player_level_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Player Level: 1"
android:textSize="20sp"
android:text="player level: 1"
android:textSize="36sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textColor="@color/white"
android:gravity="center"
android:layout_marginBottom="24dp" />
@ -38,10 +40,11 @@
android:id="@+id/xp_gain_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+0 XP"
android:textSize="22sp"
android:textColor="#50C878"
android:text="+0 xp"
android:textSize="42sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textColor="#50C878"
android:gravity="center"
android:layout_marginTop="8dp"
android:layout_marginBottom="24dp" />
@ -50,9 +53,10 @@
android:id="@+id/rewards_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="REWARDS UNLOCKED"
android:textSize="20sp"
android:text="rewards unlocked"
android:textSize="36sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:textColor="#FFD700"
android:gravity="center"
android:layout_marginTop="8dp"
@ -76,8 +80,10 @@
android:id="@+id/continue_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CONTINUE"
android:textSize="18sp"
android:text="continue"
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:layout_gravity="center"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"

View file

@ -9,10 +9,11 @@
android:id="@+id/available_themes_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="AVAILABLE THEMES"
android:text="available themes"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
android:gravity="center"
android:layout_marginBottom="16dp" />

View file

@ -1,49 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mintris</string>
<string name="game_over">Game Over</string>
<string name="score">Score</string>
<string name="level">Level</string>
<string name="lines">Lines</string>
<string name="next">Next</string>
<string name="play">Play Again</string>
<string name="resume">Resume</string>
<string name="pause">PAUSE</string>
<string name="settings">Settings</string>
<string name="start">Start Game</string>
<string name="restart">Restart</string>
<string name="select_level">Select Level</string>
<string name="sound_on">Sound: On</string>
<string name="sound_off">Sound: Off</string>
<string name="toggle_music">Toggle music</string>
<string name="high_scores">High Scores</string>
<string name="new_high_score">New High Score!</string>
<string name="save">Save</string>
<string name="back">Back</string>
<string name="app_name">mintris</string>
<string name="game_over">game over</string>
<string name="score">score</string>
<string name="level">level</string>
<string name="lines">lines</string>
<string name="next">next</string>
<string name="play">play again</string>
<string name="resume">resume</string>
<string name="pause">pause</string>
<string name="settings">settings</string>
<string name="start">start game</string>
<string name="restart">restart</string>
<string name="select_level">select level</string>
<string name="sound_on">sound: on</string>
<string name="sound_off">sound: off</string>
<string name="toggle_music">toggle music</string>
<string name="high_scores">high scores</string>
<string name="new_high_score">new high score!</string>
<string name="save">save</string>
<string name="back">back</string>
<!-- Stats Screen -->
<string name="lifetime_stats">Lifetime Stats</string>
<string name="best_performance">Best Performance</string>
<string name="total_games">Total Games: %d</string>
<string name="total_score">Total Score: %d</string>
<string name="total_lines">Total Lines: %d</string>
<string name="total_pieces">Total Pieces: %d</string>
<string name="total_time">Total Time: %s</string>
<string name="max_level">Max Level: %d</string>
<string name="max_score">Max Score: %d</string>
<string name="max_lines">Max Lines: %d</string>
<string name="stats">Stats</string>
<string name="session_stats">Session Stats</string>
<string name="session_score">Score: %d</string>
<string name="session_lines">Lines: %d</string>
<string name="session_pieces">Pieces: %d</string>
<string name="session_time">Time: %s</string>
<string name="session_level">Level: %d</string>
<string name="line_clears">Line Clears</string>
<string name="singles">Singles: %d</string>
<string name="doubles">Doubles: %d</string>
<string name="triples">Triples: %d</string>
<string name="tetrises">Tetrises: %d</string>
<string name="reset_stats">Reset Stats</string>
<string name="music">Music</string>
<string name="lifetime_stats">lifetime stats</string>
<string name="best_performance">best performance</string>
<string name="total_games">total games: %d</string>
<string name="total_score">total score: %d</string>
<string name="total_lines">total lines: %d</string>
<string name="total_pieces">total pieces: %d</string>
<string name="total_time">total time: %s</string>
<string name="max_level">max level: %d</string>
<string name="max_score">max score: %d</string>
<string name="max_lines">max lines: %d</string>
<string name="stats">stats</string>
<string name="session_stats">session stats</string>
<string name="session_score">score: %d</string>
<string name="session_lines">lines: %d</string>
<string name="session_pieces">pieces: %d</string>
<string name="session_time">time: %s</string>
<string name="session_level">level: %d</string>
<string name="line_clears">line clears</string>
<string name="singles">singles: %d</string>
<string name="doubles">doubles: %d</string>
<string name="triples">triples: %d</string>
<string name="tetrises">tetrises: %d</string>
<string name="reset_stats">reset stats</string>
<string name="music">music</string>
</resources>