mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 15:35:20 +01:00
Disable diagonal inputs and prevent accidental hard drops. Block back swipe gesture to prevent accidental app exits.
This commit is contained in:
parent
809ae33e5e
commit
8661fd8a80
3 changed files with 105 additions and 7 deletions
|
@ -2,6 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<!-- Add permission to handle system gestures if needed on some devices -->
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -14,7 +16,10 @@
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.Mintris.NoActionBar">
|
android:theme="@style/Theme.Mintris.NoActionBar"
|
||||||
|
android:immersive="true"
|
||||||
|
android:resizeableActivity="false"
|
||||||
|
android:excludeFromRecents="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
|
@ -32,6 +32,8 @@ import java.util.*
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.KeyEvent
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -73,6 +75,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// Disable Android back gesture to prevent accidental app exits
|
||||||
|
disableAndroidBackGesture()
|
||||||
|
|
||||||
// Initialize game components
|
// Initialize game components
|
||||||
gameBoard = GameBoard()
|
gameBoard = GameBoard()
|
||||||
gameHaptics = GameHaptics(this)
|
gameHaptics = GameHaptics(this)
|
||||||
|
@ -646,4 +651,83 @@ class MainActivity : AppCompatActivity() {
|
||||||
else -> Color.WHITE
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -39,7 +39,8 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
private var isRunning = false
|
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
|
// Callbacks
|
||||||
var onNextPieceChanged: (() -> Unit)? = null
|
var onNextPieceChanged: (() -> Unit)? = null
|
||||||
|
@ -128,14 +129,16 @@ class GameView @JvmOverloads constructor(
|
||||||
private var lastTapTime = 0L
|
private var lastTapTime = 0L
|
||||||
private var lastRotationTime = 0L
|
private var lastRotationTime = 0L
|
||||||
private var lastMoveTime = 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 maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels)
|
||||||
private val minTapTime = 100L // Minimum time for a tap (in milliseconds)
|
private val minTapTime = 100L // Minimum time for a tap (in milliseconds)
|
||||||
private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds)
|
private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds)
|
||||||
private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds)
|
private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds)
|
||||||
private var lockedDirection: Direction? = null // Track the locked movement direction
|
private var lockedDirection: Direction? = null // Track the locked movement direction
|
||||||
private val minMovementThreshold = 0.75f // Minimum movement threshold relative to block size
|
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 {
|
private enum class Direction {
|
||||||
HORIZONTAL, VERTICAL
|
HORIZONTAL, VERTICAL
|
||||||
|
@ -612,12 +615,14 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
// Check if movement exceeds threshold
|
// Check if movement exceeds threshold
|
||||||
if (absDeltaX > blockSize * minMovementThreshold || absDeltaY > blockSize * minMovementThreshold) {
|
if (absDeltaX > blockSize * minMovementThreshold || absDeltaY > blockSize * minMovementThreshold) {
|
||||||
// Determine dominant direction
|
// Determine dominant direction with stricter criteria
|
||||||
if (absDeltaX > absDeltaY * directionLockThreshold) {
|
if (absDeltaX > absDeltaY * directionLockThreshold) {
|
||||||
lockedDirection = Direction.HORIZONTAL
|
lockedDirection = Direction.HORIZONTAL
|
||||||
} else if (absDeltaY > absDeltaX * directionLockThreshold) {
|
} else if (absDeltaY > absDeltaX * directionLockThreshold) {
|
||||||
lockedDirection = Direction.VERTICAL
|
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 deltaY = event.y - startY
|
||||||
val deltaX = event.x - startX
|
val deltaX = event.x - startX
|
||||||
|
|
||||||
// If the movement was fast and downward, treat as hard drop
|
// Only allow hard drops with a deliberate downward swipe
|
||||||
if (moveTime > 0 && deltaY > blockSize * 0.5f && (deltaY / moveTime) * 1000 > minSwipeVelocity) {
|
// 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()
|
gameBoard.hardDrop()
|
||||||
invalidate()
|
invalidate()
|
||||||
} else if (moveTime < minTapTime &&
|
} else if (moveTime < minTapTime &&
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue