mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 15:25:20 +01:00
Optimize line clear animation for smoother gameplay: - Reduce animation duration to 100ms - Improve animation smoothness with better scaling and fade effects - Eliminate initial animation delay - Add subtle glow effects for better visual feedback
This commit is contained in:
parent
f4e5a9b651
commit
fabb2742da
7 changed files with 344 additions and 263 deletions
|
@ -16,58 +16,43 @@ import com.mintris.game.GameHaptics
|
||||||
import com.mintris.game.GameView
|
import com.mintris.game.GameView
|
||||||
import com.mintris.game.NextPieceView
|
import com.mintris.game.NextPieceView
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
|
import com.mintris.model.GameBoard
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
// UI components
|
// UI components
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var gameView: GameView
|
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 gameHaptics: GameHaptics
|
||||||
|
private lateinit var gameBoard: GameBoard
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
private var isSoundEnabled = true
|
private var isSoundEnabled = true
|
||||||
private var selectedLevel = 1
|
private var selectedLevel = 1
|
||||||
private val maxLevel = 10
|
private val maxLevel = 20
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
// Initialize game haptics
|
// Initialize game components
|
||||||
|
gameBoard = GameBoard()
|
||||||
gameHaptics = GameHaptics(this)
|
gameHaptics = GameHaptics(this)
|
||||||
|
gameView = binding.gameView
|
||||||
|
|
||||||
// Set up game view
|
// Set up game view
|
||||||
gameView = binding.gameView
|
gameView.setGameBoard(gameBoard)
|
||||||
scoreText = binding.scoreText
|
gameView.setHaptics(gameHaptics)
|
||||||
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
|
|
||||||
|
|
||||||
// Connect the next piece view to the game view
|
// Set up next piece preview
|
||||||
nextPieceView.setGameView(gameView)
|
binding.nextPieceView.setGameView(gameView)
|
||||||
|
gameBoard.onNextPieceChanged = {
|
||||||
|
binding.nextPieceView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start game immediately
|
||||||
|
startGame()
|
||||||
|
|
||||||
// Set up callbacks
|
// Set up callbacks
|
||||||
gameView.onGameStateChanged = { score, level, lines ->
|
gameView.onGameStateChanged = { score, level, lines ->
|
||||||
|
@ -99,38 +84,51 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up button click listeners with haptic feedback
|
// Set up button click listeners with haptic feedback
|
||||||
playAgainButton.setOnClickListener {
|
binding.playAgainButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
hideGameOver()
|
hideGameOver()
|
||||||
gameView.reset()
|
gameView.reset()
|
||||||
setGameLevel(selectedLevel)
|
|
||||||
gameView.start()
|
gameView.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeButton.setOnClickListener {
|
binding.resumeButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
hidePauseMenu()
|
hidePauseMenu()
|
||||||
gameView.start()
|
gameView.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsButton.setOnClickListener {
|
binding.settingsButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
toggleSound()
|
toggleSound()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up level selector with haptic feedback
|
// Set up pause menu buttons
|
||||||
levelDownButton.setOnClickListener {
|
binding.pauseStartButton.setOnClickListener {
|
||||||
if (selectedLevel > 1) {
|
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
selectedLevel--
|
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()
|
updateLevelSelector()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
levelUpButton.setOnClickListener {
|
binding.pauseLevelDownButton.setOnClickListener {
|
||||||
if (selectedLevel < maxLevel) {
|
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
selectedLevel++
|
if (selectedLevel > 1) {
|
||||||
|
selectedLevel--
|
||||||
updateLevelSelector()
|
updateLevelSelector()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,57 +136,30 @@ class MainActivity : AppCompatActivity() {
|
||||||
// Initialize level selector
|
// Initialize level selector
|
||||||
updateLevelSelector()
|
updateLevelSelector()
|
||||||
|
|
||||||
// Start game when clicking the screen initially
|
|
||||||
setupTouchToStart()
|
|
||||||
|
|
||||||
// Start with the game paused
|
|
||||||
gameView.pause()
|
|
||||||
|
|
||||||
// Enable edge-to-edge display
|
// Enable edge-to-edge display
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
window.setDecorFitsSystemWindows(false)
|
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
|
* Update UI with current game state
|
||||||
*/
|
*/
|
||||||
private fun updateUI(score: Int, level: Int, lines: Int) {
|
private fun updateUI(score: Int, level: Int, lines: Int) {
|
||||||
scoreText.text = score.toString()
|
binding.scoreText.text = score.toString()
|
||||||
levelText.text = level.toString()
|
binding.currentLevelText.text = level.toString()
|
||||||
linesText.text = lines.toString()
|
binding.linesText.text = lines.toString()
|
||||||
|
|
||||||
// Force redraw of next piece preview
|
// Force redraw of next piece preview
|
||||||
nextPieceView.invalidate()
|
binding.nextPieceView.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show game over screen
|
* Show game over screen
|
||||||
*/
|
*/
|
||||||
private fun showGameOver(score: Int) {
|
private fun showGameOver(score: Int) {
|
||||||
finalScoreText.text = getString(R.string.score) + ": " + score
|
binding.finalScoreText.text = getString(R.string.score) + ": " + score
|
||||||
gameOverContainer.visibility = View.VISIBLE
|
binding.gameOverContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
// Vibrate to indicate game over
|
// Vibrate to indicate game over
|
||||||
vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK)
|
vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK)
|
||||||
|
@ -198,21 +169,23 @@ class MainActivity : AppCompatActivity() {
|
||||||
* Hide game over screen
|
* Hide game over screen
|
||||||
*/
|
*/
|
||||||
private fun hideGameOver() {
|
private fun hideGameOver() {
|
||||||
gameOverContainer.visibility = View.GONE
|
binding.gameOverContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show pause menu
|
* Show settings menu
|
||||||
*/
|
*/
|
||||||
private fun showPauseMenu() {
|
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() {
|
private fun hidePauseMenu() {
|
||||||
pauseContainer.visibility = View.GONE
|
binding.pauseContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,7 +193,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
*/
|
*/
|
||||||
private fun toggleSound() {
|
private fun toggleSound() {
|
||||||
isSoundEnabled = !isSoundEnabled
|
isSoundEnabled = !isSoundEnabled
|
||||||
settingsButton.text = getString(
|
binding.settingsButton.text = getString(
|
||||||
if (isSoundEnabled) R.string.sound_on else R.string.sound_off
|
if (isSoundEnabled) R.string.sound_on else R.string.sound_off
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -228,6 +201,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
vibrate(VibrationEffect.EFFECT_CLICK)
|
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
|
* Trigger device vibration with predefined effect
|
||||||
*/
|
*/
|
||||||
|
@ -236,24 +217,18 @@ class MainActivity : AppCompatActivity() {
|
||||||
vibrator.vibrate(VibrationEffect.createPredefined(effectId))
|
vibrator.vibrate(VibrationEffect.createPredefined(effectId))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun startGame() {
|
||||||
* Update the level selector display
|
gameView.visibility = View.VISIBLE
|
||||||
*/
|
gameBoard.startGame()
|
||||||
private fun updateLevelSelector() {
|
gameView.start()
|
||||||
selectedLevelText.text = selectedLevel.toString()
|
hidePauseMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun restartGame() {
|
||||||
* Set the game level
|
gameBoard.reset()
|
||||||
*/
|
gameView.visibility = View.VISIBLE
|
||||||
private fun setGameLevel(level: Int) {
|
gameView.start()
|
||||||
gameView.gameBoard.level = level
|
showPauseMenu()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -261,23 +236,32 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (!gameView.isGameOver()) {
|
if (!gameView.isGameOver()) {
|
||||||
gameView.pause()
|
gameView.pause()
|
||||||
showPauseMenu()
|
showPauseMenu()
|
||||||
|
binding.pauseStartButton.visibility = View.GONE
|
||||||
|
binding.resumeButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (gameOverContainer.visibility == View.VISIBLE) {
|
if (binding.gameOverContainer.visibility == View.VISIBLE) {
|
||||||
hideGameOver()
|
hideGameOver()
|
||||||
gameView.reset()
|
gameView.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pauseContainer.visibility == View.GONE) {
|
if (binding.pauseContainer.visibility == View.GONE) {
|
||||||
gameView.pause()
|
gameView.pause()
|
||||||
showPauseMenu()
|
showPauseMenu()
|
||||||
|
binding.pauseStartButton.visibility = View.GONE
|
||||||
|
binding.resumeButton.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
hidePauseMenu()
|
hidePauseMenu()
|
||||||
gameView.start()
|
gameView.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
gameView.resume()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import android.graphics.Color
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
|
import android.graphics.BlurMaskFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
@ -33,12 +34,16 @@ class GameView @JvmOverloads constructor(
|
||||||
) : View(context, attrs, defStyleAttr) {
|
) : View(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
// Game board model
|
// Game board model
|
||||||
val gameBoard = GameBoard()
|
private var gameBoard = GameBoard()
|
||||||
|
private var gameHaptics: GameHaptics? = null
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
private var isPaused = false
|
private var isPaused = false
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
var onNextPieceChanged: (() -> Unit)? = null
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
private val blockPaint = Paint().apply {
|
private val blockPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
|
@ -53,7 +58,7 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val gridPaint = Paint().apply {
|
private val gridPaint = Paint().apply {
|
||||||
color = Color.parseColor("#222222") // Very dark gray
|
color = Color.parseColor("#222222") // Very dark gray
|
||||||
alpha = 40
|
alpha = 20 // Reduced from 40 to be more subtle
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
strokeWidth = 1f
|
strokeWidth = 1f
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
|
@ -61,19 +66,28 @@ class GameView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val glowPaint = Paint().apply {
|
private val glowPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
alpha = 80
|
alpha = 40 // Reduced from 80 for more subtlety
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.STROKE
|
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 {
|
private val borderGlowPaint = Paint().apply {
|
||||||
color = Color.CYAN
|
color = Color.WHITE
|
||||||
alpha = 120
|
alpha = 60
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = 4f
|
strokeWidth = 2f
|
||||||
setShadowLayer(10f, 0f, 0f, Color.CYAN)
|
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val lineClearPaint = Paint().apply {
|
private val lineClearPaint = Paint().apply {
|
||||||
|
@ -85,7 +99,7 @@ class GameView @JvmOverloads constructor(
|
||||||
// Animation
|
// Animation
|
||||||
private var lineClearAnimator: ValueAnimator? = null
|
private var lineClearAnimator: ValueAnimator? = null
|
||||||
private var lineClearProgress = 0f
|
private var lineClearProgress = 0f
|
||||||
private val lineClearDuration = 150L // milliseconds
|
private val lineClearDuration = 100L // milliseconds
|
||||||
|
|
||||||
// Calculate block size based on view dimensions and board size
|
// Calculate block size based on view dimensions and board size
|
||||||
private var blockSize = 0f
|
private var blockSize = 0f
|
||||||
|
@ -111,10 +125,12 @@ class GameView @JvmOverloads constructor(
|
||||||
private var startY = 0f
|
private var startY = 0f
|
||||||
private var lastTapTime = 0L
|
private var lastTapTime = 0L
|
||||||
private var lastRotationTime = 0L
|
private var lastRotationTime = 0L
|
||||||
|
private var lastMoveTime = 0L
|
||||||
private var minSwipeVelocity = 800 // Minimum velocity for swipe to be considered a hard drop
|
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 maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels)
|
||||||
private val minTapTime = 100L // Minimum time for a tap (in milliseconds)
|
private val minTapTime = 100L // Minimum time for a tap (in milliseconds)
|
||||||
private val rotationCooldown = 150L // Minimum time between rotations (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
|
// Callback for game events
|
||||||
var onGameStateChanged: ((score: Int, level: Int, lines: Int) -> Unit)? = null
|
var onGameStateChanged: ((score: Int, level: Int, lines: Int) -> Unit)? = null
|
||||||
|
@ -156,16 +172,12 @@ class GameView @JvmOverloads constructor(
|
||||||
* Start the game
|
* Start the game
|
||||||
*/
|
*/
|
||||||
fun start() {
|
fun start() {
|
||||||
if (isPaused || !isRunning) {
|
|
||||||
isPaused = false
|
isPaused = false
|
||||||
if (!isRunning) {
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
gameBoard.reset()
|
gameBoard.startGame() // Add this line to ensure a new piece is spawned
|
||||||
}
|
|
||||||
handler.post(gameLoopRunnable)
|
handler.post(gameLoopRunnable)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause the game
|
* Pause the game
|
||||||
|
@ -184,6 +196,7 @@ class GameView @JvmOverloads constructor(
|
||||||
isRunning = false
|
isRunning = false
|
||||||
isPaused = true
|
isPaused = true
|
||||||
gameBoard.reset()
|
gameBoard.reset()
|
||||||
|
gameBoard.startGame() // Add this line to ensure a new piece is spawned
|
||||||
handler.removeCallbacks(gameLoopRunnable)
|
handler.removeCallbacks(gameLoopRunnable)
|
||||||
lineClearAnimator?.cancel()
|
lineClearAnimator?.cancel()
|
||||||
invalidate()
|
invalidate()
|
||||||
|
@ -204,15 +217,13 @@ class GameView @JvmOverloads constructor(
|
||||||
gameBoard.moveDown()
|
gameBoard.moveDown()
|
||||||
|
|
||||||
// Check if lines need to be cleared and start animation if needed
|
// 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
|
// Trigger line clear callback for vibration
|
||||||
onLineClear?.invoke(gameBoard.linesToClear.size)
|
onLineClear?.invoke(gameBoard.linesToClear.size)
|
||||||
|
|
||||||
// Start line clearing animation if not already running
|
// Start line clearing animation immediately
|
||||||
if (lineClearAnimator == null || !lineClearAnimator!!.isRunning) {
|
|
||||||
startLineClearAnimation()
|
startLineClearAnimation()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI with current game state
|
// Update UI with current game state
|
||||||
onGameStateChanged?.invoke(gameBoard.score, gameBoard.level, gameBoard.lines)
|
onGameStateChanged?.invoke(gameBoard.score, gameBoard.level, gameBoard.lines)
|
||||||
|
@ -222,8 +233,13 @@ class GameView @JvmOverloads constructor(
|
||||||
* Start the line clearing animation
|
* Start the line clearing animation
|
||||||
*/
|
*/
|
||||||
private fun startLineClearAnimation() {
|
private fun startLineClearAnimation() {
|
||||||
|
// Cancel any existing animation
|
||||||
lineClearAnimator?.cancel()
|
lineClearAnimator?.cancel()
|
||||||
|
|
||||||
|
// Reset progress
|
||||||
|
lineClearProgress = 0f
|
||||||
|
|
||||||
|
// Create and start new animation immediately
|
||||||
lineClearAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
lineClearAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||||
duration = lineClearDuration
|
duration = lineClearDuration
|
||||||
interpolator = LinearInterpolator()
|
interpolator = LinearInterpolator()
|
||||||
|
@ -273,9 +289,9 @@ class GameView @JvmOverloads constructor(
|
||||||
height.toFloat() / verticalBlocks
|
height.toFloat() / verticalBlocks
|
||||||
)
|
)
|
||||||
|
|
||||||
// Center the board within the view
|
// Center horizontally and align to bottom
|
||||||
boardLeft = (width - (blockSize * horizontalBlocks)) / 2
|
boardLeft = (width - (blockSize * horizontalBlocks)) / 2
|
||||||
boardTop = (height - (blockSize * verticalBlocks)) / 2
|
boardTop = height - (blockSize * verticalBlocks) // Align to bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
@ -313,12 +329,11 @@ class GameView @JvmOverloads constructor(
|
||||||
private fun drawLineClearAnimation(canvas: Canvas) {
|
private fun drawLineClearAnimation(canvas: Canvas) {
|
||||||
// Draw non-clearing blocks
|
// Draw non-clearing blocks
|
||||||
for (y in 0 until gameBoard.height) {
|
for (y in 0 until gameBoard.height) {
|
||||||
// Skip lines that are being cleared
|
|
||||||
if (gameBoard.linesToClear.contains(y)) continue
|
if (gameBoard.linesToClear.contains(y)) continue
|
||||||
|
|
||||||
for (x in 0 until gameBoard.width) {
|
for (x in 0 until gameBoard.width) {
|
||||||
if (gameBoard.isOccupied(x, y)) {
|
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 (lineY in gameBoard.linesToClear) {
|
||||||
for (x in 0 until gameBoard.width) {
|
for (x in 0 until gameBoard.width) {
|
||||||
// Animation effects for all lines simultaneously
|
// Animation effects for all lines simultaneously
|
||||||
val brightness = 255 - (lineClearProgress * 200).toInt()
|
val brightness = 255 - (lineClearProgress * 150).toInt() // Reduced from 200 for smoother fade
|
||||||
val scale = 1.0f - lineClearProgress * 0.5f
|
val scale = 1.0f - lineClearProgress * 0.3f // Reduced from 0.5f for subtler scaling
|
||||||
|
|
||||||
// Set the paint for the clear animation
|
// Set the paint for the clear animation
|
||||||
lineClearPaint.color = Color.WHITE
|
lineClearPaint.color = Color.WHITE
|
||||||
|
@ -344,8 +359,8 @@ class GameView @JvmOverloads constructor(
|
||||||
val rect = RectF(left, top, right, bottom)
|
val rect = RectF(left, top, right, bottom)
|
||||||
canvas.drawRect(rect, lineClearPaint)
|
canvas.drawRect(rect, lineClearPaint)
|
||||||
|
|
||||||
// Add a glow effect
|
// Add a more subtle glow effect
|
||||||
lineClearPaint.setShadowLayer(10f * (1f - lineClearProgress), 0f, 0f, Color.WHITE)
|
lineClearPaint.setShadowLayer(8f * (1f - lineClearProgress), 0f, 0f, Color.WHITE)
|
||||||
canvas.drawRect(rect, lineClearPaint)
|
canvas.drawRect(rect, lineClearPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,7 +411,7 @@ class GameView @JvmOverloads constructor(
|
||||||
for (y in 0 until gameBoard.height) {
|
for (y in 0 until gameBoard.height) {
|
||||||
for (x in 0 until gameBoard.width) {
|
for (x in 0 until gameBoard.width) {
|
||||||
if (gameBoard.isOccupied(x, y)) {
|
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
|
// Only draw if within bounds and visible on screen
|
||||||
if (boardY >= 0 && boardY < gameBoard.height &&
|
if (boardY >= 0 && boardY < gameBoard.height &&
|
||||||
boardX >= 0 && boardX < gameBoard.width) {
|
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
|
// Only draw if within bounds and visible on screen
|
||||||
if (boardY >= 0 && boardY < gameBoard.height &&
|
if (boardY >= 0 && boardY < gameBoard.height &&
|
||||||
boardX >= 0 && boardX < gameBoard.width) {
|
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
|
* 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 left = boardLeft + x * blockSize
|
||||||
val top = boardTop + y * blockSize
|
val top = boardTop + y * blockSize
|
||||||
val right = left + blockSize
|
val right = left + blockSize
|
||||||
val bottom = top + blockSize
|
val bottom = top + blockSize
|
||||||
|
|
||||||
// Draw block with a slight inset to create separation
|
// Draw outer glow
|
||||||
val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
|
blockGlowPaint.color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
|
||||||
canvas.drawRect(rect, paint)
|
canvas.drawRect(left - 2f, top - 2f, right + 2f, bottom + 2f, blockGlowPaint)
|
||||||
|
|
||||||
// Draw enhanced glow effect
|
// Draw block
|
||||||
val glowRect = RectF(left, top, right, bottom)
|
blockPaint.apply {
|
||||||
val blockGlowPaint = Paint(glowPaint)
|
color = if (isGhost) Color.argb(30, 255, 255, 255) else Color.WHITE
|
||||||
if (paint == blockPaint) {
|
alpha = if (isGhost) 30 else 255
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
canvas.drawRect(left, top, right, bottom, blockPaint)
|
||||||
canvas.drawRect(glowRect, blockGlowPaint)
|
|
||||||
|
// 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)
|
// Check for double tap (rotate)
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
if (currentTime - lastTapTime < 250) {
|
if (currentTime - lastTapTime < 200) { // Reduced from 250ms for faster response
|
||||||
// Double tap detected, rotate the piece
|
// Double tap detected, rotate the piece
|
||||||
if (currentTime - lastRotationTime >= rotationCooldown) {
|
if (currentTime - lastRotationTime >= rotationCooldown) {
|
||||||
gameBoard.rotate()
|
gameBoard.rotate()
|
||||||
|
@ -538,22 +550,33 @@ class GameView @JvmOverloads constructor(
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
val deltaX = event.x - lastTouchX
|
val deltaX = event.x - lastTouchX
|
||||||
val deltaY = event.y - lastTouchY
|
val deltaY = event.y - lastTouchY
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
|
||||||
// Horizontal movement (left/right)
|
// Horizontal movement (left/right) with reduced threshold
|
||||||
if (abs(deltaX) > blockSize) {
|
if (abs(deltaX) > blockSize * 0.5f) { // Reduced from 1.0f for more responsive movement
|
||||||
if (deltaX > 0) {
|
if (deltaX > 0) {
|
||||||
gameBoard.moveRight()
|
gameBoard.moveRight()
|
||||||
} else {
|
} else {
|
||||||
gameBoard.moveLeft()
|
gameBoard.moveLeft()
|
||||||
}
|
}
|
||||||
lastTouchX = event.x
|
lastTouchX = event.x
|
||||||
|
// Add haptic feedback for movement with cooldown
|
||||||
|
if (currentTime - lastMoveTime >= moveCooldown) {
|
||||||
|
gameHaptics?.vibrateForPieceMove()
|
||||||
|
lastMoveTime = currentTime
|
||||||
|
}
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertical movement (soft drop)
|
// Vertical movement (soft drop) with reduced threshold
|
||||||
if (deltaY > blockSize / 2) {
|
if (deltaY > blockSize * 0.25f) { // Reduced from 0.5f for more responsive soft drop
|
||||||
gameBoard.moveDown()
|
gameBoard.moveDown()
|
||||||
lastTouchY = event.y
|
lastTouchY = event.y
|
||||||
|
// Add haptic feedback for movement with cooldown
|
||||||
|
if (currentTime - lastMoveTime >= moveCooldown) {
|
||||||
|
gameHaptics?.vibrateForPieceMove()
|
||||||
|
lastMoveTime = currentTime
|
||||||
|
}
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,7 +588,7 @@ class GameView @JvmOverloads constructor(
|
||||||
val deltaX = event.x - startX
|
val deltaX = event.x - startX
|
||||||
|
|
||||||
// If the movement was fast and downward, treat as hard drop
|
// 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()
|
gameBoard.hardDrop()
|
||||||
invalidate()
|
invalidate()
|
||||||
} else if (moveTime < minTapTime &&
|
} else if (moveTime < minTapTime &&
|
||||||
|
@ -606,9 +629,11 @@ class GameView @JvmOverloads constructor(
|
||||||
fun isGameOver(): Boolean = gameBoard.isGameOver
|
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
|
* Clean up resources when view is detached
|
||||||
|
@ -617,4 +642,31 @@ class GameView @JvmOverloads constructor(
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
handler.removeCallbacks(gameLoopRunnable)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
|
import android.graphics.BlurMaskFilter
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -28,10 +29,10 @@ class NextPieceView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val glowPaint = Paint().apply {
|
private val glowPaint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
alpha = 80
|
alpha = 30
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
style = Paint.Style.STROKE
|
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 previewLeft = (canvas.width - width * previewBlockSize) / 2
|
||||||
val previewTop = (canvas.height - height * 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 (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)) {
|
||||||
|
@ -68,9 +83,11 @@ class NextPieceView @JvmOverloads constructor(
|
||||||
val right = left + previewBlockSize
|
val right = left + previewBlockSize
|
||||||
val bottom = top + previewBlockSize
|
val bottom = top + previewBlockSize
|
||||||
|
|
||||||
|
// Draw block with subtle glow
|
||||||
val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
|
val rect = RectF(left + 1, top + 1, right - 1, bottom - 1)
|
||||||
canvas.drawRect(rect, blockPaint)
|
canvas.drawRect(rect, blockPaint)
|
||||||
|
|
||||||
|
// Draw subtle border glow
|
||||||
val glowRect = RectF(left, top, right, bottom)
|
val glowRect = RectF(left, top, right, bottom)
|
||||||
canvas.drawRect(glowRect, glowPaint)
|
canvas.drawRect(glowRect, glowPaint)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ class GameBoard(
|
||||||
// Callbacks for game events
|
// Callbacks for game events
|
||||||
var onPieceMove: (() -> Unit)? = null
|
var onPieceMove: (() -> Unit)? = null
|
||||||
var onPieceLock: (() -> Unit)? = null
|
var onPieceLock: (() -> Unit)? = null
|
||||||
|
var onNextPieceChanged: (() -> Unit)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
spawnNextPiece()
|
spawnNextPiece()
|
||||||
|
@ -66,6 +67,7 @@ class GameBoard(
|
||||||
|
|
||||||
// Take the next piece from the bag
|
// Take the next piece from the bag
|
||||||
nextPiece = Tetromino(bag.removeFirst())
|
nextPiece = Tetromino(bag.removeFirst())
|
||||||
|
onNextPieceChanged?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,6 +100,11 @@ class GameBoard(
|
||||||
*/
|
*/
|
||||||
fun getHoldPiece(): Tetromino? = holdPiece
|
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
|
* Spawns the current tetromino at the top of the board
|
||||||
*/
|
*/
|
||||||
|
@ -475,11 +482,6 @@ class GameBoard(
|
||||||
*/
|
*/
|
||||||
fun getCurrentPiece(): Tetromino? = currentPiece
|
fun getCurrentPiece(): Tetromino? = currentPiece
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next tetromino
|
|
||||||
*/
|
|
||||||
fun getNextPiece(): Tetromino? = nextPiece
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell in the grid is occupied
|
* 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
|
* Reset the game board
|
||||||
*/
|
*/
|
||||||
|
@ -504,10 +525,9 @@ class GameBoard(
|
||||||
|
|
||||||
// Reset game state
|
// Reset game state
|
||||||
score = 0
|
score = 0
|
||||||
level = 1
|
|
||||||
lines = 0
|
lines = 0
|
||||||
isGameOver = false
|
isGameOver = false
|
||||||
dropInterval = 1000L
|
dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
||||||
|
|
||||||
// Reset scoring state
|
// Reset scoring state
|
||||||
combo = 0
|
combo = 0
|
||||||
|
@ -520,9 +540,9 @@ class GameBoard(
|
||||||
canHold = true
|
canHold = true
|
||||||
bag.clear()
|
bag.clear()
|
||||||
|
|
||||||
// Initialize pieces
|
// Clear current and next pieces
|
||||||
spawnNextPiece()
|
currentPiece = null
|
||||||
spawnPiece()
|
nextPiece = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
9
app/src/main/res/drawable/glow_border.xml
Normal file
9
app/src/main/res/drawable/glow_border.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#FFFFFF" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<solid android:color="#00000000" />
|
||||||
|
</shape>
|
|
@ -5,112 +5,79 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<!-- Game Container with Glow -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/gameContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.mintris.game.GameView
|
<com.mintris.game.GameView
|
||||||
android:id="@+id/gameView"
|
android:id="@+id/gameView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent" />
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
<!-- Glowing Border -->
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
<View
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
android:id="@+id/glowBorder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/glow_border" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- HUD Container - Score, Level, Lines -->
|
<!-- HUD Container - Score, Level, Lines -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/hudContainer"
|
android:id="@+id/hudContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/scoreLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/score"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/scoreText"
|
android:id="@+id/scoreText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="0"
|
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold" />
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="Score: 0" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/levelLabel"
|
android:id="@+id/currentLevelText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/level"
|
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="12sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold" />
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="Level: 1" />
|
||||||
<TextView
|
|
||||||
android:id="@+id/levelText"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="1"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/linesLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/lines"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/linesText"
|
android:id="@+id/linesText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="0"
|
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp"
|
android:textSize="24sp"
|
||||||
android:textStyle="bold" />
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="Lines: 0" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Next Piece Preview -->
|
<!-- Next Piece Preview -->
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/nextPieceContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/nextLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/next"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<com.mintris.game.NextPieceView
|
<com.mintris.game.NextPieceView
|
||||||
android:id="@+id/nextPieceView"
|
android:id="@+id/nextPieceView"
|
||||||
android:layout_width="64dp"
|
android:layout_width="80dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="80dp"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_margin="16dp"
|
||||||
android:background="@color/black" />
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
</LinearLayout>
|
app:layout_constraintTop_toBottomOf="@id/hudContainer" />
|
||||||
|
|
||||||
<!-- Game Over overlay -->
|
<!-- Game Over overlay -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -149,7 +116,7 @@
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Pause overlay -->
|
<!-- Settings Menu overlay -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/pauseContainer"
|
android:id="@+id/pauseContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -159,20 +126,49 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/resumeButton"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/settings"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="32dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pauseStartButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="@string/start"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/resumeButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
android:text="@string/resume"
|
android:text="@string/resume"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pauseRestartButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="@string/restart"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/levelSelectorContainer"
|
android:id="@+id/levelSelectorContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="32dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
|
@ -191,7 +187,7 @@
|
||||||
android:layout_marginTop="8dp">
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/levelDownButton"
|
android:id="@+id/pauseLevelDownButton"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
@ -200,7 +196,7 @@
|
||||||
android:textSize="24sp" />
|
android:textSize="24sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/selectedLevelText"
|
android:id="@+id/pauseLevelText"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
@ -210,7 +206,7 @@
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/levelUpButton"
|
android:id="@+id/pauseLevelUpButton"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
@ -222,9 +218,9 @@
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/settingsButton"
|
android:id="@+id/settingsButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="200dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="32dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
android:text="@string/sound_on"
|
android:text="@string/sound_on"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Mintris</string>
|
<string name="app_name">Mintris</string>
|
||||||
<string name="game_over">GAME OVER</string>
|
<string name="game_over">Game Over</string>
|
||||||
<string name="score">SCORE</string>
|
<string name="score">Score</string>
|
||||||
<string name="level">LEVEL</string>
|
<string name="level">Level</string>
|
||||||
<string name="lines">LINES</string>
|
<string name="lines">Lines</string>
|
||||||
<string name="next">NEXT</string>
|
<string name="next">Next</string>
|
||||||
<string name="play">PLAY</string>
|
<string name="play">Play Again</string>
|
||||||
<string name="resume">RESUME</string>
|
<string name="resume">Resume</string>
|
||||||
<string name="pause">PAUSE</string>
|
<string name="pause">PAUSE</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="sound_on">Sound: ON</string>
|
<string name="start">Start Game</string>
|
||||||
<string name="sound_off">Sound: OFF</string>
|
<string name="restart">Restart</string>
|
||||||
<string name="select_level">Select Level</string>
|
<string name="select_level">Select Level</string>
|
||||||
|
<string name="sound_on">Sound: On</string>
|
||||||
|
<string name="sound_off">Sound: Off</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue