diff --git a/README.md b/README.md index 29b8edd..5bd8440 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # pixelmintdrop -A modern Tetris implementation for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design. +A modern block-stacking puzzle game for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design. ## Features ### Core Gameplay -- Classic Tetris mechanics +- Classic block-stacking mechanics - 7-bag randomizer for piece distribution - Ghost piece preview - Hard drop and soft drop @@ -29,7 +29,7 @@ The game features a comprehensive scoring system: - Single line: 40 points - Double: 100 points - Triple: 300 points -- Tetris (4 lines): 1200 points +- Quad (4 lines): 1200 points #### Multipliers @@ -48,15 +48,15 @@ The game features a comprehensive scoring system: - 4 combos: 2.5x - 5+ combos: 3.0x -3. **Back-to-Back Tetris** - - 50% bonus (1.5x) for consecutive Tetris clears - - Resets if a non-Tetris clear is performed +3. **Back-to-Back Quad** + - 50% bonus (1.5x) for consecutive quad clears + - Resets if a non-quad clear is performed 4. **Perfect Clear** - 2x for single line - 3x for double - 4x for triple - - 5x for Tetris + - 5x for quad - Awarded when clearing lines without leaving blocks 5. **All Clear** diff --git a/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt b/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt index 9f55369..7629065 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GameHaptics.kt @@ -53,7 +53,7 @@ class GameHaptics(private val context: Context) { 1 -> (50L * multiplier).toLong() // Single line: short vibration 2 -> (80L * multiplier).toLong() // Double line: slightly longer 3 -> (120L * multiplier).toLong() // Triple line: even longer - 4 -> (200L * multiplier).toLong() // Tetris: longest vibration + 4 -> (200L * multiplier).toLong() // Quad: longest vibration else -> (50L * multiplier).toLong() } @@ -61,7 +61,7 @@ class GameHaptics(private val context: Context) { 1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration 2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration 3 -> (180 * multiplier).toInt().coerceAtMost(255) // Triple line: strong vibration - 4 -> 255 // Tetris: maximum vibration + 4 -> 255 // Quad: maximum vibration else -> (80 * multiplier).toInt().coerceAtMost(255) } diff --git a/app/src/main/java/com/pixelmintdrop/game/GameView.kt b/app/src/main/java/com/pixelmintdrop/game/GameView.kt index b9cc1c9..2c901cf 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GameView.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GameView.kt @@ -21,12 +21,80 @@ import android.view.animation.LinearInterpolator import android.hardware.display.DisplayManager import android.view.Display import com.pixelmintdrop.model.GameBoard +import com.pixelmintdrop.model.GamePiece +import com.pixelmintdrop.model.GamePieceType +import com.pixelmintdrop.model.PlayerProgressionManager +import com.pixelmintdrop.model.StatsManager +import com.pixelmintdrop.model.ThemeManager +import com.pixelmintdrop.model.ThemeManager.Theme +import com.pixelmintdrop.model.ThemeManager.ThemeType +import com.pixelmintdrop.model.ThemeManager.ThemeVariant +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.* +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_LIGHT +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM +import com.pixelmintdrop.model.ThemeManager.ThemeVariant.SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_SYSTEM_DARK import com.pixelmintdrop.model.Tetromino import com.pixelmintdrop.model.TetrominoType import kotlin.math.abs /** - * GameView that renders the Tetris game and handles touch input + * GameView that renders the block-stacking game and handles touch input */ class GameView @JvmOverloads constructor( context: Context, @@ -1296,7 +1364,7 @@ class GameView @JvmOverloads constructor( // Create new animation pulseAnimator = ValueAnimator.ofFloat(0f, 1f, 0f).apply { duration = when (lineCount) { - 4 -> 2000L // Tetris - longer duration + 4 -> 2000L // Quad - longer duration 3 -> 1600L // Triples 2 -> 1200L // Doubles 1 -> 1000L // Singles diff --git a/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt b/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt index 3c15a92..83718b2 100644 --- a/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt +++ b/app/src/main/java/com/pixelmintdrop/game/GamepadController.kt @@ -256,7 +256,7 @@ class GamepadController( 1 -> 100 2 -> 150 3 -> 200 - else -> 255 // For tetris (4 lines) + else -> 255 // For quad (4 lines) } vibrateGamepad(RUMBLE_LINE_CLEAR_DURATION_MS, amplitude) } diff --git a/app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt b/app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt index d7502b5..707448b 100644 --- a/app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt +++ b/app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt @@ -11,7 +11,7 @@ import android.view.View import kotlin.math.min /** - * Custom view to display the next Tetromino piece + * Custom view to display the next game piece */ class NextPieceView @JvmOverloads constructor( context: Context, diff --git a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt index dee6d08..f9baa6d 100644 --- a/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt +++ b/app/src/main/java/com/pixelmintdrop/game/TitleScreen.kt @@ -33,7 +33,7 @@ class TitleScreen @JvmOverloads constructor( private val random = Random() private var width = 0 private var height = 0 - private val tetrominosToAdd = mutableListOf() + private val piecesToAdd = mutableListOf() private val highScoreManager = HighScoreManager(context) // Pre-allocate HighScoreManager // Touch handling variables @@ -50,8 +50,8 @@ class TitleScreen @JvmOverloads constructor( private var themeColor = Color.WHITE private var backgroundColor = Color.BLACK - // Define tetromino shapes (I, O, T, S, Z, J, L) - private val tetrominoShapes = arrayOf( + // Define piece shapes (I, O, T, S, Z, J, L) + private val pieceShapes = arrayOf( // I arrayOf( intArrayOf(0, 0, 0, 0), @@ -96,8 +96,8 @@ class TitleScreen @JvmOverloads constructor( ) ) - // Tetromino class to represent falling pieces - private class Tetromino( + // FallingPiece class to represent falling pieces + private class FallingPiece( var x: Float, var y: Float, val shape: Array, @@ -106,7 +106,7 @@ class TitleScreen @JvmOverloads constructor( val rotation: Int = 0 ) - private val tetrominos = mutableListOf() + private val pieces = mutableListOf() init { // Title text settings @@ -138,14 +138,14 @@ class TitleScreen @JvmOverloads constructor( alpha = 200 } - // General paint settings for tetrominos + // General paint settings for pieces paint.apply { color = themeColor style = Paint.Style.FILL isAntiAlias = true } - // Glow paint settings for tetrominos + // Glow paint settings for pieces glowPaint.apply { color = themeColor style = Paint.Style.FILL @@ -159,26 +159,26 @@ class TitleScreen @JvmOverloads constructor( width = w height = h - // Clear existing tetrominos - tetrominos.clear() + // Clear existing pieces + pieces.clear() - // Initialize some tetrominos + // Initialize some pieces repeat(20) { - val tetromino = createRandomTetromino() - tetrominos.add(tetromino) + val piece = createRandomPiece() + pieces.add(piece) } } - private fun createRandomTetromino(): Tetromino { + private fun createRandomPiece(): FallingPiece { val x = random.nextFloat() * (width - 150) + 50 // Keep away from edges val y = -cellSize * 4 - (random.nextFloat() * height / 2) - val shapeIndex = random.nextInt(tetrominoShapes.size) - val shape = tetrominoShapes[shapeIndex] + val shapeIndex = random.nextInt(pieceShapes.size) + val shape = pieceShapes[shapeIndex] val speed = 1f + random.nextFloat() * 2f val scale = 0.8f + random.nextFloat() * 0.4f val rotation = random.nextInt(4) * 90 - return Tetromino(x, y, shape, speed, scale, rotation) + return FallingPiece(x, y, shape, speed, scale, rotation) } override fun onDraw(canvas: Canvas) { @@ -188,37 +188,37 @@ class TitleScreen @JvmOverloads constructor( // Draw background using the current background color canvas.drawColor(backgroundColor) - // Add any pending tetrominos - tetrominos.addAll(tetrominosToAdd) - tetrominosToAdd.clear() + // Add any pending pieces + pieces.addAll(piecesToAdd) + piecesToAdd.clear() - // Update and draw falling tetrominos - val tetrominosToRemove = mutableListOf() + // Update and draw falling pieces + val piecesToRemove = mutableListOf() - for (tetromino in tetrominos) { - tetromino.y += tetromino.speed + for (piece in pieces) { + piece.y += piece.speed - // Remove tetrominos that have fallen off the screen - if (tetromino.y > height) { - tetrominosToRemove.add(tetromino) - tetrominosToAdd.add(createRandomTetromino()) + // Remove pieces that have fallen off the screen + if (piece.y > height) { + piecesToRemove.add(piece) + piecesToAdd.add(createRandomPiece()) } else { try { - // Draw the tetromino - for (y in 0 until tetromino.shape.size) { - for (x in 0 until tetromino.shape.size) { - if (tetromino.shape[y][x] == 1) { + // Draw the piece + for (y in 0 until piece.shape.size) { + for (x in 0 until piece.shape.size) { + if (piece.shape[y][x] == 1) { val left = x * cellSize val top = y * cellSize val right = left + cellSize val bottom = top + cellSize // Draw block with glow effect - canvas.withTranslation(tetromino.x, tetromino.y) { - withScale(tetromino.scale, tetromino.scale) { - withRotation(tetromino.rotation.toFloat(), - tetromino.shape.size * cellSize / 2, - tetromino.shape.size * cellSize / 2) { + canvas.withTranslation(piece.x, piece.y) { + withScale(piece.scale, piece.scale) { + withRotation(piece.rotation.toFloat(), + piece.shape.size * cellSize / 2, + piece.shape.size * cellSize / 2) { // Draw glow canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, glowPaint) // Draw block @@ -230,13 +230,13 @@ class TitleScreen @JvmOverloads constructor( } } } catch (e: Exception) { - Log.e("TitleScreen", "Error drawing tetromino", e) + Log.e("TitleScreen", "Error drawing piece", e) } } } - // Remove tetrominos that fell off the screen - tetrominos.removeAll(tetrominosToRemove) + // Remove pieces that fell off the screen + pieces.removeAll(piecesToRemove) // Draw title val titleY = height * 0.4f @@ -292,10 +292,10 @@ class TitleScreen @JvmOverloads constructor( 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 + // Update piece positions + for (piece in pieces) { + piece.x += deltaX * 0.5f + piece.y += deltaY * 0.5f } lastTouchX = event.x diff --git a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt index bf20b4d..f242c28 100644 --- a/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt +++ b/app/src/main/java/com/pixelmintdrop/model/GameBoard.kt @@ -17,18 +17,18 @@ class GameBoard( // True = occupied, False = empty private val grid = Array(height) { BooleanArray(width) { false } } - // Current active tetromino - private var currentPiece: Tetromino? = null + // Current active piece + private var currentPiece: GamePiece? = null - // Next tetromino to be played - private var nextPiece: Tetromino? = null + // Next piece to be played + private var nextPiece: GamePiece? = null // Hold piece - private var holdPiece: Tetromino? = null + private var holdPiece: GamePiece? = null private var canHold = true // 7-bag randomizer - private val bag = mutableListOf() + private val bag = mutableListOf() // Game state var score = 0 @@ -43,7 +43,7 @@ class GameBoard( // Scoring state private var combo = 0 - private var lastClearWasTetris = false + private var lastClearWasQuad = false private var lastClearWasPerfect = false private var lastClearWasAllClear = false private var lastPieceClearedLines = false // Track if the last piece placed cleared lines @@ -80,12 +80,12 @@ class GameBoard( private fun spawnNextPiece() { // If bag is empty, refill it with all piece types if (bag.isEmpty()) { - bag.addAll(TetrominoType.entries.toTypedArray()) + bag.addAll(GamePieceType.entries.toTypedArray()) bag.shuffle() } // Take the next piece from the bag - nextPiece = Tetromino(bag.removeAt(0)) + nextPiece = GamePiece(bag.removeAt(0)) onNextPieceChanged?.invoke() } @@ -122,12 +122,12 @@ class GameBoard( /** * Get the currently held piece */ - fun getHoldPiece(): Tetromino? = holdPiece + fun getHoldPiece(): GamePiece? = holdPiece /** * Get the next piece that will be spawned */ - fun getNextPiece(): Tetromino? = nextPiece + fun getNextPiece(): GamePiece? = nextPiece /** * Spawns the current tetromino at the top of the board @@ -541,8 +541,8 @@ class GameBoard( } } else 1.0 - // Calculate back-to-back Tetris bonus - val backToBackMultiplier = if (clearedLines == 4 && lastClearWasTetris) 1.5 else 1.0 + // Calculate back-to-back quad bonus + val backToBackMultiplier = if (clearedLines == 4 && lastClearWasQuad) 1.5 else 1.0 // Calculate perfect clear bonus val perfectClearMultiplier = if (isPerfectClear) { @@ -579,7 +579,7 @@ class GameBoard( }.start() // Update line clear state - lastClearWasTetris = clearedLines == 4 + lastClearWasQuad = clearedLines == 4 lastClearWasPerfect = isPerfectClear lastClearWasAllClear = isAllClear @@ -597,7 +597,7 @@ class GameBoard( */ private fun isTSpin(): Boolean { val piece = currentPiece ?: return false - if (piece.type != TetrominoType.T) return false + if (piece.type != GamePieceType.T) return false // Count occupied corners around the T piece var occupiedCorners = 0 @@ -637,7 +637,7 @@ class GameBoard( /** * Get the current tetromino */ - fun getCurrentPiece(): Tetromino? = currentPiece + fun getCurrentPiece(): GamePiece? = currentPiece /** * Check if a cell in the grid is occupied @@ -702,7 +702,7 @@ class GameBoard( // Reset scoring state combo = 0 - lastClearWasTetris = false + lastClearWasQuad = false lastClearWasPerfect = false lastClearWasAllClear = false lastPieceClearedLines = false diff --git a/app/src/main/java/com/pixelmintdrop/model/GamePiece.kt b/app/src/main/java/com/pixelmintdrop/model/GamePiece.kt new file mode 100644 index 0000000..7b4116f --- /dev/null +++ b/app/src/main/java/com/pixelmintdrop/model/GamePiece.kt @@ -0,0 +1,214 @@ +package com.pixelmintdrop.model + +/** + * Represents a game piece + */ +enum class GamePieceType { + I, J, L, O, S, T, Z +} + +class GamePiece(val type: GamePieceType) { + private var rotation = 0 + + // Each piece has 4 rotations (0, 90, 180, 270 degrees) + private val blocks: Array> = getBlocks(type) + + /** + * Get the current shape of the piece based on rotation + */ + fun getShape(): Array = blocks[rotation] + + /** + * Get the width of the current piece shape + */ + fun getWidth(): Int = blocks[rotation][0].size + + /** + * Get the height of the current piece shape + */ + fun getHeight(): Int = blocks[rotation].size + + /** + * Rotate the piece clockwise + */ + fun rotateClockwise() { + rotation = (rotation + 1) % 4 + } + + /** + * Rotate the piece counter-clockwise + */ + fun rotateCounterClockwise() { + rotation = (rotation + 3) % 4 + } + + /** + * Check if the piece's block exists at the given coordinates + */ + fun hasBlock(x: Int, y: Int): Boolean { + if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return false + return blocks[rotation][y][x] + } + + /** + * Get the block patterns for each piece type and all its rotations + */ + private fun getBlocks(type: GamePieceType): Array> { + return when (type) { + GamePieceType.I -> arrayOf( + arrayOf( + booleanArrayOf(false, false, false, false), + booleanArrayOf(true, true, true, true), + booleanArrayOf(false, false, false, false), + booleanArrayOf(false, false, false, false) + ), + arrayOf( + booleanArrayOf(false, false, true, false), + booleanArrayOf(false, false, true, false), + booleanArrayOf(false, false, true, false), + booleanArrayOf(false, false, true, false) + ), + arrayOf( + booleanArrayOf(false, false, false, false), + booleanArrayOf(false, false, false, false), + booleanArrayOf(true, true, true, true), + booleanArrayOf(false, false, false, false) + ), + arrayOf( + booleanArrayOf(false, true, false, false), + booleanArrayOf(false, true, false, false), + booleanArrayOf(false, true, false, false), + booleanArrayOf(false, true, false, false) + ) + ), + GamePieceType.J -> arrayOf( + arrayOf( + booleanArrayOf(true, false, false), + booleanArrayOf(true, true, true), + booleanArrayOf(false, false, false) + ), + arrayOf( + booleanArrayOf(false, true, true), + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, false) + ), + arrayOf( + booleanArrayOf(false, false, false), + booleanArrayOf(true, true, true), + booleanArrayOf(false, false, true) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, false), + booleanArrayOf(true, true, false) + ) + ), + GamePieceType.L -> arrayOf( + arrayOf( + booleanArrayOf(false, false, true), + booleanArrayOf(true, true, true), + booleanArrayOf(false, false, false) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, true) + ), + arrayOf( + booleanArrayOf(false, false, false), + booleanArrayOf(true, true, true), + booleanArrayOf(true, false, false) + ), + arrayOf( + booleanArrayOf(true, true, false), + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, false) + ) + ), + GamePieceType.O -> arrayOf( + arrayOf( + booleanArrayOf(true, true), + booleanArrayOf(true, true) + ), + arrayOf( + booleanArrayOf(true, true), + booleanArrayOf(true, true) + ), + arrayOf( + booleanArrayOf(true, true), + booleanArrayOf(true, true) + ), + arrayOf( + booleanArrayOf(true, true), + booleanArrayOf(true, true) + ) + ), + GamePieceType.S -> arrayOf( + arrayOf( + booleanArrayOf(false, true, true), + booleanArrayOf(true, true, false), + booleanArrayOf(false, false, false) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, true), + booleanArrayOf(false, false, true) + ), + arrayOf( + booleanArrayOf(false, false, false), + booleanArrayOf(false, true, true), + booleanArrayOf(true, true, false) + ), + arrayOf( + booleanArrayOf(true, false, false), + booleanArrayOf(true, true, false), + booleanArrayOf(false, true, false) + ) + ), + GamePieceType.T -> arrayOf( + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(true, true, true), + booleanArrayOf(false, false, false) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(false, true, true), + booleanArrayOf(false, true, false) + ), + arrayOf( + booleanArrayOf(false, false, false), + booleanArrayOf(true, true, true), + booleanArrayOf(false, true, false) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(true, true, false), + booleanArrayOf(false, true, false) + ) + ), + GamePieceType.Z -> arrayOf( + arrayOf( + booleanArrayOf(true, true, false), + booleanArrayOf(false, true, true), + booleanArrayOf(false, false, false) + ), + arrayOf( + booleanArrayOf(false, false, true), + booleanArrayOf(false, true, true), + booleanArrayOf(false, true, false) + ), + arrayOf( + booleanArrayOf(false, false, false), + booleanArrayOf(true, true, false), + booleanArrayOf(false, true, true) + ), + arrayOf( + booleanArrayOf(false, true, false), + booleanArrayOf(true, true, false), + booleanArrayOf(true, false, false) + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt b/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt deleted file mode 100644 index 6f2d708..0000000 --- a/app/src/main/java/com/pixelmintdrop/model/Tetromino.kt +++ /dev/null @@ -1,260 +0,0 @@ -package com.pixelmintdrop.model - -/** - * Represents a Tetris piece (Tetromino) - */ -enum class TetrominoType { - I, J, L, O, S, T, Z -} - -class Tetromino(val type: TetrominoType) { - - // Each tetromino has 4 rotations (0, 90, 180, 270 degrees) - private val blocks: Array> = getBlocks(type) - private var currentRotation = 0 - - // Current position in the game grid - var x = 0 - var y = 0 - - /** - * Get the current shape of the tetromino based on rotation - */ - fun getCurrentShape(): Array { - return blocks[currentRotation] - } - - /** - * Get the width of the current tetromino shape - */ - fun getWidth(): Int { - return blocks[currentRotation][0].size - } - - /** - * Get the height of the current tetromino shape - */ - fun getHeight(): Int { - return blocks[currentRotation].size - } - - /** - * Rotate the tetromino clockwise - */ - fun rotateClockwise() { - currentRotation = (currentRotation + 1) % 4 - } - - /** - * Rotate the tetromino counter-clockwise - */ - fun rotateCounterClockwise() { - currentRotation = (currentRotation + 3) % 4 - } - - /** - * Check if the tetromino's block exists at the given coordinates - */ - fun isBlockAt(blockX: Int, blockY: Int): Boolean { - val shape = blocks[currentRotation] - return if (blockY >= 0 && blockY < shape.size && - blockX >= 0 && blockX < shape[blockY].size) { - shape[blockY][blockX] - } else { - false - } - } - - companion object { - /** - * Get the block patterns for each tetromino type and all its rotations - */ - private fun getBlocks(type: TetrominoType): Array> { - return when (type) { - TetrominoType.I -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(false, false, false, false), - booleanArrayOf(true, true, true, true), - booleanArrayOf(false, false, false, false), - booleanArrayOf(false, false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, false, true, false), - booleanArrayOf(false, false, true, false), - booleanArrayOf(false, false, true, false), - booleanArrayOf(false, false, true, false) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false, false), - booleanArrayOf(false, false, false, false), - booleanArrayOf(true, true, true, true), - booleanArrayOf(false, false, false, false) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(false, true, false, false), - booleanArrayOf(false, true, false, false), - booleanArrayOf(false, true, false, false), - booleanArrayOf(false, true, false, false) - ) - ) - TetrominoType.J -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(true, false, false), - booleanArrayOf(true, true, true), - booleanArrayOf(false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, true, true), - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, false) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false), - booleanArrayOf(true, true, true), - booleanArrayOf(false, false, true) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, false), - booleanArrayOf(true, true, false) - ) - ) - TetrominoType.L -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(false, false, true), - booleanArrayOf(true, true, true), - booleanArrayOf(false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, true) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false), - booleanArrayOf(true, true, true), - booleanArrayOf(true, false, false) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(true, true, false), - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, false) - ) - ) - TetrominoType.O -> arrayOf( - // All rotations are the same for O - arrayOf( - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, false, false, false) - ), - arrayOf( - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, false, false, false) - ), - arrayOf( - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, false, false, false) - ), - arrayOf( - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, true, true, false), - booleanArrayOf(false, false, false, false) - ) - ) - TetrominoType.S -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(false, true, true), - booleanArrayOf(true, true, false), - booleanArrayOf(false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, true), - booleanArrayOf(false, false, true) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false), - booleanArrayOf(false, true, true), - booleanArrayOf(true, true, false) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(true, false, false), - booleanArrayOf(true, true, false), - booleanArrayOf(false, true, false) - ) - ) - TetrominoType.T -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(true, true, true), - booleanArrayOf(false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(false, true, true), - booleanArrayOf(false, true, false) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false), - booleanArrayOf(true, true, true), - booleanArrayOf(false, true, false) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(true, true, false), - booleanArrayOf(false, true, false) - ) - ) - TetrominoType.Z -> arrayOf( - // Rotation 0° - arrayOf( - booleanArrayOf(true, true, false), - booleanArrayOf(false, true, true), - booleanArrayOf(false, false, false) - ), - // Rotation 90° - arrayOf( - booleanArrayOf(false, false, true), - booleanArrayOf(false, true, true), - booleanArrayOf(false, true, false) - ), - // Rotation 180° - arrayOf( - booleanArrayOf(false, false, false), - booleanArrayOf(true, true, false), - booleanArrayOf(false, true, true) - ), - // Rotation 270° - arrayOf( - booleanArrayOf(false, true, false), - booleanArrayOf(true, true, false), - booleanArrayOf(true, false, false) - ) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 5591e04..71bd239 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -5,7 +5,7 @@ android:viewportWidth="108" android:viewportHeight="108"> - + @@ -19,7 +19,7 @@ android:fillColor="#FFFFFF" android:pathData="M48,48h12v12h-12z" /> - +