From 8661fd8a8011ae303d8369216f1e6b282481deff Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Fri, 28 Mar 2025 11:57:21 -0400 Subject: [PATCH] Disable diagonal inputs and prevent accidental hard drops. Block back swipe gesture to prevent accidental app exits. --- app/src/main/AndroidManifest.xml | 7 +- app/src/main/java/com/mintris/MainActivity.kt | 84 +++++++++++++++++++ .../main/java/com/mintris/game/GameView.kt | 21 +++-- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aaa37f3..924b03a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + android:theme="@style/Theme.Mintris.NoActionBar" + android:immersive="true" + android:resizeableActivity="false" + android:excludeFromRecents="false"> diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index e403d6c..2f39436 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -32,6 +32,8 @@ import java.util.* import android.graphics.Color import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import android.graphics.Rect +import android.view.KeyEvent class MainActivity : AppCompatActivity() { @@ -73,6 +75,9 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + // Disable Android back gesture to prevent accidental app exits + disableAndroidBackGesture() + // Initialize game components gameBoard = GameBoard() gameHaptics = GameHaptics(this) @@ -646,4 +651,83 @@ class MainActivity : AppCompatActivity() { else -> Color.WHITE } } + + /** + * Disables the Android system back gesture to prevent accidental exits + */ + private fun disableAndroidBackGesture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Set the entire window to be excluded from the system gesture areas + window.decorView.post { + // Create a list of rectangles representing the edges of the screen to exclude from system gestures + val gestureInsets = window.decorView.rootWindowInsets?.systemGestureInsets + if (gestureInsets != null) { + val leftEdge = Rect(0, 0, 50, window.decorView.height) + val rightEdge = Rect(window.decorView.width - 50, 0, window.decorView.width, window.decorView.height) + val bottomEdge = Rect(0, window.decorView.height - 50, window.decorView.width, window.decorView.height) + + window.decorView.systemGestureExclusionRects = listOf(leftEdge, rightEdge, bottomEdge) + } + } + } + + // Add an on back pressed callback to handle back button/gesture + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + onBackPressedDispatcher.addCallback(this, object : androidx.activity.OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + // If we're playing the game, handle it as a pause action instead of exiting + if (gameView.visibility == View.VISIBLE && !gameView.isPaused && !gameView.isGameOver()) { + gameView.pause() + gameMusic.pause() + showPauseMenu() + binding.pauseStartButton.visibility = View.GONE + binding.resumeButton.visibility = View.VISIBLE + } else if (binding.pauseContainer.visibility == View.VISIBLE) { + // If pause menu is showing, handle as a resume + resumeGame() + } else if (binding.gameOverContainer.visibility == View.VISIBLE) { + // If game over is showing, go back to title + hideGameOver() + showTitleScreen() + } else if (titleScreen.visibility == View.VISIBLE) { + // If title screen is showing, allow normal back behavior (exit app) + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + }) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // For Android 11 (R) to Android 12 (S), use the WindowInsetsController to disable gestures + window.insetsController?.systemBarsBehavior = + android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } + + /** + * Completely block the hardware back button during gameplay + */ + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + // If back button is pressed + if (keyCode == KeyEvent.KEYCODE_BACK) { + // Handle back button press as a pause action during gameplay + if (gameView.visibility == View.VISIBLE && !gameView.isPaused && !gameView.isGameOver()) { + gameView.pause() + gameMusic.pause() + showPauseMenu() + binding.pauseStartButton.visibility = View.GONE + binding.resumeButton.visibility = View.VISIBLE + return true // Consume the event + } else if (binding.pauseContainer.visibility == View.VISIBLE) { + // If pause menu is showing, handle as a resume + resumeGame() + return true // Consume the event + } else if (binding.gameOverContainer.visibility == View.VISIBLE) { + // If game over is showing, go back to title + hideGameOver() + showTitleScreen() + return true // Consume the event + } + } + return super.onKeyDown(keyCode, event) + } } \ No newline at end of file diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt index ce82647..95332db 100644 --- a/app/src/main/java/com/mintris/game/GameView.kt +++ b/app/src/main/java/com/mintris/game/GameView.kt @@ -39,7 +39,8 @@ class GameView @JvmOverloads constructor( // Game state private var isRunning = false - private var isPaused = false + var isPaused = false // Changed from private to public to allow access from MainActivity + private var score = 0 // Callbacks var onNextPieceChanged: (() -> Unit)? = null @@ -128,14 +129,16 @@ class GameView @JvmOverloads constructor( private var lastTapTime = 0L private var lastRotationTime = 0L private var lastMoveTime = 0L - private var minSwipeVelocity = 800 // Minimum velocity for swipe to be considered a hard drop + private var minSwipeVelocity = 1200 // Increased from 800 to require more deliberate swipes private val maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels) private val minTapTime = 100L // Minimum time for a tap (in milliseconds) private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds) private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds) private var lockedDirection: Direction? = null // Track the locked movement direction private val minMovementThreshold = 0.75f // Minimum movement threshold relative to block size - private val directionLockThreshold = 1.5f // Threshold for direction lock relative to block size + private val directionLockThreshold = 2.5f // Increased from 1.5f to make direction locking more aggressive + 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 private enum class Direction { HORIZONTAL, VERTICAL @@ -612,12 +615,14 @@ class GameView @JvmOverloads constructor( // Check if movement exceeds threshold if (absDeltaX > blockSize * minMovementThreshold || absDeltaY > blockSize * minMovementThreshold) { - // Determine dominant direction + // Determine dominant direction with stricter criteria if (absDeltaX > absDeltaY * directionLockThreshold) { lockedDirection = Direction.HORIZONTAL } else if (absDeltaY > absDeltaX * directionLockThreshold) { lockedDirection = Direction.VERTICAL } + // If strict direction lock is enabled and we couldn't determine a clear direction, don't set one + // This prevents diagonal movements from being recognized } } @@ -661,8 +666,12 @@ class GameView @JvmOverloads constructor( val deltaY = event.y - startY val deltaX = event.x - startX - // If the movement was fast and downward, treat as hard drop - if (moveTime > 0 && deltaY > blockSize * 0.5f && (deltaY / moveTime) * 1000 > minSwipeVelocity) { + // Only allow hard drops with a deliberate downward swipe + // Requires: predominantly vertical movement, minimum distance, and minimum velocity + if (moveTime > 0 && + deltaY > blockSize * minHardDropDistance && // Require longer swipe for hard drop + (deltaY / moveTime) * 1000 > minSwipeVelocity && + abs(deltaX) < abs(deltaY) * 0.3f) { // Require more purely vertical movement (reduced from 0.5f to 0.3f) gameBoard.hardDrop() invalidate() } else if (moveTime < minTapTime &&