mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 06:05:19 +01:00
Compare commits
8 commits
04b87e8f19
...
21b2513ad4
Author | SHA1 | Date | |
---|---|---|---|
|
21b2513ad4 | ||
|
2d9de31a38 | ||
|
f10c0260c9 | ||
|
fbecf256a2 | ||
|
ff1a0edd2f | ||
|
212e52aea8 | ||
|
de46a20fc0 | ||
|
9bef4bf003 |
11 changed files with 268 additions and 111 deletions
|
@ -62,6 +62,7 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf
|
||||||
- **Swipe down quickly:** Hard drop (instant placement).
|
- **Swipe down quickly:** Hard drop (instant placement).
|
||||||
- **Swipe down slowly:** Soft drop (faster downward movement).
|
- **Swipe down slowly:** Soft drop (faster downward movement).
|
||||||
- **Single tap:** Rotate piece.
|
- **Single tap:** Rotate piece.
|
||||||
|
- **Swipe up:** Hold piece.
|
||||||
|
|
||||||
### Visual Effects
|
### Visual Effects
|
||||||
- Silky-smooth piece movement animations.
|
- Silky-smooth piece movement animations.
|
||||||
|
@ -70,6 +71,8 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf
|
||||||
- Subtle block glow effects.
|
- Subtle block glow effects.
|
||||||
- Clean grid lines for better visibility.
|
- Clean grid lines for better visibility.
|
||||||
- Engaging animated title screen featuring falling pieces.
|
- 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
|
## Technical Details
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId "com.pixelmintdrop"
|
applicationId "com.pixelmintdrop"
|
||||||
minSdk 30
|
minSdk 30
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "0.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,12 @@ class MainActivity : AppCompatActivity(),
|
||||||
binding.gameControlsContainer.visibility = View.GONE
|
binding.gameControlsContainer.visibility = View.GONE
|
||||||
titleScreen.visibility = View.VISIBLE
|
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
|
// Set up pause button to show settings menu
|
||||||
binding.pauseButton.setOnClickListener {
|
binding.pauseButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
|
@ -1132,6 +1138,20 @@ class MainActivity : AppCompatActivity(),
|
||||||
// Check for already connected gamepads
|
// Check for already connected gamepads
|
||||||
checkGamepadConnections()
|
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 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) {
|
if (titleScreen.visibility == View.GONE && gameView.visibility == View.VISIBLE && binding.gameOverContainer.visibility == View.GONE && binding.pauseContainer.visibility == View.GONE) {
|
||||||
resumeGame()
|
resumeGame()
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.pixelmintdrop.model.GameBoard
|
||||||
import com.pixelmintdrop.model.Tetromino
|
import com.pixelmintdrop.model.Tetromino
|
||||||
import com.pixelmintdrop.model.TetrominoType
|
import com.pixelmintdrop.model.TetrominoType
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GameView that renders the Tetris game and handles touch input
|
* GameView that renders the Tetris game and handles touch input
|
||||||
|
@ -58,11 +59,11 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val borderGlowPaint = Paint().apply {
|
private val borderGlowPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
alpha = 60
|
alpha = 90 // Increased from 60
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = 2f
|
strokeWidth = 2f
|
||||||
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER) // Increased from 8f
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ghostBlockPaint = Paint().apply {
|
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 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 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 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 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 val doubleTapTimeout = 400L // Increased from 300ms to 400ms for more lenient double tap detection
|
||||||
private var lastTapX = 0f // X coordinate of last tap
|
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
|
* Calculate dimensions for the board and blocks based on view size
|
||||||
*/
|
*/
|
||||||
private fun calculateDimensions(width: Int, height: Int) {
|
private fun calculateDimensions(width: Int, height: Int) {
|
||||||
// Calculate block size based on available space
|
|
||||||
val horizontalBlocks = gameBoard.width
|
val horizontalBlocks = gameBoard.width
|
||||||
val verticalBlocks = gameBoard.height
|
val verticalBlocks = gameBoard.height
|
||||||
|
// Go back to zero padding for flush border
|
||||||
// Account for all glow effects and borders
|
val borderPadding = 0f
|
||||||
val borderPadding = 16f // Padding for border glow effects
|
|
||||||
|
// Calculate available drawing area (entire view)
|
||||||
// Calculate block size to fit the height exactly, accounting for all padding
|
val availableWidth = width.toFloat() - (borderPadding * 2)
|
||||||
blockSize = (height.toFloat() - (borderPadding * 2)) / verticalBlocks
|
val availableHeight = height.toFloat() - (borderPadding * 2)
|
||||||
|
|
||||||
// Calculate total board width
|
// Calculate potential block sizes based on fitting width and height separately
|
||||||
val totalBoardWidth = blockSize * horizontalBlocks
|
val blockSizeBasedOnWidth = availableWidth / horizontalBlocks
|
||||||
|
val blockSizeBasedOnHeight = availableHeight / verticalBlocks
|
||||||
// Center horizontally
|
|
||||||
boardLeft = (width - totalBoardWidth) / 2
|
// Use the smaller block size to ensure the entire board fits within the padded area
|
||||||
boardTop = borderPadding // Start with border padding from top
|
blockSize = minOf(blockSizeBasedOnWidth, blockSizeBasedOnHeight)
|
||||||
|
|
||||||
// Calculate the total height needed for the board
|
// Calculate the final dimensions of the board using the determined block size
|
||||||
val totalHeight = blockSize * verticalBlocks
|
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 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) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
@ -632,12 +637,19 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
val rect = RectF(left, top, right, bottom)
|
val rect = RectF(left, top, right, bottom)
|
||||||
|
|
||||||
// Draw base border with increased glow
|
// Draw base border with updated glow properties from the paint object
|
||||||
borderGlowPaint.apply {
|
|
||||||
alpha = 80 // Increased from 60
|
|
||||||
maskFilter = BlurMaskFilter(16f, BlurMaskFilter.Blur.OUTER) // Increased from 8f
|
|
||||||
}
|
|
||||||
canvas.drawRect(rect, borderGlowPaint)
|
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
|
// Draw pulsing border if animation is active
|
||||||
if (isPulsing) {
|
if (isPulsing) {
|
||||||
|
|
|
@ -238,13 +238,57 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
// Remove tetrominos that fell off the screen
|
// Remove tetrominos that fell off the screen
|
||||||
tetrominos.removeAll(tetrominosToRemove)
|
tetrominos.removeAll(tetrominosToRemove)
|
||||||
|
|
||||||
// Draw title
|
// Adjust high scores position based on title position
|
||||||
val titleY = height * 0.4f
|
val highScoreY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
canvas.drawText("Pixel Mint Drop", width / 2f, titleY, titlePaint)
|
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 halfway between top and high scores
|
||||||
|
highScoreY / 2
|
||||||
|
} else {
|
||||||
|
// In portrait mode, position halfway between top and high scores
|
||||||
|
highScoreY / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure each word without spaces
|
||||||
|
val pixelWidth = titlePaint.measureText("pixel")
|
||||||
|
val mintWidth = titlePaint.measureText("mint")
|
||||||
|
val dropWidth = titlePaint.measureText("drop")
|
||||||
|
|
||||||
|
// Define even space between words
|
||||||
|
val wordSpacing = 40f // Consistent spacing between words
|
||||||
|
|
||||||
|
// Calculate total width including spacing
|
||||||
|
val totalWidth = pixelWidth + mintWidth + dropWidth + (wordSpacing * 2)
|
||||||
|
|
||||||
|
// Start position for first word to center the entire title
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Save the original color
|
||||||
|
val originalColor = titlePaint.color
|
||||||
|
|
||||||
|
// Draw "mint" in mint color (#3EB489) with consistent spacing
|
||||||
|
titlePaint.color = Color.parseColor("#3EB489")
|
||||||
|
canvas.drawText("mint", startX + pixelWidth + wordSpacing, titleY, titlePaint)
|
||||||
|
|
||||||
|
// Restore the original color and draw "drop" with consistent spacing
|
||||||
|
titlePaint.color = originalColor
|
||||||
|
canvas.drawText("drop", startX + pixelWidth + mintWidth + (wordSpacing * 2), titleY, titlePaint)
|
||||||
|
|
||||||
// Draw high scores using pre-allocated manager
|
// Draw high scores using pre-allocated manager
|
||||||
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
||||||
val highScoreY = height * 0.5f
|
|
||||||
var lastHighScoreY = highScoreY
|
var lastHighScoreY = highScoreY
|
||||||
if (highScores.isNotEmpty()) {
|
if (highScores.isNotEmpty()) {
|
||||||
// Calculate the starting X position to center the entire block of scores
|
// Calculate the starting X position to center the entire block of scores
|
||||||
|
|
|
@ -148,17 +148,26 @@ class GameBoard(
|
||||||
currentPiece = nextPiece
|
currentPiece = nextPiece
|
||||||
spawnNextPiece()
|
spawnNextPiece()
|
||||||
|
|
||||||
// Center the piece horizontally and spawn one unit higher
|
// Center the piece horizontally
|
||||||
currentPiece?.apply {
|
currentPiece?.apply {
|
||||||
x = (width - getWidth()) / 2
|
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}")
|
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()
|
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)) {
|
if (!canMove(0, 0)) {
|
||||||
isGameOver = true
|
isGameOver = true
|
||||||
Log.d(TAG, "spawnPiece() - Game Over condition detected")
|
Log.d(TAG, "spawnPiece() - Game Over condition detected")
|
||||||
|
@ -270,42 +279,58 @@ class GameBoard(
|
||||||
* Rotate the current piece clockwise
|
* Rotate the current piece clockwise
|
||||||
*/
|
*/
|
||||||
fun rotate() {
|
fun rotate() {
|
||||||
currentPiece?.let {
|
currentPiece?.let { piece ->
|
||||||
// Save current rotation
|
// Save current rotation and position
|
||||||
val originalX = it.x
|
val originalRotationIndex = piece.getRotationIndex() // Use getter
|
||||||
val originalY = it.y
|
val originalX = piece.x
|
||||||
|
val originalY = piece.y
|
||||||
|
|
||||||
// Try to rotate
|
// 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
|
// Wall kick logic - try to move the piece if rotation causes collision
|
||||||
if (!canMove(0, 0)) {
|
// Define standard kicks (adjust based on SRS rules if needed)
|
||||||
// Try to move left
|
// Order matters: check common kicks first
|
||||||
if (canMove(-1, 0)) {
|
val kicks = listOf(
|
||||||
it.x--
|
Pair(-1, 0), Pair(1, 0), // Basic wall kicks L/R
|
||||||
}
|
Pair(-1, -1), Pair(1, -1), // Kicks slighty up (sometimes needed)
|
||||||
// Try to move right
|
Pair(0, -1), // Floor kick
|
||||||
else if (canMove(1, 0)) {
|
Pair(-2, 0), Pair(2, 0) // Kicks for I piece
|
||||||
it.x++
|
// Add more complex SRS kicks if necessary
|
||||||
}
|
)
|
||||||
// Try to move 2 spaces (for I piece)
|
|
||||||
else if (canMove(-2, 0)) {
|
var kickApplied = false
|
||||||
it.x -= 2
|
for ((kickX, kickY) in kicks) {
|
||||||
}
|
// Check canMove relative to the ORIGINAL position + kick offset
|
||||||
else if (canMove(2, 0)) {
|
// Temporarily set position for the check
|
||||||
it.x += 2
|
piece.x = originalX + kickX
|
||||||
}
|
piece.y = originalY + kickY
|
||||||
// Try to move up for floor kicks
|
if (canMove(0, 0)) { // Check validity at the kicked position
|
||||||
else if (canMove(0, -1)) {
|
Log.d(TAG, "Rotate CW kick applied: ($kickX, $kickY), new pos: (${piece.x}, ${piece.y})")
|
||||||
it.y--
|
kickApplied = true
|
||||||
}
|
onPieceMove?.invoke()
|
||||||
// Revert if can't find a valid position
|
break // Found a valid kick, stop checking
|
||||||
else {
|
|
||||||
it.rotateCounterClockwise()
|
|
||||||
it.x = originalX
|
|
||||||
it.y = originalY
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
* Rotate the current piece counterclockwise
|
||||||
*/
|
*/
|
||||||
fun rotateCounterClockwise() {
|
fun rotateCounterClockwise() {
|
||||||
currentPiece?.let {
|
currentPiece?.let { piece ->
|
||||||
// Save current rotation
|
// Save current rotation and position
|
||||||
val originalX = it.x
|
val originalRotationIndex = piece.getRotationIndex() // Use getter
|
||||||
val originalY = it.y
|
val originalX = piece.x
|
||||||
|
val originalY = piece.y
|
||||||
|
|
||||||
// Try to rotate
|
// Try to rotate
|
||||||
it.rotateCounterClockwise()
|
piece.rotateCounterClockwise()
|
||||||
|
Log.d(TAG, "Attempting rotate CCW. Original pos: ($originalX, $originalY)")
|
||||||
// Wall kick logic - try to move the piece if rotation causes collision
|
|
||||||
if (!canMove(0, 0)) {
|
// Check if the new rotation is valid at the original position
|
||||||
// Try to move left
|
if (canMove(0, 0)) {
|
||||||
if (canMove(-1, 0)) {
|
// Rotation is valid without kicks
|
||||||
it.x--
|
Log.d(TAG, "Rotate CCW successful without kick at: (${piece.x}, ${piece.y})")
|
||||||
}
|
onPieceMove?.invoke()
|
||||||
// Try to move right
|
return@let // Exit the let block
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
val boardY = newY + y
|
||||||
|
|
||||||
// Check if the position is outside the board horizontally
|
// Check if the position is outside the board horizontally
|
||||||
if (boardX < 0 || boardX >= width) {
|
val isOutsideHorizontal = boardX < 0 || boardX >= width
|
||||||
|
if (isOutsideHorizontal) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,8 +424,8 @@ class GameBoard(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the position is more than one unit above the top of the screen
|
// Check if the position is above the board (top wall collision)
|
||||||
if (boardY < -1) {
|
if (boardY < 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,21 @@ class Tetromino(val type: TetrominoType) {
|
||||||
var x = 0
|
var x = 0
|
||||||
var y = 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
|
* Get the current shape of the tetromino based on rotation
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginTop="24dp"/>
|
android:layout_marginTop="52dp"/>
|
||||||
|
|
||||||
<com.pixelmintdrop.game.HoldPieceView
|
<com.pixelmintdrop.game.HoldPieceView
|
||||||
android:id="@+id/holdPieceView"
|
android:id="@+id/holdPieceView"
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginTop="24dp"/>
|
android:layout_marginTop="52dp"/>
|
||||||
|
|
||||||
<com.pixelmintdrop.game.NextPieceView
|
<com.pixelmintdrop.game.NextPieceView
|
||||||
android:id="@+id/nextPieceView"
|
android:id="@+id/nextPieceView"
|
||||||
|
|
|
@ -26,6 +26,15 @@
|
||||||
android:fontFamily="monospace"
|
android:fontFamily="monospace"
|
||||||
android:layout_marginBottom="24dp"/>
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Enter Name"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/nameInput"
|
android:id="@+id/nameInput"
|
||||||
android:layout_width="280dp"
|
android:layout_width="280dp"
|
||||||
|
|
|
@ -53,7 +53,10 @@
|
||||||
android:id="@+id/hudContainer"
|
android:id="@+id/hudContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginTop="48dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
@ -141,7 +144,7 @@
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="48dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,15 @@
|
||||||
android:fontFamily="monospace"
|
android:fontFamily="monospace"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Enter Name"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/nameInput"
|
android:id="@+id/nameInput"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue