mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 06:05:19 +01:00
Compare commits
6 commits
53c46c9864
...
94e8d313c2
Author | SHA1 | Date | |
---|---|---|---|
|
94e8d313c2 | ||
|
7dccad8d12 | ||
|
292ea656f8 | ||
|
ce19427cca | ||
|
03ff049bef | ||
|
e23d33e2e2 |
6 changed files with 484 additions and 49 deletions
|
@ -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
|
||||
|
@ -366,28 +369,40 @@ class MainActivity : AppCompatActivity() {
|
|||
// Flag to track if high score screen will be shown
|
||||
var showingHighScore = false
|
||||
|
||||
// Show progression screen first with XP animation
|
||||
showProgressionScreen(xpGained, newRewards)
|
||||
// Play game over sound and trigger animation
|
||||
if (isSoundEnabled) {
|
||||
gameMusic.playGameOver()
|
||||
}
|
||||
|
||||
// Override the continue button behavior if high score needs to be shown
|
||||
val originalOnContinue = progressionScreen.onContinue
|
||||
// First trigger the animation in the game view
|
||||
Log.d("MainActivity", "Triggering game over animation")
|
||||
gameView.startGameOverAnimation()
|
||||
|
||||
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
|
||||
// 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)
|
||||
|
||||
// Update theme selector if new themes were unlocked
|
||||
if (newRewards.any { it.contains("Theme") }) {
|
||||
updateThemeSelector()
|
||||
// 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)
|
||||
|
|
|
@ -9,12 +9,14 @@ import com.mintris.R
|
|||
|
||||
class GameMusic(private val context: Context) {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var gameOverPlayer: MediaPlayer? = null
|
||||
private var isEnabled = true
|
||||
private var isPrepared = false
|
||||
|
||||
init {
|
||||
try {
|
||||
setupMediaPlayer()
|
||||
setupGameOverPlayer()
|
||||
} catch (e: Exception) {
|
||||
Log.e("GameMusic", "Error initializing: ${e.message}")
|
||||
}
|
||||
|
@ -46,6 +48,49 @@ class GameMusic(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupGameOverPlayer() {
|
||||
try {
|
||||
Log.d("GameMusic", "Setting up GameOver MediaPlayer")
|
||||
gameOverPlayer = MediaPlayer.create(context, R.raw.game_over).apply {
|
||||
setVolume(1.0f, 1.0f) // Increased from 0.7f to 1.0f for maximum volume
|
||||
|
||||
// Set audio attributes for better performance
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
Log.d("GameMusic", "GameOver MediaPlayer setup complete")
|
||||
} catch (e: Exception) {
|
||||
Log.e("GameMusic", "Error setting up GameOver MediaPlayer", e)
|
||||
gameOverPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
fun playGameOver() {
|
||||
if (isEnabled && gameOverPlayer != null) {
|
||||
try {
|
||||
Log.d("GameMusic", "Playing game over sound")
|
||||
// Temporarily lower background music volume
|
||||
mediaPlayer?.setVolume(0.2f, 0.2f)
|
||||
|
||||
// Play game over sound
|
||||
gameOverPlayer?.start()
|
||||
|
||||
// Restore background music volume after a delay
|
||||
gameOverPlayer?.setOnCompletionListener {
|
||||
mediaPlayer?.setVolume(0.5f, 0.5f)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("GameMusic", "Error playing game over sound: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (isEnabled && mediaPlayer != null && isPrepared) {
|
||||
try {
|
||||
|
@ -107,7 +152,9 @@ class GameMusic(private val context: Context) {
|
|||
try {
|
||||
Log.d("GameMusic", "Releasing MediaPlayer")
|
||||
mediaPlayer?.release()
|
||||
gameOverPlayer?.release()
|
||||
mediaPlayer = null
|
||||
gameOverPlayer = null
|
||||
isPrepared = false
|
||||
} catch (e: Exception) {
|
||||
Log.e("GameMusic", "Error releasing music: ${e.message}")
|
||||
|
|
|
@ -91,4 +91,26 @@ class GameHaptics(private val context: Context) {
|
|||
vibrator.vibrate(vibrationEffect)
|
||||
}
|
||||
}
|
||||
|
||||
fun vibrateForGameOver() {
|
||||
Log.d(TAG, "Attempting to vibrate for game over")
|
||||
|
||||
// Only proceed if the device has a vibrator and it's available
|
||||
if (!vibrator.hasVibrator()) return
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Create a strong, long vibration effect
|
||||
val vibrationEffect = VibrationEffect.createOneShot(300L, VibrationEffect.DEFAULT_AMPLITUDE)
|
||||
vibrator.vibrate(vibrationEffect)
|
||||
Log.d(TAG, "Game over vibration triggered successfully")
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrator.vibrate(300L)
|
||||
Log.w(TAG, "Device does not support vibration effects (Android < 8.0)")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error triggering game over vibration", e)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,6 +57,15 @@ class GameView @JvmOverloads constructor(
|
|||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val borderGlowPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 60
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2f
|
||||
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
||||
}
|
||||
|
||||
private val ghostBlockPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 80 // 30% opacity
|
||||
|
@ -89,15 +98,6 @@ class GameView @JvmOverloads constructor(
|
|||
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
|
||||
}
|
||||
|
||||
private val borderGlowPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 60
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2f
|
||||
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
||||
}
|
||||
|
||||
// Add a new paint for the pulse effect
|
||||
private val pulsePaint = Paint().apply {
|
||||
color = Color.CYAN
|
||||
|
@ -150,11 +150,12 @@ class GameView @JvmOverloads constructor(
|
|||
private var lastHoldTime = 0L // Track when the last hold occurred
|
||||
private val holdCooldown = 250L // Minimum time between holds
|
||||
private var lockedDirection: Direction? = null // Track the locked movement direction
|
||||
private val minMovementThreshold = 0.5f // Reduced from 0.75f for more sensitive movement
|
||||
private val directionLockThreshold = 1.5f // Reduced from 2.5f to make direction locking less aggressive
|
||||
private val minMovementThreshold = 0.3f // Reduced from 0.5f for more sensitive horizontal movement
|
||||
private val directionLockThreshold = 1.0f // Reduced from 1.5f to make direction locking less aggressive
|
||||
private val isStrictDirectionLock = true // Re-enabled strict direction locking to prevent diagonal inputs
|
||||
private val minHardDropDistance = 1.5f // Minimum distance (in blocks) for hard drop gesture
|
||||
private val minHardDropDistance = 2.5f // Increased from 1.5f to require more deliberate hard drops
|
||||
private val minHoldDistance = 2.0f // Minimum distance (in blocks) for hold gesture
|
||||
private val maxSoftDropDistance = 1.5f // Maximum distance for soft drop before considering hard drop
|
||||
|
||||
// Block skin
|
||||
private var currentBlockSkin: String = "block_skin_1"
|
||||
|
@ -177,6 +178,38 @@ class GameView @JvmOverloads constructor(
|
|||
private var pulseAlpha = 0f
|
||||
private var isPulsing = false
|
||||
private var linesToPulse = mutableListOf<Int>() // Track which lines are being cleared
|
||||
private var gameOverAnimator: ValueAnimator? = null
|
||||
private var gameOverAlpha = 0f
|
||||
private var isGameOverAnimating = false
|
||||
// Add new game over animation properties
|
||||
private var gameOverBlocksY = mutableListOf<Float>()
|
||||
private var gameOverBlocksX = mutableListOf<Float>()
|
||||
private var gameOverBlocksRotation = mutableListOf<Float>()
|
||||
private var gameOverBlocksSpeed = mutableListOf<Float>()
|
||||
private var gameOverBlocksSize = mutableListOf<Float>()
|
||||
private var gameOverColorTransition = 0f
|
||||
private var gameOverShakeAmount = 0f
|
||||
private var gameOverFinalAlpha = 0.8f
|
||||
|
||||
private val ghostPaint = Paint().apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2f
|
||||
color = Color.WHITE
|
||||
alpha = 180 // Increased from 100 for better visibility
|
||||
}
|
||||
|
||||
private val ghostBackgroundPaint = Paint().apply {
|
||||
style = Paint.Style.FILL
|
||||
color = Color.WHITE
|
||||
alpha = 30 // Very light background for better contrast
|
||||
}
|
||||
|
||||
private val ghostBorderPaint = Paint().apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 1f
|
||||
color = Color.WHITE
|
||||
alpha = 100 // Subtle border for better definition
|
||||
}
|
||||
|
||||
init {
|
||||
// Start with paused state
|
||||
|
@ -297,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
|
||||
|
@ -319,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)
|
||||
|
@ -330,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
|
||||
}
|
||||
|
||||
|
@ -386,12 +450,6 @@ class GameView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
// Skip drawing if paused or game over - faster return
|
||||
if (isPaused || gameBoard.isGameOver) {
|
||||
super.onDraw(canvas)
|
||||
return
|
||||
}
|
||||
|
||||
// Set hardware layer type during draw for better performance
|
||||
val wasHardwareAccelerated = isHardwareAccelerated
|
||||
if (!wasHardwareAccelerated) {
|
||||
|
@ -418,6 +476,132 @@ class GameView @JvmOverloads constructor(
|
|||
// Draw active piece
|
||||
drawActivePiece(canvas)
|
||||
}
|
||||
|
||||
// Draw game over effect if animating
|
||||
if (isGameOverAnimating) {
|
||||
// First layer - full screen glow
|
||||
val gameOverPaint = Paint().apply {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Second layer - color transition effect
|
||||
val gameOverPaint2 = Paint().apply {
|
||||
// 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(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -577,15 +761,43 @@ class GameView @JvmOverloads constructor(
|
|||
val piece = gameBoard.getCurrentPiece() ?: return
|
||||
val ghostY = gameBoard.getGhostY()
|
||||
|
||||
// Draw semi-transparent background for each block
|
||||
for (y in 0 until piece.getHeight()) {
|
||||
for (x in 0 until piece.getWidth()) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val boardX = piece.x + x
|
||||
val boardY = ghostY + y
|
||||
|
||||
// Draw ghost piece regardless of vertical position
|
||||
if (boardX >= 0 && boardX < gameBoard.width) {
|
||||
drawBlock(canvas, boardX, boardY, true, false)
|
||||
val screenX = boardLeft + boardX * blockSize
|
||||
val screenY = boardTop + boardY * blockSize
|
||||
|
||||
// Draw background
|
||||
canvas.drawRect(
|
||||
screenX + 1f,
|
||||
screenY + 1f,
|
||||
screenX + blockSize - 1f,
|
||||
screenY + blockSize - 1f,
|
||||
ghostBackgroundPaint
|
||||
)
|
||||
|
||||
// Draw border
|
||||
canvas.drawRect(
|
||||
screenX + 1f,
|
||||
screenY + 1f,
|
||||
screenX + blockSize - 1f,
|
||||
screenY + blockSize - 1f,
|
||||
ghostBorderPaint
|
||||
)
|
||||
|
||||
// Draw outline
|
||||
canvas.drawRect(
|
||||
screenX + 1f,
|
||||
screenY + 1f,
|
||||
screenX + blockSize - 1f,
|
||||
screenY + blockSize - 1f,
|
||||
ghostPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -892,9 +1104,10 @@ class GameView @JvmOverloads constructor(
|
|||
invalidate()
|
||||
}
|
||||
}
|
||||
// Check for hard drop
|
||||
// Check for hard drop (must be faster and longer than soft drop)
|
||||
else if (deltaY > blockSize * minHardDropDistance &&
|
||||
abs(deltaX) / abs(deltaY) < 0.5f) {
|
||||
abs(deltaX) / abs(deltaY) < 0.5f &&
|
||||
(deltaY / moveTime) * 1000 > minSwipeVelocity) {
|
||||
if (currentTime - lastHardDropTime < hardDropCooldown) {
|
||||
Log.d(TAG, "Hard drop blocked by cooldown - time since last: ${currentTime - lastHardDropTime}ms, cooldown: ${hardDropCooldown}ms")
|
||||
} else {
|
||||
|
@ -905,10 +1118,17 @@ class GameView @JvmOverloads constructor(
|
|||
invalidate()
|
||||
}
|
||||
}
|
||||
// Check for soft drop (slower and shorter than hard drop)
|
||||
else if (deltaY > blockSize * minMovementThreshold &&
|
||||
deltaY < blockSize * maxSoftDropDistance &&
|
||||
(deltaY / moveTime) * 1000 < minSwipeVelocity) {
|
||||
gameBoard.softDrop()
|
||||
invalidate()
|
||||
}
|
||||
// Check for rotation (quick tap with minimal movement)
|
||||
else if (moveTime < minTapTime &&
|
||||
abs(deltaY) < maxTapMovement &&
|
||||
abs(deltaX) < maxTapMovement) {
|
||||
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()
|
||||
|
@ -1076,4 +1296,125 @@ class GameView @JvmOverloads constructor(
|
|||
super.setBackgroundColor(color)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the game over animation
|
||||
*/
|
||||
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()
|
||||
|
||||
// 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).apply {
|
||||
duration = 3000L // Increased from 2000L (2 seconds) to 3000L (3 seconds)
|
||||
interpolator = LinearInterpolator()
|
||||
|
||||
addUpdateListener { animation ->
|
||||
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, progress = $progress")
|
||||
}
|
||||
|
||||
addListener(object : android.animation.AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: android.animation.Animator) {
|
||||
Log.d(TAG, "Game over animation ended - Final alpha: $gameOverFinalAlpha")
|
||||
isGameOverAnimating = true // Keep true to maintain visibility
|
||||
gameOverAlpha = gameOverFinalAlpha // Keep at 80% opacity
|
||||
invalidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,8 +97,13 @@ class GameBoard(
|
|||
if (holdPiece == null) {
|
||||
// If no piece is held, hold current piece and spawn new one
|
||||
holdPiece = current
|
||||
currentPiece = nextPiece
|
||||
spawnNextPiece()
|
||||
spawnPiece()
|
||||
// Reset position of new piece
|
||||
currentPiece?.apply {
|
||||
x = (width - getWidth()) / 2
|
||||
y = 0
|
||||
}
|
||||
} else {
|
||||
// Swap current piece with held piece
|
||||
currentPiece = holdPiece
|
||||
|
@ -131,10 +136,10 @@ class GameBoard(
|
|||
currentPiece = nextPiece
|
||||
spawnNextPiece()
|
||||
|
||||
// Center the piece horizontally
|
||||
// Center the piece horizontally and spawn one unit higher
|
||||
currentPiece?.apply {
|
||||
x = (width - getWidth()) / 2
|
||||
y = 0
|
||||
y = -1 // Spawn one unit above the top of the screen
|
||||
|
||||
Log.d(TAG, "spawnPiece() - new piece spawned at position (${x},${y}), type=${type}")
|
||||
|
||||
|
@ -321,6 +326,11 @@ class GameBoard(
|
|||
if (boardY >= 0 && grid[boardY][boardX]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the position is more than one unit above the top of the screen
|
||||
if (boardY < -1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
BIN
app/src/main/res/raw/game_over.mp3
Normal file
BIN
app/src/main/res/raw/game_over.mp3
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue