Disable diagonal inputs and prevent accidental hard drops. Block back swipe gesture to prevent accidental app exits.

This commit is contained in:
cmclark00 2025-03-28 11:57:21 -04:00
parent 809ae33e5e
commit 8661fd8a80
3 changed files with 105 additions and 7 deletions

View file

@ -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)
}
}

View file

@ -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 &&