diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt
index f9cb6a8..23a0c5d 100644
--- a/app/src/main/java/com/mintris/MainActivity.kt
+++ b/app/src/main/java/com/mintris/MainActivity.kt
@@ -16,58 +16,43 @@ import com.mintris.game.GameHaptics
import com.mintris.game.GameView
import com.mintris.game.NextPieceView
import android.view.HapticFeedbackConstants
+import com.mintris.model.GameBoard
class MainActivity : AppCompatActivity() {
// UI components
private lateinit var binding: ActivityMainBinding
private lateinit var gameView: GameView
- private lateinit var scoreText: TextView
- private lateinit var levelText: TextView
- private lateinit var linesText: TextView
- private lateinit var gameOverContainer: LinearLayout
- private lateinit var pauseContainer: LinearLayout
- private lateinit var playAgainButton: Button
- private lateinit var resumeButton: Button
- private lateinit var settingsButton: Button
- private lateinit var finalScoreText: TextView
- private lateinit var nextPieceView: NextPieceView
- private lateinit var levelDownButton: Button
- private lateinit var levelUpButton: Button
- private lateinit var selectedLevelText: TextView
private lateinit var gameHaptics: GameHaptics
+ private lateinit var gameBoard: GameBoard
// Game state
private var isSoundEnabled = true
private var selectedLevel = 1
- private val maxLevel = 10
+ private val maxLevel = 20
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
- // Initialize game haptics
+ // Initialize game components
+ gameBoard = GameBoard()
gameHaptics = GameHaptics(this)
+ gameView = binding.gameView
// Set up game view
- gameView = binding.gameView
- scoreText = binding.scoreText
- levelText = binding.levelText
- linesText = binding.linesText
- gameOverContainer = binding.gameOverContainer
- pauseContainer = binding.pauseContainer
- playAgainButton = binding.playAgainButton
- resumeButton = binding.resumeButton
- settingsButton = binding.settingsButton
- finalScoreText = binding.finalScoreText
- nextPieceView = binding.nextPieceView
- levelDownButton = binding.levelDownButton
- levelUpButton = binding.levelUpButton
- selectedLevelText = binding.selectedLevelText
+ gameView.setGameBoard(gameBoard)
+ gameView.setHaptics(gameHaptics)
- // Connect the next piece view to the game view
- nextPieceView.setGameView(gameView)
+ // Set up next piece preview
+ binding.nextPieceView.setGameView(gameView)
+ gameBoard.onNextPieceChanged = {
+ binding.nextPieceView.invalidate()
+ }
+
+ // Start game immediately
+ startGame()
// Set up callbacks
gameView.onGameStateChanged = { score, level, lines ->
@@ -99,38 +84,51 @@ class MainActivity : AppCompatActivity() {
}
// Set up button click listeners with haptic feedback
- playAgainButton.setOnClickListener {
+ binding.playAgainButton.setOnClickListener {
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
hideGameOver()
gameView.reset()
- setGameLevel(selectedLevel)
gameView.start()
}
- resumeButton.setOnClickListener {
+ binding.resumeButton.setOnClickListener {
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
hidePauseMenu()
gameView.start()
}
- settingsButton.setOnClickListener {
+ binding.settingsButton.setOnClickListener {
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
toggleSound()
}
-
- // Set up level selector with haptic feedback
- levelDownButton.setOnClickListener {
- if (selectedLevel > 1) {
- gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
- selectedLevel--
+
+ // Set up pause menu buttons
+ binding.pauseStartButton.setOnClickListener {
+ gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
+ hidePauseMenu()
+ gameView.reset()
+ gameView.start()
+ }
+
+ binding.pauseRestartButton.setOnClickListener {
+ gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
+ hidePauseMenu()
+ gameView.reset()
+ gameView.start()
+ }
+
+ binding.pauseLevelUpButton.setOnClickListener {
+ gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
+ if (selectedLevel < maxLevel) {
+ selectedLevel++
updateLevelSelector()
}
}
-
- levelUpButton.setOnClickListener {
- if (selectedLevel < maxLevel) {
- gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
- selectedLevel++
+
+ binding.pauseLevelDownButton.setOnClickListener {
+ gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
+ if (selectedLevel > 1) {
+ selectedLevel--
updateLevelSelector()
}
}
@@ -138,57 +136,30 @@ class MainActivity : AppCompatActivity() {
// Initialize level selector
updateLevelSelector()
- // Start game when clicking the screen initially
- setupTouchToStart()
-
- // Start with the game paused
- gameView.pause()
-
// Enable edge-to-edge display
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false)
}
}
- /**
- * Set up touch-to-start behavior for initial screen
- */
- private fun setupTouchToStart() {
- val touchToStart = View.OnClickListener {
- if (gameView.isGameOver()) {
- hideGameOver()
- gameView.reset()
- gameView.start()
- } else if (pauseContainer.visibility == View.VISIBLE) {
- hidePauseMenu()
- gameView.start()
- } else {
- gameView.start()
- }
- }
-
- // Add the click listener to the game view
- gameView.setOnClickListener(touchToStart)
- }
-
/**
* Update UI with current game state
*/
private fun updateUI(score: Int, level: Int, lines: Int) {
- scoreText.text = score.toString()
- levelText.text = level.toString()
- linesText.text = lines.toString()
+ binding.scoreText.text = score.toString()
+ binding.currentLevelText.text = level.toString()
+ binding.linesText.text = lines.toString()
// Force redraw of next piece preview
- nextPieceView.invalidate()
+ binding.nextPieceView.invalidate()
}
/**
* Show game over screen
*/
private fun showGameOver(score: Int) {
- finalScoreText.text = getString(R.string.score) + ": " + score
- gameOverContainer.visibility = View.VISIBLE
+ binding.finalScoreText.text = getString(R.string.score) + ": " + score
+ binding.gameOverContainer.visibility = View.VISIBLE
// Vibrate to indicate game over
vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK)
@@ -198,21 +169,23 @@ class MainActivity : AppCompatActivity() {
* Hide game over screen
*/
private fun hideGameOver() {
- gameOverContainer.visibility = View.GONE
+ binding.gameOverContainer.visibility = View.GONE
}
/**
- * Show pause menu
+ * Show settings menu
*/
private fun showPauseMenu() {
- pauseContainer.visibility = View.VISIBLE
+ binding.pauseContainer.visibility = View.VISIBLE
+ binding.pauseStartButton.visibility = View.VISIBLE
+ binding.resumeButton.visibility = View.GONE
}
/**
- * Hide pause menu
+ * Hide settings menu
*/
private fun hidePauseMenu() {
- pauseContainer.visibility = View.GONE
+ binding.pauseContainer.visibility = View.GONE
}
/**
@@ -220,7 +193,7 @@ class MainActivity : AppCompatActivity() {
*/
private fun toggleSound() {
isSoundEnabled = !isSoundEnabled
- settingsButton.text = getString(
+ binding.settingsButton.text = getString(
if (isSoundEnabled) R.string.sound_on else R.string.sound_off
)
@@ -228,6 +201,14 @@ class MainActivity : AppCompatActivity() {
vibrate(VibrationEffect.EFFECT_CLICK)
}
+ /**
+ * Update the level selector display
+ */
+ private fun updateLevelSelector() {
+ binding.pauseLevelText.text = selectedLevel.toString()
+ gameBoard.updateLevel(selectedLevel)
+ }
+
/**
* Trigger device vibration with predefined effect
*/
@@ -236,24 +217,18 @@ class MainActivity : AppCompatActivity() {
vibrator.vibrate(VibrationEffect.createPredefined(effectId))
}
- /**
- * Update the level selector display
- */
- private fun updateLevelSelector() {
- selectedLevelText.text = selectedLevel.toString()
+ private fun startGame() {
+ gameView.visibility = View.VISIBLE
+ gameBoard.startGame()
+ gameView.start()
+ hidePauseMenu()
}
- /**
- * Set the game level
- */
- private fun setGameLevel(level: Int) {
- gameView.gameBoard.level = level
- gameView.gameBoard.lines = (level - 1) * 10
- gameView.gameBoard.dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
-
- // Update UI
- levelText.text = level.toString()
- linesText.text = gameView.gameBoard.lines.toString()
+ private fun restartGame() {
+ gameBoard.reset()
+ gameView.visibility = View.VISIBLE
+ gameView.start()
+ showPauseMenu()
}
override fun onPause() {
@@ -261,23 +236,32 @@ class MainActivity : AppCompatActivity() {
if (!gameView.isGameOver()) {
gameView.pause()
showPauseMenu()
+ binding.pauseStartButton.visibility = View.GONE
+ binding.resumeButton.visibility = View.VISIBLE
}
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
- if (gameOverContainer.visibility == View.VISIBLE) {
+ if (binding.gameOverContainer.visibility == View.VISIBLE) {
hideGameOver()
gameView.reset()
return
}
- if (pauseContainer.visibility == View.GONE) {
+ if (binding.pauseContainer.visibility == View.GONE) {
gameView.pause()
showPauseMenu()
+ binding.pauseStartButton.visibility = View.GONE
+ binding.resumeButton.visibility = View.VISIBLE
} else {
hidePauseMenu()
gameView.start()
}
}
+
+ override fun onResume() {
+ super.onResume()
+ gameView.resume()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mintris/game/GameView.kt b/app/src/main/java/com/mintris/game/GameView.kt
index 5eabf4c..be53364 100644
--- a/app/src/main/java/com/mintris/game/GameView.kt
+++ b/app/src/main/java/com/mintris/game/GameView.kt
@@ -7,6 +7,7 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
+import android.graphics.BlurMaskFilter
import android.os.Build
import android.os.Handler
import android.os.Looper
@@ -33,12 +34,16 @@ class GameView @JvmOverloads constructor(
) : View(context, attrs, defStyleAttr) {
// Game board model
- val gameBoard = GameBoard()
+ private var gameBoard = GameBoard()
+ private var gameHaptics: GameHaptics? = null
// Game state
private var isRunning = false
private var isPaused = false
+ // Callbacks
+ var onNextPieceChanged: (() -> Unit)? = null
+
// Rendering
private val blockPaint = Paint().apply {
color = Color.WHITE
@@ -53,7 +58,7 @@ class GameView @JvmOverloads constructor(
private val gridPaint = Paint().apply {
color = Color.parseColor("#222222") // Very dark gray
- alpha = 40
+ alpha = 20 // Reduced from 40 to be more subtle
isAntiAlias = true
strokeWidth = 1f
style = Paint.Style.STROKE
@@ -61,19 +66,28 @@ class GameView @JvmOverloads constructor(
private val glowPaint = Paint().apply {
color = Color.WHITE
- alpha = 80
+ alpha = 40 // Reduced from 80 for more subtlety
isAntiAlias = true
style = Paint.Style.STROKE
- strokeWidth = 2f
+ strokeWidth = 1.5f
+ maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
+ }
+
+ private val blockGlowPaint = Paint().apply {
+ color = Color.WHITE
+ alpha = 60
+ isAntiAlias = true
+ style = Paint.Style.FILL
+ maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
}
private val borderGlowPaint = Paint().apply {
- color = Color.CYAN
- alpha = 120
+ color = Color.WHITE
+ alpha = 60
isAntiAlias = true
style = Paint.Style.STROKE
- strokeWidth = 4f
- setShadowLayer(10f, 0f, 0f, Color.CYAN)
+ strokeWidth = 2f
+ maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
}
private val lineClearPaint = Paint().apply {
@@ -85,7 +99,7 @@ class GameView @JvmOverloads constructor(
// Animation
private var lineClearAnimator: ValueAnimator? = null
private var lineClearProgress = 0f
- private val lineClearDuration = 150L // milliseconds
+ private val lineClearDuration = 100L // milliseconds
// Calculate block size based on view dimensions and board size
private var blockSize = 0f
@@ -111,10 +125,12 @@ class GameView @JvmOverloads constructor(
private var startY = 0f
private var lastTapTime = 0L
private var lastRotationTime = 0L
+ private var lastMoveTime = 0L
private var minSwipeVelocity = 800 // Minimum velocity for swipe to be considered a hard drop
private val maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels)
private val minTapTime = 100L // Minimum time for a tap (in milliseconds)
private val rotationCooldown = 150L // Minimum time between rotations (in milliseconds)
+ private val moveCooldown = 50L // Minimum time between move haptics (in milliseconds)
// Callback for game events
var onGameStateChanged: ((score: Int, level: Int, lines: Int) -> Unit)? = null
@@ -156,15 +172,11 @@ class GameView @JvmOverloads constructor(
* Start the game
*/
fun start() {
- if (isPaused || !isRunning) {
- isPaused = false
- if (!isRunning) {
- isRunning = true
- gameBoard.reset()
- }
- handler.post(gameLoopRunnable)
- invalidate()
- }
+ isPaused = false
+ isRunning = true
+ gameBoard.startGame() // Add this line to ensure a new piece is spawned
+ handler.post(gameLoopRunnable)
+ invalidate()
}
/**
@@ -184,6 +196,7 @@ class GameView @JvmOverloads constructor(
isRunning = false
isPaused = true
gameBoard.reset()
+ gameBoard.startGame() // Add this line to ensure a new piece is spawned
handler.removeCallbacks(gameLoopRunnable)
lineClearAnimator?.cancel()
invalidate()
@@ -204,14 +217,12 @@ class GameView @JvmOverloads constructor(
gameBoard.moveDown()
// Check if lines need to be cleared and start animation if needed
- if (gameBoard.linesToClear.isNotEmpty() && gameBoard.isLineClearAnimationInProgress) {
+ if (gameBoard.linesToClear.isNotEmpty()) {
// Trigger line clear callback for vibration
onLineClear?.invoke(gameBoard.linesToClear.size)
- // Start line clearing animation if not already running
- if (lineClearAnimator == null || !lineClearAnimator!!.isRunning) {
- startLineClearAnimation()
- }
+ // Start line clearing animation immediately
+ startLineClearAnimation()
}
// Update UI with current game state
@@ -222,8 +233,13 @@ class GameView @JvmOverloads constructor(
* Start the line clearing animation
*/
private fun startLineClearAnimation() {
+ // Cancel any existing animation
lineClearAnimator?.cancel()
+ // Reset progress
+ lineClearProgress = 0f
+
+ // Create and start new animation immediately
lineClearAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = lineClearDuration
interpolator = LinearInterpolator()
@@ -273,9 +289,9 @@ class GameView @JvmOverloads constructor(
height.toFloat() / verticalBlocks
)
- // Center the board within the view
+ // Center horizontally and align to bottom
boardLeft = (width - (blockSize * horizontalBlocks)) / 2
- boardTop = (height - (blockSize * verticalBlocks)) / 2
+ boardTop = height - (blockSize * verticalBlocks) // Align to bottom
}
override fun onDraw(canvas: Canvas) {
@@ -313,12 +329,11 @@ class GameView @JvmOverloads constructor(
private fun drawLineClearAnimation(canvas: Canvas) {
// Draw non-clearing blocks
for (y in 0 until gameBoard.height) {
- // Skip lines that are being cleared
if (gameBoard.linesToClear.contains(y)) continue
for (x in 0 until gameBoard.width) {
if (gameBoard.isOccupied(x, y)) {
- drawBlock(canvas, x, y, blockPaint)
+ drawBlock(canvas, x, y, false)
}
}
}
@@ -327,8 +342,8 @@ class GameView @JvmOverloads constructor(
for (lineY in gameBoard.linesToClear) {
for (x in 0 until gameBoard.width) {
// Animation effects for all lines simultaneously
- val brightness = 255 - (lineClearProgress * 200).toInt()
- val scale = 1.0f - lineClearProgress * 0.5f
+ val brightness = 255 - (lineClearProgress * 150).toInt() // Reduced from 200 for smoother fade
+ val scale = 1.0f - lineClearProgress * 0.3f // Reduced from 0.5f for subtler scaling
// Set the paint for the clear animation
lineClearPaint.color = Color.WHITE
@@ -344,8 +359,8 @@ class GameView @JvmOverloads constructor(
val rect = RectF(left, top, right, bottom)
canvas.drawRect(rect, lineClearPaint)
- // Add a glow effect
- lineClearPaint.setShadowLayer(10f * (1f - lineClearProgress), 0f, 0f, Color.WHITE)
+ // Add a more subtle glow effect
+ lineClearPaint.setShadowLayer(8f * (1f - lineClearProgress), 0f, 0f, Color.WHITE)
canvas.drawRect(rect, lineClearPaint)
}
}
@@ -396,7 +411,7 @@ class GameView @JvmOverloads constructor(
for (y in 0 until gameBoard.height) {
for (x in 0 until gameBoard.width) {
if (gameBoard.isOccupied(x, y)) {
- drawBlock(canvas, x, y, blockPaint)
+ drawBlock(canvas, x, y, false)
}
}
}
@@ -417,7 +432,7 @@ class GameView @JvmOverloads constructor(
// Only draw if within bounds and visible on screen
if (boardY >= 0 && boardY < gameBoard.height &&
boardX >= 0 && boardX < gameBoard.width) {
- drawBlock(canvas, boardX, boardY, blockPaint)
+ drawBlock(canvas, boardX, boardY, false)
}
}
}
@@ -440,7 +455,7 @@ class GameView @JvmOverloads constructor(
// Only draw if within bounds and visible on screen
if (boardY >= 0 && boardY < gameBoard.height &&
boardX >= 0 && boardX < gameBoard.width) {
- drawBlock(canvas, boardX, boardY, ghostBlockPaint)
+ drawBlock(canvas, boardX, boardY, true)
}
}
}
@@ -450,29 +465,26 @@ class GameView @JvmOverloads constructor(
/**
* Draw a single tetris block at the given grid position
*/
- private fun drawBlock(canvas: Canvas, x: Int, y: Int, paint: Paint) {
+ private fun drawBlock(canvas: Canvas, x: Int, y: Int, isGhost: Boolean) {
val left = boardLeft + x * blockSize
val top = boardTop + y * blockSize
val right = left + blockSize
val bottom = top + blockSize
- // Draw block with a slight inset to create separation
- val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
- canvas.drawRect(rect, paint)
+ // 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 enhanced glow effect
- val glowRect = RectF(left, top, right, bottom)
- val blockGlowPaint = Paint(glowPaint)
- if (paint == blockPaint) {
- val piece = gameBoard.getCurrentPiece()
- if (piece != null && isPositionInPiece(x, y, piece)) {
- // Set glow color based on piece type
- blockGlowPaint.color = getTetrominoColor(piece.type)
- blockGlowPaint.alpha = 150
- blockGlowPaint.setShadowLayer(3f, 0f, 0f, blockGlowPaint.color)
- }
+ // Draw block
+ blockPaint.apply {
+ color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
+ alpha = if (isGhost) 30 else 255
}
- canvas.drawRect(glowRect, blockGlowPaint)
+ 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)
}
/**
@@ -524,7 +536,7 @@ class GameView @JvmOverloads constructor(
// Check for double tap (rotate)
val currentTime = System.currentTimeMillis()
- if (currentTime - lastTapTime < 250) {
+ if (currentTime - lastTapTime < 200) { // Reduced from 250ms for faster response
// Double tap detected, rotate the piece
if (currentTime - lastRotationTime >= rotationCooldown) {
gameBoard.rotate()
@@ -538,22 +550,33 @@ class GameView @JvmOverloads constructor(
MotionEvent.ACTION_MOVE -> {
val deltaX = event.x - lastTouchX
val deltaY = event.y - lastTouchY
+ val currentTime = System.currentTimeMillis()
- // Horizontal movement (left/right)
- if (abs(deltaX) > blockSize) {
+ // Horizontal movement (left/right) with reduced threshold
+ if (abs(deltaX) > blockSize * 0.5f) { // Reduced from 1.0f for more responsive movement
if (deltaX > 0) {
gameBoard.moveRight()
} else {
gameBoard.moveLeft()
}
lastTouchX = event.x
+ // Add haptic feedback for movement with cooldown
+ if (currentTime - lastMoveTime >= moveCooldown) {
+ gameHaptics?.vibrateForPieceMove()
+ lastMoveTime = currentTime
+ }
invalidate()
}
- // Vertical movement (soft drop)
- if (deltaY > blockSize / 2) {
+ // Vertical movement (soft drop) with reduced threshold
+ if (deltaY > blockSize * 0.25f) { // Reduced from 0.5f for more responsive soft drop
gameBoard.moveDown()
lastTouchY = event.y
+ // Add haptic feedback for movement with cooldown
+ if (currentTime - lastMoveTime >= moveCooldown) {
+ gameHaptics?.vibrateForPieceMove()
+ lastMoveTime = currentTime
+ }
invalidate()
}
}
@@ -565,7 +588,7 @@ class GameView @JvmOverloads constructor(
val deltaX = event.x - startX
// If the movement was fast and downward, treat as hard drop
- if (moveTime > 0 && deltaY > blockSize && (deltaY / moveTime) * 1000 > minSwipeVelocity) {
+ if (moveTime > 0 && deltaY > blockSize * 0.5f && (deltaY / moveTime) * 1000 > minSwipeVelocity) {
gameBoard.hardDrop()
invalidate()
} else if (moveTime < minTapTime &&
@@ -606,9 +629,11 @@ class GameView @JvmOverloads constructor(
fun isGameOver(): Boolean = gameBoard.isGameOver
/**
- * Get the next tetromino
+ * Get the next piece that will be spawned
*/
- fun getNextPiece() = gameBoard.getNextPiece()
+ fun getNextPiece(): Tetromino? {
+ return gameBoard.getNextPiece()
+ }
/**
* Clean up resources when view is detached
@@ -617,4 +642,31 @@ class GameView @JvmOverloads constructor(
super.onDetachedFromWindow()
handler.removeCallbacks(gameLoopRunnable)
}
+
+ /**
+ * Set the game board for this view
+ */
+ fun setGameBoard(board: GameBoard) {
+ gameBoard = board
+ invalidate()
+ }
+
+ /**
+ * Set the haptics handler for this view
+ */
+ fun setHaptics(haptics: GameHaptics) {
+ gameHaptics = haptics
+ }
+
+ /**
+ * Resume the game
+ */
+ fun resume() {
+ if (!isRunning) {
+ isRunning = true
+ handler.post(gameLoopRunnable)
+ }
+ isPaused = false
+ invalidate()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mintris/game/NextPieceView.kt b/app/src/main/java/com/mintris/game/NextPieceView.kt
index 158d058..c82fe2c 100644
--- a/app/src/main/java/com/mintris/game/NextPieceView.kt
+++ b/app/src/main/java/com/mintris/game/NextPieceView.kt
@@ -5,6 +5,7 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
+import android.graphics.BlurMaskFilter
import android.util.AttributeSet
import android.view.View
import kotlin.math.min
@@ -28,10 +29,10 @@ class NextPieceView @JvmOverloads constructor(
private val glowPaint = Paint().apply {
color = Color.WHITE
- alpha = 80
+ alpha = 30
isAntiAlias = true
style = Paint.Style.STROKE
- strokeWidth = 2f
+ strokeWidth = 1.5f
}
/**
@@ -60,6 +61,20 @@ class NextPieceView @JvmOverloads constructor(
val previewLeft = (canvas.width - width * previewBlockSize) / 2
val previewTop = (canvas.height - height * previewBlockSize) / 2
+ // Draw subtle background glow
+ val glowPaint = Paint().apply {
+ color = Color.WHITE
+ alpha = 10
+ maskFilter = BlurMaskFilter(previewBlockSize * 0.5f, BlurMaskFilter.Blur.OUTER)
+ }
+ canvas.drawRect(
+ previewLeft - previewBlockSize,
+ previewTop - previewBlockSize,
+ previewLeft + width * previewBlockSize + previewBlockSize,
+ previewTop + height * previewBlockSize + previewBlockSize,
+ glowPaint
+ )
+
for (y in 0 until height) {
for (x in 0 until width) {
if (piece.isBlockAt(x, y)) {
@@ -68,9 +83,11 @@ class NextPieceView @JvmOverloads constructor(
val right = left + previewBlockSize
val bottom = top + previewBlockSize
+ // Draw block with subtle glow
val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
canvas.drawRect(rect, blockPaint)
+ // Draw subtle border glow
val glowRect = RectF(left, top, right, bottom)
canvas.drawRect(glowRect, glowPaint)
}
diff --git a/app/src/main/java/com/mintris/model/GameBoard.kt b/app/src/main/java/com/mintris/model/GameBoard.kt
index 8e5a4e3..15c3033 100644
--- a/app/src/main/java/com/mintris/model/GameBoard.kt
+++ b/app/src/main/java/com/mintris/model/GameBoard.kt
@@ -48,6 +48,7 @@ class GameBoard(
// Callbacks for game events
var onPieceMove: (() -> Unit)? = null
var onPieceLock: (() -> Unit)? = null
+ var onNextPieceChanged: (() -> Unit)? = null
init {
spawnNextPiece()
@@ -66,6 +67,7 @@ class GameBoard(
// Take the next piece from the bag
nextPiece = Tetromino(bag.removeFirst())
+ onNextPieceChanged?.invoke()
}
/**
@@ -98,6 +100,11 @@ class GameBoard(
*/
fun getHoldPiece(): Tetromino? = holdPiece
+ /**
+ * Get the next piece that will be spawned
+ */
+ fun getNextPiece(): Tetromino? = nextPiece
+
/**
* Spawns the current tetromino at the top of the board
*/
@@ -475,11 +482,6 @@ class GameBoard(
*/
fun getCurrentPiece(): Tetromino? = currentPiece
- /**
- * Get the next tetromino
- */
- fun getNextPiece(): Tetromino? = nextPiece
-
/**
* Check if a cell in the grid is occupied
*/
@@ -491,6 +493,25 @@ class GameBoard(
}
}
+ /**
+ * Update the current level and adjust game parameters
+ */
+ fun updateLevel(newLevel: Int) {
+ level = newLevel.coerceIn(1, 20)
+ // Update game speed based on level (NES formula)
+ dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
+ }
+
+ /**
+ * Start a new game
+ */
+ fun startGame() {
+ reset()
+ // Initialize pieces
+ spawnNextPiece()
+ spawnPiece()
+ }
+
/**
* Reset the game board
*/
@@ -504,10 +525,9 @@ class GameBoard(
// Reset game state
score = 0
- level = 1
lines = 0
isGameOver = false
- dropInterval = 1000L
+ dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
// Reset scoring state
combo = 0
@@ -520,9 +540,9 @@ class GameBoard(
canHold = true
bag.clear()
- // Initialize pieces
- spawnNextPiece()
- spawnPiece()
+ // Clear current and next pieces
+ currentPiece = null
+ nextPiece = null
}
/**
diff --git a/app/src/main/res/drawable/glow_border.xml b/app/src/main/res/drawable/glow_border.xml
new file mode 100644
index 0000000..6bf6c2a
--- /dev/null
+++ b/app/src/main/res/drawable/glow_border.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9d8a224..364cb03 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,112 +5,79 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
+ android:fitsSystemWindows="true"
tools:context=".MainActivity">
-
+
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
-
-
+ android:textSize="24sp"
+ android:fontFamily="sans-serif-light"
+ tools:text="Score: 0" />
-
-
-
-
+ android:textSize="24sp"
+ android:fontFamily="sans-serif-light"
+ tools:text="Level: 1" />
+ android:textSize="24sp"
+ android:fontFamily="sans-serif-light"
+ tools:text="Lines: 0" />
-
-
-
-
-
-
+
-
+
-
+
+
+
+
+
+
@@ -191,7 +187,7 @@
android:layout_marginTop="8dp">