From 94e8d313c2e1cda53f2bfe0d896d52c012079039 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Sun, 30 Mar 2025 19:39:35 -0400 Subject: [PATCH 01/34] Add enhanced game over animation with falling blocks, screen shake, and dynamic text effects --- app/src/main/java/com/mintris/MainActivity.kt | 49 ++-- .../main/java/com/mintris/game/GameView.kt | 256 ++++++++++++++++-- 2 files changed, 268 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index abfc24c..754ec2d 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -29,6 +29,8 @@ import androidx.activity.result.contract.ActivityResultContracts import android.graphics.Rect import android.util.Log import android.view.KeyEvent +import android.os.Handler +import android.os.Looper class MainActivity : AppCompatActivity() { @@ -320,6 +322,7 @@ class MainActivity : AppCompatActivity() { * Show game over screen */ private fun showGameOver(score: Int) { + Log.d("MainActivity", "Showing game over screen with score: $score") val gameTime = System.currentTimeMillis() - gameStartTime // Update session stats @@ -370,30 +373,36 @@ class MainActivity : AppCompatActivity() { if (isSoundEnabled) { gameMusic.playGameOver() } + + // First trigger the animation in the game view + Log.d("MainActivity", "Triggering game over animation") gameView.startGameOverAnimation() - // Show progression screen first with XP animation - showProgressionScreen(xpGained, newRewards) - - // Override the continue button behavior if high score needs to be shown - val originalOnContinue = progressionScreen.onContinue - - progressionScreen.onContinue = { - // If this is a high score, show high score entry screen - if (highScoreManager.isHighScore(score)) { - showingHighScore = true - showHighScoreEntry(score) - } else { - // Just show game over screen normally - progressionScreen.visibility = View.GONE - binding.gameOverContainer.visibility = View.VISIBLE - - // Update theme selector if new themes were unlocked - if (newRewards.any { it.contains("Theme") }) { - updateThemeSelector() + // Wait a moment before showing progression screen to let animation be visible + Handler(Looper.getMainLooper()).postDelayed({ + // Show progression screen first with XP animation + showProgressionScreen(xpGained, newRewards) + + // Override the continue button behavior if high score needs to be shown + val originalOnContinue = progressionScreen.onContinue + + progressionScreen.onContinue = { + // If this is a high score, show high score entry screen + if (highScoreManager.isHighScore(score)) { + showingHighScore = true + showHighScoreEntry(score) + } else { + // Just show game over screen normally + progressionScreen.visibility = View.GONE + binding.gameOverContainer.visibility = View.VISIBLE + + // Update theme selector if new themes were unlocked + if (newRewards.any { it.contains("Theme") }) { + updateThemeSelector() + } } } - } + }, 2000) // Increased from 1000ms (1 second) to 2000ms (2 seconds) // Vibrate to indicate game over vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK) diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt index 741b662..24e8f0c 100644 --- a/app/src/main/java/com/mintris/game/GameView.kt +++ b/app/src/main/java/com/mintris/game/GameView.kt @@ -181,6 +181,15 @@ class GameView @JvmOverloads constructor( private var gameOverAnimator: ValueAnimator? = null private var gameOverAlpha = 0f private var isGameOverAnimating = false + // Add new game over animation properties + private var gameOverBlocksY = mutableListOf() + private var gameOverBlocksX = mutableListOf() + private var gameOverBlocksRotation = mutableListOf() + private var gameOverBlocksSpeed = mutableListOf() + private var gameOverBlocksSize = mutableListOf() + private var gameOverColorTransition = 0f + private var gameOverShakeAmount = 0f + private var gameOverFinalAlpha = 0.8f private val ghostPaint = Paint().apply { style = Paint.Style.STROKE @@ -321,6 +330,17 @@ class GameView @JvmOverloads constructor( * Start the game */ fun start() { + // Reset game over animation state + isGameOverAnimating = false + gameOverAlpha = 0f + gameOverBlocksY.clear() + gameOverBlocksX.clear() + gameOverBlocksRotation.clear() + gameOverBlocksSpeed.clear() + gameOverBlocksSize.clear() + gameOverColorTransition = 0f + gameOverShakeAmount = 0f + isPaused = false isRunning = true gameBoard.startGame() // Add this line to ensure a new piece is spawned @@ -343,6 +363,18 @@ class GameView @JvmOverloads constructor( fun reset() { isRunning = false isPaused = true + + // Reset game over animation state + isGameOverAnimating = false + gameOverAlpha = 0f + gameOverBlocksY.clear() + gameOverBlocksX.clear() + gameOverBlocksRotation.clear() + gameOverBlocksSpeed.clear() + gameOverBlocksSize.clear() + gameOverColorTransition = 0f + gameOverShakeAmount = 0f + gameBoard.reset() gameBoard.startGame() // Add this line to ensure a new piece is spawned handler.removeCallbacks(gameLoopRunnable) @@ -354,9 +386,17 @@ class GameView @JvmOverloads constructor( */ private fun update() { if (gameBoard.isGameOver) { - isRunning = false - isPaused = true - onGameOver?.invoke(gameBoard.score) + // Only trigger game over handling once when transitioning to game over state + if (isRunning) { + Log.d(TAG, "Game has ended - transitioning to game over state") + isRunning = false + isPaused = true + + // Always trigger animation for each game over + Log.d(TAG, "Triggering game over animation from update()") + startGameOverAnimation() + onGameOver?.invoke(gameBoard.score) + } return } @@ -439,30 +479,128 @@ class GameView @JvmOverloads constructor( // Draw game over effect if animating if (isGameOverAnimating) { + // First layer - full screen glow val gameOverPaint = Paint().apply { - color = Color.WHITE - alpha = (200 * gameOverAlpha).toInt() // Increased from 128 to 200 + color = Color.RED // Change to red for more striking game over indication + alpha = (230 * gameOverAlpha).toInt() // Increased opacity isAntiAlias = true style = Paint.Style.FILL // Only apply blur if alpha is greater than 0 if (gameOverAlpha > 0) { - maskFilter = BlurMaskFilter(64f * gameOverAlpha, BlurMaskFilter.Blur.OUTER) // Increased from 48f to 64f + maskFilter = BlurMaskFilter(64f * gameOverAlpha, BlurMaskFilter.Blur.OUTER) } } + + // Apply screen shake if active + if (gameOverShakeAmount > 0) { + canvas.save() + val shakeOffsetX = (Math.random() * 2 - 1) * gameOverShakeAmount * 20 // Doubled for more visible shake + val shakeOffsetY = (Math.random() * 2 - 1) * gameOverShakeAmount * 20 + canvas.translate(shakeOffsetX.toFloat(), shakeOffsetY.toFloat()) + } + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gameOverPaint) - // Add a second layer for more intensity + // Second layer - color transition effect val gameOverPaint2 = Paint().apply { - color = Color.WHITE - alpha = (100 * gameOverAlpha).toInt() + // Transition from bright red to theme color + val transitionColor = if (gameOverColorTransition < 0.5f) { + Color.RED + } else { + val transition = (gameOverColorTransition - 0.5f) * 2f + val red = Color.red(currentThemeColor) + val green = Color.green(currentThemeColor) + val blue = Color.blue(currentThemeColor) + Color.argb( + (150 * gameOverAlpha).toInt(), // Increased opacity + (255 - (255-red) * transition).toInt(), + (green * transition).toInt(), // Transition from 0 (red) to theme green + (blue * transition).toInt() // Transition from 0 (red) to theme blue + ) + } + color = transitionColor + alpha = (150 * gameOverAlpha).toInt() // Increased opacity isAntiAlias = true style = Paint.Style.FILL // Only apply blur if alpha is greater than 0 if (gameOverAlpha > 0) { - maskFilter = BlurMaskFilter(32f * gameOverAlpha, BlurMaskFilter.Blur.OUTER) + maskFilter = BlurMaskFilter(48f * gameOverAlpha, BlurMaskFilter.Blur.OUTER) // Increased blur } } canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gameOverPaint2) + + // Draw "GAME OVER" text + if (gameOverAlpha > 0.5f) { + val textPaint = Paint().apply { + color = Color.WHITE + alpha = (255 * Math.min(1f, (gameOverAlpha - 0.5f) * 2)).toInt() + isAntiAlias = true + textSize = blockSize * 1.5f // Reduced from 2f to 1.5f to fit on screen + textAlign = Paint.Align.CENTER + typeface = android.graphics.Typeface.DEFAULT_BOLD + style = Paint.Style.FILL_AND_STROKE + strokeWidth = blockSize * 0.08f // Proportionally reduced from 0.1f + } + + // Draw text with glow + val glowPaint = Paint(textPaint).apply { + maskFilter = BlurMaskFilter(blockSize * 0.4f, BlurMaskFilter.Blur.NORMAL) // Reduced from 0.5f + alpha = (200 * Math.min(1f, (gameOverAlpha - 0.5f) * 2)).toInt() + color = Color.RED + strokeWidth = blockSize * 0.15f // Reduced from 0.2f + } + + val xPos = width / 2f + val yPos = height / 3f + + // Measure text width to check if it fits + val textWidth = textPaint.measureText("GAME OVER") + + // If text would still be too wide, scale it down further + if (textWidth > width * 0.9f) { + val scaleFactor = width * 0.9f / textWidth + textPaint.textSize *= scaleFactor + glowPaint.textSize *= scaleFactor + } + + canvas.drawText("GAME OVER", xPos, yPos, glowPaint) + canvas.drawText("GAME OVER", xPos, yPos, textPaint) + } + + // Draw falling blocks + if (gameOverBlocksY.isNotEmpty()) { + for (i in gameOverBlocksY.indices) { + val x = gameOverBlocksX[i] + val y = gameOverBlocksY[i] + val rotation = gameOverBlocksRotation[i] + val size = gameOverBlocksSize[i] * blockSize + + // Skip blocks that have fallen off the screen + if (y > height) continue + + // Draw each falling block with rotation + canvas.save() + canvas.translate(x, y) + canvas.rotate(rotation) + + // Create a pulsing effect for the falling blocks + val blockPaint = Paint(blockPaint) + blockPaint.alpha = (255 * gameOverAlpha * (1.0f - y / height.toFloat() * 0.7f)).toInt() + + // Draw block with glow effect + val blockGlowPaint = Paint(blockGlowPaint) + blockGlowPaint.alpha = (200 * gameOverAlpha * (1.0f - y / height.toFloat() * 0.5f)).toInt() + canvas.drawRect(-size/2, -size/2, size/2, size/2, blockGlowPaint) + canvas.drawRect(-size/2, -size/2, size/2, size/2, blockPaint) + + canvas.restore() + } + } + + // Reset any transformations from screen shake + if (gameOverShakeAmount > 0) { + canvas.restore() + } } } @@ -1165,6 +1303,12 @@ class GameView @JvmOverloads constructor( fun startGameOverAnimation() { Log.d(TAG, "Starting game over animation") + // Check if game over already showing + if (isGameOverAnimating && gameOverAlpha > 0.5f) { + Log.d(TAG, "Game over animation already active - skipping") + return + } + // Cancel any existing animations pulseAnimator?.cancel() gameOverAnimator?.cancel() @@ -1172,27 +1316,105 @@ class GameView @JvmOverloads constructor( // Trigger haptic feedback gameHaptics?.vibrateForGameOver() + // Force immediate visual feedback + isGameOverAnimating = true + gameOverAlpha = 0.3f + invalidate() + + // Generate falling blocks based on current board state + generateGameOverBlocks() + // Create new game over animation - gameOverAnimator = ValueAnimator.ofFloat(0f, 1f, 0.8f).apply { - duration = 1000L // 1 second total + gameOverAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 3000L // Increased from 2000L (2 seconds) to 3000L (3 seconds) interpolator = LinearInterpolator() addUpdateListener { animation -> - gameOverAlpha = animation.animatedValue as Float + val progress = animation.animatedValue as Float + + // Main alpha transition for overlay + gameOverAlpha = when { + progress < 0.2f -> progress * 5f // Quick fade in (first 20% of animation) + else -> 1f // Hold at full opacity + } + + // Color transition effect (start after 40% of animation) + gameOverColorTransition = when { + progress < 0.4f -> 0f + progress < 0.8f -> (progress - 0.4f) * 2.5f // Transition during 40%-80% + else -> 1f + } + + // Screen shake effect (strongest at beginning, fades out) + gameOverShakeAmount = when { + progress < 0.3f -> progress * 3.33f // Ramp up + progress < 0.6f -> 1f - (progress - 0.3f) * 3.33f // Ramp down + else -> 0f // No shake + } + + // Update falling blocks + updateGameOverBlocks() + isGameOverAnimating = true invalidate() - Log.d(TAG, "Game over animation update: alpha = $gameOverAlpha") + Log.d(TAG, "Game over animation update: alpha = $gameOverAlpha, progress = $progress") } addListener(object : android.animation.AnimatorListenerAdapter() { override fun onAnimationEnd(animation: android.animation.Animator) { - isGameOverAnimating = false - gameOverAlpha = 0.8f // Keep at 80% opacity + Log.d(TAG, "Game over animation ended - Final alpha: $gameOverFinalAlpha") + isGameOverAnimating = true // Keep true to maintain visibility + gameOverAlpha = gameOverFinalAlpha // Keep at 80% opacity invalidate() - Log.d(TAG, "Game over animation ended") } }) } gameOverAnimator?.start() } + + /** + * Generate falling blocks for the game over animation based on current board state + */ + private fun generateGameOverBlocks() { + // Clear existing blocks + gameOverBlocksY.clear() + gameOverBlocksX.clear() + gameOverBlocksRotation.clear() + gameOverBlocksSpeed.clear() + gameOverBlocksSize.clear() + + // Generate 30-40 blocks across the board + val numBlocks = (30 + Math.random() * 10).toInt() + + for (i in 0 until numBlocks) { + // Start positions - distribute across the board width but clustered near the top + gameOverBlocksX.add(boardLeft + (Math.random() * gameBoard.width * blockSize).toFloat()) + gameOverBlocksY.add((boardTop - blockSize * 2 + Math.random() * height * 0.3f).toFloat()) + + // Random rotation + gameOverBlocksRotation.add((Math.random() * 360).toFloat()) + + // Random fall speed (some faster, some slower) + gameOverBlocksSpeed.add((5 + Math.random() * 15).toFloat()) + + // Slightly varied block sizes + gameOverBlocksSize.add((0.8f + Math.random() * 0.4f).toFloat()) + } + } + + /** + * Update the position of falling blocks in the game over animation + */ + private fun updateGameOverBlocks() { + for (i in gameOverBlocksY.indices) { + // Update Y position based on speed + gameOverBlocksY[i] += gameOverBlocksSpeed[i] + + // Update rotation + gameOverBlocksRotation[i] += gameOverBlocksSpeed[i] * 0.5f + + // Accelerate falling + gameOverBlocksSpeed[i] *= 1.03f + } + } } From b481fb4e8042ba7989200a829359baa95e081e5d Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 31 Mar 2025 03:46:05 -0400 Subject: [PATCH 02/34] Enhance landscape support: Add click handlers for theme and block skin selectors, improve UI layout in landscape mode --- app/src/main/AndroidManifest.xml | 1 - app/src/main/java/com/mintris/MainActivity.kt | 53 +- .../main/java/com/mintris/game/GameView.kt | 140 +++- .../main/res/layout-land/activity_main.xml | 606 ++++++++++++++++++ .../main/res/layout-land/activity_stats.xml | 194 ++++++ .../main/res/layout-land/high_score_entry.xml | 51 ++ app/src/main/res/layout-land/high_scores.xml | 38 ++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 1042 insertions(+), 42 deletions(-) create mode 100644 app/src/main/res/layout-land/activity_main.xml create mode 100644 app/src/main/res/layout-land/activity_stats.xml create mode 100644 app/src/main/res/layout-land/high_score_entry.xml create mode 100644 app/src/main/res/layout-land/high_scores.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 924b03a..8815afb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,6 @@ (R.id.inPauseThemeSelector) + inPauseThemeSelector?.onThemeSelected = { themeId: String -> + // Apply the new theme + applyTheme(themeId) + + // Provide haptic feedback + gameHaptics.vibrateForPieceLock() + + // Refresh the pause menu + showPauseMenu() + } + // Set up block skin selector blockSkinSelector.onBlockSkinSelected = { skinId: String -> // Apply the new block skin @@ -145,6 +158,19 @@ class MainActivity : AppCompatActivity() { gameHaptics.vibrateForPieceLock() } + // Set up landscape mode block skin selector if available + val inPauseBlockSkinSelector = findViewById(R.id.inPauseBlockSkinSelector) + inPauseBlockSkinSelector?.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 @@ -437,8 +463,8 @@ class MainActivity : AppCompatActivity() { binding.resumeButton.visibility = View.GONE // Update level badge - binding.pauseLevelBadge.setLevel(progressionManager.getPlayerLevel()) - binding.pauseLevelBadge.setThemeColor(getThemeColor(currentTheme)) + binding.pauseLevelBadge?.setLevel(progressionManager.getPlayerLevel()) + binding.pauseLevelBadge?.setThemeColor(getThemeColor(currentTheme)) // Get theme color val textColor = getThemeColor(currentTheme) @@ -461,26 +487,41 @@ class MainActivity : AppCompatActivity() { binding.resumeButton.setTextColor(textColor) binding.highScoresButton.setTextColor(textColor) binding.statsButton.setTextColor(textColor) - binding.pauseLevelText.setTextColor(textColor) + binding.pauseLevelText?.setTextColor(textColor) binding.pauseLevelUpButton.setTextColor(textColor) binding.pauseLevelDownButton.setTextColor(textColor) binding.settingsButton.setTextColor(textColor) binding.musicToggle.setColorFilter(textColor) // Apply theme colors to text elements - binding.settingsTitle.setTextColor(textColor) + binding.settingsTitle?.setTextColor(textColor) binding.selectLevelText.setTextColor(textColor) binding.musicText.setTextColor(textColor) - // Update theme selector + // Update theme selector - handle both standard and landscape versions updateThemeSelector() - // Update block skin selector + // Handle landscape mode theme selectors (using null-safe calls) + val inPauseThemeSelector = findViewById(R.id.inPauseThemeSelector) + inPauseThemeSelector?.updateThemes( + unlockedThemes = progressionManager.getUnlockedThemes(), + currentTheme = currentTheme + ) + + // Update block skin selector - handle both standard and landscape versions blockSkinSelector.updateBlockSkins( progressionManager.getUnlockedBlocks(), gameView.getCurrentBlockSkin(), progressionManager.getPlayerLevel() ) + + // Handle landscape mode block skin selectors (using null-safe calls) + val inPauseBlockSkinSelector = findViewById(R.id.inPauseBlockSkinSelector) + inPauseBlockSkinSelector?.updateBlockSkins( + progressionManager.getUnlockedBlocks(), + gameView.getCurrentBlockSkin(), + progressionManager.getPlayerLevel() + ) } /** diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt index 24e8f0c..f5043f1 100644 --- a/app/src/main/java/com/mintris/game/GameView.kt +++ b/app/src/main/java/com/mintris/game/GameView.kt @@ -1019,6 +1019,16 @@ class GameView @JvmOverloads constructor( return true } + // Define the game board boundaries + val boardRight = boardLeft + (gameBoard.width * blockSize) + val boardBottom = boardTop + (gameBoard.height * blockSize) + + // Determine if touch is on left side, right side, or within board area + val isLeftSide = event.x < boardLeft + val isRightSide = event.x > boardRight + val isWithinBoard = event.x >= boardLeft && event.x <= boardRight && + event.y >= boardTop && event.y <= boardBottom + when (event.action) { MotionEvent.ACTION_DOWN -> { startX = event.x @@ -1051,36 +1061,81 @@ class GameView @JvmOverloads constructor( } } - // Handle movement based on locked direction - when (lockedDirection) { - Direction.HORIZONTAL -> { - if (abs(deltaX) > blockSize * minMovementThreshold) { - if (deltaX > 0) { - gameBoard.moveRight() - } else { - gameBoard.moveLeft() - } - lastTouchX = event.x - if (currentTime - lastMoveTime >= moveCooldown) { - gameHaptics?.vibrateForPieceMove() - lastMoveTime = currentTime - } + // Special handling for landscape mode - side controls + if (isLeftSide) { + // Left side controls - move left + if (deltaY < -blockSize * minMovementThreshold) { + // Swipe up on left side - rotate + if (currentTime - lastRotationTime >= rotationCooldown) { + gameBoard.rotate() + lastRotationTime = currentTime + gameHaptics?.vibrateForPieceMove() invalidate() } + } else if (deltaY > blockSize * minMovementThreshold) { + // Swipe down on left side - move left + gameBoard.moveLeft() + lastTouchY = event.y + if (currentTime - lastMoveTime >= moveCooldown) { + gameHaptics?.vibrateForPieceMove() + lastMoveTime = currentTime + } + invalidate() } - Direction.VERTICAL -> { - if (deltaY > blockSize * minMovementThreshold) { - gameBoard.softDrop() - lastTouchY = event.y - if (currentTime - lastMoveTime >= moveCooldown) { - gameHaptics?.vibrateForPieceMove() - lastMoveTime = currentTime - } + } else if (isRightSide) { + // Right side controls - move right + if (deltaY < -blockSize * minMovementThreshold) { + // Swipe up on right side - hold piece + if (currentTime - lastHoldTime >= holdCooldown) { + gameBoard.holdPiece() + lastHoldTime = currentTime + gameHaptics?.vibrateForPieceMove() invalidate() } + } else if (deltaY > blockSize * minMovementThreshold) { + // Swipe down on right side - move right + gameBoard.moveRight() + lastTouchY = event.y + if (currentTime - lastMoveTime >= moveCooldown) { + gameHaptics?.vibrateForPieceMove() + lastMoveTime = currentTime + } + invalidate() } - null -> { - // No direction lock yet, don't process movement + } + // Standard touch controls for main board area or portrait mode + else { + // Handle movement based on locked direction + when (lockedDirection) { + Direction.HORIZONTAL -> { + if (abs(deltaX) > blockSize * minMovementThreshold) { + if (deltaX > 0) { + gameBoard.moveRight() + } else { + gameBoard.moveLeft() + } + lastTouchX = event.x + if (currentTime - lastMoveTime >= moveCooldown) { + gameHaptics?.vibrateForPieceMove() + lastMoveTime = currentTime + } + invalidate() + } + } + Direction.VERTICAL -> { + if (deltaY > blockSize * minMovementThreshold) { + gameBoard.softDrop() + lastTouchY = event.y + if (currentTime - lastMoveTime >= moveCooldown) { + gameHaptics?.vibrateForPieceMove() + lastMoveTime = currentTime + } + invalidate() + } + } + null -> { + // No direction lock yet, don't process movement + } } } } @@ -1090,6 +1145,32 @@ class GameView @JvmOverloads constructor( val deltaY = event.y - startY val moveTime = currentTime - lastTapTime + // Special handling for taps on game board or sides + if (moveTime < minTapTime * 1.5 && + abs(deltaY) < maxTapMovement * 1.5 && + abs(deltaX) < maxTapMovement * 1.5) { + + if (isLeftSide) { + // Tap on left side - move left + gameBoard.moveLeft() + gameHaptics?.vibrateForPieceMove() + invalidate() + } else if (isRightSide) { + // Tap on right side - move right + gameBoard.moveRight() + gameHaptics?.vibrateForPieceMove() + invalidate() + } else if (isWithinBoard && currentTime - lastRotationTime >= rotationCooldown) { + // Tap on board - rotate + gameBoard.rotate() + lastRotationTime = currentTime + gameHaptics?.vibrateForPieceMove() + invalidate() + } + return true + } + + // Long swipe handling for hard drops and holds // Check for hold gesture (swipe up) if (deltaY < -blockSize * minHoldDistance && abs(deltaX) / abs(deltaY) < 0.5f) { @@ -1125,17 +1206,6 @@ class GameView @JvmOverloads constructor( gameBoard.softDrop() invalidate() } - // Check for rotation (quick tap with minimal movement) - else if (moveTime < minTapTime * 1.5 && // Increased from 1.0 to 1.5 for more lenient timing - abs(deltaY) < maxTapMovement * 1.5 && // Increased from 1.0 to 1.5 for more lenient movement - abs(deltaX) < maxTapMovement * 1.5) { // Increased from 1.0 to 1.5 for more lenient movement - if (currentTime - lastRotationTime >= rotationCooldown) { - Log.d(TAG, "Rotation detected - moveTime: $moveTime, deltaX: $deltaX, deltaY: $deltaY") - gameBoard.rotate() - lastRotationTime = currentTime - invalidate() - } - } // Reset direction lock lockedDirection = null diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..20ad154 --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +