Added block skin selection feature and removed powerups

This commit is contained in:
cmclark00 2025-03-28 15:45:20 -04:00
parent 7614cef7e5
commit 5861644883
6 changed files with 486 additions and 79 deletions

View file

@ -19,6 +19,8 @@ import com.mintris.model.HighScoreManager
import com.mintris.model.PlayerProgressionManager
import com.mintris.model.StatsManager
import com.mintris.ui.ProgressionScreen
import com.mintris.ui.ThemeSelector
import com.mintris.ui.BlockSkinSelector
import java.text.SimpleDateFormat
import java.util.*
import android.graphics.Color
@ -45,6 +47,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var statsManager: StatsManager
private lateinit var progressionManager: PlayerProgressionManager
private lateinit var progressionScreen: ProgressionScreen
private lateinit var themeSelector: ThemeSelector
private lateinit var blockSkinSelector: BlockSkinSelector
// Game state
private var isSoundEnabled = true
@ -84,11 +88,16 @@ class MainActivity : AppCompatActivity() {
highScoreManager = HighScoreManager(this)
statsManager = StatsManager(this)
progressionManager = PlayerProgressionManager(this)
themeSelector = binding.themeSelector
blockSkinSelector = binding.blockSkinSelector
// Load and apply theme preference
currentTheme = loadThemePreference()
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)
@ -102,8 +111,7 @@ class MainActivity : AppCompatActivity() {
}
// Set up theme selector
val themeSelector = binding.themeSelector
themeSelector.onThemeSelected = { themeId ->
themeSelector.onThemeSelected = { themeId: String ->
// Apply the new theme
applyTheme(themeId)
@ -116,6 +124,18 @@ class MainActivity : AppCompatActivity() {
}
}
// Set up block skin selector
blockSkinSelector.onBlockSkinSelected = { skinId: String ->
// Apply the new block skin
gameView.setBlockSkin(skinId)
// Save the selection
progressionManager.setSelectedBlockSkin(skinId)
// Provide haptic feedback
gameHaptics.vibrateForPieceLock()
}
// Set up title screen
titleScreen.onStartGame = {
titleScreen.visibility = View.GONE
@ -426,6 +446,13 @@ class MainActivity : AppCompatActivity() {
// Update theme selector
updateThemeSelector()
// Update block skin selector
blockSkinSelector.updateBlockSkins(
progressionManager.getUnlockedBlocks(),
gameView.getCurrentBlockSkin(),
progressionManager.getPlayerLevel()
)
}
/**
@ -564,7 +591,7 @@ class MainActivity : AppCompatActivity() {
// Save the selected theme
currentTheme = themeId
saveThemePreference(themeId)
progressionManager.setSelectedTheme(themeId)
// Apply theme to title screen if it's visible
if (titleScreen.visibility == View.VISIBLE) {
@ -618,22 +645,6 @@ class MainActivity : AppCompatActivity() {
gameView.invalidate()
}
/**
* Save the selected theme in preferences
*/
private fun saveThemePreference(themeId: String) {
val prefs = getSharedPreferences("mintris_settings", Context.MODE_PRIVATE)
prefs.edit().putString("selected_theme", themeId).apply()
}
/**
* Load the saved theme preference
*/
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", Context.MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
}
/**
* Get the appropriate color for the current theme
*/

View file

@ -144,6 +144,10 @@ class GameView @JvmOverloads constructor(
private val isStrictDirectionLock = true // Enable strict direction locking to prevent diagonal inputs
private val minHardDropDistance = 1.5f // Minimum distance (in blocks) for hard drop gesture
// Block skin
private var currentBlockSkin: String = "block_skin_1"
private val blockSkinPaints = mutableMapOf<String, Paint>()
private enum class Direction {
HORIZONTAL, VERTICAL
}
@ -206,8 +210,64 @@ class GameView @JvmOverloads constructor(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setSystemGestureExclusionRects(listOf(Rect(0, 0, width, height)))
}
// Initialize block skin paints
initializeBlockSkinPaints()
}
/**
* Initialize paints for different block skins
*/
private fun initializeBlockSkinPaints() {
// Classic skin
blockSkinPaints["block_skin_1"] = Paint().apply {
color = Color.WHITE
isAntiAlias = true
}
// Neon skin
blockSkinPaints["block_skin_2"] = Paint().apply {
color = Color.parseColor("#FF00FF")
isAntiAlias = true
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
}
// Retro skin
blockSkinPaints["block_skin_3"] = Paint().apply {
color = Color.parseColor("#FF5A5F")
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = 2f
}
// Minimalist skin
blockSkinPaints["block_skin_4"] = Paint().apply {
color = Color.BLACK
isAntiAlias = true
style = Paint.Style.FILL
}
// Galaxy skin
blockSkinPaints["block_skin_5"] = Paint().apply {
color = Color.parseColor("#66FCF1")
isAntiAlias = true
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
}
}
/**
* Set the current block skin
*/
fun setBlockSkin(skinId: String) {
currentBlockSkin = skinId
invalidate()
}
/**
* Get the current block skin
*/
fun getCurrentBlockSkin(): String = currentBlockSkin
/**
* Start the game
*/
@ -519,16 +579,19 @@ class GameView @JvmOverloads constructor(
// Save canvas state before drawing block effects
canvas.save()
// Get the current block skin paint
val paint = blockSkinPaints[currentBlockSkin] ?: blockSkinPaints["block_skin_1"]!!
// 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.apply {
color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
// Draw block with current skin
paint.apply {
color = if (isGhost) Color.argb(30, 255, 255, 255) else paint.color
alpha = if (isGhost) 30 else 255
}
canvas.drawRect(left, top, right, bottom, blockPaint)
canvas.drawRect(left, top, right, bottom, paint)
// Draw inner glow
glowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE

View file

@ -20,7 +20,6 @@ class PlayerProgressionManager(context: Context) {
// Track unlocked rewards
private val unlockedThemes = mutableSetOf<String>()
private val unlockedBlocks = mutableSetOf<String>()
private val unlockedPowers = mutableSetOf<String>()
private val unlockedBadges = mutableSetOf<String>()
// XP gained in the current session
@ -41,18 +40,21 @@ class PlayerProgressionManager(context: Context) {
// 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)
}
// Add default block skin if nothing is unlocked
if (unlockedBlocks.isEmpty()) {
unlockedBlocks.add("block_skin_1")
}
}
/**
@ -65,7 +67,6 @@ class PlayerProgressionManager(context: Context) {
.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()
}
@ -179,30 +180,6 @@ class PlayerProgressionManager(context: Context) {
}
}
// 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}"
@ -214,11 +191,38 @@ class PlayerProgressionManager(context: Context) {
return newRewards
}
/**
* Check and unlock any rewards the player should have based on their current level
* This ensures players don't miss unlocks if they level up multiple times at once
*/
private fun checkAllUnlocksForCurrentLevel() {
// Check theme unlocks
if (playerLevel >= 5) unlockedThemes.add(THEME_NEON)
if (playerLevel >= 10) unlockedThemes.add(THEME_MONOCHROME)
if (playerLevel >= 15) unlockedThemes.add(THEME_RETRO)
if (playerLevel >= 20) unlockedThemes.add(THEME_MINIMALIST)
if (playerLevel >= 25) unlockedThemes.add(THEME_GALAXY)
// Check block skin unlocks
for (i in 1..5) {
val requiredLevel = i * 7
if (playerLevel >= requiredLevel) {
unlockedBlocks.add("block_skin_$i")
}
}
// Save any newly unlocked items
saveProgress()
}
/**
* Start a new progression session
*/
fun startNewSession() {
sessionXPGained = 0
// Ensure all appropriate unlocks are available
checkAllUnlocksForCurrentLevel()
}
// Getters
@ -227,7 +231,6 @@ class PlayerProgressionManager(context: Context) {
fun getXPForNextLevel(): Long = calculateXPForLevel(playerLevel)
fun getSessionXPGained(): Long = sessionXPGained
fun getUnlockedThemes(): Set<String> = unlockedThemes.toSet()
fun getUnlockedPowers(): Set<String> = unlockedPowers.toSet()
fun getUnlockedBlocks(): Set<String> = unlockedBlocks.toSet()
fun getUnlockedBadges(): Set<String> = unlockedBadges.toSet()
@ -238,13 +241,6 @@ class PlayerProgressionManager(context: Context) {
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
*/
@ -266,12 +262,14 @@ class PlayerProgressionManager(context: Context) {
unlockedThemes.clear()
unlockedBlocks.clear()
unlockedPowers.clear()
unlockedBadges.clear()
// Add default theme
unlockedThemes.add(THEME_CLASSIC)
// Add default block skin
unlockedBlocks.add("block_skin_1")
saveProgress()
}
@ -282,8 +280,9 @@ class PlayerProgressionManager(context: Context) {
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"
private const val KEY_SELECTED_BLOCK_SKIN = "selected_block_skin"
private const val KEY_SELECTED_THEME = "selected_theme"
// XP curve parameters
private const val BASE_XP = 4000.0 // Base XP for level 1 (reduced from 5000)
@ -313,20 +312,6 @@ class PlayerProgressionManager(context: Context) {
THEME_MINIMALIST to 20,
THEME_GALAXY to 25
)
// 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"
// Map of powers to required levels
val POWER_REQUIRED_LEVELS = mapOf(
POWER_FREEZE_TIME to 8,
POWER_BLOCK_SWAP to 12,
POWER_SAFE_LANDING to 18,
POWER_PERFECT_CLEAR to 30
)
}
/**
@ -337,9 +322,34 @@ class PlayerProgressionManager(context: Context) {
}
/**
* Get the required level for a specific power
* Set the selected block skin
*/
fun getRequiredLevelForPower(powerId: String): Int {
return POWER_REQUIRED_LEVELS[powerId] ?: 1
fun setSelectedBlockSkin(skinId: String) {
if (unlockedBlocks.contains(skinId)) {
prefs.edit().putString(KEY_SELECTED_BLOCK_SKIN, skinId).apply()
}
}
/**
* Get the selected block skin
*/
fun getSelectedBlockSkin(): String {
return prefs.getString(KEY_SELECTED_BLOCK_SKIN, "block_skin_1") ?: "block_skin_1"
}
/**
* Set the selected theme
*/
fun setSelectedTheme(themeId: String) {
if (unlockedThemes.contains(themeId)) {
prefs.edit().putString(KEY_SELECTED_THEME, themeId).apply()
}
}
/**
* Get the selected theme
*/
fun getSelectedTheme(): String {
return prefs.getString(KEY_SELECTED_THEME, THEME_CLASSIC) ?: THEME_CLASSIC
}
}

View file

@ -0,0 +1,288 @@
package com.mintris.ui
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.GridLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import com.mintris.R
import com.mintris.model.PlayerProgressionManager
/**
* UI component for selecting block skins
*/
class BlockSkinSelector @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val skinsGrid: GridLayout
private val availableSkinsLabel: TextView
// Callback when a block skin is selected
var onBlockSkinSelected: ((String) -> Unit)? = null
// Currently selected block skin
private var selectedSkin: String = "block_skin_1"
// Block skin cards
private val skinCards = mutableMapOf<String, CardView>()
// Player level for determining what should be unlocked
private var playerLevel: Int = 1
init {
// Inflate the layout
LayoutInflater.from(context).inflate(R.layout.block_skin_selector, this, true)
// Get references to views
skinsGrid = findViewById(R.id.skins_grid)
availableSkinsLabel = findViewById(R.id.available_skins_label)
}
/**
* Update the block skin selector with unlocked skins
*/
fun updateBlockSkins(unlockedSkins: Set<String>, currentSkin: String, playerLevel: Int = 1) {
// Store player level
this.playerLevel = playerLevel
// Clear existing skin cards
skinsGrid.removeAllViews()
skinCards.clear()
// Update selected skin
selectedSkin = currentSkin
// Get all possible skins and their details
val allSkins = getBlockSkins()
// Add skin cards to the grid
allSkins.forEach { (skinId, skinInfo) ->
val isUnlocked = unlockedSkins.contains(skinId) || playerLevel >= skinInfo.unlockLevel
val isSelected = skinId == selectedSkin
val skinCard = createBlockSkinCard(skinId, skinInfo, isUnlocked, isSelected)
skinCards[skinId] = skinCard
skinsGrid.addView(skinCard)
}
}
/**
* Create a card for a block skin
*/
private fun createBlockSkinCard(
skinId: String,
skinInfo: BlockSkinInfo,
isUnlocked: Boolean,
isSelected: Boolean
): CardView {
// Create the card
val card = CardView(context).apply {
id = View.generateViewId()
radius = 12f
cardElevation = if (isSelected) 8f else 2f
useCompatPadding = true
// Set card background color based on skin
setCardBackgroundColor(skinInfo.backgroundColor)
// Add more noticeable visual indicator for selected skin
if (isSelected) {
setContentPadding(4, 4, 4, 4)
// Create a gradient drawable for the border
val gradientDrawable = android.graphics.drawable.GradientDrawable().apply {
setColor(skinInfo.backgroundColor)
setStroke(6, Color.WHITE) // Thicker border
cornerRadius = 12f
}
background = gradientDrawable
// Add glow effect via elevation
cardElevation = 12f
}
// Set card dimensions
val cardSize = resources.getDimensionPixelSize(R.dimen.theme_card_size)
layoutParams = GridLayout.LayoutParams().apply {
width = cardSize
height = cardSize
columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
rowSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
setMargins(8, 8, 8, 8)
}
// Apply locked/selected state visuals
alpha = if (isUnlocked) 1.0f else 0.5f
}
// Create block skin content container
val container = FrameLayout(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
}
// Create the block skin preview
val blockSkinPreview = View(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
// Set the background color
setBackgroundColor(skinInfo.backgroundColor)
}
// Add a label with the skin name
val skinLabel = TextView(context).apply {
text = skinInfo.displayName
setTextColor(skinInfo.textColor)
textSize = 14f
textAlignment = View.TEXT_ALIGNMENT_CENTER
// Position at the bottom of the card
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = android.view.Gravity.BOTTOM or android.view.Gravity.CENTER_HORIZONTAL
setMargins(4, 4, 4, 8)
}
}
// Add level requirement for locked skins
val levelRequirement = TextView(context).apply {
text = "Level ${skinInfo.unlockLevel}"
setTextColor(Color.WHITE)
textSize = 12f
textAlignment = View.TEXT_ALIGNMENT_CENTER
visibility = if (isUnlocked) View.GONE else View.VISIBLE
// Position at the center of the card
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = android.view.Gravity.CENTER
}
// Make text bold and more visible for better readability
typeface = android.graphics.Typeface.DEFAULT_BOLD
setShadowLayer(3f, 1f, 1f, Color.BLACK)
}
// Add a lock icon if the skin is locked
val lockOverlay = View(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
// Add lock icon or visual indicator
setBackgroundResource(R.drawable.lock_overlay)
visibility = if (isUnlocked) View.GONE else View.VISIBLE
}
// Add all elements to container
container.addView(blockSkinPreview)
container.addView(skinLabel)
container.addView(lockOverlay)
container.addView(levelRequirement)
// Add container to card
card.addView(container)
// Set up click listener only for unlocked skins
if (isUnlocked) {
card.setOnClickListener {
// Only trigger callback if this isn't already the selected skin
if (skinId != selectedSkin) {
// Update previously selected card
skinCards[selectedSkin]?.let { prevCard ->
prevCard.cardElevation = 2f
// Reset any special styling
prevCard.background = null
prevCard.setCardBackgroundColor(getBlockSkins()[selectedSkin]?.backgroundColor ?: Color.BLACK)
}
// Update visual state of newly selected card
card.cardElevation = 12f
// Flash animation for selection feedback
val flashColor = Color.WHITE
val originalColor = skinInfo.backgroundColor
// Create animator for flash effect
val flashAnimator = android.animation.ValueAnimator.ofArgb(flashColor, originalColor)
flashAnimator.duration = 300 // 300ms
flashAnimator.addUpdateListener { animator ->
val color = animator.animatedValue as Int
card.setCardBackgroundColor(color)
}
flashAnimator.start()
// Update selected skin
selectedSkin = skinId
// Notify listener
onBlockSkinSelected?.invoke(skinId)
}
}
}
return card
}
/**
* Data class for block skin information
*/
data class BlockSkinInfo(
val displayName: String,
val backgroundColor: Int,
val textColor: Int,
val unlockLevel: Int
)
/**
* Get all available block skins with their details
*/
private fun getBlockSkins(): Map<String, BlockSkinInfo> {
return mapOf(
"block_skin_1" to BlockSkinInfo(
displayName = "Classic",
backgroundColor = Color.BLACK,
textColor = Color.WHITE,
unlockLevel = 1
),
"block_skin_2" to BlockSkinInfo(
displayName = "Neon",
backgroundColor = Color.parseColor("#0D0221"),
textColor = Color.parseColor("#FF00FF"),
unlockLevel = 7
),
"block_skin_3" to BlockSkinInfo(
displayName = "Retro",
backgroundColor = Color.parseColor("#3F2832"),
textColor = Color.parseColor("#FF5A5F"),
unlockLevel = 14
),
"block_skin_4" to BlockSkinInfo(
displayName = "Minimalist",
backgroundColor = Color.WHITE,
textColor = Color.BLACK,
unlockLevel = 21
),
"block_skin_5" to BlockSkinInfo(
displayName = "Galaxy",
backgroundColor = Color.parseColor("#0B0C10"),
textColor = Color.parseColor("#66FCF1"),
unlockLevel = 28
)
)
}
}