mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-17 20:15:21 +01:00
Compare commits
5 commits
f5f135ff27
...
7babaeca50
Author | SHA1 | Date | |
---|---|---|---|
|
7babaeca50 | ||
|
e26c6ebd8c | ||
|
df9957580e | ||
|
22fd887037 | ||
|
38163c33a3 |
48 changed files with 990 additions and 1816 deletions
18
README.md
18
README.md
|
@ -1,11 +1,11 @@
|
|||
# Mintris
|
||||
# pixelmintdrop
|
||||
|
||||
A modern Tetris implementation for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design.
|
||||
A modern block-stacking puzzle game for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design.
|
||||
|
||||
## Features
|
||||
|
||||
### Core Gameplay
|
||||
- Classic Tetris mechanics
|
||||
- Classic block-stacking mechanics
|
||||
- 7-bag randomizer for piece distribution
|
||||
- Ghost piece preview
|
||||
- Hard drop and soft drop
|
||||
|
@ -29,7 +29,7 @@ The game features a comprehensive scoring system:
|
|||
- Single line: 40 points
|
||||
- Double: 100 points
|
||||
- Triple: 300 points
|
||||
- Tetris (4 lines): 1200 points
|
||||
- Quad (4 lines): 1200 points
|
||||
|
||||
#### Multipliers
|
||||
|
||||
|
@ -48,15 +48,15 @@ The game features a comprehensive scoring system:
|
|||
- 4 combos: 2.5x
|
||||
- 5+ combos: 3.0x
|
||||
|
||||
3. **Back-to-Back Tetris**
|
||||
- 50% bonus (1.5x) for consecutive Tetris clears
|
||||
- Resets if a non-Tetris clear is performed
|
||||
3. **Back-to-Back Quad**
|
||||
- 50% bonus (1.5x) for consecutive quad clears
|
||||
- Resets if a non-quad clear is performed
|
||||
|
||||
4. **Perfect Clear**
|
||||
- 2x for single line
|
||||
- 3x for double
|
||||
- 4x for triple
|
||||
- 5x for Tetris
|
||||
- 5x for quad
|
||||
- Awarded when clearing lines without leaving blocks
|
||||
|
||||
5. **All Clear**
|
||||
|
@ -219,7 +219,7 @@ private fun onGameOver(score: Long) {
|
|||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/cmclark00/mintris.git
|
||||
git clone https://github.com/cmclark00/pixelmintdrop.git
|
||||
```
|
||||
|
||||
2. Open the project in Android Studio
|
||||
|
|
|
@ -4,13 +4,13 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace 'com.mintris'
|
||||
compileSdk 34
|
||||
namespace "com.pixelmintdrop"
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.mintris"
|
||||
applicationId "com.pixelmintdrop"
|
||||
minSdk 30
|
||||
targetSdk 34
|
||||
targetSdk 35
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
|
@ -27,6 +27,7 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
dataBinding true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
|
10
app/proguard-rules.pro
vendored
10
app/proguard-rules.pro
vendored
|
@ -6,17 +6,17 @@
|
|||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Keep models intact
|
||||
-keep class com.mintris.model.** { *; }
|
||||
-keep class com.pixelmintdrop.model.** { *; }
|
||||
|
||||
# Keep game classes intact to prevent issues
|
||||
-keep class com.mintris.game.** { *; }
|
||||
-keep class com.pixelmintdrop.game.** { *; }
|
||||
|
||||
# Preserve critical classes that might be used through reflection
|
||||
-keep class com.mintris.audio.GameMusic { *; }
|
||||
-keep class com.mintris.ui.** { *; }
|
||||
-keep class com.pixelmintdrop.audio.GameMusic { *; }
|
||||
-keep class com.pixelmintdrop.ui.** { *; }
|
||||
|
||||
# Keep all public methods in the MainActivity
|
||||
-keepclassmembers class com.mintris.MainActivity {
|
||||
-keepclassmembers class com.pixelmintdrop.MainActivity {
|
||||
public *;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Mintris">
|
||||
android:theme="@style/Theme.pixelmintdrop">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Mintris.NoActionBar"
|
||||
android:theme="@style/Theme.pixelmintdrop.NoActionBar"
|
||||
android:immersive="true"
|
||||
android:resizeableActivity="false"
|
||||
android:excludeFromRecents="false"
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
package com.mintris.game
|
||||
|
||||
import android.content.Context
|
||||
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
|
||||
|
||||
/**
|
||||
* Custom view to display the next Tetromino piece
|
||||
*/
|
||||
class NextPieceView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private var gameView: GameView? = null
|
||||
|
||||
// Rendering
|
||||
private val blockPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val glowPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 30
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 1.5f
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the game view to get the next piece from
|
||||
*/
|
||||
fun setGameView(gameView: GameView) {
|
||||
this.gameView = gameView
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
// Get the next piece from game view
|
||||
gameView?.let {
|
||||
it.getNextPiece()?.let { piece ->
|
||||
val width = piece.getWidth()
|
||||
val height = piece.getHeight()
|
||||
|
||||
// Calculate block size for the preview (smaller than main board)
|
||||
val previewBlockSize = min(
|
||||
canvas.width.toFloat() / (width + 2),
|
||||
canvas.height.toFloat() / (height + 2)
|
||||
)
|
||||
|
||||
// Center the piece in the preview area
|
||||
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)) {
|
||||
val left = previewLeft + x * previewBlockSize
|
||||
val top = previewTop + y * previewBlockSize
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,765 +0,0 @@
|
|||
package com.mintris.model
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Represents the game board (grid) and manages game state
|
||||
*/
|
||||
class GameBoard(
|
||||
val width: Int = 10,
|
||||
val height: Int = 20
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "GameBoard"
|
||||
}
|
||||
|
||||
// Board grid to track locked pieces
|
||||
// True = occupied, False = empty
|
||||
private val grid = Array(height) { BooleanArray(width) { false } }
|
||||
|
||||
// Current active tetromino
|
||||
private var currentPiece: Tetromino? = null
|
||||
|
||||
// Next tetromino to be played
|
||||
private var nextPiece: Tetromino? = null
|
||||
|
||||
// Hold piece
|
||||
private var holdPiece: Tetromino? = null
|
||||
private var canHold = true
|
||||
|
||||
// 7-bag randomizer
|
||||
private val bag = mutableListOf<TetrominoType>()
|
||||
|
||||
// Game state
|
||||
var score = 0
|
||||
var level = 1
|
||||
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
|
||||
private var combo = 0
|
||||
private var lastClearWasTetris = false
|
||||
private var lastClearWasPerfect = false
|
||||
private var lastClearWasAllClear = false
|
||||
private var lastPieceClearedLines = false // Track if the last piece placed cleared lines
|
||||
|
||||
// Animation state
|
||||
var linesToClear = mutableListOf<Int>()
|
||||
var isLineClearAnimationInProgress = false
|
||||
|
||||
// Initial game speed (milliseconds per drop)
|
||||
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(TetrominoType.entries.toTypedArray())
|
||||
bag.shuffle()
|
||||
}
|
||||
|
||||
// Take the next piece from the bag
|
||||
nextPiece = Tetromino(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
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently held piece
|
||||
*/
|
||||
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
|
||||
*/
|
||||
fun spawnPiece() {
|
||||
Log.d(TAG, "spawnPiece() started - current states: isHardDropInProgress=$isHardDropInProgress, isPieceLocking=$isPieceLocking")
|
||||
|
||||
currentPiece = nextPiece
|
||||
spawnNextPiece()
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current piece left
|
||||
*/
|
||||
fun moveLeft() {
|
||||
if (canMove(-1, 0)) {
|
||||
currentPiece?.x = currentPiece?.x?.minus(1) ?: 0
|
||||
onPieceMove?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current piece right
|
||||
*/
|
||||
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)) {
|
||||
val boardX = newX + x
|
||||
val boardY = newY + y
|
||||
|
||||
// Check if the position is outside the board horizontally
|
||||
if (boardX < 0 || boardX >= width) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the position is below the board
|
||||
if (boardY >= height) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the position is already occupied (but not if it's above the board)
|
||||
if (boardY >= 0 && grid[boardY][boardX]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the position is more than one unit above the top of the screen
|
||||
if (boardY < -1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the current piece in place
|
||||
*/
|
||||
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")
|
||||
isPieceLocking = true
|
||||
|
||||
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)) {
|
||||
val boardX = piece.x + x
|
||||
val boardY = piece.y + y
|
||||
|
||||
// Only add to grid if within bounds
|
||||
if (boardY >= 0 && boardY < height && boardX >= 0 && boardX < width) {
|
||||
grid[boardY][boardX] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the piece lock vibration
|
||||
onPieceLock?.invoke()
|
||||
|
||||
// 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
|
||||
Log.d(TAG, "lockPiece() - about to spawn new piece at y=${piece.y}, isHardDropInProgress=$isHardDropInProgress")
|
||||
|
||||
// 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) {
|
||||
if (grid[y].all { it }) {
|
||||
// Line is full, add to lines to clear
|
||||
linesToClear.add(y)
|
||||
shiftAmount++
|
||||
} else if (shiftAmount > 0) {
|
||||
// Shift this row down by shiftAmount
|
||||
System.arraycopy(grid[y], 0, grid[y + shiftAmount], 0, width)
|
||||
}
|
||||
y--
|
||||
}
|
||||
|
||||
// Store the last cleared lines
|
||||
lastClearedLines.clear()
|
||||
lastClearedLines.addAll(linesToClear)
|
||||
|
||||
// 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
|
||||
for (y in 0 until shiftAmount) {
|
||||
java.util.Arrays.fill(grid[y], false)
|
||||
}
|
||||
|
||||
Thread {
|
||||
calculateScore(shiftAmount)
|
||||
}.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 Tetris bonus
|
||||
val backToBackMultiplier = if (clearedLines == 4 && lastClearWasTetris) 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
|
||||
lastClearWasTetris = 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 != TetrominoType.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(): Tetromino? = 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
|
||||
lastClearWasTetris = 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,196 +0,0 @@
|
|||
package com.mintris.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
||||
class StatsManager(context: Context) {
|
||||
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 totalTetrises: Int = 0
|
||||
|
||||
// Session stats
|
||||
private var sessionScore: Int = 0
|
||||
private var sessionLines: Int = 0
|
||||
private var sessionPieces: Int = 0
|
||||
private var sessionTime: Long = 0
|
||||
private var sessionLevel: Int = 0
|
||||
|
||||
// Line clear stats (session)
|
||||
private var sessionSingles: Int = 0
|
||||
private var sessionDoubles: Int = 0
|
||||
private var sessionTriples: Int = 0
|
||||
private var sessionTetrises: Int = 0
|
||||
|
||||
init {
|
||||
loadStats()
|
||||
}
|
||||
|
||||
private fun loadStats() {
|
||||
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)
|
||||
totalTetrises = prefs.getInt(KEY_TOTAL_TETRISES, 0)
|
||||
}
|
||||
|
||||
private fun saveStats() {
|
||||
prefs.edit()
|
||||
.putInt(KEY_TOTAL_GAMES, totalGames)
|
||||
.putLong(KEY_TOTAL_SCORE, totalScore)
|
||||
.putInt(KEY_TOTAL_LINES, totalLines)
|
||||
.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_TETRISES, totalTetrises)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun startNewSession() {
|
||||
sessionScore = 0
|
||||
sessionLines = 0
|
||||
sessionPieces = 0
|
||||
sessionTime = 0
|
||||
sessionLevel = 0
|
||||
sessionSingles = 0
|
||||
sessionDoubles = 0
|
||||
sessionTriples = 0
|
||||
sessionTetrises = 0
|
||||
}
|
||||
|
||||
fun updateSessionStats(score: Int, lines: Int, pieces: Int, time: Long, level: Int) {
|
||||
sessionScore = score
|
||||
sessionLines = lines
|
||||
sessionPieces = pieces
|
||||
sessionTime = time
|
||||
sessionLevel = level
|
||||
}
|
||||
|
||||
fun recordLineClear(lineCount: Int) {
|
||||
when (lineCount) {
|
||||
1 -> {
|
||||
sessionSingles++
|
||||
totalSingles++
|
||||
}
|
||||
2 -> {
|
||||
sessionDoubles++
|
||||
totalDoubles++
|
||||
}
|
||||
3 -> {
|
||||
sessionTriples++
|
||||
totalTriples++
|
||||
}
|
||||
4 -> {
|
||||
sessionTetrises++
|
||||
totalTetrises++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 getTotalTetrises(): Int = totalTetrises
|
||||
|
||||
// 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 getSessionTetrises(): Int = sessionTetrises
|
||||
|
||||
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
|
||||
totalTetrises = 0
|
||||
|
||||
// Save the reset stats
|
||||
saveStats()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mintris_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_TETRISES = "total_tetrises"
|
||||
}
|
||||
}
|
|
@ -1,16 +1,12 @@
|
|||
package com.mintris
|
||||
package com.pixelmintdrop
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.mintris.databinding.HighScoreEntryBinding
|
||||
import com.mintris.model.HighScore
|
||||
import com.mintris.model.HighScoreManager
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.databinding.HighScoreEntryBinding
|
||||
import com.pixelmintdrop.model.HighScore
|
||||
import com.pixelmintdrop.model.HighScoreManager
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import android.graphics.Color
|
||||
import android.view.KeyEvent
|
||||
import android.view.InputDevice
|
|
@ -1,15 +1,12 @@
|
|||
package com.mintris
|
||||
package com.pixelmintdrop
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mintris.databinding.HighScoresBinding
|
||||
import com.mintris.model.HighScoreAdapter
|
||||
import com.mintris.model.HighScoreManager
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.databinding.HighScoresBinding
|
||||
import com.pixelmintdrop.model.HighScoreAdapter
|
||||
import com.pixelmintdrop.model.HighScoreManager
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import android.graphics.Color
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris
|
||||
package com.pixelmintdrop
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -9,18 +9,18 @@ import android.os.Vibrator
|
|||
import android.view.View
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.mintris.databinding.ActivityMainBinding
|
||||
import com.mintris.game.GameHaptics
|
||||
import com.mintris.game.GameView
|
||||
import com.mintris.game.TitleScreen
|
||||
import com.mintris.model.GameBoard
|
||||
import com.mintris.audio.GameMusic
|
||||
import com.mintris.model.HighScoreManager
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.mintris.model.StatsManager
|
||||
import com.mintris.ui.ProgressionScreen
|
||||
import com.mintris.ui.ThemeSelector
|
||||
import com.mintris.ui.BlockSkinSelector
|
||||
import com.pixelmintdrop.databinding.ActivityMainBinding
|
||||
import com.pixelmintdrop.game.GameHaptics
|
||||
import com.pixelmintdrop.game.GameView
|
||||
import com.pixelmintdrop.game.TitleScreen
|
||||
import com.pixelmintdrop.model.GameBoard
|
||||
import com.pixelmintdrop.audio.GameMusic
|
||||
import com.pixelmintdrop.model.HighScoreManager
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.model.StatsManager
|
||||
import com.pixelmintdrop.ui.ProgressionScreen
|
||||
import com.pixelmintdrop.ui.ThemeSelector
|
||||
import com.pixelmintdrop.ui.BlockSkinSelector
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import android.graphics.Color
|
||||
|
@ -32,12 +32,11 @@ import android.view.KeyEvent
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import com.mintris.game.GamepadController
|
||||
import com.pixelmintdrop.game.GamepadController
|
||||
import android.view.InputDevice
|
||||
import android.widget.Toast
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.IntentFilter
|
||||
import android.app.AlertDialog
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.widget.Button
|
||||
|
@ -153,7 +152,7 @@ class MainActivity : AppCompatActivity(),
|
|||
pauseMenuScrollView = binding.pauseMenuScrollView
|
||||
|
||||
// Load random mode setting
|
||||
isRandomModeEnabled = getSharedPreferences("com.mintris.preferences", Context.MODE_PRIVATE)
|
||||
isRandomModeEnabled = getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.preferences", Context.MODE_PRIVATE)
|
||||
.getBoolean("random_mode_enabled", false)
|
||||
|
||||
// Initialize gamepad controller
|
||||
|
@ -302,7 +301,14 @@ class MainActivity : AppCompatActivity(),
|
|||
statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel)
|
||||
|
||||
// Handle progression - XP earned, potential level up
|
||||
val xpGained = progressionManager.calculateGameXP(finalScore, gameBoard.lines, currentLevel, timePlayedMs, statsManager.getSessionTetrises(), 0)
|
||||
val xpGained = progressionManager.calculateGameXP(
|
||||
score = finalScore,
|
||||
lines = gameBoard.lines,
|
||||
level = currentLevel,
|
||||
timePlayedMs = timePlayedMs,
|
||||
quadCount = statsManager.getSessionQuads(),
|
||||
perfectClearCount = statsManager.getSessionPerfectClears()
|
||||
)
|
||||
val newRewards = progressionManager.addXP(xpGained)
|
||||
|
||||
// Show progression screen if player earned XP
|
||||
|
@ -472,9 +478,9 @@ class MainActivity : AppCompatActivity(),
|
|||
score = score,
|
||||
lines = gameBoard.lines,
|
||||
level = currentLevel,
|
||||
gameTime = gameTime,
|
||||
tetrisCount = statsManager.getSessionTetrises(),
|
||||
perfectClearCount = 0 // Implement perfect clear tracking if needed
|
||||
timePlayedMs = gameTime,
|
||||
quadCount = statsManager.getSessionQuads(),
|
||||
perfectClearCount = statsManager.getSessionPerfectClears()
|
||||
)
|
||||
|
||||
// Add XP and check for rewards
|
||||
|
@ -497,7 +503,7 @@ class MainActivity : AppCompatActivity(),
|
|||
binding.sessionSinglesText.text = getString(R.string.singles, statsManager.getSessionSingles())
|
||||
binding.sessionDoublesText.text = getString(R.string.doubles, statsManager.getSessionDoubles())
|
||||
binding.sessionTriplesText.text = getString(R.string.triples, statsManager.getSessionTriples())
|
||||
binding.sessionTetrisesText.text = getString(R.string.tetrises, statsManager.getSessionTetrises())
|
||||
binding.sessionQuadsText.text = getString(R.string.quads, statsManager.getSessionQuads())
|
||||
|
||||
// Flag to track if high score screen will be shown
|
||||
var showingHighScore = false
|
||||
|
@ -624,7 +630,7 @@ class MainActivity : AppCompatActivity(),
|
|||
isEnabled = progressionManager.getPlayerLevel() >= 5
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
isRandomModeEnabled = isChecked
|
||||
getSharedPreferences("com.mintris.preferences", Context.MODE_PRIVATE)
|
||||
getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.preferences", Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean("random_mode_enabled", isChecked)
|
||||
.apply()
|
||||
|
@ -912,7 +918,14 @@ class MainActivity : AppCompatActivity(),
|
|||
statsManager.updateSessionStats(finalScore, gameBoard.lines, piecesPlaced, timePlayedMs, currentLevel)
|
||||
|
||||
// Handle progression - XP earned, potential level up
|
||||
val xpGained = progressionManager.calculateGameXP(finalScore, gameBoard.lines, currentLevel, timePlayedMs, statsManager.getSessionTetrises(), 0)
|
||||
val xpGained = progressionManager.calculateGameXP(
|
||||
score = finalScore,
|
||||
lines = gameBoard.lines,
|
||||
level = currentLevel,
|
||||
timePlayedMs = timePlayedMs,
|
||||
quadCount = statsManager.getSessionQuads(),
|
||||
perfectClearCount = statsManager.getSessionPerfectClears()
|
||||
)
|
||||
val newRewards = progressionManager.addXP(xpGained)
|
||||
|
||||
// Show progression screen if player earned XP
|
||||
|
@ -1211,7 +1224,7 @@ class MainActivity : AppCompatActivity(),
|
|||
* Check if user has seen the gamepad help
|
||||
*/
|
||||
private fun hasSeenGamepadHelp(): Boolean {
|
||||
val prefs = getSharedPreferences("com.mintris.preferences", Context.MODE_PRIVATE)
|
||||
val prefs = getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.preferences", Context.MODE_PRIVATE)
|
||||
return prefs.getBoolean("has_seen_gamepad_help", false)
|
||||
}
|
||||
|
||||
|
@ -1219,7 +1232,7 @@ class MainActivity : AppCompatActivity(),
|
|||
* Mark that user has seen the gamepad help
|
||||
*/
|
||||
private fun markGamepadHelpSeen() {
|
||||
val prefs = getSharedPreferences("com.mintris.preferences", Context.MODE_PRIVATE)
|
||||
val prefs = getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.preferences", Context.MODE_PRIVATE)
|
||||
prefs.edit().putBoolean("has_seen_gamepad_help", true).apply()
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package com.mintris
|
||||
package com.pixelmintdrop
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.mintris.databinding.ActivityStatsBinding
|
||||
import com.mintris.model.StatsManager
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.databinding.ActivityStatsBinding
|
||||
import com.pixelmintdrop.model.StatsManager
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import android.graphics.Color
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -80,7 +78,7 @@ class StatsActivity : AppCompatActivity() {
|
|||
binding.totalSinglesText.setTextColor(textColor)
|
||||
binding.totalDoublesText.setTextColor(textColor)
|
||||
binding.totalTriplesText.setTextColor(textColor)
|
||||
binding.totalTetrisesText.setTextColor(textColor)
|
||||
binding.totalQuadsText.setTextColor(textColor)
|
||||
binding.maxLevelText.setTextColor(textColor)
|
||||
binding.maxScoreText.setTextColor(textColor)
|
||||
binding.maxLinesText.setTextColor(textColor)
|
||||
|
@ -118,7 +116,7 @@ class StatsActivity : AppCompatActivity() {
|
|||
binding.totalSinglesText.text = getString(R.string.singles, statsManager.getTotalSingles())
|
||||
binding.totalDoublesText.text = getString(R.string.doubles, statsManager.getTotalDoubles())
|
||||
binding.totalTriplesText.text = getString(R.string.triples, statsManager.getTotalTriples())
|
||||
binding.totalTetrisesText.text = getString(R.string.tetrises, statsManager.getTotalTetrises())
|
||||
binding.totalQuadsText.text = getString(R.string.quads, statsManager.getTotalQuads())
|
||||
|
||||
// Update best performance stats
|
||||
binding.maxLevelText.text = getString(R.string.max_level, statsManager.getMaxLevel())
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris
|
||||
package com.pixelmintdrop
|
||||
|
||||
import android.graphics.Color
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.accessibility
|
||||
package com.pixelmintdrop.accessibility
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
@ -8,9 +8,9 @@ import android.view.accessibility.AccessibilityManager
|
|||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.mintris.R
|
||||
import com.mintris.game.GameView
|
||||
import com.mintris.model.TetrominoType
|
||||
import com.pixelmintdrop.R
|
||||
import com.pixelmintdrop.game.GameView
|
||||
import com.pixelmintdrop.model.GamePieceType
|
||||
|
||||
/**
|
||||
* 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"
|
||||
|
||||
return when (pieceType) {
|
||||
TetrominoType.I -> "I piece, long bar"
|
||||
TetrominoType.J -> "J piece, hook shape pointing left"
|
||||
TetrominoType.L -> "L piece, hook shape pointing right"
|
||||
TetrominoType.O -> "O piece, square shape"
|
||||
TetrominoType.S -> "S piece, zigzag shape"
|
||||
TetrominoType.T -> "T piece, T shape"
|
||||
TetrominoType.Z -> "Z piece, reverse zigzag shape"
|
||||
GamePieceType.I -> "I piece, long bar"
|
||||
GamePieceType.J -> "J piece, hook shape pointing left"
|
||||
GamePieceType.L -> "L piece, hook shape pointing right"
|
||||
GamePieceType.O -> "O piece, square shape"
|
||||
GamePieceType.S -> "S piece, zigzag shape"
|
||||
GamePieceType.T -> "T piece, T shape"
|
||||
GamePieceType.Z -> "Z piece, reverse zigzag shape"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,4 +134,19 @@ class GameAccessibilityHelper(private val context: Context) {
|
|||
fun announceLevelUp(view: View, newLevel: Int) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package com.mintris.audio
|
||||
package com.pixelmintdrop.audio
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaPlayer
|
||||
import android.media.AudioAttributes
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.mintris.R
|
||||
import com.pixelmintdrop.R
|
||||
|
||||
class GameMusic(private val context: Context) {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
@ -53,7 +53,7 @@ class GameHaptics(private val context: Context) {
|
|||
1 -> (50L * multiplier).toLong() // Single line: short vibration
|
||||
2 -> (80L * multiplier).toLong() // Double line: slightly longer
|
||||
3 -> (120L * multiplier).toLong() // Triple line: even longer
|
||||
4 -> (200L * multiplier).toLong() // Tetris: longest vibration
|
||||
4 -> (200L * multiplier).toLong() // Quad: longest vibration
|
||||
else -> (50L * multiplier).toLong()
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ class GameHaptics(private val context: Context) {
|
|||
1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration
|
||||
2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration
|
||||
3 -> (180 * multiplier).toInt().coerceAtMost(255) // Triple line: strong vibration
|
||||
4 -> 255 // Tetris: maximum vibration
|
||||
4 -> 255 // Quad: maximum vibration
|
||||
else -> (80 * multiplier).toInt().coerceAtMost(255)
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import com.google.gson.Gson
|
||||
import com.mintris.audio.GameMusic
|
||||
import com.mintris.model.GameBoard
|
||||
import com.mintris.model.HighScoreManager
|
||||
import com.mintris.model.StatsManager
|
||||
import com.pixelmintdrop.audio.GameMusic
|
||||
import com.pixelmintdrop.model.GameBoard
|
||||
import com.pixelmintdrop.model.HighScoreManager
|
||||
import com.pixelmintdrop.model.StatsManager
|
||||
|
||||
/**
|
||||
* Handles the game's lifecycle events to ensure proper resource management
|
||||
|
@ -15,7 +15,7 @@ import com.mintris.model.StatsManager
|
|||
*/
|
||||
class GameLifecycleManager(private val context: Context) {
|
||||
private val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences("com.mintris.game_state", Context.MODE_PRIVATE)
|
||||
context.getSharedPreferences("com.com.pixelmintgames.pixelmintdrop.game_state", Context.MODE_PRIVATE)
|
||||
private val gson = Gson()
|
||||
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
|
@ -20,14 +20,19 @@ import android.view.View
|
|||
import android.view.animation.LinearInterpolator
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.view.Display
|
||||
import com.mintris.model.GameBoard
|
||||
import com.mintris.model.Tetromino
|
||||
import com.mintris.model.TetrominoType
|
||||
import com.pixelmintdrop.model.GameBoard
|
||||
import com.pixelmintdrop.model.GamePiece
|
||||
import com.pixelmintdrop.model.GamePieceType
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.model.StatsManager
|
||||
import com.pixelmintdrop.model.ThemeManager
|
||||
import com.pixelmintdrop.model.ThemeManager.Theme
|
||||
import com.pixelmintdrop.model.ThemeManager.ThemeType
|
||||
import com.pixelmintdrop.model.ThemeManager.ThemeVariant
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* GameView that renders the Tetris game and handles touch input
|
||||
* GameView that renders the block-stacking game and handles touch input
|
||||
*/
|
||||
class GameView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
@ -45,9 +50,13 @@ class GameView @JvmOverloads constructor(
|
|||
|
||||
// Game state
|
||||
private var isRunning = false
|
||||
var isPaused = false // Changed from private to public to allow access from MainActivity
|
||||
var isPaused = false
|
||||
private var score = 0
|
||||
|
||||
// Current piece
|
||||
private var currentPiece: GamePiece? = null
|
||||
private var nextPiece: GamePiece? = null
|
||||
|
||||
// Callbacks
|
||||
var onNextPieceChanged: (() -> Unit)? = null
|
||||
|
||||
|
@ -68,22 +77,22 @@ class GameView @JvmOverloads constructor(
|
|||
|
||||
private val ghostBlockPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 80 // 30% opacity
|
||||
alpha = 80
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val gridPaint = Paint().apply {
|
||||
color = Color.parseColor("#222222") // Very dark gray
|
||||
alpha = 20 // Reduced from 40 to be more subtle
|
||||
color = Color.parseColor("#222222")
|
||||
alpha = 20
|
||||
isAntiAlias = true
|
||||
strokeWidth = 1f
|
||||
style = Paint.Style.STROKE
|
||||
maskFilter = null // Ensure no blur effect on grid lines
|
||||
maskFilter = null
|
||||
}
|
||||
|
||||
private val glowPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 40 // Reduced from 80 for more subtlety
|
||||
alpha = 40
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 1.5f
|
||||
|
@ -98,24 +107,20 @@ class GameView @JvmOverloads constructor(
|
|||
maskFilter = BlurMaskFilter(12f, BlurMaskFilter.Blur.OUTER)
|
||||
}
|
||||
|
||||
// Add a new paint for the pulse effect
|
||||
private val pulsePaint = Paint().apply {
|
||||
color = Color.CYAN
|
||||
alpha = 255
|
||||
isAntiAlias = true
|
||||
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()
|
||||
|
||||
// Calculate block size based on view dimensions and board size
|
||||
private var blockSize = 0f
|
||||
private var boardLeft = 0f
|
||||
private var boardTop = 0f
|
||||
|
||||
// Game loop handler and runnable
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val gameLoopRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
|
@ -127,7 +132,6 @@ class GameView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Touch parameters
|
||||
private var lastTouchX = 0f
|
||||
private var lastTouchY = 0f
|
||||
private var startX = 0f
|
||||
|
@ -216,7 +220,7 @@ class GameView @JvmOverloads constructor(
|
|||
pause()
|
||||
|
||||
// Load saved block skin
|
||||
val prefs = context.getSharedPreferences("mintris_progression", Context.MODE_PRIVATE)
|
||||
val prefs = context.getSharedPreferences("pixelmintdrop_progression", Context.MODE_PRIVATE)
|
||||
currentBlockSkin = prefs.getString("selected_block_skin", "block_skin_1") ?: "block_skin_1"
|
||||
|
||||
// Connect our callbacks to the GameBoard
|
||||
|
@ -329,7 +333,7 @@ class GameView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
// Save the selection to SharedPreferences
|
||||
val prefs = context.getSharedPreferences("mintris_progression", Context.MODE_PRIVATE)
|
||||
val prefs = context.getSharedPreferences("pixelmintdrop_progression", Context.MODE_PRIVATE)
|
||||
prefs.edit().putString("selected_block_skin", skinId).commit()
|
||||
|
||||
// Force a refresh of the view
|
||||
|
@ -428,197 +432,29 @@ class GameView @JvmOverloads constructor(
|
|||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
|
||||
// Force hardware acceleration - Critical for performance
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
// Calculate block size based on view dimensions and board size
|
||||
blockSize = minOf(
|
||||
w / (gameBoard.width + 2f), // Add padding
|
||||
h / (gameBoard.height + 2f) // Add padding
|
||||
)
|
||||
|
||||
// Update gesture exclusion rect for edge-to-edge rendering
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
setSystemGestureExclusionRects(listOf(Rect(0, 0, w, h)))
|
||||
}
|
||||
|
||||
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")
|
||||
// Center the board
|
||||
boardLeft = (w - gameBoard.width * blockSize) / 2f
|
||||
boardTop = (h - gameBoard.height * blockSize) / 2f
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Draw background (already black from theme)
|
||||
// Draw the game board
|
||||
drawBoard(canvas)
|
||||
|
||||
// Draw board border glow
|
||||
drawBoardBorder(canvas)
|
||||
// Draw ghost piece
|
||||
drawGhostPiece(canvas)
|
||||
|
||||
// Draw grid (very subtle)
|
||||
drawGrid(canvas)
|
||||
|
||||
// Draw locked pieces
|
||||
drawLockedBlocks(canvas)
|
||||
|
||||
if (!gameBoard.isGameOver && isRunning) {
|
||||
// Draw ghost piece (landing preview)
|
||||
drawGhostPiece(canvas)
|
||||
|
||||
// Draw active piece
|
||||
drawActivePiece(canvas)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
// Draw current piece
|
||||
currentPiece?.let { piece ->
|
||||
drawPiece(canvas, piece)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,219 +612,52 @@ class GameView @JvmOverloads constructor(
|
|||
* Draw the ghost piece (landing preview)
|
||||
*/
|
||||
private fun drawGhostPiece(canvas: Canvas) {
|
||||
val piece = gameBoard.getCurrentPiece() ?: return
|
||||
val ghostY = gameBoard.getGhostY()
|
||||
|
||||
// Draw semi-transparent background for each block
|
||||
for (y in 0 until piece.getHeight()) {
|
||||
for (x in 0 until piece.getWidth()) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val boardX = piece.x + x
|
||||
val boardY = ghostY + y
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
currentPiece?.let { piece ->
|
||||
val ghostY = calculateGhostY(piece)
|
||||
val originalY = piece.y
|
||||
piece.y = ghostY
|
||||
|
||||
ghostBlockPaint.alpha = 50
|
||||
drawPiece(canvas, piece, alpha = 50)
|
||||
|
||||
piece.y = originalY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bottom = top + blockSize
|
||||
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()
|
||||
|
||||
// Save canvas state before drawing block effects
|
||||
canvas.save()
|
||||
|
||||
// Get the current block skin paint
|
||||
val paint = blockSkinPaints[currentBlockSkin] ?: blockSkinPaints["block_skin_1"]!!
|
||||
|
||||
// Create a clone of the paint to avoid modifying the original
|
||||
val blockPaint = Paint(paint)
|
||||
|
||||
// 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)
|
||||
blockPaint.alpha = alpha
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val left = offsetX + (piece.x + x) * blockSize
|
||||
val top = offsetY + (piece.y + y) * blockSize
|
||||
val right = left + blockSize
|
||||
val bottom = top + blockSize
|
||||
|
||||
// Draw block with subtle glow
|
||||
blockGlowPaint.alpha = alpha / 4
|
||||
canvas.drawRect(left, top, right, bottom, blockGlowPaint)
|
||||
|
||||
// Draw the main block
|
||||
canvas.drawRect(left + 1f, top + 1f, right - 1f, bottom - 1f, blockPaint)
|
||||
|
||||
// Draw border glow
|
||||
glowPaint.alpha = alpha / 2
|
||||
canvas.drawRect(left, top, right, bottom, glowPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1026,16 +695,7 @@ class GameView @JvmOverloads constructor(
|
|||
|
||||
// Custom touch event handling
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (!isRunning || isPaused || gameBoard.isGameOver) {
|
||||
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
|
||||
}
|
||||
if (!isRunning || isPaused) return false
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
|
@ -1043,120 +703,53 @@ class GameView @JvmOverloads constructor(
|
|||
startY = event.y
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
lastTapTime = currentTime // Set the tap time when touch starts
|
||||
|
||||
// Reset direction lock
|
||||
lockedDirection = null
|
||||
lastTapTime = System.currentTimeMillis()
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val deltaX = event.x - lastTouchX
|
||||
val deltaY = event.y - lastTouchY
|
||||
val dx = event.x - lastTouchX
|
||||
val dy = event.y - lastTouchY
|
||||
|
||||
// Check if we should lock direction
|
||||
if (lockedDirection == null) {
|
||||
val absDeltaX = abs(deltaX)
|
||||
val absDeltaY = abs(deltaY)
|
||||
|
||||
if (absDeltaX > blockSize * directionLockThreshold ||
|
||||
absDeltaY > blockSize * directionLockThreshold) {
|
||||
// Lock to the dominant direction
|
||||
lockedDirection = if (absDeltaX > absDeltaY) {
|
||||
Direction.HORIZONTAL
|
||||
} else {
|
||||
Direction.VERTICAL
|
||||
}
|
||||
// Horizontal movement
|
||||
if (abs(dx) > blockSize / 2) {
|
||||
if (dx > 0) {
|
||||
movePieceRight()
|
||||
} else {
|
||||
movePieceLeft()
|
||||
}
|
||||
lastTouchX = event.x
|
||||
}
|
||||
|
||||
// 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
|
||||
if (currentTime - lastMoveTime >= moveCooldown) {
|
||||
gameHaptics?.vibrateForPieceMove()
|
||||
lastMoveTime = currentTime
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
Direction.VERTICAL -> {
|
||||
if (deltaY > blockSize * minMovementThreshold) {
|
||||
gameBoard.softDrop()
|
||||
lastTouchY = event.y
|
||||
if (currentTime - lastMoveTime >= moveCooldown) {
|
||||
gameHaptics?.vibrateForPieceMove()
|
||||
lastMoveTime = currentTime
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
// No direction lock yet, don't process movement
|
||||
}
|
||||
// Vertical movement (soft drop)
|
||||
if (dy > blockSize / 2) {
|
||||
dropPiece()
|
||||
lastTouchY = event.y
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val deltaX = event.x - startX
|
||||
val deltaY = event.y - startY
|
||||
val moveTime = currentTime - lastTapTime
|
||||
val dx = event.x - startX
|
||||
val dy = event.y - startY
|
||||
val tapTime = System.currentTimeMillis() - lastTapTime
|
||||
|
||||
// Handle taps for rotation
|
||||
if (moveTime < minTapTime * 1.5 &&
|
||||
abs(deltaY) < maxTapMovement * 1.5 &&
|
||||
abs(deltaX) < maxTapMovement * 1.5) {
|
||||
|
||||
if (currentTime - lastRotationTime >= rotationCooldown) {
|
||||
gameBoard.rotate()
|
||||
lastRotationTime = currentTime
|
||||
gameHaptics?.vibrateForPieceMove()
|
||||
invalidate()
|
||||
}
|
||||
return true
|
||||
// Detect tap for rotation
|
||||
if (abs(dx) < blockSize / 2 && abs(dy) < blockSize / 2 && tapTime < 200) {
|
||||
rotatePiece()
|
||||
}
|
||||
|
||||
// Handle gestures
|
||||
// Check for hold gesture (swipe up)
|
||||
if (deltaY < -blockSize * minHoldDistance &&
|
||||
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()
|
||||
// Detect swipe down for hard drop
|
||||
if (dy > height / 4) {
|
||||
hardDrop()
|
||||
}
|
||||
|
||||
// Reset direction lock
|
||||
lockedDirection = null
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1182,7 +775,7 @@ class GameView @JvmOverloads constructor(
|
|||
/**
|
||||
* Get the next piece that will be spawned
|
||||
*/
|
||||
fun getNextPiece(): Tetromino? {
|
||||
fun getNextPiece(): GamePiece? {
|
||||
return gameBoard.getNextPiece()
|
||||
}
|
||||
|
||||
|
@ -1542,4 +1135,103 @@ class GameView @JvmOverloads constructor(
|
|||
gameHaptics?.vibrateForPieceMove()
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.view.InputDevice
|
||||
|
@ -8,13 +8,11 @@ import android.util.Log
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.view.InputDevice.MotionRange
|
||||
import android.os.Vibrator
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
||||
/**
|
||||
* GamepadController handles gamepad input for the Mintris game.
|
||||
* GamepadController handles gamepad input for the pixelmintdrop game.
|
||||
* Supports multiple gamepad types including:
|
||||
* - Microsoft Xbox controllers
|
||||
* - Sony PlayStation controllers
|
||||
|
@ -258,7 +256,7 @@ class GamepadController(
|
|||
1 -> 100
|
||||
2 -> 150
|
||||
3 -> 200
|
||||
else -> 255 // For tetris (4 lines)
|
||||
else -> 255 // For quad (4 lines)
|
||||
}
|
||||
vibrateGamepad(RUMBLE_LINE_CLEAR_DURATION_MS, amplitude)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BlurMaskFilter
|
||||
|
@ -7,8 +7,7 @@ import android.graphics.Color
|
|||
import android.graphics.Paint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.mintris.model.GameBoard
|
||||
import com.mintris.model.Tetromino
|
||||
import com.pixelmintdrop.model.GameBoard
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
97
app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt
Normal file
97
app/src/main/java/com/pixelmintdrop/game/NextPieceView.kt
Normal file
|
@ -0,0 +1,97 @@
|
|||
package com.pixelmintdrop.game
|
||||
|
||||
import android.content.Context
|
||||
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
|
||||
|
||||
/**
|
||||
* Custom view to display the next game piece
|
||||
*/
|
||||
class NextPieceView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private var gameView: GameView? = null
|
||||
|
||||
// Rendering
|
||||
private val blockPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val glowPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 30
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 1.5f
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the game view to get the next piece from
|
||||
*/
|
||||
fun setGameView(gameView: GameView) {
|
||||
this.gameView = gameView
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
// Get the next piece from game view
|
||||
gameView?.getNextPiece()?.let { piece ->
|
||||
val width = piece.getWidth()
|
||||
val height = piece.getHeight()
|
||||
|
||||
// Calculate block size for the preview (smaller than main board)
|
||||
val previewBlockSize = min(
|
||||
canvas.width.toFloat() / (width.toFloat() + 2f),
|
||||
canvas.height.toFloat() / (height.toFloat() + 2f)
|
||||
)
|
||||
|
||||
// Center the piece in the preview area
|
||||
val previewLeft = (canvas.width.toFloat() - width.toFloat() * previewBlockSize) / 2f
|
||||
val previewTop = (canvas.height.toFloat() - height.toFloat() * previewBlockSize) / 2f
|
||||
|
||||
// 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.toFloat() * previewBlockSize + previewBlockSize,
|
||||
previewTop + height.toFloat() * previewBlockSize + previewBlockSize,
|
||||
glowPaint
|
||||
)
|
||||
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val left = previewLeft + x.toFloat() * previewBlockSize
|
||||
val top = previewTop + y.toFloat() * previewBlockSize
|
||||
val right = left + previewBlockSize
|
||||
val bottom = top + previewBlockSize
|
||||
|
||||
// Draw block with subtle glow
|
||||
val rect = RectF(left + 1f, top + 1f, right - 1f, bottom - 1f)
|
||||
canvas.drawRect(rect, blockPaint)
|
||||
|
||||
// Draw subtle border glow
|
||||
val glowRect = RectF(left, top, right, bottom)
|
||||
canvas.drawRect(glowRect, glowPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.game
|
||||
package com.pixelmintdrop.game
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
|
@ -10,9 +10,9 @@ import android.view.MotionEvent
|
|||
import android.view.View
|
||||
import java.util.Random
|
||||
import android.util.Log
|
||||
import com.mintris.model.HighScoreManager
|
||||
import com.mintris.model.HighScore
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.model.HighScoreManager
|
||||
import com.pixelmintdrop.model.HighScore
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import kotlin.math.abs
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.core.graphics.withScale
|
||||
|
@ -33,7 +33,7 @@ class TitleScreen @JvmOverloads constructor(
|
|||
private val random = Random()
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private val tetrominosToAdd = mutableListOf<Tetromino>()
|
||||
private val piecesToAdd = mutableListOf<FallingPiece>()
|
||||
private val highScoreManager = HighScoreManager(context) // Pre-allocate HighScoreManager
|
||||
|
||||
// Touch handling variables
|
||||
|
@ -50,8 +50,8 @@ class TitleScreen @JvmOverloads constructor(
|
|||
private var themeColor = Color.WHITE
|
||||
private var backgroundColor = Color.BLACK
|
||||
|
||||
// Define tetromino shapes (I, O, T, S, Z, J, L)
|
||||
private val tetrominoShapes = arrayOf(
|
||||
// Define piece shapes (I, O, T, S, Z, J, L)
|
||||
private val pieceShapes = arrayOf(
|
||||
// I
|
||||
arrayOf(
|
||||
intArrayOf(0, 0, 0, 0),
|
||||
|
@ -96,8 +96,8 @@ class TitleScreen @JvmOverloads constructor(
|
|||
)
|
||||
)
|
||||
|
||||
// Tetromino class to represent falling pieces
|
||||
private class Tetromino(
|
||||
// FallingPiece class to represent falling pieces
|
||||
private class FallingPiece(
|
||||
var x: Float,
|
||||
var y: Float,
|
||||
val shape: Array<IntArray>,
|
||||
|
@ -106,7 +106,7 @@ class TitleScreen @JvmOverloads constructor(
|
|||
val rotation: Int = 0
|
||||
)
|
||||
|
||||
private val tetrominos = mutableListOf<Tetromino>()
|
||||
private val pieces = mutableListOf<FallingPiece>()
|
||||
|
||||
init {
|
||||
// Title text settings
|
||||
|
@ -138,14 +138,14 @@ class TitleScreen @JvmOverloads constructor(
|
|||
alpha = 200
|
||||
}
|
||||
|
||||
// General paint settings for tetrominos
|
||||
// General paint settings for pieces
|
||||
paint.apply {
|
||||
color = themeColor
|
||||
style = Paint.Style.FILL
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
// Glow paint settings for tetrominos
|
||||
// Glow paint settings for pieces
|
||||
glowPaint.apply {
|
||||
color = themeColor
|
||||
style = Paint.Style.FILL
|
||||
|
@ -159,26 +159,26 @@ class TitleScreen @JvmOverloads constructor(
|
|||
width = w
|
||||
height = h
|
||||
|
||||
// Clear existing tetrominos
|
||||
tetrominos.clear()
|
||||
// Clear existing pieces
|
||||
pieces.clear()
|
||||
|
||||
// Initialize some tetrominos
|
||||
// Initialize some pieces
|
||||
repeat(20) {
|
||||
val tetromino = createRandomTetromino()
|
||||
tetrominos.add(tetromino)
|
||||
val piece = createRandomPiece()
|
||||
pieces.add(piece)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRandomTetromino(): Tetromino {
|
||||
private fun createRandomPiece(): FallingPiece {
|
||||
val x = random.nextFloat() * (width - 150) + 50 // Keep away from edges
|
||||
val y = -cellSize * 4 - (random.nextFloat() * height / 2)
|
||||
val shapeIndex = random.nextInt(tetrominoShapes.size)
|
||||
val shape = tetrominoShapes[shapeIndex]
|
||||
val shapeIndex = random.nextInt(pieceShapes.size)
|
||||
val shape = pieceShapes[shapeIndex]
|
||||
val speed = 1f + random.nextFloat() * 2f
|
||||
val scale = 0.8f + random.nextFloat() * 0.4f
|
||||
val rotation = random.nextInt(4) * 90
|
||||
|
||||
return Tetromino(x, y, shape, speed, scale, rotation)
|
||||
return FallingPiece(x, y, shape, speed, scale, rotation)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
|
@ -188,37 +188,37 @@ class TitleScreen @JvmOverloads constructor(
|
|||
// Draw background using the current background color
|
||||
canvas.drawColor(backgroundColor)
|
||||
|
||||
// Add any pending tetrominos
|
||||
tetrominos.addAll(tetrominosToAdd)
|
||||
tetrominosToAdd.clear()
|
||||
// Add any pending pieces
|
||||
pieces.addAll(piecesToAdd)
|
||||
piecesToAdd.clear()
|
||||
|
||||
// Update and draw falling tetrominos
|
||||
val tetrominosToRemove = mutableListOf<Tetromino>()
|
||||
// Update and draw falling pieces
|
||||
val piecesToRemove = mutableListOf<FallingPiece>()
|
||||
|
||||
for (tetromino in tetrominos) {
|
||||
tetromino.y += tetromino.speed
|
||||
for (piece in pieces) {
|
||||
piece.y += piece.speed
|
||||
|
||||
// Remove tetrominos that have fallen off the screen
|
||||
if (tetromino.y > height) {
|
||||
tetrominosToRemove.add(tetromino)
|
||||
tetrominosToAdd.add(createRandomTetromino())
|
||||
// Remove pieces that have fallen off the screen
|
||||
if (piece.y > height) {
|
||||
piecesToRemove.add(piece)
|
||||
piecesToAdd.add(createRandomPiece())
|
||||
} else {
|
||||
try {
|
||||
// Draw the tetromino
|
||||
for (y in 0 until tetromino.shape.size) {
|
||||
for (x in 0 until tetromino.shape.size) {
|
||||
if (tetromino.shape[y][x] == 1) {
|
||||
// Draw the piece
|
||||
for (y in 0 until piece.shape.size) {
|
||||
for (x in 0 until piece.shape.size) {
|
||||
if (piece.shape[y][x] == 1) {
|
||||
val left = x * cellSize
|
||||
val top = y * cellSize
|
||||
val right = left + cellSize
|
||||
val bottom = top + cellSize
|
||||
|
||||
// Draw block with glow effect
|
||||
canvas.withTranslation(tetromino.x, tetromino.y) {
|
||||
withScale(tetromino.scale, tetromino.scale) {
|
||||
withRotation(tetromino.rotation.toFloat(),
|
||||
tetromino.shape.size * cellSize / 2,
|
||||
tetromino.shape.size * cellSize / 2) {
|
||||
canvas.withTranslation(piece.x, piece.y) {
|
||||
withScale(piece.scale, piece.scale) {
|
||||
withRotation(piece.rotation.toFloat(),
|
||||
piece.shape.size * cellSize / 2,
|
||||
piece.shape.size * cellSize / 2) {
|
||||
// Draw glow
|
||||
canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, glowPaint)
|
||||
// Draw block
|
||||
|
@ -230,17 +230,17 @@ class TitleScreen @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("TitleScreen", "Error drawing tetromino", e)
|
||||
Log.e("TitleScreen", "Error drawing piece", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tetrominos that fell off the screen
|
||||
tetrominos.removeAll(tetrominosToRemove)
|
||||
// Remove pieces that fell off the screen
|
||||
pieces.removeAll(piecesToRemove)
|
||||
|
||||
// Draw title
|
||||
val titleY = height * 0.4f
|
||||
canvas.drawText("mintris", width / 2f, titleY, titlePaint)
|
||||
canvas.drawText("Pixel Mint Drop", width / 2f, titleY, titlePaint)
|
||||
|
||||
// Draw high scores using pre-allocated manager
|
||||
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
||||
|
@ -292,10 +292,10 @@ class TitleScreen @JvmOverloads constructor(
|
|||
val deltaX = event.x - lastTouchX
|
||||
val deltaY = event.y - lastTouchY
|
||||
|
||||
// Update tetromino positions
|
||||
for (tetromino in tetrominos) {
|
||||
tetromino.x += deltaX * 0.5f
|
||||
tetromino.y += deltaY * 0.5f
|
||||
// Update piece positions
|
||||
for (piece in pieces) {
|
||||
piece.x += deltaX * 0.5f
|
||||
piece.y += deltaY * 0.5f
|
||||
}
|
||||
|
||||
lastTouchX = event.x
|
199
app/src/main/java/com/pixelmintdrop/model/GameBoard.kt
Normal file
199
app/src/main/java/com/pixelmintdrop/model/GameBoard.kt
Normal file
|
@ -0,0 +1,199 @@
|
|||
package com.pixelmintdrop.model
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Represents the game board and manages piece placement and collision detection
|
||||
*/
|
||||
class GameBoard {
|
||||
companion object {
|
||||
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 state
|
||||
private val board = Array(HEIGHT) { Array(WIDTH) { false } }
|
||||
private var currentPiece: GamePiece? = null
|
||||
private var nextPiece: GamePiece? = null
|
||||
|
||||
// Game state
|
||||
var dropInterval = INITIAL_DROP_INTERVAL
|
||||
private set
|
||||
|
||||
val width: Int = WIDTH
|
||||
val height: Int = HEIGHT
|
||||
|
||||
fun reset() {
|
||||
// Clear the board
|
||||
for (y in 0 until HEIGHT) {
|
||||
for (x in 0 until WIDTH) {
|
||||
board[y][x] = false
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pieces
|
||||
currentPiece = null
|
||||
nextPiece = null
|
||||
|
||||
// Reset drop interval
|
||||
dropInterval = INITIAL_DROP_INTERVAL
|
||||
}
|
||||
|
||||
fun spawnNewPiece(): GamePiece {
|
||||
// Get the next piece or create a new one if none exists
|
||||
currentPiece = nextPiece ?: createRandomPiece()
|
||||
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!!
|
||||
}
|
||||
|
||||
fun getNextPiece(): GamePiece? = nextPiece
|
||||
|
||||
fun getCurrentPiece(): GamePiece? = currentPiece
|
||||
|
||||
fun hasBlockAt(x: Int, y: Int): Boolean {
|
||||
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return false
|
||||
return board[y][x]
|
||||
}
|
||||
|
||||
fun wouldCollide(piece: GamePiece, newX: Int, newY: Int): Boolean {
|
||||
val width = piece.getWidth()
|
||||
val height = piece.getHeight()
|
||||
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val boardX = newX + x
|
||||
val boardY = newY + y
|
||||
|
||||
// Check board boundaries
|
||||
if (boardX < 0 || boardX >= WIDTH || boardY >= HEIGHT) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check collision with placed blocks
|
||||
if (boardY >= 0 && board[boardY][boardX]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun movePiece(piece: GamePiece, dx: Int, dy: Int): Boolean {
|
||||
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
|
||||
}
|
||||
|
||||
fun rotatePiece(piece: GamePiece): Boolean {
|
||||
// Save current state
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// If no wall kick worked, revert rotation
|
||||
piece.rotation = originalRotation
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun lockPiece(piece: GamePiece) {
|
||||
val width = piece.getWidth()
|
||||
val height = piece.getHeight()
|
||||
|
||||
// Place the piece on the board
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (piece.isBlockAt(x, y)) {
|
||||
val boardX = piece.x + x
|
||||
val boardY = piece.y + y
|
||||
if (boardY >= 0 && boardY < HEIGHT && boardX >= 0 && boardX < WIDTH) {
|
||||
board[boardY][boardX] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any completed lines
|
||||
clearLines()
|
||||
}
|
||||
|
||||
private fun clearLines() {
|
||||
var linesCleared = 0
|
||||
var y = HEIGHT - 1
|
||||
|
||||
while (y >= 0) {
|
||||
if (isLineFull(y)) {
|
||||
// Move all lines above down
|
||||
for (moveY in y downTo 1) {
|
||||
for (x in 0 until WIDTH) {
|
||||
board[moveY][x] = board[moveY - 1][x]
|
||||
}
|
||||
}
|
||||
// Clear top line
|
||||
for (x in 0 until WIDTH) {
|
||||
board[0][x] = false
|
||||
}
|
||||
linesCleared++
|
||||
} else {
|
||||
y--
|
||||
}
|
||||
}
|
||||
|
||||
// Increase speed based on lines cleared
|
||||
if (linesCleared > 0) {
|
||||
dropInterval = (dropInterval * 0.95).toLong().coerceAtLeast(MIN_DROP_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLineFull(y: Int): Boolean {
|
||||
for (x in 0 until WIDTH) {
|
||||
if (!board[y][x]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun createRandomPiece(): GamePiece {
|
||||
return GamePiece(GamePieceType.values().random())
|
||||
}
|
||||
}
|
48
app/src/main/java/com/pixelmintdrop/model/GamePiece.kt
Normal file
48
app/src/main/java/com/pixelmintdrop/model/GamePiece.kt
Normal file
|
@ -0,0 +1,48 @@
|
|||
package com.pixelmintdrop.model
|
||||
|
||||
/**
|
||||
* Represents a game piece with its type, position, and rotation
|
||||
*/
|
||||
class GamePiece(val type: GamePieceType) {
|
||||
var x: Int = 0
|
||||
var y: Int = 0
|
||||
var rotation: Int = 0
|
||||
private set
|
||||
|
||||
private val blocks: Array<Array<Boolean>> = type.getBlocks()
|
||||
|
||||
fun getWidth(): Int = blocks[0].size
|
||||
|
||||
fun getHeight(): Int = blocks.size
|
||||
|
||||
fun isBlockAt(x: Int, y: Int): Boolean {
|
||||
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return false
|
||||
return blocks[y][x]
|
||||
}
|
||||
|
||||
fun rotate() {
|
||||
rotation = (rotation + 1) % 4
|
||||
rotateBlocks()
|
||||
}
|
||||
|
||||
private fun rotateBlocks() {
|
||||
val width = getWidth()
|
||||
val height = getHeight()
|
||||
val rotated = Array(width) { Array(height) { false } }
|
||||
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
when (rotation) {
|
||||
1 -> rotated[x][height - 1 - y] = blocks[y][x]
|
||||
2 -> rotated[height - 1 - y][width - 1 - x] = blocks[y][x]
|
||||
3 -> rotated[width - 1 - x][y] = blocks[y][x]
|
||||
else -> rotated[y][x] = blocks[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.indices.forEach { i ->
|
||||
blocks[i] = rotated[i].copyOf()
|
||||
}
|
||||
}
|
||||
}
|
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.model
|
||||
package com.pixelmintdrop.model
|
||||
|
||||
data class HighScore(
|
||||
val name: String,
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.model
|
||||
package com.pixelmintdrop.model
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
|
@ -6,7 +6,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mintris.R
|
||||
import com.pixelmintdrop.R
|
||||
|
||||
class HighScoreAdapter : RecyclerView.Adapter<HighScoreAdapter.HighScoreViewHolder>() {
|
||||
private var highScores: List<HighScore> = emptyList()
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.model
|
||||
package com.pixelmintdrop.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
@ -12,7 +12,7 @@ class HighScoreManager(private val context: Context) {
|
|||
private val type: Type = object : TypeToken<List<HighScore>>() {}.type
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mintris_highscores"
|
||||
private const val PREFS_NAME = "pixelmintdrop_highscores"
|
||||
private const val KEY_HIGHSCORES = "highscores"
|
||||
private const val MAX_HIGHSCORES = 5
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package com.mintris.model
|
||||
package com.pixelmintdrop.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.mintris.R
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.min
|
||||
|
@ -94,24 +93,31 @@ class PlayerProgressionManager(context: Context) {
|
|||
/**
|
||||
* Calculate XP from a game session based on score, lines, level, etc.
|
||||
*/
|
||||
fun calculateGameXP(score: Int, lines: Int, level: Int, gameTime: Long,
|
||||
tetrisCount: Int, perfectClearCount: Int): Long {
|
||||
// Base XP from score with level multiplier (capped at level 10)
|
||||
val cappedLevel = min(level, 10)
|
||||
val scoreXP = (score * (1 + LEVEL_MULTIPLIER * cappedLevel)).toLong()
|
||||
|
||||
// XP from lines cleared (reduced for higher levels)
|
||||
val linesXP = lines * XP_PER_LINE * (1 - (level - 1) * 0.05).coerceAtLeast(0.5)
|
||||
|
||||
// XP from special moves (reduced for higher levels)
|
||||
val tetrisBonus = tetrisCount * TETRIS_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5)
|
||||
val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS * (1 - (level - 1) * 0.05).coerceAtLeast(0.5)
|
||||
|
||||
// Time bonus (reduced for longer games)
|
||||
val timeBonus = (gameTime / 60000) * TIME_XP_PER_MINUTE * (1 - (gameTime / 3600000) * 0.1).coerceAtLeast(0.5)
|
||||
|
||||
// Calculate total XP
|
||||
return (scoreXP + linesXP + tetrisBonus + perfectClearBonus + timeBonus).toLong()
|
||||
fun calculateGameXP(
|
||||
score: Int,
|
||||
lines: Int,
|
||||
level: Int,
|
||||
timePlayedMs: Long,
|
||||
quadCount: Int,
|
||||
perfectClearCount: Int
|
||||
): Long {
|
||||
// Calculate base XP from score
|
||||
val scoreXP = score * BASE_SCORE_XP * level
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,7 +298,7 @@ class PlayerProgressionManager(context: Context) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mintris_progression"
|
||||
private const val PREFS_NAME = "pixelmintdrop_progression"
|
||||
private const val KEY_PLAYER_LEVEL = "player_level"
|
||||
private const val KEY_PLAYER_XP = "player_xp"
|
||||
private const val KEY_TOTAL_XP_EARNED = "total_xp_earned"
|
||||
|
@ -328,6 +334,12 @@ class PlayerProgressionManager(context: Context) {
|
|||
THEME_MINIMALIST to 20,
|
||||
THEME_GALAXY to 25
|
||||
)
|
||||
|
||||
private const val BASE_SCORE_XP = 0.1 // XP per score point
|
||||
private const val BASE_LINES_XP = 100.0 // XP per line cleared
|
||||
private const val BASE_QUAD_BONUS = 500.0 // Bonus XP for clearing 4 lines at once
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
78
app/src/main/java/com/pixelmintdrop/model/StatsManager.kt
Normal file
78
app/src/main/java/com/pixelmintdrop/model/StatsManager.kt
Normal file
|
@ -0,0 +1,78 @@
|
|||
package com.pixelmintdrop.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
||||
/**
|
||||
* Manages game statistics and high scores
|
||||
*/
|
||||
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)
|
||||
|
||||
// Session stats
|
||||
private var sessionScore = 0
|
||||
private var sessionLines = 0
|
||||
private var sessionQuads = 0
|
||||
private var sessionPerfectClears = 0
|
||||
|
||||
fun getHighScore(): Int = prefs.getInt(KEY_HIGH_SCORE, 0)
|
||||
fun getTotalGames(): Int = prefs.getInt(KEY_TOTAL_GAMES, 0)
|
||||
fun getTotalLines(): Int = prefs.getInt(KEY_TOTAL_LINES, 0)
|
||||
fun getTotalQuads(): Int = prefs.getInt(KEY_TOTAL_QUADS, 0)
|
||||
fun getTotalPerfectClears(): Int = prefs.getInt(KEY_TOTAL_PERFECT_CLEARS, 0)
|
||||
|
||||
fun getSessionScore(): Int = sessionScore
|
||||
fun getSessionLines(): Int = sessionLines
|
||||
fun getSessionQuads(): Int = sessionQuads
|
||||
fun getSessionPerfectClears(): Int = sessionPerfectClears
|
||||
|
||||
fun updateStats(score: Int, lines: Int, isQuad: Boolean, isPerfectClear: Boolean) {
|
||||
// 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()
|
||||
}
|
||||
|
||||
// Update total stats
|
||||
prefs.edit()
|
||||
.putInt(KEY_TOTAL_LINES, getTotalLines() + lines)
|
||||
.putInt(KEY_TOTAL_QUADS, getTotalQuads() + if (isQuad) 1 else 0)
|
||||
.putInt(KEY_TOTAL_PERFECT_CLEARS, getTotalPerfectClears() + if (isPerfectClear) 1 else 0)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun startNewGame() {
|
||||
// Increment total games counter
|
||||
prefs.edit().putInt(KEY_TOTAL_GAMES, getTotalGames() + 1).apply()
|
||||
}
|
||||
|
||||
fun resetSession() {
|
||||
sessionScore = 0
|
||||
sessionLines = 0
|
||||
sessionQuads = 0
|
||||
sessionPerfectClears = 0
|
||||
}
|
||||
|
||||
fun resetAllStats() {
|
||||
prefs.edit().clear().apply()
|
||||
resetSession()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.mintris.model
|
||||
package com.pixelmintdrop.model
|
||||
|
||||
/**
|
||||
* Represents a Tetris piece (Tetromino)
|
||||
* Represents a game piece (Tetromino)
|
||||
*/
|
||||
enum class TetrominoType {
|
||||
I, J, L, O, S, T, Z
|
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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
|
@ -9,9 +9,7 @@ import android.widget.FrameLayout
|
|||
import android.widget.GridLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.mintris.R
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import android.animation.ValueAnimator
|
||||
import com.pixelmintdrop.R
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
|
||||
/**
|
|
@ -1,14 +1,10 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import com.mintris.R
|
||||
import com.mintris.databinding.ActivityMainBinding
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import kotlin.math.roundToInt
|
||||
import com.pixelmintdrop.databinding.ActivityMainBinding
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
|
||||
/**
|
||||
* Handles UI updates and state management for the game interface
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
|
@ -12,8 +12,8 @@ import android.view.animation.OvershootInterpolator
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.mintris.R
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import com.pixelmintdrop.R
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
|
||||
/**
|
||||
* Screen that displays player progression, XP gain, and unlocked rewards
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
|
@ -9,9 +9,8 @@ import android.widget.FrameLayout
|
|||
import android.widget.GridLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.mintris.R
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import android.animation.ValueAnimator
|
||||
import com.pixelmintdrop.R
|
||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.Log
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.mintris.ui
|
||||
package com.pixelmintdrop.ui
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
|
@ -9,8 +9,6 @@ import android.graphics.RectF
|
|||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.mintris.R
|
||||
|
||||
/**
|
||||
* Custom progress bar for displaying player XP with animation capabilities
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
|
||||
<!-- T-Tetromino -->
|
||||
<!-- T-piece -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M36,36h12v12h-12z" />
|
||||
|
@ -19,7 +19,7 @@
|
|||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M48,48h12v12h-12z" />
|
||||
|
||||
<!-- L-Tetromino -->
|
||||
<!-- L-piece -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M36,60h12v12h-12z" />
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.mintris.game.GameView
|
||||
<com.pixelmintdrop.game.GameView
|
||||
android:id="@+id/gameView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
@ -72,7 +72,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="24dp"/>
|
||||
|
||||
<com.mintris.game.HoldPieceView
|
||||
<com.pixelmintdrop.game.HoldPieceView
|
||||
android:id="@+id/holdPieceView"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
|
@ -125,7 +125,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="24dp"/>
|
||||
|
||||
<com.mintris.game.NextPieceView
|
||||
<com.pixelmintdrop.game.NextPieceView
|
||||
android:id="@+id/nextPieceView"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
|
@ -188,7 +188,7 @@
|
|||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Title Screen -->
|
||||
<com.mintris.game.TitleScreen
|
||||
<com.pixelmintdrop.game.TitleScreen
|
||||
android:id="@+id/titleScreen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -198,7 +198,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Progression Screen -->
|
||||
<com.mintris.ui.ProgressionScreen
|
||||
<com.pixelmintdrop.ui.ProgressionScreen
|
||||
android:id="@+id/progressionScreen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -209,7 +209,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Theme Selector -->
|
||||
<com.mintris.ui.ThemeSelector
|
||||
<com.pixelmintdrop.ui.ThemeSelector
|
||||
android:id="@+id/themeSelector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -220,7 +220,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Block Skin Selector -->
|
||||
<com.mintris.ui.BlockSkinSelector
|
||||
<com.pixelmintdrop.ui.BlockSkinSelector
|
||||
android:id="@+id/blockSkinSelector"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -592,7 +592,7 @@
|
|||
android:fontFamily="sans-serif" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sessionTetrisesText"
|
||||
android:id="@+id/sessionQuadsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
|
@ -655,7 +655,7 @@
|
|||
android:textAllCaps="false"
|
||||
android:singleLine="true" />
|
||||
|
||||
<com.mintris.ui.LevelBadge
|
||||
<com.pixelmintdrop.ui.LevelBadge
|
||||
android:id="@+id/customizationLevelBadge"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
|
@ -683,7 +683,7 @@
|
|||
android:paddingBottom="32dp">
|
||||
|
||||
<!-- Theme Selector -->
|
||||
<com.mintris.ui.ThemeSelector
|
||||
<com.pixelmintdrop.ui.ThemeSelector
|
||||
android:id="@+id/customizationThemeSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -691,7 +691,7 @@
|
|||
android:focusableInTouchMode="true" />
|
||||
|
||||
<!-- Block Skin Selector -->
|
||||
<com.mintris.ui.BlockSkinSelector
|
||||
<com.pixelmintdrop.ui.BlockSkinSelector
|
||||
android:id="@+id/customizationBlockSkinSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalTetrisesText"
|
||||
android:id="@+id/totalQuadsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.mintris.game.GameView
|
||||
<com.pixelmintdrop.game.GameView
|
||||
android:id="@+id/gameView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
@ -33,7 +33,7 @@
|
|||
</FrameLayout>
|
||||
|
||||
<!-- Title Screen -->
|
||||
<com.mintris.game.TitleScreen
|
||||
<com.pixelmintdrop.game.TitleScreen
|
||||
android:id="@+id/titleScreen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -116,7 +116,7 @@
|
|||
android:fontFamily="sans-serif"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<com.mintris.game.NextPieceView
|
||||
<com.pixelmintdrop.game.NextPieceView
|
||||
android:id="@+id/nextPieceView"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp" />
|
||||
|
@ -136,7 +136,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
<!-- Hold Piece Preview -->
|
||||
<com.mintris.game.HoldPieceView
|
||||
<com.pixelmintdrop.game.HoldPieceView
|
||||
android:id="@+id/holdPieceView"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
|
@ -266,7 +266,7 @@
|
|||
android:fontFamily="sans-serif" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sessionTetrisesText"
|
||||
android:id="@+id/sessionQuadsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
|
@ -289,7 +289,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
<!-- Player Progression Screen -->
|
||||
<com.mintris.ui.ProgressionScreen
|
||||
<com.pixelmintdrop.ui.ProgressionScreen
|
||||
android:id="@+id/progressionScreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -328,7 +328,7 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<com.mintris.ui.LevelBadge
|
||||
<com.pixelmintdrop.ui.LevelBadge
|
||||
android:id="@+id/pauseLevelBadge"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
|
@ -567,7 +567,7 @@
|
|||
android:textAllCaps="false"
|
||||
android:singleLine="true" />
|
||||
|
||||
<com.mintris.ui.LevelBadge
|
||||
<com.pixelmintdrop.ui.LevelBadge
|
||||
android:id="@+id/customizationLevelBadge"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
|
@ -592,14 +592,14 @@
|
|||
android:paddingBottom="32dp">
|
||||
|
||||
<!-- Theme Selector -->
|
||||
<com.mintris.ui.ThemeSelector
|
||||
<com.pixelmintdrop.ui.ThemeSelector
|
||||
android:id="@+id/customizationThemeSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Block Skin Selector -->
|
||||
<com.mintris.ui.BlockSkinSelector
|
||||
<com.pixelmintdrop.ui.BlockSkinSelector
|
||||
android:id="@+id/customizationBlockSkinSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalTetrisesText"
|
||||
android:id="@+id/totalQuadsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<com.mintris.ui.XPProgressBar
|
||||
<com.pixelmintdrop.ui.XPProgressBar
|
||||
android:id="@+id/xp_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">mintris</string>
|
||||
<string name="app_name">Pixel Mint Drop</string>
|
||||
<string name="game_over">game over</string>
|
||||
<string name="score">score</string>
|
||||
<string name="level">level</string>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<string name="singles">singles: %d</string>
|
||||
<string name="doubles">doubles: %d</string>
|
||||
<string name="triples">triples: %d</string>
|
||||
<string name="tetrises">tetrises: %d</string>
|
||||
<string name="quads">quads: %d</string>
|
||||
<string name="reset_stats">reset stats</string>
|
||||
<string name="music">music</string>
|
||||
<string name="customization">Customization</string>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Base application theme -->
|
||||
<style name="Theme.Mintris" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<style name="Theme.pixelmintdrop" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<item name="colorPrimary">@color/white</item>
|
||||
<item name="colorPrimaryDark">@color/black</item>
|
||||
<item name="colorAccent">@color/white</item>
|
||||
|
@ -11,7 +11,7 @@
|
|||
</style>
|
||||
|
||||
<!-- No action bar theme -->
|
||||
<style name="Theme.Mintris.NoActionBar">
|
||||
<style name="Theme.pixelmintdrop.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
android:shortcutLongLabel="@string/shortcut_new_game_long_label">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetPackage="com.mintris"
|
||||
android:targetClass="com.mintris.MainActivity">
|
||||
android:targetPackage="com.pixelmintdrop"
|
||||
android:targetClass="com.pixelmintdrop.MainActivity">
|
||||
<extra android:name="action" android:value="new_game" />
|
||||
</intent>
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
|
@ -23,8 +23,8 @@
|
|||
android:shortcutLongLabel="@string/shortcut_high_scores_long_label">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetPackage="com.mintris"
|
||||
android:targetClass="com.mintris.HighScoresActivity" />
|
||||
android:targetPackage="com.pixelmintdrop"
|
||||
android:targetClass="com.pixelmintdrop.HighScoresActivity" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
|
@ -1,2 +1,2 @@
|
|||
rootProject.name = "Mintris"
|
||||
rootProject.name = "pixelmintdrop"
|
||||
include ':app'
|
12
tatus
12
tatus
|
@ -1,8 +1,8 @@
|
|||
app/src/main/java/com/mintris/MainActivity.kt
|
||||
app/src/main/java/com/mintris/audio/GameMusic.kt
|
||||
app/src/main/java/com/mintris/model/PlayerProgressionManager.kt
|
||||
app/src/main/java/com/mintris/ui/ProgressionScreen.kt
|
||||
app/src/main/java/com/mintris/ui/ThemeSelector.kt
|
||||
app/src/main/java/com/mintris/ui/XPProgressBar.kt
|
||||
app/src/main/java/com/pixelmintdrop/MainActivity.kt
|
||||
app/src/main/java/com/pixelmintdrop/audio/GameMusic.kt
|
||||
app/src/main/java/com/pixelmintdrop/model/PlayerProgressionManager.kt
|
||||
app/src/main/java/com/pixelmintdrop/ui/ProgressionScreen.kt
|
||||
app/src/main/java/com/pixelmintdrop/ui/ThemeSelector.kt
|
||||
app/src/main/java/com/pixelmintdrop/ui/XPProgressBar.kt
|
||||
app/src/main/res/drawable/rounded_button.xml
|
||||
app/src/main/res/layout/progression_screen.xml
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue