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 # 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 ## Features
### Core Gameplay ### Core Gameplay
- Classic Tetris mechanics - Classic block-stacking mechanics
- 7-bag randomizer for piece distribution - 7-bag randomizer for piece distribution
- Ghost piece preview - Ghost piece preview
- Hard drop and soft drop - Hard drop and soft drop
@ -29,7 +29,7 @@ The game features a comprehensive scoring system:
- Single line: 40 points - Single line: 40 points
- Double: 100 points - Double: 100 points
- Triple: 300 points - Triple: 300 points
- Tetris (4 lines): 1200 points - Quad (4 lines): 1200 points
#### Multipliers #### Multipliers
@ -48,15 +48,15 @@ The game features a comprehensive scoring system:
- 4 combos: 2.5x - 4 combos: 2.5x
- 5+ combos: 3.0x - 5+ combos: 3.0x
3. **Back-to-Back Tetris** 3. **Back-to-Back Quad**
- 50% bonus (1.5x) for consecutive Tetris clears - 50% bonus (1.5x) for consecutive quad clears
- Resets if a non-Tetris clear is performed - Resets if a non-quad clear is performed
4. **Perfect Clear** 4. **Perfect Clear**
- 2x for single line - 2x for single line
- 3x for double - 3x for double
- 4x for triple - 4x for triple
- 5x for Tetris - 5x for quad
- Awarded when clearing lines without leaving blocks - Awarded when clearing lines without leaving blocks
5. **All Clear** 5. **All Clear**

View file

@ -53,7 +53,7 @@ class GameHaptics(private val context: Context) {
1 -> (50L * multiplier).toLong() // Single line: short vibration 1 -> (50L * multiplier).toLong() // Single line: short vibration
2 -> (80L * multiplier).toLong() // Double line: slightly longer 2 -> (80L * multiplier).toLong() // Double line: slightly longer
3 -> (120L * multiplier).toLong() // Triple line: even 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() else -> (50L * multiplier).toLong()
} }
@ -61,7 +61,7 @@ class GameHaptics(private val context: Context) {
1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration 1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration
2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration 2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration
3 -> (180 * multiplier).toInt().coerceAtMost(255) // Triple line: strong 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) else -> (80 * multiplier).toInt().coerceAtMost(255)
} }

View file

@ -21,12 +21,80 @@ import android.view.animation.LinearInterpolator
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.view.Display import android.view.Display
import com.pixelmintdrop.model.GameBoard 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.Tetromino
import com.pixelmintdrop.model.TetrominoType import com.pixelmintdrop.model.TetrominoType
import kotlin.math.abs 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( class GameView @JvmOverloads constructor(
context: Context, context: Context,
@ -1296,7 +1364,7 @@ class GameView @JvmOverloads constructor(
// Create new animation // Create new animation
pulseAnimator = ValueAnimator.ofFloat(0f, 1f, 0f).apply { pulseAnimator = ValueAnimator.ofFloat(0f, 1f, 0f).apply {
duration = when (lineCount) { duration = when (lineCount) {
4 -> 2000L // Tetris - longer duration 4 -> 2000L // Quad - longer duration
3 -> 1600L // Triples 3 -> 1600L // Triples
2 -> 1200L // Doubles 2 -> 1200L // Doubles
1 -> 1000L // Singles 1 -> 1000L // Singles

View file

@ -256,7 +256,7 @@ class GamepadController(
1 -> 100 1 -> 100
2 -> 150 2 -> 150
3 -> 200 3 -> 200
else -> 255 // For tetris (4 lines) else -> 255 // For quad (4 lines)
} }
vibrateGamepad(RUMBLE_LINE_CLEAR_DURATION_MS, amplitude) vibrateGamepad(RUMBLE_LINE_CLEAR_DURATION_MS, amplitude)
} }

View file

@ -11,7 +11,7 @@ import android.view.View
import kotlin.math.min import kotlin.math.min
/** /**
* Custom view to display the next Tetromino piece * Custom view to display the next game piece
*/ */
class NextPieceView @JvmOverloads constructor( class NextPieceView @JvmOverloads constructor(
context: Context, context: Context,

View file

@ -33,7 +33,7 @@ class TitleScreen @JvmOverloads constructor(
private val random = Random() private val random = Random()
private var width = 0 private var width = 0
private var height = 0 private var height = 0
private val tetrominosToAdd = mutableListOf<Tetromino>() private val piecesToAdd = mutableListOf<FallingPiece>()
private val highScoreManager = HighScoreManager(context) // Pre-allocate HighScoreManager private val highScoreManager = HighScoreManager(context) // Pre-allocate HighScoreManager
// Touch handling variables // Touch handling variables
@ -50,8 +50,8 @@ class TitleScreen @JvmOverloads constructor(
private var themeColor = Color.WHITE private var themeColor = Color.WHITE
private var backgroundColor = Color.BLACK private var backgroundColor = Color.BLACK
// Define tetromino shapes (I, O, T, S, Z, J, L) // Define piece shapes (I, O, T, S, Z, J, L)
private val tetrominoShapes = arrayOf( private val pieceShapes = arrayOf(
// I // I
arrayOf( arrayOf(
intArrayOf(0, 0, 0, 0), intArrayOf(0, 0, 0, 0),
@ -96,8 +96,8 @@ class TitleScreen @JvmOverloads constructor(
) )
) )
// Tetromino class to represent falling pieces // FallingPiece class to represent falling pieces
private class Tetromino( private class FallingPiece(
var x: Float, var x: Float,
var y: Float, var y: Float,
val shape: Array<IntArray>, val shape: Array<IntArray>,
@ -106,7 +106,7 @@ class TitleScreen @JvmOverloads constructor(
val rotation: Int = 0 val rotation: Int = 0
) )
private val tetrominos = mutableListOf<Tetromino>() private val pieces = mutableListOf<FallingPiece>()
init { init {
// Title text settings // Title text settings
@ -138,14 +138,14 @@ class TitleScreen @JvmOverloads constructor(
alpha = 200 alpha = 200
} }
// General paint settings for tetrominos // General paint settings for pieces
paint.apply { paint.apply {
color = themeColor color = themeColor
style = Paint.Style.FILL style = Paint.Style.FILL
isAntiAlias = true isAntiAlias = true
} }
// Glow paint settings for tetrominos // Glow paint settings for pieces
glowPaint.apply { glowPaint.apply {
color = themeColor color = themeColor
style = Paint.Style.FILL style = Paint.Style.FILL
@ -159,26 +159,26 @@ class TitleScreen @JvmOverloads constructor(
width = w width = w
height = h height = h
// Clear existing tetrominos // Clear existing pieces
tetrominos.clear() pieces.clear()
// Initialize some tetrominos // Initialize some pieces
repeat(20) { repeat(20) {
val tetromino = createRandomTetromino() val piece = createRandomPiece()
tetrominos.add(tetromino) pieces.add(piece)
} }
} }
private fun createRandomTetromino(): Tetromino { private fun createRandomPiece(): FallingPiece {
val x = random.nextFloat() * (width - 150) + 50 // Keep away from edges val x = random.nextFloat() * (width - 150) + 50 // Keep away from edges
val y = -cellSize * 4 - (random.nextFloat() * height / 2) val y = -cellSize * 4 - (random.nextFloat() * height / 2)
val shapeIndex = random.nextInt(tetrominoShapes.size) val shapeIndex = random.nextInt(pieceShapes.size)
val shape = tetrominoShapes[shapeIndex] val shape = pieceShapes[shapeIndex]
val speed = 1f + random.nextFloat() * 2f val speed = 1f + random.nextFloat() * 2f
val scale = 0.8f + random.nextFloat() * 0.4f val scale = 0.8f + random.nextFloat() * 0.4f
val rotation = random.nextInt(4) * 90 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) { override fun onDraw(canvas: Canvas) {
@ -188,37 +188,37 @@ class TitleScreen @JvmOverloads constructor(
// Draw background using the current background color // Draw background using the current background color
canvas.drawColor(backgroundColor) canvas.drawColor(backgroundColor)
// Add any pending tetrominos // Add any pending pieces
tetrominos.addAll(tetrominosToAdd) pieces.addAll(piecesToAdd)
tetrominosToAdd.clear() piecesToAdd.clear()
// Update and draw falling tetrominos // Update and draw falling pieces
val tetrominosToRemove = mutableListOf<Tetromino>() val piecesToRemove = mutableListOf<FallingPiece>()
for (tetromino in tetrominos) { for (piece in pieces) {
tetromino.y += tetromino.speed piece.y += piece.speed
// Remove tetrominos that have fallen off the screen // Remove pieces that have fallen off the screen
if (tetromino.y > height) { if (piece.y > height) {
tetrominosToRemove.add(tetromino) piecesToRemove.add(piece)
tetrominosToAdd.add(createRandomTetromino()) piecesToAdd.add(createRandomPiece())
} else { } else {
try { try {
// Draw the tetromino // Draw the piece
for (y in 0 until tetromino.shape.size) { for (y in 0 until piece.shape.size) {
for (x in 0 until tetromino.shape.size) { for (x in 0 until piece.shape.size) {
if (tetromino.shape[y][x] == 1) { if (piece.shape[y][x] == 1) {
val left = x * cellSize val left = x * cellSize
val top = y * cellSize val top = y * cellSize
val right = left + cellSize val right = left + cellSize
val bottom = top + cellSize val bottom = top + cellSize
// Draw block with glow effect // Draw block with glow effect
canvas.withTranslation(tetromino.x, tetromino.y) { canvas.withTranslation(piece.x, piece.y) {
withScale(tetromino.scale, tetromino.scale) { withScale(piece.scale, piece.scale) {
withRotation(tetromino.rotation.toFloat(), withRotation(piece.rotation.toFloat(),
tetromino.shape.size * cellSize / 2, piece.shape.size * cellSize / 2,
tetromino.shape.size * cellSize / 2) { piece.shape.size * cellSize / 2) {
// Draw glow // Draw glow
canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, glowPaint) canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, glowPaint)
// Draw block // Draw block
@ -230,13 +230,13 @@ class TitleScreen @JvmOverloads constructor(
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("TitleScreen", "Error drawing tetromino", e) Log.e("TitleScreen", "Error drawing piece", e)
} }
} }
} }
// Remove tetrominos that fell off the screen // Remove pieces that fell off the screen
tetrominos.removeAll(tetrominosToRemove) pieces.removeAll(piecesToRemove)
// Draw title // Draw title
val titleY = height * 0.4f val titleY = height * 0.4f
@ -292,10 +292,10 @@ class TitleScreen @JvmOverloads constructor(
val deltaX = event.x - lastTouchX val deltaX = event.x - lastTouchX
val deltaY = event.y - lastTouchY val deltaY = event.y - lastTouchY
// Update tetromino positions // Update piece positions
for (tetromino in tetrominos) { for (piece in pieces) {
tetromino.x += deltaX * 0.5f piece.x += deltaX * 0.5f
tetromino.y += deltaY * 0.5f piece.y += deltaY * 0.5f
} }
lastTouchX = event.x lastTouchX = event.x

View file

@ -17,18 +17,18 @@ class GameBoard(
// True = occupied, False = empty // True = occupied, False = empty
private val grid = Array(height) { BooleanArray(width) { false } } private val grid = Array(height) { BooleanArray(width) { false } }
// Current active tetromino // Current active piece
private var currentPiece: Tetromino? = null private var currentPiece: GamePiece? = null
// Next tetromino to be played // Next piece to be played
private var nextPiece: Tetromino? = null private var nextPiece: GamePiece? = null
// Hold piece // Hold piece
private var holdPiece: Tetromino? = null private var holdPiece: GamePiece? = null
private var canHold = true private var canHold = true
// 7-bag randomizer // 7-bag randomizer
private val bag = mutableListOf<TetrominoType>() private val bag = mutableListOf<GamePieceType>()
// Game state // Game state
var score = 0 var score = 0
@ -43,7 +43,7 @@ class GameBoard(
// Scoring state // Scoring state
private var combo = 0 private var combo = 0
private var lastClearWasTetris = false private var lastClearWasQuad = false
private var lastClearWasPerfect = false private var lastClearWasPerfect = false
private var lastClearWasAllClear = false private var lastClearWasAllClear = false
private var lastPieceClearedLines = false // Track if the last piece placed cleared lines private var lastPieceClearedLines = false // Track if the last piece placed cleared lines
@ -80,12 +80,12 @@ class GameBoard(
private fun spawnNextPiece() { private fun spawnNextPiece() {
// If bag is empty, refill it with all piece types // If bag is empty, refill it with all piece types
if (bag.isEmpty()) { if (bag.isEmpty()) {
bag.addAll(TetrominoType.entries.toTypedArray()) bag.addAll(GamePieceType.entries.toTypedArray())
bag.shuffle() bag.shuffle()
} }
// Take the next piece from the bag // Take the next piece from the bag
nextPiece = Tetromino(bag.removeAt(0)) nextPiece = GamePiece(bag.removeAt(0))
onNextPieceChanged?.invoke() onNextPieceChanged?.invoke()
} }
@ -122,12 +122,12 @@ class GameBoard(
/** /**
* Get the currently held piece * Get the currently held piece
*/ */
fun getHoldPiece(): Tetromino? = holdPiece fun getHoldPiece(): GamePiece? = holdPiece
/** /**
* Get the next piece that will be spawned * 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 * Spawns the current tetromino at the top of the board
@ -541,8 +541,8 @@ class GameBoard(
} }
} else 1.0 } else 1.0
// Calculate back-to-back Tetris bonus // Calculate back-to-back quad bonus
val backToBackMultiplier = if (clearedLines == 4 && lastClearWasTetris) 1.5 else 1.0 val backToBackMultiplier = if (clearedLines == 4 && lastClearWasQuad) 1.5 else 1.0
// Calculate perfect clear bonus // Calculate perfect clear bonus
val perfectClearMultiplier = if (isPerfectClear) { val perfectClearMultiplier = if (isPerfectClear) {
@ -579,7 +579,7 @@ class GameBoard(
}.start() }.start()
// Update line clear state // Update line clear state
lastClearWasTetris = clearedLines == 4 lastClearWasQuad = clearedLines == 4
lastClearWasPerfect = isPerfectClear lastClearWasPerfect = isPerfectClear
lastClearWasAllClear = isAllClear lastClearWasAllClear = isAllClear
@ -597,7 +597,7 @@ class GameBoard(
*/ */
private fun isTSpin(): Boolean { private fun isTSpin(): Boolean {
val piece = currentPiece ?: return false 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 // Count occupied corners around the T piece
var occupiedCorners = 0 var occupiedCorners = 0
@ -637,7 +637,7 @@ class GameBoard(
/** /**
* Get the current tetromino * Get the current tetromino
*/ */
fun getCurrentPiece(): Tetromino? = currentPiece fun getCurrentPiece(): GamePiece? = currentPiece
/** /**
* Check if a cell in the grid is occupied * Check if a cell in the grid is occupied
@ -702,7 +702,7 @@ class GameBoard(
// Reset scoring state // Reset scoring state
combo = 0 combo = 0
lastClearWasTetris = false lastClearWasQuad = false
lastClearWasPerfect = false lastClearWasPerfect = false
lastClearWasAllClear = false lastClearWasAllClear = false
lastPieceClearedLines = 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:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<!-- T-Tetromino --> <!-- T-piece -->
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M36,36h12v12h-12z" /> android:pathData="M36,36h12v12h-12z" />
@ -19,7 +19,7 @@
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M48,48h12v12h-12z" /> android:pathData="M48,48h12v12h-12z" />
<!-- L-Tetromino --> <!-- L-piece -->
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M36,60h12v12h-12z" /> android:pathData="M36,60h12v12h-12z" />