Remove Tetris references and rename to generic game pieces

This commit is contained in:
cmclark00 2025-04-01 05:18:47 -04:00
parent df9957580e
commit e26c6ebd8c
10 changed files with 358 additions and 336 deletions

View file

@ -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**

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}

View file

@ -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,

View file

@ -33,7 +33,7 @@ class TitleScreen @JvmOverloads constructor(
private val random = Random()
private var width = 0
private var height = 0
private val tetrominosToAdd = mutableListOf<Tetromino>()
private val piecesToAdd = mutableListOf<FallingPiece>()
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<IntArray>,
@ -106,7 +106,7 @@ class TitleScreen @JvmOverloads constructor(
val rotation: Int = 0
)
private val tetrominos = mutableListOf<Tetromino>()
private val pieces = mutableListOf<FallingPiece>()
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<Tetromino>()
// Update and draw falling pieces
val piecesToRemove = mutableListOf<FallingPiece>()
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

View file

@ -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<TetrominoType>()
private val bag = mutableListOf<GamePieceType>()
// 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

View file

@ -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<Array<BooleanArray>> = getBlocks(type)
/**
* Get the current shape of the piece based on rotation
*/
fun getShape(): Array<BooleanArray> = 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<Array<BooleanArray>> {
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)
)
)
}
}
}

View file

@ -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<Array<BooleanArray>> = 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<BooleanArray> {
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<Array<BooleanArray>> {
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)
)
)
}
}
}
}

View file

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<!-- T-Tetromino -->
<!-- T-piece -->
<path
android:fillColor="#FFFFFF"
android:pathData="M36,36h12v12h-12z" />
@ -19,7 +19,7 @@
android:fillColor="#FFFFFF"
android:pathData="M48,48h12v12h-12z" />
<!-- L-Tetromino -->
<!-- L-piece -->
<path
android:fillColor="#FFFFFF"
android:pathData="M36,60h12v12h-12z" />