mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-17 23:45:22 +01:00
Update game piece handling and rename tetris to quad
This commit is contained in:
parent
e26c6ebd8c
commit
7babaeca50
10 changed files with 886 additions and 1738 deletions
|
@ -10,7 +10,7 @@ import android.widget.TextView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import com.pixelmintdrop.R
|
import com.pixelmintdrop.R
|
||||||
import com.pixelmintdrop.game.GameView
|
import com.pixelmintdrop.game.GameView
|
||||||
import com.pixelmintdrop.model.TetrominoType
|
import com.pixelmintdrop.model.GamePieceType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to improve the game's accessibility for users with visual impairments
|
* Helper class to improve the game's accessibility for users with visual impairments
|
||||||
|
@ -111,13 +111,13 @@ class GameAccessibilityHelper(private val context: Context) {
|
||||||
val pieceType = gameView.getCurrentPieceType() ?: return "No piece"
|
val pieceType = gameView.getCurrentPieceType() ?: return "No piece"
|
||||||
|
|
||||||
return when (pieceType) {
|
return when (pieceType) {
|
||||||
TetrominoType.I -> "I piece, long bar"
|
GamePieceType.I -> "I piece, long bar"
|
||||||
TetrominoType.J -> "J piece, hook shape pointing left"
|
GamePieceType.J -> "J piece, hook shape pointing left"
|
||||||
TetrominoType.L -> "L piece, hook shape pointing right"
|
GamePieceType.L -> "L piece, hook shape pointing right"
|
||||||
TetrominoType.O -> "O piece, square shape"
|
GamePieceType.O -> "O piece, square shape"
|
||||||
TetrominoType.S -> "S piece, zigzag shape"
|
GamePieceType.S -> "S piece, zigzag shape"
|
||||||
TetrominoType.T -> "T piece, T shape"
|
GamePieceType.T -> "T piece, T shape"
|
||||||
TetrominoType.Z -> "Z piece, reverse zigzag shape"
|
GamePieceType.Z -> "Z piece, reverse zigzag shape"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,4 +134,19 @@ class GameAccessibilityHelper(private val context: Context) {
|
||||||
fun announceLevelUp(view: View, newLevel: Int) {
|
fun announceLevelUp(view: View, newLevel: Int) {
|
||||||
announceGameEvent(view, "Level up! Now at level $newLevel")
|
announceGameEvent(view, "Level up! Now at level $newLevel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the piece name for accessibility announcements
|
||||||
|
*/
|
||||||
|
private fun getPieceName(type: GamePieceType): String {
|
||||||
|
return when (type) {
|
||||||
|
GamePieceType.I -> "I piece"
|
||||||
|
GamePieceType.J -> "J piece"
|
||||||
|
GamePieceType.L -> "L piece"
|
||||||
|
GamePieceType.O -> "O piece"
|
||||||
|
GamePieceType.S -> "S piece"
|
||||||
|
GamePieceType.T -> "T piece"
|
||||||
|
GamePieceType.Z -> "Z piece"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -29,68 +29,6 @@ import com.pixelmintdrop.model.ThemeManager
|
||||||
import com.pixelmintdrop.model.ThemeManager.Theme
|
import com.pixelmintdrop.model.ThemeManager.Theme
|
||||||
import com.pixelmintdrop.model.ThemeManager.ThemeType
|
import com.pixelmintdrop.model.ThemeManager.ThemeType
|
||||||
import com.pixelmintdrop.model.ThemeManager.ThemeVariant
|
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
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,9 +50,13 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
var isPaused = false // Changed from private to public to allow access from MainActivity
|
var isPaused = false
|
||||||
private var score = 0
|
private var score = 0
|
||||||
|
|
||||||
|
// Current piece
|
||||||
|
private var currentPiece: GamePiece? = null
|
||||||
|
private var nextPiece: GamePiece? = null
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
var onNextPieceChanged: (() -> Unit)? = null
|
var onNextPieceChanged: (() -> Unit)? = null
|
||||||
|
|
||||||
|
@ -135,22 +77,22 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val ghostBlockPaint = Paint().apply {
|
private val ghostBlockPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
alpha = 80 // 30% opacity
|
alpha = 80
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val gridPaint = Paint().apply {
|
private val gridPaint = Paint().apply {
|
||||||
color = Color.parseColor("#222222") // Very dark gray
|
color = Color.parseColor("#222222")
|
||||||
alpha = 20 // Reduced from 40 to be more subtle
|
alpha = 20
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
strokeWidth = 1f
|
strokeWidth = 1f
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
maskFilter = null // Ensure no blur effect on grid lines
|
maskFilter = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val glowPaint = Paint().apply {
|
private val glowPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
alpha = 40 // Reduced from 80 for more subtlety
|
alpha = 40
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = 1.5f
|
strokeWidth = 1.5f
|
||||||
|
@ -165,24 +107,20 @@ class GameView @JvmOverloads constructor(
|
||||||
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
|
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new paint for the pulse effect
|
|
||||||
private val pulsePaint = Paint().apply {
|
private val pulsePaint = Paint().apply {
|
||||||
color = Color.CYAN
|
color = Color.CYAN
|
||||||
alpha = 255
|
alpha = 255
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.FILL
|
style = Paint.Style.FILL
|
||||||
maskFilter = BlurMaskFilter(32f, BlurMaskFilter.Blur.OUTER) // Increased from 16f to 32f
|
maskFilter = BlurMaskFilter(32f, BlurMaskFilter.Blur.OUTER)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-allocate paint objects to avoid GC
|
|
||||||
private val tmpPaint = Paint()
|
private val tmpPaint = Paint()
|
||||||
|
|
||||||
// Calculate block size based on view dimensions and board size
|
|
||||||
private var blockSize = 0f
|
private var blockSize = 0f
|
||||||
private var boardLeft = 0f
|
private var boardLeft = 0f
|
||||||
private var boardTop = 0f
|
private var boardTop = 0f
|
||||||
|
|
||||||
// Game loop handler and runnable
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val gameLoopRunnable = object : Runnable {
|
private val gameLoopRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -194,7 +132,6 @@ class GameView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch parameters
|
|
||||||
private var lastTouchX = 0f
|
private var lastTouchX = 0f
|
||||||
private var lastTouchY = 0f
|
private var lastTouchY = 0f
|
||||||
private var startX = 0f
|
private var startX = 0f
|
||||||
|
@ -495,197 +432,29 @@ class GameView @JvmOverloads constructor(
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
|
||||||
// Force hardware acceleration - Critical for performance
|
// Calculate block size based on view dimensions and board size
|
||||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
blockSize = minOf(
|
||||||
|
w / (gameBoard.width + 2f), // Add padding
|
||||||
|
h / (gameBoard.height + 2f) // Add padding
|
||||||
|
)
|
||||||
|
|
||||||
// Update gesture exclusion rect for edge-to-edge rendering
|
// Center the board
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
boardLeft = (w - gameBoard.width * blockSize) / 2f
|
||||||
setSystemGestureExclusionRects(listOf(Rect(0, 0, w, h)))
|
boardTop = (h - gameBoard.height * blockSize) / 2f
|
||||||
}
|
|
||||||
|
|
||||||
calculateDimensions(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate dimensions for the board and blocks based on view size
|
|
||||||
*/
|
|
||||||
private fun calculateDimensions(width: Int, height: Int) {
|
|
||||||
// Calculate block size based on available space
|
|
||||||
val horizontalBlocks = gameBoard.width
|
|
||||||
val verticalBlocks = gameBoard.height
|
|
||||||
|
|
||||||
// Account for all glow effects and borders
|
|
||||||
val borderPadding = 16f // Padding for border glow effects
|
|
||||||
|
|
||||||
// Calculate block size to fit the height exactly, accounting for all padding
|
|
||||||
blockSize = (height.toFloat() - (borderPadding * 2)) / verticalBlocks
|
|
||||||
|
|
||||||
// Calculate total board width
|
|
||||||
val totalBoardWidth = blockSize * horizontalBlocks
|
|
||||||
|
|
||||||
// Center horizontally
|
|
||||||
boardLeft = (width - totalBoardWidth) / 2
|
|
||||||
boardTop = borderPadding // Start with border padding from top
|
|
||||||
|
|
||||||
// Calculate the total height needed for the board
|
|
||||||
val totalHeight = blockSize * verticalBlocks
|
|
||||||
|
|
||||||
// Log dimensions for debugging
|
|
||||||
Log.d(TAG, "Board dimensions: width=$width, height=$height, blockSize=$blockSize, boardLeft=$boardLeft, boardTop=$boardTop, totalHeight=$totalHeight")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
// Set hardware layer type during draw for better performance
|
|
||||||
val wasHardwareAccelerated = isHardwareAccelerated
|
|
||||||
if (!wasHardwareAccelerated) {
|
|
||||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
// Draw background (already black from theme)
|
// Draw the game board
|
||||||
|
drawBoard(canvas)
|
||||||
|
|
||||||
// Draw board border glow
|
// Draw ghost piece
|
||||||
drawBoardBorder(canvas)
|
|
||||||
|
|
||||||
// Draw grid (very subtle)
|
|
||||||
drawGrid(canvas)
|
|
||||||
|
|
||||||
// Draw locked pieces
|
|
||||||
drawLockedBlocks(canvas)
|
|
||||||
|
|
||||||
if (!gameBoard.isGameOver && isRunning) {
|
|
||||||
// Draw ghost piece (landing preview)
|
|
||||||
drawGhostPiece(canvas)
|
drawGhostPiece(canvas)
|
||||||
|
|
||||||
// Draw active piece
|
// Draw current piece
|
||||||
drawActivePiece(canvas)
|
currentPiece?.let { piece ->
|
||||||
}
|
drawPiece(canvas, piece)
|
||||||
|
|
||||||
// Draw game over effect if animating
|
|
||||||
if (isGameOverAnimating) {
|
|
||||||
// First layer - full screen glow
|
|
||||||
val gameOverPaint = Paint().apply {
|
|
||||||
color = Color.RED // Change to red for more striking game over indication
|
|
||||||
alpha = (230 * gameOverAlpha).toInt() // Increased opacity
|
|
||||||
isAntiAlias = true
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
// Only apply blur if alpha is greater than 0
|
|
||||||
if (gameOverAlpha > 0) {
|
|
||||||
maskFilter = BlurMaskFilter(64f * gameOverAlpha, BlurMaskFilter.Blur.OUTER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply screen shake if active
|
|
||||||
if (gameOverShakeAmount > 0) {
|
|
||||||
canvas.save()
|
|
||||||
val shakeOffsetX = (Math.random() * 2 - 1) * gameOverShakeAmount * 20 // Doubled for more visible shake
|
|
||||||
val shakeOffsetY = (Math.random() * 2 - 1) * gameOverShakeAmount * 20
|
|
||||||
canvas.translate(shakeOffsetX.toFloat(), shakeOffsetY.toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gameOverPaint)
|
|
||||||
|
|
||||||
// Second layer - color transition effect
|
|
||||||
val gameOverPaint2 = Paint().apply {
|
|
||||||
// Transition from bright red to theme color
|
|
||||||
val transitionColor = if (gameOverColorTransition < 0.5f) {
|
|
||||||
Color.RED
|
|
||||||
} else {
|
|
||||||
val transition = (gameOverColorTransition - 0.5f) * 2f
|
|
||||||
val red = Color.red(currentThemeColor)
|
|
||||||
val green = Color.green(currentThemeColor)
|
|
||||||
val blue = Color.blue(currentThemeColor)
|
|
||||||
Color.argb(
|
|
||||||
(150 * gameOverAlpha).toInt(), // Increased opacity
|
|
||||||
(255 - (255-red) * transition).toInt(),
|
|
||||||
(green * transition).toInt(), // Transition from 0 (red) to theme green
|
|
||||||
(blue * transition).toInt() // Transition from 0 (red) to theme blue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
color = transitionColor
|
|
||||||
alpha = (150 * gameOverAlpha).toInt() // Increased opacity
|
|
||||||
isAntiAlias = true
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
// Only apply blur if alpha is greater than 0
|
|
||||||
if (gameOverAlpha > 0) {
|
|
||||||
maskFilter = BlurMaskFilter(48f * gameOverAlpha, BlurMaskFilter.Blur.OUTER) // Increased blur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gameOverPaint2)
|
|
||||||
|
|
||||||
// Draw "GAME OVER" text
|
|
||||||
if (gameOverAlpha > 0.5f) {
|
|
||||||
val textPaint = Paint().apply {
|
|
||||||
color = Color.WHITE
|
|
||||||
alpha = (255 * Math.min(1f, (gameOverAlpha - 0.5f) * 2)).toInt()
|
|
||||||
isAntiAlias = true
|
|
||||||
textSize = blockSize * 1.5f // Reduced from 2f to 1.5f to fit on screen
|
|
||||||
textAlign = Paint.Align.CENTER
|
|
||||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
|
||||||
style = Paint.Style.FILL_AND_STROKE
|
|
||||||
strokeWidth = blockSize * 0.08f // Proportionally reduced from 0.1f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw text with glow
|
|
||||||
val glowPaint = Paint(textPaint).apply {
|
|
||||||
maskFilter = BlurMaskFilter(blockSize * 0.4f, BlurMaskFilter.Blur.NORMAL) // Reduced from 0.5f
|
|
||||||
alpha = (200 * Math.min(1f, (gameOverAlpha - 0.5f) * 2)).toInt()
|
|
||||||
color = Color.RED
|
|
||||||
strokeWidth = blockSize * 0.15f // Reduced from 0.2f
|
|
||||||
}
|
|
||||||
|
|
||||||
val xPos = width / 2f
|
|
||||||
val yPos = height / 3f
|
|
||||||
|
|
||||||
// Measure text width to check if it fits
|
|
||||||
val textWidth = textPaint.measureText("GAME OVER")
|
|
||||||
|
|
||||||
// If text would still be too wide, scale it down further
|
|
||||||
if (textWidth > width * 0.9f) {
|
|
||||||
val scaleFactor = width * 0.9f / textWidth
|
|
||||||
textPaint.textSize *= scaleFactor
|
|
||||||
glowPaint.textSize *= scaleFactor
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawText("GAME OVER", xPos, yPos, glowPaint)
|
|
||||||
canvas.drawText("GAME OVER", xPos, yPos, textPaint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw falling blocks
|
|
||||||
if (gameOverBlocksY.isNotEmpty()) {
|
|
||||||
for (i in gameOverBlocksY.indices) {
|
|
||||||
val x = gameOverBlocksX[i]
|
|
||||||
val y = gameOverBlocksY[i]
|
|
||||||
val rotation = gameOverBlocksRotation[i]
|
|
||||||
val size = gameOverBlocksSize[i] * blockSize
|
|
||||||
|
|
||||||
// Skip blocks that have fallen off the screen
|
|
||||||
if (y > height) continue
|
|
||||||
|
|
||||||
// Draw each falling block with rotation
|
|
||||||
canvas.save()
|
|
||||||
canvas.translate(x, y)
|
|
||||||
canvas.rotate(rotation)
|
|
||||||
|
|
||||||
// Create a pulsing effect for the falling blocks
|
|
||||||
val blockPaint = Paint(blockPaint)
|
|
||||||
blockPaint.alpha = (255 * gameOverAlpha * (1.0f - y / height.toFloat() * 0.7f)).toInt()
|
|
||||||
|
|
||||||
// Draw block with glow effect
|
|
||||||
val blockGlowPaint = Paint(blockGlowPaint)
|
|
||||||
blockGlowPaint.alpha = (200 * gameOverAlpha * (1.0f - y / height.toFloat() * 0.5f)).toInt()
|
|
||||||
canvas.drawRect(-size/2, -size/2, size/2, size/2, blockGlowPaint)
|
|
||||||
canvas.drawRect(-size/2, -size/2, size/2, size/2, blockPaint)
|
|
||||||
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset any transformations from screen shake
|
|
||||||
if (gameOverShakeAmount > 0) {
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -843,219 +612,52 @@ class GameView @JvmOverloads constructor(
|
||||||
* Draw the ghost piece (landing preview)
|
* Draw the ghost piece (landing preview)
|
||||||
*/
|
*/
|
||||||
private fun drawGhostPiece(canvas: Canvas) {
|
private fun drawGhostPiece(canvas: Canvas) {
|
||||||
val piece = gameBoard.getCurrentPiece() ?: return
|
currentPiece?.let { piece ->
|
||||||
val ghostY = gameBoard.getGhostY()
|
val ghostY = calculateGhostY(piece)
|
||||||
|
val originalY = piece.y
|
||||||
|
piece.y = ghostY
|
||||||
|
|
||||||
// Draw semi-transparent background for each block
|
ghostBlockPaint.alpha = 50
|
||||||
for (y in 0 until piece.getHeight()) {
|
drawPiece(canvas, piece, alpha = 50)
|
||||||
for (x in 0 until piece.getWidth()) {
|
|
||||||
|
piece.y = originalY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateGhostY(piece: GamePiece): Int {
|
||||||
|
var testY = piece.y
|
||||||
|
while (testY < gameBoard.height && !gameBoard.wouldCollide(piece, piece.x, testY + 1)) {
|
||||||
|
testY++
|
||||||
|
}
|
||||||
|
return testY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawPiece(canvas: Canvas, piece: GamePiece, offsetX: Float = 0f, offsetY: Float = 0f, alpha: Int = 255) {
|
||||||
|
val width = piece.getWidth()
|
||||||
|
val height = piece.getHeight()
|
||||||
|
|
||||||
|
blockPaint.alpha = alpha
|
||||||
|
for (y in 0 until height) {
|
||||||
|
for (x in 0 until width) {
|
||||||
if (piece.isBlockAt(x, y)) {
|
if (piece.isBlockAt(x, y)) {
|
||||||
val boardX = piece.x + x
|
val left = offsetX + (piece.x + x) * blockSize
|
||||||
val boardY = ghostY + y
|
val top = offsetY + (piece.y + y) * blockSize
|
||||||
|
|
||||||
if (boardX >= 0 && boardX < gameBoard.width) {
|
|
||||||
val screenX = boardLeft + boardX * blockSize
|
|
||||||
val screenY = boardTop + boardY * blockSize
|
|
||||||
|
|
||||||
// Draw background
|
|
||||||
canvas.drawRect(
|
|
||||||
screenX + 1f,
|
|
||||||
screenY + 1f,
|
|
||||||
screenX + blockSize - 1f,
|
|
||||||
screenY + blockSize - 1f,
|
|
||||||
ghostBackgroundPaint
|
|
||||||
)
|
|
||||||
|
|
||||||
// Draw border
|
|
||||||
canvas.drawRect(
|
|
||||||
screenX + 1f,
|
|
||||||
screenY + 1f,
|
|
||||||
screenX + blockSize - 1f,
|
|
||||||
screenY + blockSize - 1f,
|
|
||||||
ghostBorderPaint
|
|
||||||
)
|
|
||||||
|
|
||||||
// Draw outline
|
|
||||||
canvas.drawRect(
|
|
||||||
screenX + 1f,
|
|
||||||
screenY + 1f,
|
|
||||||
screenX + blockSize - 1f,
|
|
||||||
screenY + blockSize - 1f,
|
|
||||||
ghostPaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a single tetris block at the given grid position
|
|
||||||
*/
|
|
||||||
private fun drawBlock(canvas: Canvas, x: Int, y: Int, isGhost: Boolean, isPulsingLine: Boolean) {
|
|
||||||
val left = boardLeft + x * blockSize
|
|
||||||
val top = boardTop + y * blockSize
|
|
||||||
val right = left + blockSize
|
val right = left + blockSize
|
||||||
val bottom = top + blockSize
|
val bottom = top + blockSize
|
||||||
|
|
||||||
// Save canvas state before drawing block effects
|
// Draw block with subtle glow
|
||||||
canvas.save()
|
blockGlowPaint.alpha = alpha / 4
|
||||||
|
canvas.drawRect(left, top, right, bottom, blockGlowPaint)
|
||||||
|
|
||||||
// Get the current block skin paint
|
// Draw the main block
|
||||||
val paint = blockSkinPaints[currentBlockSkin] ?: blockSkinPaints["block_skin_1"]!!
|
canvas.drawRect(left + 1f, top + 1f, right - 1f, bottom - 1f, blockPaint)
|
||||||
|
|
||||||
// Create a clone of the paint to avoid modifying the original
|
// Draw border glow
|
||||||
val blockPaint = Paint(paint)
|
glowPaint.alpha = alpha / 2
|
||||||
|
canvas.drawRect(left, top, right, bottom, glowPaint)
|
||||||
// Draw block based on current skin
|
|
||||||
when (currentBlockSkin) {
|
|
||||||
"block_skin_1" -> { // Classic
|
|
||||||
// Draw outer glow
|
|
||||||
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
|
|
||||||
canvas.drawRect(left - 2f, top - 2f, right + 2f, bottom + 2f, blockGlowPaint)
|
|
||||||
|
|
||||||
// Draw block
|
|
||||||
blockPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
|
|
||||||
blockPaint.alpha = if (isGhost) 30 else 255
|
|
||||||
canvas.drawRect(left, top, right, bottom, blockPaint)
|
|
||||||
|
|
||||||
// Draw inner glow
|
|
||||||
glowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
|
|
||||||
canvas.drawRect(left + 1f, top + 1f, right - 1f, bottom - 1f, glowPaint)
|
|
||||||
}
|
|
||||||
"block_skin_2" -> { // Neon
|
|
||||||
// Stronger outer glow for neon skin
|
|
||||||
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 0, 255) else Color.parseColor("#FF00FF")
|
|
||||||
blockGlowPaint.maskFilter = BlurMaskFilter(16f, BlurMaskFilter.Blur.OUTER)
|
|
||||||
canvas.drawRect(left - 4f, top - 4f, right + 4f, bottom + 4f, blockGlowPaint)
|
|
||||||
|
|
||||||
// For neon, use semi-translucent fill with strong glowing edges
|
|
||||||
blockPaint.style = Paint.Style.FILL_AND_STROKE
|
|
||||||
blockPaint.strokeWidth = 2f
|
|
||||||
blockPaint.maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL)
|
|
||||||
|
|
||||||
if (isGhost) {
|
|
||||||
blockPaint.color = Color.argb(30, 255, 0, 255)
|
|
||||||
blockPaint.alpha = 30
|
|
||||||
} else {
|
|
||||||
blockPaint.color = Color.parseColor("#66004D") // Darker magenta fill
|
|
||||||
blockPaint.alpha = 170 // More opaque to be more visible
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw block with neon effect
|
|
||||||
canvas.drawRect(left, top, right, bottom, blockPaint)
|
|
||||||
|
|
||||||
// Draw a brighter border for better visibility
|
|
||||||
val borderPaint = Paint().apply {
|
|
||||||
color = Color.parseColor("#FF00FF")
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
strokeWidth = 3f
|
|
||||||
alpha = 255
|
|
||||||
isAntiAlias = true
|
|
||||||
maskFilter = BlurMaskFilter(6f, BlurMaskFilter.Blur.NORMAL)
|
|
||||||
}
|
|
||||||
canvas.drawRect(left, top, right, bottom, borderPaint)
|
|
||||||
|
|
||||||
// Inner glow for neon blocks
|
|
||||||
glowPaint.color = if (isGhost) Color.argb(10, 255, 0, 255) else Color.parseColor("#FF00FF")
|
|
||||||
glowPaint.alpha = if (isGhost) 10 else 100
|
|
||||||
glowPaint.style = Paint.Style.STROKE
|
|
||||||
glowPaint.strokeWidth = 2f
|
|
||||||
glowPaint.maskFilter = BlurMaskFilter(4f, BlurMaskFilter.Blur.NORMAL)
|
|
||||||
canvas.drawRect(left + 4f, top + 4f, right - 4f, bottom - 4f, glowPaint)
|
|
||||||
}
|
|
||||||
"block_skin_3" -> { // Retro
|
|
||||||
// Draw pixelated block with retro effect
|
|
||||||
blockPaint.color = if (isGhost) Color.argb(30, 255, 90, 95) else Color.parseColor("#FF5A5F")
|
|
||||||
blockPaint.alpha = if (isGhost) 30 else 255
|
|
||||||
|
|
||||||
// Draw main block
|
|
||||||
canvas.drawRect(left, top, right, bottom, blockPaint)
|
|
||||||
|
|
||||||
// Draw pixelated highlights
|
|
||||||
val highlightPaint = Paint().apply {
|
|
||||||
color = Color.parseColor("#FF8A8F")
|
|
||||||
isAntiAlias = false
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
}
|
|
||||||
// Top and left highlights
|
|
||||||
canvas.drawRect(left, top, right - 2f, top + 2f, highlightPaint)
|
|
||||||
canvas.drawRect(left, top, left + 2f, bottom - 2f, highlightPaint)
|
|
||||||
|
|
||||||
// Draw pixelated shadows
|
|
||||||
val shadowPaint = Paint().apply {
|
|
||||||
color = Color.parseColor("#CC4A4F")
|
|
||||||
isAntiAlias = false
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
}
|
|
||||||
// Bottom and right shadows
|
|
||||||
canvas.drawRect(left + 2f, bottom - 2f, right, bottom, shadowPaint)
|
|
||||||
canvas.drawRect(right - 2f, top + 2f, right, bottom - 2f, shadowPaint)
|
|
||||||
}
|
|
||||||
"block_skin_4" -> { // Minimalist
|
|
||||||
// Draw clean, simple block with subtle border
|
|
||||||
blockPaint.color = if (isGhost) Color.argb(30, 0, 0, 0) else Color.BLACK
|
|
||||||
blockPaint.alpha = if (isGhost) 30 else 255
|
|
||||||
blockPaint.style = Paint.Style.FILL
|
|
||||||
canvas.drawRect(left, top, right, bottom, blockPaint)
|
|
||||||
|
|
||||||
// Draw subtle border
|
|
||||||
val borderPaint = Paint().apply {
|
|
||||||
color = Color.parseColor("#333333")
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
strokeWidth = 1f
|
|
||||||
isAntiAlias = true
|
|
||||||
}
|
|
||||||
canvas.drawRect(left, top, right, bottom, borderPaint)
|
|
||||||
}
|
|
||||||
"block_skin_5" -> { // Galaxy
|
|
||||||
// Draw cosmic glow effect
|
|
||||||
blockGlowPaint.color = if (isGhost) Color.argb(30, 102, 252, 241) else Color.parseColor("#66FCF1")
|
|
||||||
blockGlowPaint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.OUTER)
|
|
||||||
canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, blockGlowPaint)
|
|
||||||
|
|
||||||
// Draw main block with gradient
|
|
||||||
val gradient = LinearGradient(
|
|
||||||
left, top, right, bottom,
|
|
||||||
Color.parseColor("#66FCF1"),
|
|
||||||
Color.parseColor("#45B7AF"),
|
|
||||||
Shader.TileMode.CLAMP
|
|
||||||
)
|
|
||||||
blockPaint.shader = gradient
|
|
||||||
blockPaint.color = if (isGhost) Color.argb(30, 102, 252, 241) else Color.parseColor("#66FCF1")
|
|
||||||
blockPaint.alpha = if (isGhost) 30 else 255
|
|
||||||
blockPaint.style = Paint.Style.FILL
|
|
||||||
canvas.drawRect(left, top, right, bottom, blockPaint)
|
|
||||||
|
|
||||||
// Draw star-like sparkles
|
|
||||||
if (!isGhost) {
|
|
||||||
val sparklePaint = Paint().apply {
|
|
||||||
color = Color.WHITE
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
isAntiAlias = true
|
|
||||||
maskFilter = BlurMaskFilter(4f, BlurMaskFilter.Blur.NORMAL)
|
|
||||||
}
|
|
||||||
// Add small white dots for sparkle effect
|
|
||||||
canvas.drawCircle(left + 4f, top + 4f, 1f, sparklePaint)
|
|
||||||
canvas.drawCircle(right - 4f, bottom - 4f, 1f, sparklePaint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw pulse effect if animation is active and this is a pulsing line
|
|
||||||
if (isPulsing && isPulsingLine) {
|
|
||||||
val pulseBlockPaint = Paint().apply {
|
|
||||||
color = Color.WHITE
|
|
||||||
alpha = (255 * pulseAlpha).toInt()
|
|
||||||
isAntiAlias = true
|
|
||||||
style = Paint.Style.FILL
|
|
||||||
maskFilter = BlurMaskFilter(40f * (1f + pulseAlpha), BlurMaskFilter.Blur.OUTER)
|
|
||||||
}
|
|
||||||
canvas.drawRect(left - 16f, top - 16f, right + 16f, bottom + 16f, pulseBlockPaint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore canvas state after drawing block effects
|
|
||||||
canvas.restore()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1093,16 +695,7 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
// Custom touch event handling
|
// Custom touch event handling
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
if (!isRunning || isPaused || gameBoard.isGameOver) {
|
if (!isRunning || isPaused) return false
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore touch events during the freeze period after a piece locks
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime < touchFreezeUntil) {
|
|
||||||
Log.d(TAG, "Ignoring touch event - freeze active for ${touchFreezeUntil - currentTime}ms more")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
@ -1110,121 +703,54 @@ class GameView @JvmOverloads constructor(
|
||||||
startY = event.y
|
startY = event.y
|
||||||
lastTouchX = event.x
|
lastTouchX = event.x
|
||||||
lastTouchY = event.y
|
lastTouchY = event.y
|
||||||
lastTapTime = currentTime // Set the tap time when touch starts
|
lastTapTime = System.currentTimeMillis()
|
||||||
|
return true
|
||||||
// Reset direction lock
|
|
||||||
lockedDirection = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
val deltaX = event.x - lastTouchX
|
val dx = event.x - lastTouchX
|
||||||
val deltaY = event.y - lastTouchY
|
val dy = event.y - lastTouchY
|
||||||
|
|
||||||
// Check if we should lock direction
|
// Horizontal movement
|
||||||
if (lockedDirection == null) {
|
if (abs(dx) > blockSize / 2) {
|
||||||
val absDeltaX = abs(deltaX)
|
if (dx > 0) {
|
||||||
val absDeltaY = abs(deltaY)
|
movePieceRight()
|
||||||
|
|
||||||
if (absDeltaX > blockSize * directionLockThreshold ||
|
|
||||||
absDeltaY > blockSize * directionLockThreshold) {
|
|
||||||
// Lock to the dominant direction
|
|
||||||
lockedDirection = if (absDeltaX > absDeltaY) {
|
|
||||||
Direction.HORIZONTAL
|
|
||||||
} else {
|
} else {
|
||||||
Direction.VERTICAL
|
movePieceLeft()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle movement based on locked direction
|
|
||||||
when (lockedDirection) {
|
|
||||||
Direction.HORIZONTAL -> {
|
|
||||||
if (abs(deltaX) > blockSize * minMovementThreshold) {
|
|
||||||
if (deltaX > 0) {
|
|
||||||
gameBoard.moveRight()
|
|
||||||
} else {
|
|
||||||
gameBoard.moveLeft()
|
|
||||||
}
|
}
|
||||||
lastTouchX = event.x
|
lastTouchX = event.x
|
||||||
if (currentTime - lastMoveTime >= moveCooldown) {
|
|
||||||
gameHaptics?.vibrateForPieceMove()
|
|
||||||
lastMoveTime = currentTime
|
|
||||||
}
|
}
|
||||||
invalidate()
|
|
||||||
}
|
// Vertical movement (soft drop)
|
||||||
}
|
if (dy > blockSize / 2) {
|
||||||
Direction.VERTICAL -> {
|
dropPiece()
|
||||||
if (deltaY > blockSize * minMovementThreshold) {
|
|
||||||
gameBoard.softDrop()
|
|
||||||
lastTouchY = event.y
|
lastTouchY = event.y
|
||||||
if (currentTime - lastMoveTime >= moveCooldown) {
|
|
||||||
gameHaptics?.vibrateForPieceMove()
|
|
||||||
lastMoveTime = currentTime
|
|
||||||
}
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
null -> {
|
|
||||||
// No direction lock yet, don't process movement
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
val deltaX = event.x - startX
|
val dx = event.x - startX
|
||||||
val deltaY = event.y - startY
|
val dy = event.y - startY
|
||||||
val moveTime = currentTime - lastTapTime
|
val tapTime = System.currentTimeMillis() - lastTapTime
|
||||||
|
|
||||||
// Handle taps for rotation
|
// Detect tap for rotation
|
||||||
if (moveTime < minTapTime * 1.5 &&
|
if (abs(dx) < blockSize / 2 && abs(dy) < blockSize / 2 && tapTime < 200) {
|
||||||
abs(deltaY) < maxTapMovement * 1.5 &&
|
rotatePiece()
|
||||||
abs(deltaX) < maxTapMovement * 1.5) {
|
|
||||||
|
|
||||||
if (currentTime - lastRotationTime >= rotationCooldown) {
|
|
||||||
gameBoard.rotate()
|
|
||||||
lastRotationTime = currentTime
|
|
||||||
gameHaptics?.vibrateForPieceMove()
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle gestures
|
// Detect swipe down for hard drop
|
||||||
// Check for hold gesture (swipe up)
|
if (dy > height / 4) {
|
||||||
if (deltaY < -blockSize * minHoldDistance &&
|
hardDrop()
|
||||||
abs(deltaX) / abs(deltaY) < 0.5f) {
|
|
||||||
if (currentTime - lastHoldTime >= holdCooldown) {
|
|
||||||
gameBoard.holdPiece()
|
|
||||||
lastHoldTime = currentTime
|
|
||||||
gameHaptics?.vibrateForPieceMove()
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for hard drop (must be faster and longer than soft drop)
|
|
||||||
else if (deltaY > blockSize * minHardDropDistance &&
|
|
||||||
abs(deltaX) / abs(deltaY) < 0.5f &&
|
|
||||||
(deltaY / moveTime) * 1000 > minSwipeVelocity) {
|
|
||||||
if (currentTime - lastHardDropTime >= hardDropCooldown) {
|
|
||||||
gameBoard.hardDrop()
|
|
||||||
lastHardDropTime = currentTime
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for soft drop (slower and shorter than hard drop)
|
|
||||||
else if (deltaY > blockSize * minMovementThreshold &&
|
|
||||||
deltaY < blockSize * maxSoftDropDistance &&
|
|
||||||
(deltaY / moveTime) * 1000 < minSwipeVelocity) {
|
|
||||||
gameBoard.softDrop()
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset direction lock
|
|
||||||
lockedDirection = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current score
|
* Get the current score
|
||||||
|
@ -1249,7 +775,7 @@ class GameView @JvmOverloads constructor(
|
||||||
/**
|
/**
|
||||||
* Get the next piece that will be spawned
|
* Get the next piece that will be spawned
|
||||||
*/
|
*/
|
||||||
fun getNextPiece(): Tetromino? {
|
fun getNextPiece(): GamePiece? {
|
||||||
return gameBoard.getNextPiece()
|
return gameBoard.getNextPiece()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1364,7 +890,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 // Quad - longer duration
|
4 -> 2000L // Tetris - longer duration
|
||||||
3 -> 1600L // Triples
|
3 -> 1600L // Triples
|
||||||
2 -> 1200L // Doubles
|
2 -> 1200L // Doubles
|
||||||
1 -> 1000L // Singles
|
1 -> 1000L // Singles
|
||||||
|
@ -1609,4 +1135,103 @@ class GameView @JvmOverloads constructor(
|
||||||
gameHaptics?.vibrateForPieceMove()
|
gameHaptics?.vibrateForPieceMove()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getNextPiece(): GamePiece? = nextPiece
|
||||||
|
|
||||||
|
fun getCurrentPiece(): GamePiece? = currentPiece
|
||||||
|
|
||||||
|
fun spawnNewPiece() {
|
||||||
|
currentPiece = gameBoard.spawnNewPiece()
|
||||||
|
nextPiece = gameBoard.getNextPiece()
|
||||||
|
onNextPieceChanged?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotatePiece() {
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
if (gameBoard.rotatePiece(piece)) {
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun movePieceLeft() {
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
if (gameBoard.movePiece(piece, -1, 0)) {
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun movePieceRight() {
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
if (gameBoard.movePiece(piece, 1, 0)) {
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dropPiece() {
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
if (gameBoard.movePiece(piece, 0, 1)) {
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hardDrop() {
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
val ghostY = calculateGhostY(piece)
|
||||||
|
if (ghostY > piece.y) {
|
||||||
|
piece.y = ghostY
|
||||||
|
gameBoard.lockPiece(piece)
|
||||||
|
spawnNewPiece()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startGame() {
|
||||||
|
if (!isRunning) {
|
||||||
|
isRunning = true
|
||||||
|
isPaused = false
|
||||||
|
gameBoard.reset()
|
||||||
|
spawnNewPiece()
|
||||||
|
handler.post(gameLoopRunnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pauseGame() {
|
||||||
|
if (isRunning && !isPaused) {
|
||||||
|
isPaused = true
|
||||||
|
handler.removeCallbacks(gameLoopRunnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resumeGame() {
|
||||||
|
if (isRunning && isPaused) {
|
||||||
|
isPaused = false
|
||||||
|
handler.post(gameLoopRunnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopGame() {
|
||||||
|
isRunning = false
|
||||||
|
isPaused = false
|
||||||
|
handler.removeCallbacks(gameLoopRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetGame() {
|
||||||
|
stopGame()
|
||||||
|
gameBoard.reset()
|
||||||
|
currentPiece = null
|
||||||
|
nextPiece = null
|
||||||
|
score = 0
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getScore(): Int = score
|
||||||
|
|
||||||
|
fun setGameHaptics(haptics: GameHaptics?) {
|
||||||
|
gameHaptics = haptics
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,20 +46,19 @@ class NextPieceView @JvmOverloads constructor(
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
// Get the next piece from game view
|
// Get the next piece from game view
|
||||||
gameView?.let {
|
gameView?.getNextPiece()?.let { piece ->
|
||||||
it.getNextPiece()?.let { piece ->
|
|
||||||
val width = piece.getWidth()
|
val width = piece.getWidth()
|
||||||
val height = piece.getHeight()
|
val height = piece.getHeight()
|
||||||
|
|
||||||
// Calculate block size for the preview (smaller than main board)
|
// Calculate block size for the preview (smaller than main board)
|
||||||
val previewBlockSize = min(
|
val previewBlockSize = min(
|
||||||
canvas.width.toFloat() / (width + 2),
|
canvas.width.toFloat() / (width.toFloat() + 2f),
|
||||||
canvas.height.toFloat() / (height + 2)
|
canvas.height.toFloat() / (height.toFloat() + 2f)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Center the piece in the preview area
|
// Center the piece in the preview area
|
||||||
val previewLeft = (canvas.width - width * previewBlockSize) / 2
|
val previewLeft = (canvas.width.toFloat() - width.toFloat() * previewBlockSize) / 2f
|
||||||
val previewTop = (canvas.height - height * previewBlockSize) / 2
|
val previewTop = (canvas.height.toFloat() - height.toFloat() * previewBlockSize) / 2f
|
||||||
|
|
||||||
// Draw subtle background glow
|
// Draw subtle background glow
|
||||||
val glowPaint = Paint().apply {
|
val glowPaint = Paint().apply {
|
||||||
|
@ -70,21 +69,21 @@ class NextPieceView @JvmOverloads constructor(
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
previewLeft - previewBlockSize,
|
previewLeft - previewBlockSize,
|
||||||
previewTop - previewBlockSize,
|
previewTop - previewBlockSize,
|
||||||
previewLeft + width * previewBlockSize + previewBlockSize,
|
previewLeft + width.toFloat() * previewBlockSize + previewBlockSize,
|
||||||
previewTop + height * previewBlockSize + previewBlockSize,
|
previewTop + height.toFloat() * previewBlockSize + previewBlockSize,
|
||||||
glowPaint
|
glowPaint
|
||||||
)
|
)
|
||||||
|
|
||||||
for (y in 0 until height) {
|
for (y in 0 until height) {
|
||||||
for (x in 0 until width) {
|
for (x in 0 until width) {
|
||||||
if (piece.isBlockAt(x, y)) {
|
if (piece.isBlockAt(x, y)) {
|
||||||
val left = previewLeft + x * previewBlockSize
|
val left = previewLeft + x.toFloat() * previewBlockSize
|
||||||
val top = previewTop + y * previewBlockSize
|
val top = previewTop + y.toFloat() * previewBlockSize
|
||||||
val right = left + previewBlockSize
|
val right = left + previewBlockSize
|
||||||
val bottom = top + previewBlockSize
|
val bottom = top + previewBlockSize
|
||||||
|
|
||||||
// Draw block with subtle glow
|
// Draw block with subtle glow
|
||||||
val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
|
val rect = RectF(left + 1f, top + 1f, right - 1f, bottom - 1f)
|
||||||
canvas.drawRect(rect, blockPaint)
|
canvas.drawRect(rect, blockPaint)
|
||||||
|
|
||||||
// Draw subtle border glow
|
// Draw subtle border glow
|
||||||
|
@ -96,4 +95,3 @@ class NextPieceView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -3,763 +3,197 @@ package com.pixelmintdrop.model
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the game board (grid) and manages game state
|
* Represents the game board and manages piece placement and collision detection
|
||||||
*/
|
*/
|
||||||
class GameBoard(
|
class GameBoard {
|
||||||
val width: Int = 10,
|
|
||||||
val height: Int = 20
|
|
||||||
) {
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "GameBoard"
|
const val WIDTH = 10
|
||||||
|
const val HEIGHT = 20
|
||||||
|
const val INITIAL_DROP_INTERVAL = 1000L // 1 second
|
||||||
|
const val MIN_DROP_INTERVAL = 100L // 0.1 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Board grid to track locked pieces
|
// Board state
|
||||||
// True = occupied, False = empty
|
private val board = Array(HEIGHT) { Array(WIDTH) { false } }
|
||||||
private val grid = Array(height) { BooleanArray(width) { false } }
|
|
||||||
|
|
||||||
// Current active piece
|
|
||||||
private var currentPiece: GamePiece? = null
|
private var currentPiece: GamePiece? = null
|
||||||
|
|
||||||
// Next piece to be played
|
|
||||||
private var nextPiece: GamePiece? = null
|
private var nextPiece: GamePiece? = null
|
||||||
|
|
||||||
// Hold piece
|
|
||||||
private var holdPiece: GamePiece? = null
|
|
||||||
private var canHold = true
|
|
||||||
|
|
||||||
// 7-bag randomizer
|
|
||||||
private val bag = mutableListOf<GamePieceType>()
|
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
var score = 0
|
var dropInterval = INITIAL_DROP_INTERVAL
|
||||||
var level = 1
|
private set
|
||||||
var startingLevel = 1 // Add this line to track the starting level
|
|
||||||
var lines = 0
|
|
||||||
var isGameOver = false
|
|
||||||
var isHardDropInProgress = false // Make public
|
|
||||||
var isPieceLocking = false // Make public
|
|
||||||
private var isPlayerSoftDrop = false // Track if the drop is player-initiated
|
|
||||||
private var lastLevel = 1 // Add this to track the previous level
|
|
||||||
|
|
||||||
// Scoring state
|
val width: Int = WIDTH
|
||||||
private var combo = 0
|
val height: Int = HEIGHT
|
||||||
private var lastClearWasQuad = false
|
|
||||||
private var lastClearWasPerfect = false
|
|
||||||
private var lastClearWasAllClear = false
|
|
||||||
private var lastPieceClearedLines = false // Track if the last piece placed cleared lines
|
|
||||||
|
|
||||||
// Animation state
|
fun reset() {
|
||||||
var linesToClear = mutableListOf<Int>()
|
// Clear the board
|
||||||
var isLineClearAnimationInProgress = false
|
for (y in 0 until HEIGHT) {
|
||||||
|
for (x in 0 until WIDTH) {
|
||||||
// Initial game speed (milliseconds per drop)
|
board[y][x] = false
|
||||||
var dropInterval = 1000L
|
|
||||||
|
|
||||||
// Callbacks for game events
|
|
||||||
var onPieceMove: (() -> Unit)? = null
|
|
||||||
var onPieceLock: (() -> Unit)? = null
|
|
||||||
var onNextPieceChanged: (() -> Unit)? = null
|
|
||||||
var onLineClear: ((Int, List<Int>) -> Unit)? = null
|
|
||||||
var onPiecePlaced: (() -> Unit)? = null // New callback for when a piece is placed
|
|
||||||
|
|
||||||
// Store the last cleared lines
|
|
||||||
private val lastClearedLines = mutableListOf<Int>()
|
|
||||||
|
|
||||||
// Add spawn protection variables
|
|
||||||
private var pieceSpawnTime = 0L
|
|
||||||
private val spawnGracePeriod = 250L // Changed from 150ms to 250ms
|
|
||||||
|
|
||||||
init {
|
|
||||||
spawnNextPiece()
|
|
||||||
spawnPiece()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the next tetromino piece using 7-bag randomizer
|
|
||||||
*/
|
|
||||||
private fun spawnNextPiece() {
|
|
||||||
// If bag is empty, refill it with all piece types
|
|
||||||
if (bag.isEmpty()) {
|
|
||||||
bag.addAll(GamePieceType.entries.toTypedArray())
|
|
||||||
bag.shuffle()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the next piece from the bag
|
|
||||||
nextPiece = GamePiece(bag.removeAt(0))
|
|
||||||
onNextPieceChanged?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hold the current piece
|
|
||||||
*/
|
|
||||||
fun holdPiece() {
|
|
||||||
if (!canHold) return
|
|
||||||
|
|
||||||
val current = currentPiece
|
|
||||||
if (holdPiece == null) {
|
|
||||||
// If no piece is held, hold current piece and spawn new one
|
|
||||||
holdPiece = current
|
|
||||||
currentPiece = nextPiece
|
|
||||||
spawnNextPiece()
|
|
||||||
// Reset position of new piece
|
|
||||||
currentPiece?.apply {
|
|
||||||
x = (width - getWidth()) / 2
|
|
||||||
y = 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Swap current piece with held piece
|
|
||||||
currentPiece = holdPiece
|
|
||||||
holdPiece = current
|
|
||||||
// Reset position of swapped piece
|
|
||||||
currentPiece?.apply {
|
|
||||||
x = (width - getWidth()) / 2
|
|
||||||
y = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
canHold = false
|
|
||||||
|
// Reset pieces
|
||||||
|
currentPiece = null
|
||||||
|
nextPiece = null
|
||||||
|
|
||||||
|
// Reset drop interval
|
||||||
|
dropInterval = INITIAL_DROP_INTERVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun spawnNewPiece(): GamePiece {
|
||||||
* Get the currently held piece
|
// Get the next piece or create a new one if none exists
|
||||||
*/
|
currentPiece = nextPiece ?: createRandomPiece()
|
||||||
fun getHoldPiece(): GamePiece? = holdPiece
|
nextPiece = createRandomPiece()
|
||||||
|
|
||||||
|
// Position the piece at the top center of the board
|
||||||
|
currentPiece?.let { piece ->
|
||||||
|
piece.x = (WIDTH - piece.getWidth()) / 2
|
||||||
|
piece.y = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentPiece!!
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next piece that will be spawned
|
|
||||||
*/
|
|
||||||
fun getNextPiece(): GamePiece? = nextPiece
|
fun getNextPiece(): GamePiece? = nextPiece
|
||||||
|
|
||||||
/**
|
fun getCurrentPiece(): GamePiece? = currentPiece
|
||||||
* Spawns the current tetromino at the top of the board
|
|
||||||
*/
|
|
||||||
fun spawnPiece() {
|
|
||||||
Log.d(TAG, "spawnPiece() started - current states: isHardDropInProgress=$isHardDropInProgress, isPieceLocking=$isPieceLocking")
|
|
||||||
|
|
||||||
currentPiece = nextPiece
|
fun hasBlockAt(x: Int, y: Int): Boolean {
|
||||||
spawnNextPiece()
|
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return false
|
||||||
|
return board[y][x]
|
||||||
// Center the piece horizontally and spawn one unit higher
|
|
||||||
currentPiece?.apply {
|
|
||||||
x = (width - getWidth()) / 2
|
|
||||||
y = -1 // Spawn one unit above the top of the screen
|
|
||||||
|
|
||||||
Log.d(TAG, "spawnPiece() - new piece spawned at position (${x},${y}), type=${type}")
|
|
||||||
|
|
||||||
// Set the spawn time for the grace period
|
|
||||||
pieceSpawnTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
// Check if the piece can be placed (Game Over condition)
|
|
||||||
if (!canMove(0, 0)) {
|
|
||||||
isGameOver = true
|
|
||||||
Log.d(TAG, "spawnPiece() - Game Over condition detected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun wouldCollide(piece: GamePiece, newX: Int, newY: Int): Boolean {
|
||||||
* Move the current piece left
|
val width = piece.getWidth()
|
||||||
*/
|
val height = piece.getHeight()
|
||||||
fun moveLeft() {
|
|
||||||
if (canMove(-1, 0)) {
|
|
||||||
currentPiece?.x = currentPiece?.x?.minus(1) ?: 0
|
|
||||||
onPieceMove?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
for (y in 0 until height) {
|
||||||
* Move the current piece right
|
for (x in 0 until width) {
|
||||||
*/
|
|
||||||
fun moveRight() {
|
|
||||||
if (canMove(1, 0)) {
|
|
||||||
currentPiece?.x = currentPiece?.x?.plus(1) ?: 0
|
|
||||||
onPieceMove?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move the current piece down (soft drop)
|
|
||||||
*/
|
|
||||||
fun moveDown(): Boolean {
|
|
||||||
// Don't allow movement if a hard drop is in progress or piece is locking
|
|
||||||
if (isHardDropInProgress || isPieceLocking) return false
|
|
||||||
|
|
||||||
return if (canMove(0, 1)) {
|
|
||||||
currentPiece?.y = currentPiece?.y?.plus(1) ?: 0
|
|
||||||
// Only add soft drop points if it's a player-initiated drop
|
|
||||||
if (isPlayerSoftDrop) {
|
|
||||||
score += 1
|
|
||||||
}
|
|
||||||
onPieceMove?.invoke()
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// Check if we're within the spawn grace period
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - pieceSpawnTime < spawnGracePeriod) {
|
|
||||||
Log.d(TAG, "moveDown() - not locking piece due to spawn grace period (${currentTime - pieceSpawnTime}ms < ${spawnGracePeriod}ms)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
lockPiece()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Player-initiated soft drop
|
|
||||||
*/
|
|
||||||
fun softDrop() {
|
|
||||||
isPlayerSoftDrop = true
|
|
||||||
moveDown()
|
|
||||||
isPlayerSoftDrop = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hard drop the current piece
|
|
||||||
*/
|
|
||||||
fun hardDrop() {
|
|
||||||
if (isHardDropInProgress || isPieceLocking) {
|
|
||||||
Log.d(TAG, "hardDrop() called but blocked: isHardDropInProgress=$isHardDropInProgress, isPieceLocking=$isPieceLocking")
|
|
||||||
return // Prevent multiple hard drops
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're within the spawn grace period
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - pieceSpawnTime < spawnGracePeriod) {
|
|
||||||
Log.d(TAG, "hardDrop() - blocked due to spawn grace period (${currentTime - pieceSpawnTime}ms < ${spawnGracePeriod}ms)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "hardDrop() started - setting isHardDropInProgress=true")
|
|
||||||
isHardDropInProgress = true
|
|
||||||
val piece = currentPiece ?: return
|
|
||||||
|
|
||||||
// Count how many cells the piece will drop
|
|
||||||
var dropDistance = 0
|
|
||||||
while (canMove(0, dropDistance + 1)) {
|
|
||||||
dropDistance++
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "hardDrop() - piece will drop $dropDistance cells, position before: (${piece.x},${piece.y})")
|
|
||||||
|
|
||||||
// Move piece down until it can't move anymore
|
|
||||||
while (canMove(0, 1)) {
|
|
||||||
piece.y++
|
|
||||||
onPieceMove?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "hardDrop() - piece final position: (${piece.x},${piece.y})")
|
|
||||||
|
|
||||||
// Add hard drop points (2 points per cell)
|
|
||||||
score += dropDistance * 2
|
|
||||||
|
|
||||||
// Lock the piece immediately
|
|
||||||
lockPiece()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotate the current piece clockwise
|
|
||||||
*/
|
|
||||||
fun rotate() {
|
|
||||||
currentPiece?.let {
|
|
||||||
// Save current rotation
|
|
||||||
val originalX = it.x
|
|
||||||
val originalY = it.y
|
|
||||||
|
|
||||||
// Try to rotate
|
|
||||||
it.rotateClockwise()
|
|
||||||
|
|
||||||
// Wall kick logic - try to move the piece if rotation causes collision
|
|
||||||
if (!canMove(0, 0)) {
|
|
||||||
// Try to move left
|
|
||||||
if (canMove(-1, 0)) {
|
|
||||||
it.x--
|
|
||||||
}
|
|
||||||
// Try to move right
|
|
||||||
else if (canMove(1, 0)) {
|
|
||||||
it.x++
|
|
||||||
}
|
|
||||||
// Try to move 2 spaces (for I piece)
|
|
||||||
else if (canMove(-2, 0)) {
|
|
||||||
it.x -= 2
|
|
||||||
}
|
|
||||||
else if (canMove(2, 0)) {
|
|
||||||
it.x += 2
|
|
||||||
}
|
|
||||||
// Try to move up for floor kicks
|
|
||||||
else if (canMove(0, -1)) {
|
|
||||||
it.y--
|
|
||||||
}
|
|
||||||
// Revert if can't find a valid position
|
|
||||||
else {
|
|
||||||
it.rotateCounterClockwise()
|
|
||||||
it.x = originalX
|
|
||||||
it.y = originalY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotate the current piece counterclockwise
|
|
||||||
*/
|
|
||||||
fun rotateCounterClockwise() {
|
|
||||||
currentPiece?.let {
|
|
||||||
// Save current rotation
|
|
||||||
val originalX = it.x
|
|
||||||
val originalY = it.y
|
|
||||||
|
|
||||||
// Try to rotate
|
|
||||||
it.rotateCounterClockwise()
|
|
||||||
|
|
||||||
// Wall kick logic - try to move the piece if rotation causes collision
|
|
||||||
if (!canMove(0, 0)) {
|
|
||||||
// Try to move left
|
|
||||||
if (canMove(-1, 0)) {
|
|
||||||
it.x--
|
|
||||||
}
|
|
||||||
// Try to move right
|
|
||||||
else if (canMove(1, 0)) {
|
|
||||||
it.x++
|
|
||||||
}
|
|
||||||
// Try to move 2 spaces (for I piece)
|
|
||||||
else if (canMove(-2, 0)) {
|
|
||||||
it.x -= 2
|
|
||||||
}
|
|
||||||
else if (canMove(2, 0)) {
|
|
||||||
it.x += 2
|
|
||||||
}
|
|
||||||
// Try to move up for floor kicks
|
|
||||||
else if (canMove(0, -1)) {
|
|
||||||
it.y--
|
|
||||||
}
|
|
||||||
// Revert if can't find a valid position
|
|
||||||
else {
|
|
||||||
it.rotateClockwise()
|
|
||||||
it.x = originalX
|
|
||||||
it.y = originalY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current piece can move to the given position
|
|
||||||
*/
|
|
||||||
fun canMove(deltaX: Int, deltaY: Int): Boolean {
|
|
||||||
val piece = currentPiece ?: return false
|
|
||||||
|
|
||||||
val newX = piece.x + deltaX
|
|
||||||
val newY = piece.y + deltaY
|
|
||||||
|
|
||||||
for (y in 0 until piece.getHeight()) {
|
|
||||||
for (x in 0 until piece.getWidth()) {
|
|
||||||
if (piece.isBlockAt(x, y)) {
|
if (piece.isBlockAt(x, y)) {
|
||||||
val boardX = newX + x
|
val boardX = newX + x
|
||||||
val boardY = newY + y
|
val boardY = newY + y
|
||||||
|
|
||||||
// Check if the position is outside the board horizontally
|
// Check board boundaries
|
||||||
if (boardX < 0 || boardX >= width) {
|
if (boardX < 0 || boardX >= WIDTH || boardY >= HEIGHT) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check collision with placed blocks
|
||||||
|
if (boardY >= 0 && board[boardY][boardX]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the position is below the board
|
fun movePiece(piece: GamePiece, dx: Int, dy: Int): Boolean {
|
||||||
if (boardY >= height) {
|
val newX = piece.x + dx
|
||||||
|
val newY = piece.y + dy
|
||||||
|
|
||||||
|
if (!wouldCollide(piece, newX, newY)) {
|
||||||
|
piece.x = newX
|
||||||
|
piece.y = newY
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the position is already occupied (but not if it's above the board)
|
fun rotatePiece(piece: GamePiece): Boolean {
|
||||||
if (boardY >= 0 && grid[boardY][boardX]) {
|
// Save current state
|
||||||
return false
|
val originalRotation = piece.rotation
|
||||||
|
|
||||||
|
// Try to rotate
|
||||||
|
piece.rotate()
|
||||||
|
|
||||||
|
// Check if new position is valid
|
||||||
|
if (wouldCollide(piece, piece.x, piece.y)) {
|
||||||
|
// If not valid, try wall kicks
|
||||||
|
val kicks = arrayOf(
|
||||||
|
Pair(1, 0), // Try moving right
|
||||||
|
Pair(-1, 0), // Try moving left
|
||||||
|
Pair(0, -1), // Try moving up
|
||||||
|
Pair(2, 0), // Try moving right 2
|
||||||
|
Pair(-2, 0) // Try moving left 2
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((kickX, kickY) in kicks) {
|
||||||
|
if (!wouldCollide(piece, piece.x + kickX, piece.y + kickY)) {
|
||||||
|
piece.x += kickX
|
||||||
|
piece.y += kickY
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the position is more than one unit above the top of the screen
|
// If no wall kick worked, revert rotation
|
||||||
if (boardY < -1) {
|
piece.rotation = originalRotation
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun lockPiece(piece: GamePiece) {
|
||||||
* Lock the current piece in place
|
val width = piece.getWidth()
|
||||||
*/
|
val height = piece.getHeight()
|
||||||
private fun lockPiece() {
|
|
||||||
if (isPieceLocking) {
|
|
||||||
Log.d(TAG, "lockPiece() called but blocked: isPieceLocking=$isPieceLocking")
|
|
||||||
return // Prevent recursive locking
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "lockPiece() started - setting isPieceLocking=true, current isHardDropInProgress=$isHardDropInProgress")
|
// Place the piece on the board
|
||||||
isPieceLocking = true
|
for (y in 0 until height) {
|
||||||
|
for (x in 0 until width) {
|
||||||
val piece = currentPiece ?: return
|
|
||||||
|
|
||||||
// Add the piece to the grid
|
|
||||||
for (y in 0 until piece.getHeight()) {
|
|
||||||
for (x in 0 until piece.getWidth()) {
|
|
||||||
if (piece.isBlockAt(x, y)) {
|
if (piece.isBlockAt(x, y)) {
|
||||||
val boardX = piece.x + x
|
val boardX = piece.x + x
|
||||||
val boardY = piece.y + y
|
val boardY = piece.y + y
|
||||||
|
if (boardY >= 0 && boardY < HEIGHT && boardX >= 0 && boardX < WIDTH) {
|
||||||
// Only add to grid if within bounds
|
board[boardY][boardX] = true
|
||||||
if (boardY >= 0 && boardY < height && boardX >= 0 && boardX < width) {
|
|
||||||
grid[boardY][boardX] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger the piece lock vibration
|
// Clear any completed lines
|
||||||
onPieceLock?.invoke()
|
clearLines()
|
||||||
|
|
||||||
// Notify that a piece was placed
|
|
||||||
onPiecePlaced?.invoke()
|
|
||||||
|
|
||||||
// Find and clear lines immediately
|
|
||||||
findAndClearLines()
|
|
||||||
|
|
||||||
// IMPORTANT: Reset the hard drop flag before spawning a new piece
|
|
||||||
// This prevents the immediate hard drop of the next piece
|
|
||||||
if (isHardDropInProgress) {
|
|
||||||
Log.d(TAG, "lockPiece() - resetting isHardDropInProgress=false BEFORE spawning new piece")
|
|
||||||
isHardDropInProgress = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log piece position before spawning new piece
|
private fun clearLines() {
|
||||||
Log.d(TAG, "lockPiece() - about to spawn new piece at y=${piece.y}, isHardDropInProgress=$isHardDropInProgress")
|
var linesCleared = 0
|
||||||
|
var y = HEIGHT - 1
|
||||||
// Spawn new piece immediately
|
|
||||||
spawnPiece()
|
|
||||||
|
|
||||||
// Allow holding piece again after locking
|
|
||||||
canHold = true
|
|
||||||
|
|
||||||
// Reset locking state
|
|
||||||
isPieceLocking = false
|
|
||||||
Log.d(TAG, "lockPiece() completed - reset flags: isPieceLocking=false, isHardDropInProgress=$isHardDropInProgress")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find and clear completed lines immediately
|
|
||||||
*/
|
|
||||||
private fun findAndClearLines() {
|
|
||||||
// Quick scan for completed lines
|
|
||||||
var shiftAmount = 0
|
|
||||||
var y = height - 1
|
|
||||||
val linesToClear = mutableListOf<Int>()
|
|
||||||
|
|
||||||
while (y >= 0) {
|
while (y >= 0) {
|
||||||
if (grid[y].all { it }) {
|
if (isLineFull(y)) {
|
||||||
// Line is full, add to lines to clear
|
// Move all lines above down
|
||||||
linesToClear.add(y)
|
for (moveY in y downTo 1) {
|
||||||
shiftAmount++
|
for (x in 0 until WIDTH) {
|
||||||
} else if (shiftAmount > 0) {
|
board[moveY][x] = board[moveY - 1][x]
|
||||||
// Shift this row down by shiftAmount
|
|
||||||
System.arraycopy(grid[y], 0, grid[y + shiftAmount], 0, width)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Clear top line
|
||||||
|
for (x in 0 until WIDTH) {
|
||||||
|
board[0][x] = false
|
||||||
|
}
|
||||||
|
linesCleared++
|
||||||
|
} else {
|
||||||
y--
|
y--
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store the last cleared lines
|
// Increase speed based on lines cleared
|
||||||
lastClearedLines.clear()
|
if (linesCleared > 0) {
|
||||||
lastClearedLines.addAll(linesToClear)
|
dropInterval = (dropInterval * 0.95).toLong().coerceAtLeast(MIN_DROP_INTERVAL)
|
||||||
|
|
||||||
// If lines were cleared, calculate score in background and trigger callback
|
|
||||||
if (shiftAmount > 0) {
|
|
||||||
// Log line clear
|
|
||||||
Log.d(TAG, "Lines cleared: $shiftAmount")
|
|
||||||
|
|
||||||
// Trigger line clear callback on main thread with the lines that were cleared
|
|
||||||
val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
||||||
mainHandler.post {
|
|
||||||
// Call the line clear callback with the cleared line count
|
|
||||||
try {
|
|
||||||
Log.d(TAG, "Triggering onLineClear callback with $shiftAmount lines")
|
|
||||||
val clearedLines = getLastClearedLines()
|
|
||||||
onLineClear?.invoke(shiftAmount, clearedLines)
|
|
||||||
Log.d(TAG, "onLineClear callback completed successfully")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error in onLineClear callback", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear top rows after callback
|
private fun isLineFull(y: Int): Boolean {
|
||||||
for (y in 0 until shiftAmount) {
|
for (x in 0 until WIDTH) {
|
||||||
java.util.Arrays.fill(grid[y], false)
|
if (!board[y][x]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread {
|
private fun createRandomPiece(): GamePiece {
|
||||||
calculateScore(shiftAmount)
|
return GamePiece(GamePieceType.values().random())
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update combo based on whether this piece cleared lines
|
|
||||||
if (shiftAmount > 0) {
|
|
||||||
if (lastPieceClearedLines) {
|
|
||||||
combo++
|
|
||||||
} else {
|
|
||||||
combo = 1 // Start new combo
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
combo = 0 // Reset combo if no lines cleared
|
|
||||||
}
|
|
||||||
lastPieceClearedLines = shiftAmount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate score for cleared lines
|
|
||||||
*/
|
|
||||||
private fun calculateScore(clearedLines: Int) {
|
|
||||||
// Pre-calculated score multipliers for better performance
|
|
||||||
val baseScore = when (clearedLines) {
|
|
||||||
1 -> 40
|
|
||||||
2 -> 100
|
|
||||||
3 -> 300
|
|
||||||
4 -> 1200
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for perfect clear (no blocks left)
|
|
||||||
val isPerfectClear = !grid.any { row -> row.any { it } }
|
|
||||||
|
|
||||||
// Check for all clear (no blocks in playfield)
|
|
||||||
val isAllClear = !grid.any { row -> row.any { it } } &&
|
|
||||||
currentPiece == null &&
|
|
||||||
nextPiece == null
|
|
||||||
|
|
||||||
// Calculate combo multiplier
|
|
||||||
val comboMultiplier = if (combo > 0) {
|
|
||||||
when (combo) {
|
|
||||||
1 -> 1.0
|
|
||||||
2 -> 1.5
|
|
||||||
3 -> 2.0
|
|
||||||
4 -> 2.5
|
|
||||||
else -> 3.0
|
|
||||||
}
|
|
||||||
} 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) {
|
|
||||||
when (clearedLines) {
|
|
||||||
1 -> 2.0
|
|
||||||
2 -> 3.0
|
|
||||||
3 -> 4.0
|
|
||||||
4 -> 5.0
|
|
||||||
else -> 1.0
|
|
||||||
}
|
|
||||||
} else 1.0
|
|
||||||
|
|
||||||
// Calculate all clear bonus
|
|
||||||
val allClearMultiplier = if (isAllClear) 2.0 else 1.0
|
|
||||||
|
|
||||||
// Calculate T-Spin bonus
|
|
||||||
val tSpinMultiplier = if (isTSpin()) {
|
|
||||||
when (clearedLines) {
|
|
||||||
1 -> 2.0
|
|
||||||
2 -> 4.0
|
|
||||||
3 -> 6.0
|
|
||||||
else -> 1.0
|
|
||||||
}
|
|
||||||
} else 1.0
|
|
||||||
|
|
||||||
// Calculate final score with all multipliers
|
|
||||||
val finalScore = (baseScore * level * comboMultiplier *
|
|
||||||
backToBackMultiplier * perfectClearMultiplier *
|
|
||||||
allClearMultiplier * tSpinMultiplier).toInt()
|
|
||||||
|
|
||||||
// Update score on main thread
|
|
||||||
Thread {
|
|
||||||
score += finalScore
|
|
||||||
}.start()
|
|
||||||
|
|
||||||
// Update line clear state
|
|
||||||
lastClearWasQuad = clearedLines == 4
|
|
||||||
lastClearWasPerfect = isPerfectClear
|
|
||||||
lastClearWasAllClear = isAllClear
|
|
||||||
|
|
||||||
// Update lines cleared and level
|
|
||||||
lines += clearedLines
|
|
||||||
// Calculate level based on lines cleared, but ensure it's never below the starting level
|
|
||||||
level = Math.max((lines / 10) + 1, startingLevel)
|
|
||||||
|
|
||||||
// Update game speed based on level (NES formula)
|
|
||||||
dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the last move was a T-Spin
|
|
||||||
*/
|
|
||||||
private fun isTSpin(): Boolean {
|
|
||||||
val piece = currentPiece ?: return false
|
|
||||||
if (piece.type != GamePieceType.T) return false
|
|
||||||
|
|
||||||
// Count occupied corners around the T piece
|
|
||||||
var occupiedCorners = 0
|
|
||||||
val centerX = piece.x + 1
|
|
||||||
val centerY = piece.y + 1
|
|
||||||
|
|
||||||
// Check all four corners
|
|
||||||
if (isOccupied(centerX - 1, centerY - 1)) occupiedCorners++
|
|
||||||
if (isOccupied(centerX + 1, centerY - 1)) occupiedCorners++
|
|
||||||
if (isOccupied(centerX - 1, centerY + 1)) occupiedCorners++
|
|
||||||
if (isOccupied(centerX + 1, centerY + 1)) occupiedCorners++
|
|
||||||
|
|
||||||
// T-Spin requires at least 3 occupied corners
|
|
||||||
return occupiedCorners >= 3
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ghost piece position (preview of where piece will land)
|
|
||||||
*/
|
|
||||||
fun getGhostY(): Int {
|
|
||||||
val piece = currentPiece ?: return 0
|
|
||||||
var ghostY = piece.y
|
|
||||||
|
|
||||||
// Find how far the piece can move down
|
|
||||||
while (true) {
|
|
||||||
if (canMove(0, ghostY - piece.y + 1)) {
|
|
||||||
ghostY++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure ghostY doesn't exceed the board height
|
|
||||||
return ghostY.coerceAtMost(height - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current tetromino
|
|
||||||
*/
|
|
||||||
fun getCurrentPiece(): GamePiece? = currentPiece
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a cell in the grid is occupied
|
|
||||||
*/
|
|
||||||
fun isOccupied(x: Int, y: Int): Boolean {
|
|
||||||
return if (x in 0 until width && y in 0 until height) {
|
|
||||||
grid[y][x]
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a line is completely filled
|
|
||||||
*/
|
|
||||||
fun isLineFull(y: Int): Boolean {
|
|
||||||
return if (y in 0 until height) {
|
|
||||||
grid[y].all { it }
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the current level and adjust game parameters
|
|
||||||
*/
|
|
||||||
fun updateLevel(newLevel: Int) {
|
|
||||||
lastLevel = level
|
|
||||||
level = newLevel.coerceIn(1, 20)
|
|
||||||
startingLevel = level // Store the starting level
|
|
||||||
dropInterval = getDropIntervalForLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a new game
|
|
||||||
*/
|
|
||||||
fun startGame() {
|
|
||||||
reset()
|
|
||||||
// Initialize pieces
|
|
||||||
spawnNextPiece()
|
|
||||||
spawnPiece()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the game board
|
|
||||||
*/
|
|
||||||
fun reset() {
|
|
||||||
// Clear the grid
|
|
||||||
for (y in 0 until height) {
|
|
||||||
for (x in 0 until width) {
|
|
||||||
grid[y][x] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset game state
|
|
||||||
score = 0
|
|
||||||
level = startingLevel // Use starting level instead of resetting to 1
|
|
||||||
lastLevel = level // Reset lastLevel to match the current level
|
|
||||||
lines = 0
|
|
||||||
isGameOver = false
|
|
||||||
dropInterval = getDropIntervalForLevel(level) // Use helper method
|
|
||||||
|
|
||||||
// Reset scoring state
|
|
||||||
combo = 0
|
|
||||||
lastClearWasQuad = false
|
|
||||||
lastClearWasPerfect = false
|
|
||||||
lastClearWasAllClear = false
|
|
||||||
lastPieceClearedLines = false
|
|
||||||
|
|
||||||
// Reset piece state
|
|
||||||
holdPiece = null
|
|
||||||
canHold = true
|
|
||||||
bag.clear()
|
|
||||||
|
|
||||||
// Clear current and next pieces
|
|
||||||
currentPiece = null
|
|
||||||
nextPiece = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear completed lines and move blocks down (legacy method, kept for reference)
|
|
||||||
*/
|
|
||||||
private fun clearLines(): Int {
|
|
||||||
return linesToClear.size // Return the number of lines that will be cleared
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current combo count
|
|
||||||
*/
|
|
||||||
fun getCombo(): Int = combo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of lines that were most recently cleared
|
|
||||||
*/
|
|
||||||
private fun getLastClearedLines(): List<Int> {
|
|
||||||
return lastClearedLines.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the last level
|
|
||||||
*/
|
|
||||||
fun getLastLevel(): Int = lastLevel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the game state (called by game loop)
|
|
||||||
*/
|
|
||||||
fun update() {
|
|
||||||
if (!isGameOver) {
|
|
||||||
moveDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the drop interval for the given level
|
|
||||||
*/
|
|
||||||
private fun getDropIntervalForLevel(level: Int): Long {
|
|
||||||
val cappedLevel = level.coerceIn(1, 20)
|
|
||||||
// Update game speed based on level (NES formula)
|
|
||||||
return (1000 * Math.pow(0.8, (cappedLevel - 1).toDouble())).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the game level
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -1,214 +1,48 @@
|
||||||
package com.pixelmintdrop.model
|
package com.pixelmintdrop.model
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a game piece
|
* Represents a game piece with its type, position, and rotation
|
||||||
*/
|
*/
|
||||||
enum class GamePieceType {
|
|
||||||
I, J, L, O, S, T, Z
|
|
||||||
}
|
|
||||||
|
|
||||||
class GamePiece(val type: GamePieceType) {
|
class GamePiece(val type: GamePieceType) {
|
||||||
private var rotation = 0
|
var x: Int = 0
|
||||||
|
var y: Int = 0
|
||||||
|
var rotation: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
// Each piece has 4 rotations (0, 90, 180, 270 degrees)
|
private val blocks: Array<Array<Boolean>> = type.getBlocks()
|
||||||
private val blocks: Array<Array<BooleanArray>> = getBlocks(type)
|
|
||||||
|
|
||||||
/**
|
fun getWidth(): Int = blocks[0].size
|
||||||
* Get the current shape of the piece based on rotation
|
|
||||||
*/
|
|
||||||
fun getShape(): Array<BooleanArray> = blocks[rotation]
|
|
||||||
|
|
||||||
/**
|
fun getHeight(): Int = blocks.size
|
||||||
* Get the width of the current piece shape
|
|
||||||
*/
|
|
||||||
fun getWidth(): Int = blocks[rotation][0].size
|
|
||||||
|
|
||||||
/**
|
fun isBlockAt(x: Int, y: Int): Boolean {
|
||||||
* 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
|
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return false
|
||||||
return blocks[rotation][y][x]
|
return blocks[y][x]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun rotate() {
|
||||||
* Get the block patterns for each piece type and all its rotations
|
rotation = (rotation + 1) % 4
|
||||||
*/
|
rotateBlocks()
|
||||||
private fun getBlocks(type: GamePieceType): Array<Array<BooleanArray>> {
|
}
|
||||||
return when (type) {
|
|
||||||
GamePieceType.I -> arrayOf(
|
private fun rotateBlocks() {
|
||||||
arrayOf(
|
val width = getWidth()
|
||||||
booleanArrayOf(false, false, false, false),
|
val height = getHeight()
|
||||||
booleanArrayOf(true, true, true, true),
|
val rotated = Array(width) { Array(height) { false } }
|
||||||
booleanArrayOf(false, false, false, false),
|
|
||||||
booleanArrayOf(false, false, false, false)
|
for (y in 0 until height) {
|
||||||
),
|
for (x in 0 until width) {
|
||||||
arrayOf(
|
when (rotation) {
|
||||||
booleanArrayOf(false, false, true, false),
|
1 -> rotated[x][height - 1 - y] = blocks[y][x]
|
||||||
booleanArrayOf(false, false, true, false),
|
2 -> rotated[height - 1 - y][width - 1 - x] = blocks[y][x]
|
||||||
booleanArrayOf(false, false, true, false),
|
3 -> rotated[width - 1 - x][y] = blocks[y][x]
|
||||||
booleanArrayOf(false, false, true, false)
|
else -> rotated[y][x] = blocks[y][x]
|
||||||
),
|
}
|
||||||
arrayOf(
|
}
|
||||||
booleanArrayOf(false, false, false, false),
|
}
|
||||||
booleanArrayOf(false, false, false, false),
|
|
||||||
booleanArrayOf(true, true, true, true),
|
blocks.indices.forEach { i ->
|
||||||
booleanArrayOf(false, false, false, false)
|
blocks[i] = rotated[i].copyOf()
|
||||||
),
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
48
app/src/main/java/com/pixelmintdrop/model/GamePieceType.kt
Normal file
48
app/src/main/java/com/pixelmintdrop/model/GamePieceType.kt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package com.pixelmintdrop.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the different types of game pieces
|
||||||
|
*/
|
||||||
|
enum class GamePieceType {
|
||||||
|
I, J, L, O, S, T, Z;
|
||||||
|
|
||||||
|
fun getBlocks(): Array<Array<Boolean>> {
|
||||||
|
return when (this) {
|
||||||
|
I -> arrayOf(
|
||||||
|
arrayOf(false, false, false, false),
|
||||||
|
arrayOf(true, true, true, true),
|
||||||
|
arrayOf(false, false, false, false),
|
||||||
|
arrayOf(false, false, false, false)
|
||||||
|
)
|
||||||
|
J -> arrayOf(
|
||||||
|
arrayOf(true, false, false),
|
||||||
|
arrayOf(true, true, true),
|
||||||
|
arrayOf(false, false, false)
|
||||||
|
)
|
||||||
|
L -> arrayOf(
|
||||||
|
arrayOf(false, false, true),
|
||||||
|
arrayOf(true, true, true),
|
||||||
|
arrayOf(false, false, false)
|
||||||
|
)
|
||||||
|
O -> arrayOf(
|
||||||
|
arrayOf(true, true),
|
||||||
|
arrayOf(true, true)
|
||||||
|
)
|
||||||
|
S -> arrayOf(
|
||||||
|
arrayOf(false, true, true),
|
||||||
|
arrayOf(true, true, false),
|
||||||
|
arrayOf(false, false, false)
|
||||||
|
)
|
||||||
|
T -> arrayOf(
|
||||||
|
arrayOf(false, true, false),
|
||||||
|
arrayOf(true, true, true),
|
||||||
|
arrayOf(false, false, false)
|
||||||
|
)
|
||||||
|
Z -> arrayOf(
|
||||||
|
arrayOf(true, true, false),
|
||||||
|
arrayOf(false, true, true),
|
||||||
|
arrayOf(false, false, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,15 +93,31 @@ class PlayerProgressionManager(context: Context) {
|
||||||
/**
|
/**
|
||||||
* Calculate XP from a game session based on score, lines, level, etc.
|
* Calculate XP from a game session based on score, lines, level, etc.
|
||||||
*/
|
*/
|
||||||
fun calculateGameXP(score: Int, lines: Int, level: Int, timePlayedMs: Long,
|
fun calculateGameXP(
|
||||||
quadCount: Int, perfectClearCount: Int): Long {
|
score: Int,
|
||||||
val scoreXP = (score * SCORE_XP_MULTIPLIER).toLong()
|
lines: Int,
|
||||||
val linesXP = (lines * LINES_XP_MULTIPLIER).toLong()
|
level: Int,
|
||||||
val quadBonus = (quadCount * QUAD_XP_BONUS).toLong()
|
timePlayedMs: Long,
|
||||||
val perfectClearBonus = (perfectClearCount * 100).toLong()
|
quadCount: Int,
|
||||||
val timeBonus = (timePlayedMs * TIME_XP_MULTIPLIER).toLong()
|
perfectClearCount: Int
|
||||||
|
): Long {
|
||||||
|
// Calculate base XP from score
|
||||||
|
val scoreXP = score * BASE_SCORE_XP * level
|
||||||
|
|
||||||
return scoreXP + linesXP + quadBonus + perfectClearBonus + timeBonus
|
// Calculate XP from lines cleared
|
||||||
|
val linesXP = lines * BASE_LINES_XP * level
|
||||||
|
|
||||||
|
// Calculate quad bonus
|
||||||
|
val quadBonus = quadCount * BASE_QUAD_BONUS * level
|
||||||
|
|
||||||
|
// Calculate perfect clear bonus
|
||||||
|
val perfectClearBonus = perfectClearCount * BASE_PERFECT_CLEAR_BONUS * level
|
||||||
|
|
||||||
|
// Calculate time bonus (convert ms to seconds)
|
||||||
|
val timeBonus = (timePlayedMs / 1000.0) * BASE_TIME_XP * level
|
||||||
|
|
||||||
|
// Sum all XP components
|
||||||
|
return (scoreXP + linesXP + quadBonus + perfectClearBonus + timeBonus).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -319,10 +335,11 @@ class PlayerProgressionManager(context: Context) {
|
||||||
THEME_GALAXY to 25
|
THEME_GALAXY to 25
|
||||||
)
|
)
|
||||||
|
|
||||||
private const val SCORE_XP_MULTIPLIER = 0.1
|
private const val BASE_SCORE_XP = 0.1 // XP per score point
|
||||||
private const val LINES_XP_MULTIPLIER = 5
|
private const val BASE_LINES_XP = 100.0 // XP per line cleared
|
||||||
private const val QUAD_XP_BONUS = 50
|
private const val BASE_QUAD_BONUS = 500.0 // Bonus XP for clearing 4 lines at once
|
||||||
private const val TIME_XP_MULTIPLIER = 0.01
|
private const val BASE_PERFECT_CLEAR_BONUS = 1000.0 // Bonus XP for perfect clear
|
||||||
|
private const val BASE_TIME_XP = 1.0 // XP per second played
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,211 +3,76 @@ package com.pixelmintdrop.model
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages game statistics and high scores
|
||||||
|
*/
|
||||||
class StatsManager(context: Context) {
|
class StatsManager(context: Context) {
|
||||||
|
companion object {
|
||||||
|
private const val PREFS_NAME = "game_stats"
|
||||||
|
private const val KEY_HIGH_SCORE = "high_score"
|
||||||
|
private const val KEY_TOTAL_GAMES = "total_games"
|
||||||
|
private const val KEY_TOTAL_LINES = "total_lines"
|
||||||
|
private const val KEY_TOTAL_QUADS = "total_quads"
|
||||||
|
private const val KEY_TOTAL_PERFECT_CLEARS = "total_perfect_clears"
|
||||||
|
private const val KEY_SESSION_SCORE = "session_score"
|
||||||
|
private const val KEY_SESSION_LINES = "session_lines"
|
||||||
|
private const val KEY_SESSION_QUADS = "session_quads"
|
||||||
|
private const val KEY_SESSION_PERFECT_CLEARS = "session_perfect_clears"
|
||||||
|
}
|
||||||
|
|
||||||
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
// Lifetime stats
|
|
||||||
private var totalGames: Int = 0
|
|
||||||
private var totalScore: Long = 0
|
|
||||||
private var totalLines: Int = 0
|
|
||||||
private var totalPieces: Int = 0
|
|
||||||
private var totalTime: Long = 0
|
|
||||||
private var maxLevel: Int = 0
|
|
||||||
private var maxScore: Int = 0
|
|
||||||
private var maxLines: Int = 0
|
|
||||||
|
|
||||||
// Line clear stats (lifetime)
|
|
||||||
private var totalSingles: Int = 0
|
|
||||||
private var totalDoubles: Int = 0
|
|
||||||
private var totalTriples: Int = 0
|
|
||||||
private var totalQuads: Int = 0
|
|
||||||
|
|
||||||
// Session stats
|
// Session stats
|
||||||
private var sessionScore: Int = 0
|
private var sessionScore = 0
|
||||||
private var sessionLines: Int = 0
|
private var sessionLines = 0
|
||||||
private var sessionPieces: Int = 0
|
private var sessionQuads = 0
|
||||||
private var sessionTime: Long = 0
|
private var sessionPerfectClears = 0
|
||||||
private var sessionLevel: Int = 0
|
|
||||||
|
|
||||||
// Line clear stats (session)
|
fun getHighScore(): Int = prefs.getInt(KEY_HIGH_SCORE, 0)
|
||||||
private var sessionSingles: Int = 0
|
fun getTotalGames(): Int = prefs.getInt(KEY_TOTAL_GAMES, 0)
|
||||||
private var sessionDoubles: Int = 0
|
fun getTotalLines(): Int = prefs.getInt(KEY_TOTAL_LINES, 0)
|
||||||
private var sessionTriples: Int = 0
|
fun getTotalQuads(): Int = prefs.getInt(KEY_TOTAL_QUADS, 0)
|
||||||
private var sessionQuads: Int = 0
|
fun getTotalPerfectClears(): Int = prefs.getInt(KEY_TOTAL_PERFECT_CLEARS, 0)
|
||||||
|
|
||||||
// Perfect clear stats
|
fun getSessionScore(): Int = sessionScore
|
||||||
private var sessionPerfectClears: Int = 0
|
fun getSessionLines(): Int = sessionLines
|
||||||
private var totalPerfectClears: Int = 0
|
fun getSessionQuads(): Int = sessionQuads
|
||||||
|
fun getSessionPerfectClears(): Int = sessionPerfectClears
|
||||||
|
|
||||||
init {
|
fun updateStats(score: Int, lines: Int, isQuad: Boolean, isPerfectClear: Boolean) {
|
||||||
loadStats()
|
// Update session stats
|
||||||
|
sessionScore += score
|
||||||
|
sessionLines += lines
|
||||||
|
if (isQuad) sessionQuads++
|
||||||
|
if (isPerfectClear) sessionPerfectClears++
|
||||||
|
|
||||||
|
// Update high score if needed
|
||||||
|
if (sessionScore > getHighScore()) {
|
||||||
|
prefs.edit().putInt(KEY_HIGH_SCORE, sessionScore).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadStats() {
|
// Update total stats
|
||||||
totalGames = prefs.getInt(KEY_TOTAL_GAMES, 0)
|
|
||||||
totalScore = prefs.getLong(KEY_TOTAL_SCORE, 0)
|
|
||||||
totalLines = prefs.getInt(KEY_TOTAL_LINES, 0)
|
|
||||||
totalPieces = prefs.getInt(KEY_TOTAL_PIECES, 0)
|
|
||||||
totalTime = prefs.getLong(KEY_TOTAL_TIME, 0)
|
|
||||||
maxLevel = prefs.getInt(KEY_MAX_LEVEL, 0)
|
|
||||||
maxScore = prefs.getInt(KEY_MAX_SCORE, 0)
|
|
||||||
maxLines = prefs.getInt(KEY_MAX_LINES, 0)
|
|
||||||
|
|
||||||
// Load line clear stats
|
|
||||||
totalSingles = prefs.getInt(KEY_TOTAL_SINGLES, 0)
|
|
||||||
totalDoubles = prefs.getInt(KEY_TOTAL_DOUBLES, 0)
|
|
||||||
totalTriples = prefs.getInt(KEY_TOTAL_TRIPLES, 0)
|
|
||||||
totalQuads = prefs.getInt(KEY_TOTAL_QUADS, 0)
|
|
||||||
|
|
||||||
// Load perfect clear stats
|
|
||||||
totalPerfectClears = prefs.getInt(KEY_TOTAL_PERFECT_CLEARS, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveStats() {
|
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putInt(KEY_TOTAL_GAMES, totalGames)
|
.putInt(KEY_TOTAL_LINES, getTotalLines() + lines)
|
||||||
.putLong(KEY_TOTAL_SCORE, totalScore)
|
.putInt(KEY_TOTAL_QUADS, getTotalQuads() + if (isQuad) 1 else 0)
|
||||||
.putInt(KEY_TOTAL_LINES, totalLines)
|
.putInt(KEY_TOTAL_PERFECT_CLEARS, getTotalPerfectClears() + if (isPerfectClear) 1 else 0)
|
||||||
.putInt(KEY_TOTAL_PIECES, totalPieces)
|
|
||||||
.putLong(KEY_TOTAL_TIME, totalTime)
|
|
||||||
.putInt(KEY_MAX_LEVEL, maxLevel)
|
|
||||||
.putInt(KEY_MAX_SCORE, maxScore)
|
|
||||||
.putInt(KEY_MAX_LINES, maxLines)
|
|
||||||
.putInt(KEY_TOTAL_SINGLES, totalSingles)
|
|
||||||
.putInt(KEY_TOTAL_DOUBLES, totalDoubles)
|
|
||||||
.putInt(KEY_TOTAL_TRIPLES, totalTriples)
|
|
||||||
.putInt(KEY_TOTAL_QUADS, totalQuads)
|
|
||||||
.putInt(KEY_TOTAL_PERFECT_CLEARS, totalPerfectClears)
|
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startNewSession() {
|
fun startNewGame() {
|
||||||
|
// Increment total games counter
|
||||||
|
prefs.edit().putInt(KEY_TOTAL_GAMES, getTotalGames() + 1).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSession() {
|
||||||
sessionScore = 0
|
sessionScore = 0
|
||||||
sessionLines = 0
|
sessionLines = 0
|
||||||
sessionPieces = 0
|
|
||||||
sessionTime = 0
|
|
||||||
sessionLevel = 0
|
|
||||||
sessionSingles = 0
|
|
||||||
sessionDoubles = 0
|
|
||||||
sessionTriples = 0
|
|
||||||
sessionQuads = 0
|
sessionQuads = 0
|
||||||
sessionPerfectClears = 0
|
sessionPerfectClears = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSessionStats(score: Int, lines: Int, pieces: Int, time: Long, level: Int) {
|
fun resetAllStats() {
|
||||||
sessionScore = score
|
prefs.edit().clear().apply()
|
||||||
sessionLines = lines
|
resetSession()
|
||||||
sessionPieces = pieces
|
|
||||||
sessionTime = time
|
|
||||||
sessionLevel = level
|
|
||||||
}
|
|
||||||
|
|
||||||
fun recordLineClear(lineCount: Int) {
|
|
||||||
when (lineCount) {
|
|
||||||
1 -> {
|
|
||||||
sessionSingles++
|
|
||||||
totalSingles++
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
sessionDoubles++
|
|
||||||
totalDoubles++
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
sessionTriples++
|
|
||||||
totalTriples++
|
|
||||||
}
|
|
||||||
4 -> {
|
|
||||||
sessionQuads++
|
|
||||||
totalQuads++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endSession() {
|
|
||||||
totalGames++
|
|
||||||
totalScore += sessionScore
|
|
||||||
totalLines += sessionLines
|
|
||||||
totalPieces += sessionPieces
|
|
||||||
totalTime += sessionTime
|
|
||||||
|
|
||||||
if (sessionLevel > maxLevel) maxLevel = sessionLevel
|
|
||||||
if (sessionScore > maxScore) maxScore = sessionScore
|
|
||||||
if (sessionLines > maxLines) maxLines = sessionLines
|
|
||||||
|
|
||||||
saveStats()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters for lifetime stats
|
|
||||||
fun getTotalGames(): Int = totalGames
|
|
||||||
fun getTotalScore(): Long = totalScore
|
|
||||||
fun getTotalLines(): Int = totalLines
|
|
||||||
fun getTotalPieces(): Int = totalPieces
|
|
||||||
fun getTotalTime(): Long = totalTime
|
|
||||||
fun getMaxLevel(): Int = maxLevel
|
|
||||||
fun getMaxScore(): Int = maxScore
|
|
||||||
fun getMaxLines(): Int = maxLines
|
|
||||||
|
|
||||||
// Getters for line clear stats (lifetime)
|
|
||||||
fun getTotalSingles(): Int = totalSingles
|
|
||||||
fun getTotalDoubles(): Int = totalDoubles
|
|
||||||
fun getTotalTriples(): Int = totalTriples
|
|
||||||
fun getTotalQuads(): Int = totalQuads
|
|
||||||
|
|
||||||
// Getters for session stats
|
|
||||||
fun getSessionScore(): Int = sessionScore
|
|
||||||
fun getSessionLines(): Int = sessionLines
|
|
||||||
fun getSessionPieces(): Int = sessionPieces
|
|
||||||
fun getSessionTime(): Long = sessionTime
|
|
||||||
fun getSessionLevel(): Int = sessionLevel
|
|
||||||
|
|
||||||
// Getters for line clear stats (session)
|
|
||||||
fun getSessionSingles(): Int = sessionSingles
|
|
||||||
fun getSessionDoubles(): Int = sessionDoubles
|
|
||||||
fun getSessionTriples(): Int = sessionTriples
|
|
||||||
fun getSessionQuads(): Int = sessionQuads
|
|
||||||
|
|
||||||
// Getters for perfect clear stats
|
|
||||||
fun getSessionPerfectClears(): Int = sessionPerfectClears
|
|
||||||
fun getTotalPerfectClears(): Int = totalPerfectClears
|
|
||||||
|
|
||||||
fun resetStats() {
|
|
||||||
// Reset all lifetime stats
|
|
||||||
totalGames = 0
|
|
||||||
totalScore = 0
|
|
||||||
totalLines = 0
|
|
||||||
totalPieces = 0
|
|
||||||
totalTime = 0
|
|
||||||
maxLevel = 0
|
|
||||||
maxScore = 0
|
|
||||||
maxLines = 0
|
|
||||||
|
|
||||||
// Reset line clear stats
|
|
||||||
totalSingles = 0
|
|
||||||
totalDoubles = 0
|
|
||||||
totalTriples = 0
|
|
||||||
totalQuads = 0
|
|
||||||
|
|
||||||
// Reset perfect clear stats
|
|
||||||
totalPerfectClears = 0
|
|
||||||
|
|
||||||
// Save the reset stats
|
|
||||||
saveStats()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PREFS_NAME = "pixelmintdrop_stats"
|
|
||||||
private const val KEY_TOTAL_GAMES = "total_games"
|
|
||||||
private const val KEY_TOTAL_SCORE = "total_score"
|
|
||||||
private const val KEY_TOTAL_LINES = "total_lines"
|
|
||||||
private const val KEY_TOTAL_PIECES = "total_pieces"
|
|
||||||
private const val KEY_TOTAL_TIME = "total_time"
|
|
||||||
private const val KEY_MAX_LEVEL = "max_level"
|
|
||||||
private const val KEY_MAX_SCORE = "max_score"
|
|
||||||
private const val KEY_MAX_LINES = "max_lines"
|
|
||||||
private const val KEY_TOTAL_SINGLES = "total_singles"
|
|
||||||
private const val KEY_TOTAL_DOUBLES = "total_doubles"
|
|
||||||
private const val KEY_TOTAL_TRIPLES = "total_triples"
|
|
||||||
private const val KEY_TOTAL_QUADS = "total_quads"
|
|
||||||
private const val KEY_TOTAL_PERFECT_CLEARS = "total_perfect_clears"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
260
app/src/main/java/com/pixelmintdrop/model/Tetromino.kt
Normal file
260
app/src/main/java/com/pixelmintdrop/model/Tetromino.kt
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
package com.pixelmintdrop.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a game 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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
app/src/main/java/com/pixelmintdrop/theme/ThemeManager.kt
Normal file
52
app/src/main/java/com/pixelmintdrop/theme/ThemeManager.kt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package com.pixelmintdrop.theme
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
|
||||||
|
object ThemeManager {
|
||||||
|
// Theme colors
|
||||||
|
const val COLOR_CLASSIC_BACKGROUND = Color.BLACK
|
||||||
|
const val COLOR_CLASSIC_FOREGROUND = Color.WHITE
|
||||||
|
const val COLOR_CLASSIC_ACCENT = Color.CYAN
|
||||||
|
|
||||||
|
const val COLOR_NEON_BACKGROUND = 0xFF0D0221.toInt()
|
||||||
|
const val COLOR_NEON_FOREGROUND = 0xFFFF00FF.toInt()
|
||||||
|
const val COLOR_NEON_ACCENT = 0xFF00FFFF.toInt()
|
||||||
|
|
||||||
|
const val COLOR_MONOCHROME_BACKGROUND = 0xFF1A1A1A.toInt()
|
||||||
|
const val COLOR_MONOCHROME_FOREGROUND = Color.LTGRAY
|
||||||
|
const val COLOR_MONOCHROME_ACCENT = Color.WHITE
|
||||||
|
|
||||||
|
const val COLOR_RETRO_BACKGROUND = 0xFF3F2832.toInt()
|
||||||
|
const val COLOR_RETRO_FOREGROUND = 0xFFFF5A5F.toInt()
|
||||||
|
const val COLOR_RETRO_ACCENT = 0xFFFFB400.toInt()
|
||||||
|
|
||||||
|
const val COLOR_MINIMALIST_BACKGROUND = Color.WHITE
|
||||||
|
const val COLOR_MINIMALIST_FOREGROUND = Color.BLACK
|
||||||
|
const val COLOR_MINIMALIST_ACCENT = Color.DKGRAY
|
||||||
|
|
||||||
|
const val COLOR_GALAXY_BACKGROUND = 0xFF0B0C10.toInt()
|
||||||
|
const val COLOR_GALAXY_FOREGROUND = 0xFF66FCF1.toInt()
|
||||||
|
const val COLOR_GALAXY_ACCENT = 0xFF45A29E.toInt()
|
||||||
|
|
||||||
|
// Block colors for each piece type
|
||||||
|
const val COLOR_I_PIECE = 0xFF00F0F0.toInt()
|
||||||
|
const val COLOR_J_PIECE = 0xFF0000F0.toInt()
|
||||||
|
const val COLOR_L_PIECE = 0xFFF0A000.toInt()
|
||||||
|
const val COLOR_O_PIECE = 0xFFF0F000.toInt()
|
||||||
|
const val COLOR_S_PIECE = 0xFF00F000.toInt()
|
||||||
|
const val COLOR_T_PIECE = 0xFFA000F0.toInt()
|
||||||
|
const val COLOR_Z_PIECE = 0xFFF00000.toInt()
|
||||||
|
|
||||||
|
// Ghost piece colors
|
||||||
|
const val COLOR_GHOST_PIECE = 0x40FFFFFF
|
||||||
|
const val COLOR_GHOST_PIECE_GLOW = 0x20FFFFFF
|
||||||
|
|
||||||
|
// Grid colors
|
||||||
|
const val COLOR_GRID_LINE = 0x20FFFFFF
|
||||||
|
const val COLOR_GRID_BORDER = 0x40FFFFFF
|
||||||
|
|
||||||
|
// Effect colors
|
||||||
|
const val COLOR_LINE_CLEAR_FLASH = 0x80FFFFFF
|
||||||
|
const val COLOR_PERFECT_CLEAR_FLASH = 0xFFFFD700.toInt()
|
||||||
|
const val COLOR_COMBO_FLASH = 0x60FFFFFF
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue