diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index 350a8f2..9d8e35d 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -39,7 +39,9 @@ import android.content.BroadcastReceiver import android.content.IntentFilter import android.app.AlertDialog -class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionListener { +class MainActivity : AppCompatActivity(), + GamepadController.GamepadConnectionListener, + GamepadController.GamepadMenuListener { companion object { private const val TAG = "MainActivity" @@ -121,6 +123,7 @@ class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionLis // Initialize gamepad controller gamepadController = GamepadController(gameView) gamepadController.setGamepadConnectionListener(this) + gamepadController.setGamepadMenuListener(this) // Set up touch event forwarding binding.touchInterceptor?.setOnTouchListener { _, event -> @@ -1067,4 +1070,23 @@ class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionLis // Display progression data progressionScreen.showProgress(progressionManager, xpGained, newRewards, currentTheme) } + + /** + * Implements GamepadMenuListener to handle start button press + */ + override fun onPauseRequested() { + runOnUiThread { + 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 + hidePauseMenu() + resumeGame() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/mintris/game/GamepadController.kt b/app/src/main/java/com/mintris/game/GamepadController.kt index 5e81d60..d00816f 100644 --- a/app/src/main/java/com/mintris/game/GamepadController.kt +++ b/app/src/main/java/com/mintris/game/GamepadController.kt @@ -10,6 +10,8 @@ import android.os.Build import android.os.VibrationEffect import android.view.InputDevice.MotionRange import android.os.Vibrator +import android.os.Handler +import android.os.Looper /** * GamepadController handles gamepad input for the Mintris game. @@ -34,6 +36,9 @@ class GamepadController( private const val HARD_DROP_COOLDOWN_MS = 200L private const val HOLD_COOLDOWN_MS = 250L + // Continuous movement repeat delay + private const val CONTINUOUS_MOVEMENT_DELAY_MS = 80L + // Rumble patterns private const val RUMBLE_MOVE_DURATION_MS = 20L private const val RUMBLE_ROTATE_DURATION_MS = 30L @@ -102,14 +107,51 @@ class GamepadController( private var isMovingRight = false private var isMovingDown = false + // Handler for continuous movement + private val handler = Handler(Looper.getMainLooper()) + private val moveLeftRunnable = object : Runnable { + override fun run() { + if (isMovingLeft && gameView.isActive()) { + gameView.moveLeft() + vibrateForPieceMove() + handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS) + } + } + } + + private val moveRightRunnable = object : Runnable { + override fun run() { + if (isMovingRight && gameView.isActive()) { + gameView.moveRight() + vibrateForPieceMove() + handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS) + } + } + } + + private val moveDownRunnable = object : Runnable { + override fun run() { + if (isMovingDown && gameView.isActive()) { + gameView.softDrop() + vibrateForPieceMove() + handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS) + } + } + } + // Callback interfaces interface GamepadConnectionListener { fun onGamepadConnected(gamepadName: String) fun onGamepadDisconnected(gamepadName: String) } - // Listener for gamepad connection + interface GamepadMenuListener { + fun onPauseRequested() + } + + // Listeners private var connectionListener: GamepadConnectionListener? = null + private var menuListener: GamepadMenuListener? = null // Currently active gamepad for rumble private var activeGamepad: InputDevice? = null @@ -121,6 +163,13 @@ class GamepadController( connectionListener = listener } + /** + * Set a listener for gamepad menu events (pause/start button) + */ + fun setGamepadMenuListener(listener: GamepadMenuListener) { + menuListener = listener + } + /** * Function to check for newly connected gamepads. * Call this periodically from the activity to detect connection changes. @@ -219,29 +268,35 @@ class GamepadController( when (keyCode) { // D-pad and analog movement KeyEvent.KEYCODE_DPAD_LEFT -> { - if (!isMovingLeft && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + if (!isMovingLeft) { gameView.moveLeft() vibrateForPieceMove() lastMoveTime = currentTime isMovingLeft = true + // Start continuous movement after initial input + handler.postDelayed(moveLeftRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true } } KeyEvent.KEYCODE_DPAD_RIGHT -> { - if (!isMovingRight && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + if (!isMovingRight) { gameView.moveRight() vibrateForPieceMove() lastMoveTime = currentTime isMovingRight = true + // Start continuous movement after initial input + handler.postDelayed(moveRightRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true } } KeyEvent.KEYCODE_DPAD_DOWN -> { - if (!isMovingDown && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + if (!isMovingDown) { gameView.softDrop() vibrateForPieceMove() lastMoveTime = currentTime isMovingDown = true + // Start continuous movement after initial input + handler.postDelayed(moveDownRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true } } @@ -253,6 +308,11 @@ class GamepadController( return true } } + // Start button (pause) + KeyEvent.KEYCODE_BUTTON_START -> { + menuListener?.onPauseRequested() + return true + } // Rotation buttons - supporting multiple buttons for different controllers KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_X, @@ -281,14 +341,17 @@ class GamepadController( when (keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> { isMovingLeft = false + handler.removeCallbacks(moveLeftRunnable) return true } KeyEvent.KEYCODE_DPAD_RIGHT -> { isMovingRight = false + handler.removeCallbacks(moveRightRunnable) return true } KeyEvent.KEYCODE_DPAD_DOWN -> { isMovingDown = false + handler.removeCallbacks(moveDownRunnable) return true } } @@ -320,38 +383,52 @@ class GamepadController( // Apply deadzone if (Math.abs(axisX) > STICK_DEADZONE) { - if (axisX > 0 && !isMovingRight && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + if (axisX > 0 && !isMovingRight) { gameView.moveRight() vibrateForPieceMove() lastMoveTime = currentTime isMovingRight = true isMovingLeft = false + + // Start continuous movement after initial input + handler.removeCallbacks(moveLeftRunnable) + handler.postDelayed(moveRightRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true - } else if (axisX < 0 && !isMovingLeft && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + } else if (axisX < 0 && !isMovingLeft) { gameView.moveLeft() vibrateForPieceMove() lastMoveTime = currentTime isMovingLeft = true isMovingRight = false + + // Start continuous movement after initial input + handler.removeCallbacks(moveRightRunnable) + handler.postDelayed(moveLeftRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true } } else { // Reset horizontal movement flags when stick returns to center isMovingLeft = false isMovingRight = false + handler.removeCallbacks(moveLeftRunnable) + handler.removeCallbacks(moveRightRunnable) } if (Math.abs(axisY) > STICK_DEADZONE) { - if (axisY > 0 && !isMovingDown && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) { + if (axisY > 0 && !isMovingDown) { gameView.softDrop() vibrateForPieceMove() lastMoveTime = currentTime isMovingDown = true + + // Start continuous movement after initial input + handler.postDelayed(moveDownRunnable, CONTINUOUS_MOVEMENT_DELAY_MS) return true } } else { // Reset vertical movement flag when stick returns to center isMovingDown = false + handler.removeCallbacks(moveDownRunnable) } // Check right analog stick for rotation