From 9bef4bf003076fd3261102f5a637352e058787d4 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:25:40 -0400 Subject: [PATCH 1/8] Enhance: Improve game mechanics and visuals in GameView and GameBoard - Increased glow effect and adjusted border properties for better visual appeal. - Updated block size calculation to ensure proper fitting within the view. - Modified rotation logic in GameBoard to include advanced wall kick mechanics for smoother gameplay. - Adjusted piece spawning to start from the top row and refined hold mechanics. - Added getter and setter for rotation index in Tetromino for better encapsulation. --- .../java/com/pixelmintdrop/game/GameView.kt | 66 +++--- .../java/com/pixelmintdrop/model/GameBoard.kt | 190 +++++++++++------- .../java/com/pixelmintdrop/model/Tetromino.kt | 15 ++ 3 files changed, 170 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/game/GameView.kt b/app/src/main/java/com/pixelmintdrop/game/GameView.kt index 305e2f8..4eb2e50 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GameView.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GameView.kt @@ -24,6 +24,7 @@ import com.pixelmintdrop.model.GameBoard import com.pixelmintdrop.model.Tetromino import com.pixelmintdrop.model.TetrominoType import kotlin.math.abs +import kotlin.math.min /** * GameView that renders the Tetris game and handles touch input @@ -58,11 +59,11 @@ class GameView @JvmOverloads constructor( private val borderGlowPaint = Paint().apply { color = Color.WHITE - alpha = 60 + alpha = 90 // Increased from 60 isAntiAlias = true style = Paint.Style.STROKE strokeWidth = 2f - maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER) + maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER) // Increased from 8f } private val ghostBlockPaint = Paint().apply { @@ -141,7 +142,7 @@ class GameView @JvmOverloads constructor( private var minSwipeVelocity = 1200 // Increased from 800 to require more deliberate swipes private val maxTapMovement = 30f // Increased from 20f to 30f for more lenient tap detection private val minTapTime = 100L // Minimum time for a tap (in milliseconds) - private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds) + private val rotationCooldown = 100L // Reduced from 150L to allow faster rotation taps private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds) private val doubleTapTimeout = 400L // Increased from 300ms to 400ms for more lenient double tap detection private var lastTapX = 0f // X coordinate of last tap @@ -442,28 +443,32 @@ class GameView @JvmOverloads constructor( * Calculate dimensions for the board and blocks based on view size */ private fun calculateDimensions(width: Int, height: Int) { - // Calculate block size based on available space val horizontalBlocks = gameBoard.width val verticalBlocks = gameBoard.height - - // Account for all glow effects and borders - val borderPadding = 16f // Padding for border glow effects - - // Calculate block size to fit the height exactly, accounting for all padding - blockSize = (height.toFloat() - (borderPadding * 2)) / verticalBlocks - - // Calculate total board width - val totalBoardWidth = blockSize * horizontalBlocks - - // Center horizontally - boardLeft = (width - totalBoardWidth) / 2 - boardTop = borderPadding // Start with border padding from top - - // Calculate the total height needed for the board - val totalHeight = blockSize * verticalBlocks - + // Go back to zero padding for flush border + val borderPadding = 0f + + // Calculate available drawing area (entire view) + val availableWidth = width.toFloat() - (borderPadding * 2) + val availableHeight = height.toFloat() - (borderPadding * 2) + + // Calculate potential block sizes based on fitting width and height separately + val blockSizeBasedOnWidth = availableWidth / horizontalBlocks + val blockSizeBasedOnHeight = availableHeight / verticalBlocks + + // Use the smaller block size to ensure the entire board fits within the padded area + blockSize = minOf(blockSizeBasedOnWidth, blockSizeBasedOnHeight) + + // Calculate the final dimensions of the board using the determined block size + val finalBoardWidth = blockSize * horizontalBlocks + val finalBoardHeight = blockSize * verticalBlocks + + // Center the final board area within the entire view + boardLeft = (width.toFloat() - finalBoardWidth) / 2 + boardTop = (height.toFloat() - finalBoardHeight) / 2 + // Log dimensions for debugging - Log.d(TAG, "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight") + Log.d(TAG, "Board dimensions (Small Padding, Centered): view($width, $height), block($blockSize), board($finalBoardWidth, $finalBoardHeight), pos($boardLeft, $boardTop)") } override fun onDraw(canvas: Canvas) { @@ -632,12 +637,19 @@ class GameView @JvmOverloads constructor( val rect = RectF(left, top, right, bottom) - // Draw base border with increased glow - borderGlowPaint.apply { - alpha = 80 // Increased from 60 - maskFilter = BlurMaskFilter(16f, BlurMaskFilter.Blur.OUTER) // Increased from 8f - } + // Draw base border with updated glow properties from the paint object canvas.drawRect(rect, borderGlowPaint) + + // Draw a sharp inner line for a flush appearance + val sharpBorderPaint = Paint().apply { + color = Color.WHITE + alpha = 90 // Slightly less intense than glow + isAntiAlias = true + style = Paint.Style.STROKE + strokeWidth = 1f // Very thin line + maskFilter = null // No blur + } + canvas.drawRect(rect, sharpBorderPaint) // Draw pulsing border if animation is active if (isPulsing) { diff --git a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt index bca6233..1802863 100644 --- a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt +++ b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt @@ -148,17 +148,26 @@ class GameBoard( currentPiece = nextPiece spawnNextPiece() - // Center the piece horizontally and spawn one unit higher + // Center the piece horizontally currentPiece?.apply { x = (width - getWidth()) / 2 - y = -1 // Spawn one unit above the top of the screen - + // Spawn piece at the top row (y=0) + y = 0 + Log.d(TAG, "spawnPiece() - new piece spawned at position (${x},${y}), type=${type}") - // Set the spawn time for the grace period + // Allow holding again if a new piece spawns naturally + if (!isPieceLocking) { // Avoid resetting hold during lockPiece sequence + canHold = true + } + + // Reset soft drop flag + isPlayerSoftDrop = false + + // Record spawn time for grace period pieceSpawnTime = System.currentTimeMillis() - // Check if the piece can be placed (Game Over condition) + // Check if the piece can be placed at y=0 (Game Over condition) if (!canMove(0, 0)) { isGameOver = true Log.d(TAG, "spawnPiece() - Game Over condition detected") @@ -270,42 +279,58 @@ class GameBoard( * Rotate the current piece clockwise */ fun rotate() { - currentPiece?.let { - // Save current rotation - val originalX = it.x - val originalY = it.y - + currentPiece?.let { piece -> + // Save current rotation and position + val originalRotationIndex = piece.getRotationIndex() // Use getter + val originalX = piece.x + val originalY = piece.y + // Try to rotate - it.rotateClockwise() - + piece.rotateClockwise() + Log.d(TAG, "Attempting rotate CW. Original pos: ($originalX, $originalY)") + + // Check if the new rotation is valid at the original position + if (canMove(0, 0)) { + // Rotation is valid without kicks + Log.d(TAG, "Rotate CW successful without kick at: (${piece.x}, ${piece.y})") + onPieceMove?.invoke() + return@let // Exit the let block + } + // Wall kick logic - try to move the piece if rotation causes collision - if (!canMove(0, 0)) { - // Try to move left - if (canMove(-1, 0)) { - it.x-- - } - // Try to move right - else if (canMove(1, 0)) { - it.x++ - } - // Try to move 2 spaces (for I piece) - else if (canMove(-2, 0)) { - it.x -= 2 - } - else if (canMove(2, 0)) { - it.x += 2 - } - // Try to move up for floor kicks - else if (canMove(0, -1)) { - it.y-- - } - // Revert if can't find a valid position - else { - it.rotateCounterClockwise() - it.x = originalX - it.y = originalY + // Define standard kicks (adjust based on SRS rules if needed) + // Order matters: check common kicks first + val kicks = listOf( + Pair(-1, 0), Pair(1, 0), // Basic wall kicks L/R + Pair(-1, -1), Pair(1, -1), // Kicks slighty up (sometimes needed) + Pair(0, -1), // Floor kick + Pair(-2, 0), Pair(2, 0) // Kicks for I piece + // Add more complex SRS kicks if necessary + ) + + var kickApplied = false + for ((kickX, kickY) in kicks) { + // Check canMove relative to the ORIGINAL position + kick offset + // Temporarily set position for the check + piece.x = originalX + kickX + piece.y = originalY + kickY + if (canMove(0, 0)) { // Check validity at the kicked position + Log.d(TAG, "Rotate CW kick applied: ($kickX, $kickY), new pos: (${piece.x}, ${piece.y})") + kickApplied = true + onPieceMove?.invoke() + break // Found a valid kick, stop checking } } + + // Revert if no kick worked + if (!kickApplied) { + Log.d(TAG, "Rotate CW failed - reverting rotation and position") + piece.setRotationIndex(originalRotationIndex) // Use setter + piece.x = originalX // Revert position + piece.y = originalY + // Do not call onPieceMove if rotation failed + } + // No need for onPieceMove here as it's called when kick succeeds or initial rotation is fine } } @@ -313,42 +338,58 @@ class GameBoard( * Rotate the current piece counterclockwise */ fun rotateCounterClockwise() { - currentPiece?.let { - // Save current rotation - val originalX = it.x - val originalY = it.y - + currentPiece?.let { piece -> + // Save current rotation and position + val originalRotationIndex = piece.getRotationIndex() // Use getter + val originalX = piece.x + val originalY = piece.y + // Try to rotate - it.rotateCounterClockwise() - - // Wall kick logic - try to move the piece if rotation causes collision - if (!canMove(0, 0)) { - // Try to move left - if (canMove(-1, 0)) { - it.x-- - } - // Try to move right - else if (canMove(1, 0)) { - it.x++ - } - // Try to move 2 spaces (for I piece) - else if (canMove(-2, 0)) { - it.x -= 2 - } - else if (canMove(2, 0)) { - it.x += 2 - } - // Try to move up for floor kicks - else if (canMove(0, -1)) { - it.y-- - } - // Revert if can't find a valid position - else { - it.rotateClockwise() - it.x = originalX - it.y = originalY - } + piece.rotateCounterClockwise() + Log.d(TAG, "Attempting rotate CCW. Original pos: ($originalX, $originalY)") + + // Check if the new rotation is valid at the original position + if (canMove(0, 0)) { + // Rotation is valid without kicks + Log.d(TAG, "Rotate CCW successful without kick at: (${piece.x}, ${piece.y})") + onPieceMove?.invoke() + return@let // Exit the let block } + + // Wall kick logic - try to move the piece if rotation causes collision + // Define standard kicks (adjust based on SRS rules if needed) + // Order matters: check common kicks first + val kicks = listOf( + Pair(1, 0), Pair(-1, 0), // Basic wall kicks R/L (opposite order might be better for CCW?) + Pair(1, -1), Pair(-1, -1), // Kicks slighty up + Pair(0, -1), // Floor kick + Pair(2, 0), Pair(-2, 0) // Kicks for I piece + // Add more complex SRS kicks if necessary + ) + + var kickApplied = false + for ((kickX, kickY) in kicks) { + // Check canMove relative to the ORIGINAL position + kick offset + // Temporarily set position for the check + piece.x = originalX + kickX + piece.y = originalY + kickY + if (canMove(0, 0)) { // Check validity at the kicked position + Log.d(TAG, "Rotate CCW kick applied: ($kickX, $kickY), new pos: (${piece.x}, ${piece.y})") + kickApplied = true + onPieceMove?.invoke() + break // Found a valid kick, stop checking + } + } + + // Revert if no kick worked + if (!kickApplied) { + Log.d(TAG, "Rotate CCW failed - reverting rotation and position") + piece.setRotationIndex(originalRotationIndex) // Use setter + piece.x = originalX // Revert position + piece.y = originalY + // Do not call onPieceMove if rotation failed + } + // No need for onPieceMove here as it's called when kick succeeds or initial rotation is fine } } @@ -368,7 +409,8 @@ class GameBoard( val boardY = newY + y // Check if the position is outside the board horizontally - if (boardX < 0 || boardX >= width) { + val isOutsideHorizontal = boardX < 0 || boardX >= width + if (isOutsideHorizontal) { return false } @@ -382,8 +424,8 @@ class GameBoard( return false } - // Check if the position is more than one unit above the top of the screen - if (boardY < -1) { + // Check if the position is above the board (top wall collision) + if (boardY < 0) { return false } } diff --git a/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt b/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt index 6f2d708..f0a02d6 100644 --- a/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt +++ b/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt @@ -17,6 +17,21 @@ class Tetromino(val type: TetrominoType) { var x = 0 var y = 0 + /** + * Get the current rotation index (0-3) + */ + fun getRotationIndex(): Int { + return currentRotation + } + + /** + * Set the current rotation index (0-3) + * Internal visibility allows access within the same module (e.g., from GameBoard) + */ + internal fun setRotationIndex(index: Int) { + currentRotation = index.coerceIn(0, 3) // Ensure value stays within 0-3 + } + /** * Get the current shape of the tetromino based on rotation */ From de46a20fc0b6b241f559b43b795f7ebd2eb2bbf2 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:45:26 -0400 Subject: [PATCH 2/8] Adjust position of hold piece and score/lines to align with playable area --- app/src/main/res/layout-land/activity_main.xml | 4 ++-- app/src/main/res/layout/activity_main.xml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index eb8a729..715a14d 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -70,7 +70,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:layout_marginTop="24dp"/> + android:layout_marginTop="52dp"/> + android:layout_marginTop="52dp"/> @@ -141,7 +144,7 @@ android:layout_width="60dp" android:layout_height="60dp" android:layout_marginStart="16dp" - android:layout_marginTop="16dp" + android:layout_marginTop="48dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> From 212e52aea80ece105637e3dffe95f6753aa18076 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:46:15 -0400 Subject: [PATCH 3/8] Update title screen to show 'Mint' in mint color and disable controls in landscape mode --- .../com/pixelmintdrop/game/TitleScreen.kt | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index dee6d08..9056190 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -238,9 +238,26 @@ class TitleScreen @JvmOverloads constructor( // Remove tetrominos that fell off the screen tetrominos.removeAll(tetrominosToRemove) - // Draw title + // Draw title with "Mint" in mint color val titleY = height * 0.4f - canvas.drawText("Pixel Mint Drop", width / 2f, titleY, titlePaint) + val fullTitle = "Pixel Mint Drop" + val pixelWidth = titlePaint.measureText("Pixel ") + val mintWidth = titlePaint.measureText("Mint") + val dropWidth = titlePaint.measureText(" Drop") + + // Draw "Pixel" in theme color + canvas.drawText("Pixel ", width / 2f - (mintWidth + dropWidth) / 2, titleY, titlePaint) + + // Save the original color + val originalColor = titlePaint.color + + // Draw "Mint" in mint color (#3EB489) + titlePaint.color = Color.parseColor("#3EB489") + canvas.drawText("Mint", width / 2f - (dropWidth) / 2, titleY, titlePaint) + + // Restore the original color and draw "Drop" + titlePaint.color = originalColor + canvas.drawText(" Drop", width / 2f + (mintWidth) / 2, titleY, titlePaint) // Draw high scores using pre-allocated manager val highScores: List = highScoreManager.getHighScores() @@ -280,6 +297,8 @@ class TitleScreen @JvmOverloads constructor( } override fun onTouchEvent(event: MotionEvent): Boolean { + val isLandscape = resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + when (event.action) { MotionEvent.ACTION_DOWN -> { startX = event.x @@ -289,18 +308,22 @@ class TitleScreen @JvmOverloads constructor( return true } MotionEvent.ACTION_MOVE -> { - val deltaX = event.x - lastTouchX - val deltaY = event.y - lastTouchY - - // Update tetromino positions - for (tetromino in tetrominos) { - tetromino.x += deltaX * 0.5f - tetromino.y += deltaY * 0.5f + // Skip tetromino movement in landscape mode + if (!isLandscape) { + val deltaX = event.x - lastTouchX + val deltaY = event.y - lastTouchY + + // Update tetromino positions + for (tetromino in tetrominos) { + tetromino.x += deltaX * 0.5f + tetromino.y += deltaY * 0.5f + } + + invalidate() } lastTouchX = event.x lastTouchY = event.y - invalidate() return true } MotionEvent.ACTION_UP -> { From ff1a0edd2f57d46cdf70c52a5674ac73cc2f9619 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:50:47 -0400 Subject: [PATCH 4/8] Hide control panels in landscape mode when title screen is visible --- .../java/com/pixelmintdrop/MainActivity.kt | 20 +++++++++++++++++ .../com/pixelmintdrop/game/TitleScreen.kt | 22 +++++++------------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/MainActivity.kt b/app/src/main/java/com/pixelmintdrop/MainActivity.kt index da12577..5701d19 100644 --- a/app/src/main/java/com/pixelmintdrop/MainActivity.kt +++ b/app/src/main/java/com/pixelmintdrop/MainActivity.kt @@ -357,6 +357,12 @@ class MainActivity : AppCompatActivity(), binding.gameControlsContainer.visibility = View.GONE titleScreen.visibility = View.VISIBLE + // Hide landscape control panels if in landscape mode and title screen is visible + if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + binding.leftControlsPanel?.visibility = View.GONE + binding.rightControlsPanel?.visibility = View.GONE + } + // Set up pause button to show settings menu binding.pauseButton.setOnClickListener { gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY) @@ -1132,6 +1138,20 @@ class MainActivity : AppCompatActivity(), // Check for already connected gamepads checkGamepadConnections() + // Update visibility of control panels in landscape orientation based on title screen visibility + if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + if (titleScreen.visibility == View.VISIBLE) { + // Hide control panels when title screen is visible + binding.leftControlsPanel?.visibility = View.GONE + binding.rightControlsPanel?.visibility = View.GONE + } else if (gameView.visibility == View.VISIBLE && binding.gameOverContainer.visibility == View.GONE + && binding.pauseContainer.visibility == View.GONE) { + // Show control panels when game is active + binding.leftControlsPanel?.visibility = View.VISIBLE + binding.rightControlsPanel?.visibility = View.VISIBLE + } + } + // If we're on the title screen, don't auto-resume the game if (titleScreen.visibility == View.GONE && gameView.visibility == View.VISIBLE && binding.gameOverContainer.visibility == View.GONE && binding.pauseContainer.visibility == View.GONE) { resumeGame() diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index 9056190..42ba605 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -297,8 +297,6 @@ class TitleScreen @JvmOverloads constructor( } override fun onTouchEvent(event: MotionEvent): Boolean { - val isLandscape = resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE - when (event.action) { MotionEvent.ACTION_DOWN -> { startX = event.x @@ -308,22 +306,18 @@ class TitleScreen @JvmOverloads constructor( return true } MotionEvent.ACTION_MOVE -> { - // Skip tetromino movement in landscape mode - if (!isLandscape) { - val deltaX = event.x - lastTouchX - val deltaY = event.y - lastTouchY - - // Update tetromino positions - for (tetromino in tetrominos) { - tetromino.x += deltaX * 0.5f - tetromino.y += deltaY * 0.5f - } - - invalidate() + val deltaX = event.x - lastTouchX + val deltaY = event.y - lastTouchY + + // Update tetromino positions + for (tetromino in tetrominos) { + tetromino.x += deltaX * 0.5f + tetromino.y += deltaY * 0.5f } lastTouchX = event.x lastTouchY = event.y + invalidate() return true } MotionEvent.ACTION_UP -> { From fbecf256a2773e3b98ef4b8b609ed6544750566e Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:55:09 -0400 Subject: [PATCH 5/8] Fix title text: make lowercase and correct overlapping issue --- .../com/pixelmintdrop/game/TitleScreen.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index 42ba605..55000c5 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -240,24 +240,30 @@ class TitleScreen @JvmOverloads constructor( // Draw title with "Mint" in mint color val titleY = height * 0.4f - val fullTitle = "Pixel Mint Drop" - val pixelWidth = titlePaint.measureText("Pixel ") - val mintWidth = titlePaint.measureText("Mint") - val dropWidth = titlePaint.measureText(" Drop") + val fullTitle = "pixel mint drop" + val pixelWidth = titlePaint.measureText("pixel ") + val mintWidth = titlePaint.measureText("mint") + val dropWidth = titlePaint.measureText(" drop") - // Draw "Pixel" in theme color - canvas.drawText("Pixel ", width / 2f - (mintWidth + dropWidth) / 2, titleY, titlePaint) + // Calculate total width + val totalWidth = pixelWidth + mintWidth + dropWidth + + // Start position for "pixel" + val startX = width / 2f - totalWidth / 2 + + // Draw "pixel" in theme color + canvas.drawText("pixel ", startX, titleY, titlePaint) // Save the original color val originalColor = titlePaint.color - // Draw "Mint" in mint color (#3EB489) + // Draw "mint" in mint color (#3EB489) titlePaint.color = Color.parseColor("#3EB489") - canvas.drawText("Mint", width / 2f - (dropWidth) / 2, titleY, titlePaint) + canvas.drawText("mint", startX + pixelWidth, titleY, titlePaint) - // Restore the original color and draw "Drop" + // Restore the original color and draw "drop" titlePaint.color = originalColor - canvas.drawText(" Drop", width / 2f + (mintWidth) / 2, titleY, titlePaint) + canvas.drawText(" drop", startX + pixelWidth + mintWidth, titleY, titlePaint) // Draw high scores using pre-allocated manager val highScores: List = highScoreManager.getHighScores() From f10c0260c9a42b7a2900412cd08696d0f67eb064 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Tue, 1 Apr 2025 23:59:02 -0400 Subject: [PATCH 6/8] Center title text with even spacing between words --- .../com/pixelmintdrop/game/TitleScreen.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index 55000c5..22f55da 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -238,32 +238,36 @@ class TitleScreen @JvmOverloads constructor( // Remove tetrominos that fell off the screen tetrominos.removeAll(tetrominosToRemove) - // Draw title with "Mint" in mint color + // Draw title with "mint" in mint color val titleY = height * 0.4f - val fullTitle = "pixel mint drop" - val pixelWidth = titlePaint.measureText("pixel ") + + // Measure each word without spaces + val pixelWidth = titlePaint.measureText("pixel") val mintWidth = titlePaint.measureText("mint") - val dropWidth = titlePaint.measureText(" drop") + val dropWidth = titlePaint.measureText("drop") - // Calculate total width - val totalWidth = pixelWidth + mintWidth + dropWidth + // Define even space between words + val wordSpacing = 40f // Consistent spacing between words - // Start position for "pixel" + // Calculate total width including spacing + val totalWidth = pixelWidth + mintWidth + dropWidth + (wordSpacing * 2) + + // Start position for first word to center the entire title val startX = width / 2f - totalWidth / 2 // Draw "pixel" in theme color - canvas.drawText("pixel ", startX, titleY, titlePaint) + canvas.drawText("pixel", startX, titleY, titlePaint) // Save the original color val originalColor = titlePaint.color - // Draw "mint" in mint color (#3EB489) + // Draw "mint" in mint color (#3EB489) with consistent spacing titlePaint.color = Color.parseColor("#3EB489") - canvas.drawText("mint", startX + pixelWidth, titleY, titlePaint) + canvas.drawText("mint", startX + pixelWidth + wordSpacing, titleY, titlePaint) - // Restore the original color and draw "drop" + // Restore the original color and draw "drop" with consistent spacing titlePaint.color = originalColor - canvas.drawText(" drop", startX + pixelWidth + mintWidth, titleY, titlePaint) + canvas.drawText("drop", startX + pixelWidth + mintWidth + (wordSpacing * 2), titleY, titlePaint) // Draw high scores using pre-allocated manager val highScores: List = highScoreManager.getHighScores() From 2d9de31a38313b84b0f11bcff1ee4eb581eda58d Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Wed, 2 Apr 2025 00:05:35 -0400 Subject: [PATCH 7/8] Center title above high scores for both orientations --- .../com/pixelmintdrop/game/TitleScreen.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index 22f55da..18249de 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -238,8 +238,14 @@ class TitleScreen @JvmOverloads constructor( // Remove tetrominos that fell off the screen tetrominos.removeAll(tetrominosToRemove) - // Draw title with "mint" in mint color - val titleY = height * 0.4f + // Position title based on orientation for optimal positioning + val titleY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + // In landscape mode, position in upper third of screen + height * 0.3f + } else { + // In portrait mode, center above high scores + height * 0.35f + } // Measure each word without spaces val pixelWidth = titlePaint.measureText("pixel") @@ -271,7 +277,14 @@ class TitleScreen @JvmOverloads constructor( // Draw high scores using pre-allocated manager val highScores: List = highScoreManager.getHighScores() - val highScoreY = height * 0.5f + + // Adjust high scores position based on title position + val highScoreY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + height * 0.45f // In landscape, position high scores below title with more space + } else { + height * 0.5f // In portrait, keep in middle of screen + } + var lastHighScoreY = highScoreY if (highScores.isNotEmpty()) { // Calculate the starting X position to center the entire block of scores From 21b2513ad457cbc46e4d20fb2cf06a0244e05a51 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Wed, 2 Apr 2025 00:29:50 -0400 Subject: [PATCH 8/8] Enhance: Add hold piece control and theme customization options - Introduced a new control for holding pieces with a swipe up gesture. - Added multiple theme options with a Random Mode that changes themes automatically after clearing lines. - Updated README to reflect new controls and visual features. - Incremented version code and updated version name in build.gradle. - Adjusted title and high score positioning based on screen orientation in TitleScreen.kt. - Added "Enter Name" prompt in high score entry layout for better user experience. --- README.md | 3 ++ app/build.gradle | 4 +-- .../com/pixelmintdrop/game/TitleScreen.kt | 28 +++++++++++-------- .../main/res/layout-land/high_score_entry.xml | 9 ++++++ app/src/main/res/layout/high_score_entry.xml | 9 ++++++ 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index eb42044..3e4bad7 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf - **Swipe down quickly:** Hard drop (instant placement). - **Swipe down slowly:** Soft drop (faster downward movement). - **Single tap:** Rotate piece. +- **Swipe up:** Hold piece. ### Visual Effects - Silky-smooth piece movement animations. @@ -70,6 +71,8 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf - Subtle block glow effects. - Clean grid lines for better visibility. - Engaging animated title screen featuring falling pieces. +- Multiple theme options with ability to change manually or enable Random Mode (unlocked when 2+ themes are available). +- In Random Mode, themes change automatically every 10 line clears (1 level). ## Technical Details diff --git a/app/build.gradle b/app/build.gradle index bcde979..0466731 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.pixelmintdrop" minSdk 30 targetSdk 35 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index 18249de..3fece82 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -238,13 +238,20 @@ class TitleScreen @JvmOverloads constructor( // Remove tetrominos that fell off the screen tetrominos.removeAll(tetrominosToRemove) + // Adjust high scores position based on title position + val highScoreY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + height * 0.45f // In landscape, position high scores below title with more space + } else { + height * 0.5f // In portrait, keep in middle of screen + } + // Position title based on orientation for optimal positioning val titleY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { - // In landscape mode, position in upper third of screen - height * 0.3f + // In landscape mode, position halfway between top and high scores + highScoreY / 2 } else { - // In portrait mode, center above high scores - height * 0.35f + // In portrait mode, position halfway between top and high scores + highScoreY / 2 } // Measure each word without spaces @@ -259,7 +266,11 @@ class TitleScreen @JvmOverloads constructor( val totalWidth = pixelWidth + mintWidth + dropWidth + (wordSpacing * 2) // Start position for first word to center the entire title - val startX = width / 2f - totalWidth / 2 + val startX = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + width / 2f - totalWidth / 2 // Center in landscape + } else { + width / 2f - totalWidth / 2 + 100f // Add offset to the right in portrait + } // Draw "pixel" in theme color canvas.drawText("pixel", startX, titleY, titlePaint) @@ -278,13 +289,6 @@ class TitleScreen @JvmOverloads constructor( // Draw high scores using pre-allocated manager val highScores: List = highScoreManager.getHighScores() - // Adjust high scores position based on title position - val highScoreY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { - height * 0.45f // In landscape, position high scores below title with more space - } else { - height * 0.5f // In portrait, keep in middle of screen - } - var lastHighScoreY = highScoreY if (highScores.isNotEmpty()) { // Calculate the starting X position to center the entire block of scores diff --git a/app/src/main/res/layout-land/high_score_entry.xml b/app/src/main/res/layout-land/high_score_entry.xml index a048da5..dd8f437 100644 --- a/app/src/main/res/layout-land/high_score_entry.xml +++ b/app/src/main/res/layout-land/high_score_entry.xml @@ -26,6 +26,15 @@ android:fontFamily="monospace" android:layout_marginBottom="24dp"/> + + + +