Fix menu shifting issue and code cleanup. Fix menu shifting by adding fillViewport and padding to ScrollView. Added missing getLastClearedLines method. Improved code quality with proper logging constants and removed unused imports.

This commit is contained in:
cmclark00 2025-03-28 12:33:42 -04:00
parent 8661fd8a80
commit 9ab9b53407
5 changed files with 119 additions and 73 deletions

View file

@ -1,26 +1,18 @@
package com.mintris
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import android.view.HapticFeedbackConstants
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.mintris.databinding.ActivityMainBinding
import com.mintris.game.GameHaptics
import com.mintris.game.GameView
import com.mintris.game.NextPieceView
import com.mintris.game.TitleScreen
import android.view.HapticFeedbackConstants
import com.mintris.model.GameBoard
import com.mintris.audio.GameMusic
import com.mintris.model.HighScoreManager
@ -33,10 +25,15 @@ import android.graphics.Color
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import android.graphics.Rect
import android.util.Log
import android.view.KeyEvent
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
// UI components
private lateinit var binding: ActivityMainBinding
private lateinit var gameView: GameView
@ -165,18 +162,18 @@ class MainActivity : AppCompatActivity() {
}
gameView.onLineClear = { lineCount ->
android.util.Log.d("MainActivity", "Received line clear callback: $lineCount lines")
Log.d(TAG, "Received line clear callback: $lineCount lines")
// Use enhanced haptic feedback for line clears
if (isSoundEnabled) {
android.util.Log.d("MainActivity", "Sound is enabled, triggering haptic feedback")
Log.d(TAG, "Sound is enabled, triggering haptic feedback")
try {
gameHaptics.vibrateForLineClear(lineCount)
android.util.Log.d("MainActivity", "Haptic feedback triggered successfully")
Log.d(TAG, "Haptic feedback triggered successfully")
} catch (e: Exception) {
android.util.Log.e("MainActivity", "Error triggering haptic feedback", e)
Log.e(TAG, "Error triggering haptic feedback", e)
}
} else {
android.util.Log.d("MainActivity", "Sound is disabled, skipping haptic feedback")
Log.d(TAG, "Sound is disabled, skipping haptic feedback")
}
// Record line clear in stats
statsManager.recordLineClear(lineCount)

View file

@ -7,15 +7,66 @@ import android.os.Vibrator
import android.os.VibratorManager
import android.view.HapticFeedbackConstants
import android.view.View
import android.util.Log
/**
* Handles haptic feedback for game events
*/
class GameHaptics(private val context: Context) {
private val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
companion object {
private const val TAG = "GameHaptics"
}
// Vibrator service
private val vibrator: Vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
// Vibrate for line clear (more intense for more lines)
fun vibrateForLineClear(lineCount: Int) {
Log.d(TAG, "Attempting to vibrate for $lineCount lines")
// Only proceed if the device has a vibrator and it's available
if (!vibrator.hasVibrator()) return
// Scale duration and amplitude based on line count
// More lines = longer and stronger vibration
val duration = when(lineCount) {
1 -> 50L // Single line: short vibration
2 -> 80L // Double line: slightly longer
3 -> 120L // Triple line: even longer
4 -> 200L // Tetris: longest vibration
else -> 50L
}
val amplitude = when(lineCount) {
1 -> 80 // Single line: mild vibration (80/255)
2 -> 120 // Double line: medium vibration (120/255)
3 -> 180 // Triple line: strong vibration (180/255)
4 -> 255 // Tetris: maximum vibration (255/255)
else -> 80
}
Log.d(TAG, "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude")
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(duration, amplitude))
Log.d(TAG, "Vibration triggered successfully")
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(duration)
Log.w(TAG, "Device does not support vibration effects (Android < 8.0)")
}
} catch (e: Exception) {
Log.e(TAG, "Error triggering vibration", e)
}
}
fun performHapticFeedback(view: View, feedbackType: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -26,40 +77,6 @@ class GameHaptics(private val context: Context) {
}
}
fun vibrateForLineClear(lineCount: Int) {
android.util.Log.d("GameHaptics", "Attempting to vibrate for $lineCount lines")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val duration = when (lineCount) {
4 -> 200L // Tetris - doubled from 100L
3 -> 160L // Triples - doubled from 80L
2 -> 120L // Doubles - doubled from 60L
1 -> 80L // Singles - doubled from 40L
else -> 0L
}
val amplitude = when (lineCount) {
4 -> 255 // Full amplitude for Tetris
3 -> 230 // 90% amplitude for triples
2 -> 180 // 70% amplitude for doubles
1 -> 128 // 50% amplitude for singles
else -> 0
}
android.util.Log.d("GameHaptics", "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude")
if (duration > 0 && amplitude > 0) {
try {
val vibrationEffect = VibrationEffect.createOneShot(duration, amplitude)
vibrator.vibrate(vibrationEffect)
android.util.Log.d("GameHaptics", "Vibration triggered successfully")
} catch (e: Exception) {
android.util.Log.e("GameHaptics", "Error triggering vibration", e)
}
}
} else {
android.util.Log.w("GameHaptics", "Device does not support vibration effects (Android < 8.0)")
}
}
fun vibrateForPieceLock() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect = VibrationEffect.createOneShot(50L, VibrationEffect.DEFAULT_AMPLITUDE)

View file

@ -12,12 +12,12 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.view.WindowManager
import android.view.Display
import android.hardware.display.DisplayManager
import android.view.Display
import com.mintris.model.GameBoard
import com.mintris.model.Tetromino
import com.mintris.model.TetrominoType
@ -33,6 +33,10 @@ class GameView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
companion object {
private const val TAG = "GameView"
}
// Game board model
private var gameBoard = GameBoard()
private var gameHaptics: GameHaptics? = null
@ -165,17 +169,17 @@ class GameView @JvmOverloads constructor(
gameBoard.onPieceMove = { onPieceMove?.invoke() }
gameBoard.onPieceLock = { onPieceLock?.invoke() }
gameBoard.onLineClear = { lineCount, clearedLines ->
android.util.Log.d("GameView", "Received line clear from GameBoard: $lineCount lines")
Log.d(TAG, "Received line clear from GameBoard: $lineCount lines")
try {
onLineClear?.invoke(lineCount)
// Use the lines that were cleared directly
linesToPulse.clear()
linesToPulse.addAll(clearedLines)
android.util.Log.d("GameView", "Found ${linesToPulse.size} lines to pulse")
Log.d(TAG, "Found ${linesToPulse.size} lines to pulse")
startPulseAnimation(lineCount)
android.util.Log.d("GameView", "Forwarded line clear callback")
Log.d(TAG, "Forwarded line clear callback")
} catch (e: Exception) {
android.util.Log.e("GameView", "Error forwarding line clear callback", e)
Log.e(TAG, "Error forwarding line clear callback", e)
}
}
@ -184,7 +188,11 @@ class GameView @JvmOverloads constructor(
// Set better frame rate using modern APIs
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
displayManager.getDisplay(Display.DEFAULT_DISPLAY)
} else {
displayManager.displays.firstOrNull()
}
display?.let { disp ->
val refreshRate = disp.refreshRate
// Set game loop interval based on refresh rate, but don't go faster than the base interval
@ -289,7 +297,7 @@ class GameView @JvmOverloads constructor(
val totalHeight = blockSize * verticalBlocks
// Log dimensions for debugging
android.util.Log.d("GameView", "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight")
Log.d(TAG, "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight")
}
override fun onDraw(canvas: Canvas) {
@ -739,17 +747,17 @@ class GameView @JvmOverloads constructor(
gameBoard.onPieceMove = { onPieceMove?.invoke() }
gameBoard.onPieceLock = { onPieceLock?.invoke() }
gameBoard.onLineClear = { lineCount, clearedLines ->
android.util.Log.d("GameView", "Received line clear from GameBoard: $lineCount lines")
Log.d(TAG, "Received line clear from GameBoard: $lineCount lines")
try {
onLineClear?.invoke(lineCount)
// Use the lines that were cleared directly
linesToPulse.clear()
linesToPulse.addAll(clearedLines)
android.util.Log.d("GameView", "Found ${linesToPulse.size} lines to pulse")
Log.d(TAG, "Found ${linesToPulse.size} lines to pulse")
startPulseAnimation(lineCount)
android.util.Log.d("GameView", "Forwarded line clear callback")
Log.d(TAG, "Forwarded line clear callback")
} catch (e: Exception) {
android.util.Log.e("GameView", "Error forwarding line clear callback", e)
Log.e(TAG, "Error forwarding line clear callback", e)
}
}
@ -785,7 +793,7 @@ class GameView @JvmOverloads constructor(
* Start the pulse animation for line clear
*/
private fun startPulseAnimation(lineCount: Int) {
android.util.Log.d("GameView", "Starting pulse animation for $lineCount lines")
Log.d(TAG, "Starting pulse animation for $lineCount lines")
// Cancel any existing animation
pulseAnimator?.cancel()
@ -804,7 +812,7 @@ class GameView @JvmOverloads constructor(
pulseAlpha = animation.animatedValue as Float
isPulsing = true
invalidate()
android.util.Log.d("GameView", "Pulse animation update: alpha = $pulseAlpha")
Log.d(TAG, "Pulse animation update: alpha = $pulseAlpha")
}
addListener(object : android.animation.AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: android.animation.Animator) {
@ -812,7 +820,7 @@ class GameView @JvmOverloads constructor(
pulseAlpha = 0f
linesToPulse.clear()
invalidate()
android.util.Log.d("GameView", "Pulse animation ended")
Log.d(TAG, "Pulse animation ended")
}
})
}

View file

@ -1,6 +1,6 @@
package com.mintris.model
import kotlin.random.Random
import android.util.Log
/**
* Represents the game board (grid) and manages game state
@ -9,6 +9,10 @@ class GameBoard(
val width: Int = 10,
val height: Int = 20
) {
companion object {
private const val TAG = "GameBoard"
}
// Board grid to track locked pieces
// True = occupied, False = empty
private val grid = Array(height) { BooleanArray(width) { false } }
@ -55,6 +59,9 @@ class GameBoard(
var onNextPieceChanged: (() -> Unit)? = null
var onLineClear: ((Int, List<Int>) -> Unit)? = null
// Store the last cleared lines
private val lastClearedLines = mutableListOf<Int>()
init {
spawnNextPiece()
spawnPiece()
@ -66,7 +73,7 @@ class GameBoard(
private fun spawnNextPiece() {
// If bag is empty, refill it with all piece types
if (bag.isEmpty()) {
bag.addAll(TetrominoType.values())
bag.addAll(TetrominoType.entries.toTypedArray())
bag.shuffle()
}
@ -326,18 +333,26 @@ class GameBoard(
y--
}
// Store the last cleared lines
lastClearedLines.clear()
lastClearedLines.addAll(linesToClear)
// If lines were cleared, calculate score in background and trigger callback
if (shiftAmount > 0) {
android.util.Log.d("GameBoard", "Lines cleared: $shiftAmount")
// Log line clear
Log.d(TAG, "Lines cleared: $shiftAmount")
// Trigger line clear callback on main thread with the lines that were cleared
val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
mainHandler.post {
android.util.Log.d("GameBoard", "Triggering onLineClear callback with $shiftAmount lines")
// Call the line clear callback with the cleared line count
try {
onLineClear?.invoke(shiftAmount, linesToClear) // Pass the lines that were cleared
android.util.Log.d("GameBoard", "onLineClear callback completed successfully")
Log.d(TAG, "Triggering onLineClear callback with $shiftAmount lines")
val clearedLines = getLastClearedLines()
onLineClear?.invoke(shiftAmount, clearedLines)
Log.d(TAG, "onLineClear callback completed successfully")
} catch (e: Exception) {
android.util.Log.e("GameBoard", "Error in onLineClear callback", e)
Log.e(TAG, "Error in onLineClear callback", e)
}
}
@ -582,4 +597,11 @@ class GameBoard(
* Get the current combo count
*/
fun getCombo(): Int = combo
/**
* Get the list of lines that were most recently cleared
*/
private fun getLastClearedLines(): List<Int> {
return lastClearedLines.toList()
}
}