mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 06:05:19 +01:00
Compare commits
5 commits
44c4c73feb
...
0f1404d969
Author | SHA1 | Date | |
---|---|---|---|
|
0f1404d969 | ||
|
2da2444758 | ||
|
222f48e8c4 | ||
|
8125d0f7b3 | ||
|
cdd80e5796 |
9 changed files with 657 additions and 63 deletions
|
@ -28,7 +28,12 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".HighScoresActivity"
|
android:name=".HighScoresActivity"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
android:exported="false"
|
||||||
android:exported="false" />
|
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".StatsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
|
@ -21,6 +21,9 @@ import com.mintris.model.GameBoard
|
||||||
import com.mintris.audio.GameMusic
|
import com.mintris.audio.GameMusic
|
||||||
import com.mintris.model.HighScoreManager
|
import com.mintris.model.HighScoreManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import com.mintris.model.StatsManager
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -32,6 +35,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var gameMusic: GameMusic
|
private lateinit var gameMusic: GameMusic
|
||||||
private lateinit var titleScreen: TitleScreen
|
private lateinit var titleScreen: TitleScreen
|
||||||
private lateinit var highScoreManager: HighScoreManager
|
private lateinit var highScoreManager: HighScoreManager
|
||||||
|
private lateinit var statsManager: StatsManager
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
private var isSoundEnabled = true
|
private var isSoundEnabled = true
|
||||||
|
@ -40,6 +44,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val maxLevel = 20
|
private val maxLevel = 20
|
||||||
private var currentScore = 0
|
private var currentScore = 0
|
||||||
private var currentLevel = 1
|
private var currentLevel = 1
|
||||||
|
private var gameStartTime: Long = 0
|
||||||
|
private var piecesPlaced: Int = 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -53,6 +59,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
titleScreen = binding.titleScreen
|
titleScreen = binding.titleScreen
|
||||||
gameMusic = GameMusic(this)
|
gameMusic = GameMusic(this)
|
||||||
highScoreManager = HighScoreManager(this)
|
highScoreManager = HighScoreManager(this)
|
||||||
|
statsManager = StatsManager(this)
|
||||||
|
|
||||||
// Set up game view
|
// Set up game view
|
||||||
gameView.setGameBoard(gameBoard)
|
gameView.setGameBoard(gameBoard)
|
||||||
|
@ -117,6 +124,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
} else {
|
} else {
|
||||||
android.util.Log.d("MainActivity", "Sound is disabled, skipping haptic feedback")
|
android.util.Log.d("MainActivity", "Sound is disabled, skipping haptic feedback")
|
||||||
}
|
}
|
||||||
|
// Record line clear in stats
|
||||||
|
statsManager.recordLineClear(lineCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add callbacks for piece movement and locking
|
// Add callbacks for piece movement and locking
|
||||||
|
@ -130,6 +139,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (isSoundEnabled) {
|
if (isSoundEnabled) {
|
||||||
gameHaptics.vibrateForPieceLock()
|
gameHaptics.vibrateForPieceLock()
|
||||||
}
|
}
|
||||||
|
piecesPlaced++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up button click listeners with haptic feedback
|
// Set up button click listeners with haptic feedback
|
||||||
|
@ -187,6 +197,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up stats button
|
||||||
|
binding.statsButton.setOnClickListener {
|
||||||
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
|
val intent = Intent(this, StatsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize level selector
|
// Initialize level selector
|
||||||
updateLevelSelector()
|
updateLevelSelector()
|
||||||
|
|
||||||
|
@ -205,6 +222,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding.linesText.text = lines.toString()
|
binding.linesText.text = lines.toString()
|
||||||
binding.comboText.text = gameBoard.getCombo().toString()
|
binding.comboText.text = gameBoard.getCombo().toString()
|
||||||
|
|
||||||
|
// Update current level for stats
|
||||||
|
currentLevel = level
|
||||||
|
|
||||||
// Force redraw of next piece preview
|
// Force redraw of next piece preview
|
||||||
binding.nextPieceView.invalidate()
|
binding.nextPieceView.invalidate()
|
||||||
}
|
}
|
||||||
|
@ -213,7 +233,35 @@ class MainActivity : AppCompatActivity() {
|
||||||
* Show game over screen
|
* Show game over screen
|
||||||
*/
|
*/
|
||||||
private fun showGameOver(score: Int) {
|
private fun showGameOver(score: Int) {
|
||||||
binding.finalScoreText.text = getString(R.string.score) + ": " + score
|
val gameTime = System.currentTimeMillis() - gameStartTime
|
||||||
|
|
||||||
|
// Update session stats
|
||||||
|
statsManager.updateSessionStats(
|
||||||
|
score = score,
|
||||||
|
lines = gameBoard.lines,
|
||||||
|
pieces = piecesPlaced,
|
||||||
|
time = gameTime,
|
||||||
|
level = currentLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// End session and save stats
|
||||||
|
statsManager.endSession()
|
||||||
|
|
||||||
|
// Update session stats display
|
||||||
|
val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||||
|
timeFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
binding.sessionScoreText.text = getString(R.string.session_score, score)
|
||||||
|
binding.sessionLinesText.text = getString(R.string.session_lines, gameBoard.lines)
|
||||||
|
binding.sessionPiecesText.text = getString(R.string.session_pieces, piecesPlaced)
|
||||||
|
binding.sessionTimeText.text = getString(R.string.session_time, timeFormat.format(gameTime))
|
||||||
|
binding.sessionLevelText.text = getString(R.string.session_level, currentLevel)
|
||||||
|
|
||||||
|
// Update session line clear stats
|
||||||
|
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())
|
||||||
|
|
||||||
// Check if this is a high score
|
// Check if this is a high score
|
||||||
if (highScoreManager.isHighScore(score)) {
|
if (highScoreManager.isHighScore(score)) {
|
||||||
|
@ -291,10 +339,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun startGame() {
|
private fun startGame() {
|
||||||
gameView.start()
|
gameView.start()
|
||||||
gameMusic.setEnabled(isMusicEnabled) // Explicitly set enabled state
|
gameMusic.setEnabled(isMusicEnabled)
|
||||||
if (isMusicEnabled) {
|
if (isMusicEnabled) {
|
||||||
gameMusic.start()
|
gameMusic.start()
|
||||||
}
|
}
|
||||||
|
gameStartTime = System.currentTimeMillis()
|
||||||
|
piecesPlaced = 0
|
||||||
|
statsManager.startNewSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restartGame() {
|
private fun restartGame() {
|
||||||
|
|
54
app/src/main/java/com/mintris/StatsActivity.kt
Normal file
54
app/src/main/java/com/mintris/StatsActivity.kt
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package com.mintris
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.mintris.databinding.ActivityStatsBinding
|
||||||
|
import com.mintris.model.StatsManager
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class StatsActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityStatsBinding
|
||||||
|
private lateinit var statsManager: StatsManager
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityStatsBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
statsManager = StatsManager(this)
|
||||||
|
|
||||||
|
// Set up back button
|
||||||
|
binding.backButton.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateStats() {
|
||||||
|
// Format time duration
|
||||||
|
val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||||
|
timeFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
// Update lifetime stats
|
||||||
|
binding.totalGamesText.text = getString(R.string.total_games, statsManager.getTotalGames())
|
||||||
|
binding.totalScoreText.text = getString(R.string.total_score, statsManager.getTotalScore())
|
||||||
|
binding.totalLinesText.text = getString(R.string.total_lines, statsManager.getTotalLines())
|
||||||
|
binding.totalPiecesText.text = getString(R.string.total_pieces, statsManager.getTotalPieces())
|
||||||
|
binding.totalTimeText.text = getString(R.string.total_time, timeFormat.format(statsManager.getTotalTime()))
|
||||||
|
|
||||||
|
// Update line clear stats
|
||||||
|
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())
|
||||||
|
|
||||||
|
// Update best performance stats
|
||||||
|
binding.maxLevelText.text = getString(R.string.max_level, statsManager.getMaxLevel())
|
||||||
|
binding.maxScoreText.text = getString(R.string.max_score, statsManager.getMaxScore())
|
||||||
|
binding.maxLinesText.text = getString(R.string.max_lines, statsManager.getMaxLines())
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,10 @@ import java.util.Random
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.mintris.model.HighScoreManager
|
import com.mintris.model.HighScoreManager
|
||||||
import com.mintris.model.HighScore
|
import com.mintris.model.HighScore
|
||||||
|
import kotlin.math.abs
|
||||||
|
import androidx.core.graphics.withTranslation
|
||||||
|
import androidx.core.graphics.withScale
|
||||||
|
import androidx.core.graphics.withRotation
|
||||||
|
|
||||||
class TitleScreen @JvmOverloads constructor(
|
class TitleScreen @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -29,6 +33,14 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
private var width = 0
|
private var width = 0
|
||||||
private var height = 0
|
private var height = 0
|
||||||
private val tetrominosToAdd = mutableListOf<Tetromino>()
|
private val tetrominosToAdd = mutableListOf<Tetromino>()
|
||||||
|
private val highScoreManager = HighScoreManager(context) // Pre-allocate HighScoreManager
|
||||||
|
|
||||||
|
// Touch handling variables
|
||||||
|
private var startX = 0f
|
||||||
|
private var startY = 0f
|
||||||
|
private var lastTouchX = 0f
|
||||||
|
private var lastTouchY = 0f
|
||||||
|
private val maxTapMovement = 20f // Maximum movement allowed for a tap (in pixels)
|
||||||
|
|
||||||
// Callback for when the user touches the screen
|
// Callback for when the user touches the screen
|
||||||
var onStartGame: (() -> Unit)? = null
|
var onStartGame: (() -> Unit)? = null
|
||||||
|
@ -187,58 +199,31 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
tetrominosToAdd.add(createRandomTetromino())
|
tetrominosToAdd.add(createRandomTetromino())
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// Save canvas state before rotation
|
|
||||||
canvas.save()
|
|
||||||
|
|
||||||
// Translate to the tetromino's position
|
|
||||||
canvas.translate(tetromino.x, tetromino.y)
|
|
||||||
|
|
||||||
// Scale according to the tetromino's scale factor
|
|
||||||
canvas.scale(tetromino.scale, tetromino.scale)
|
|
||||||
|
|
||||||
// Rotate around the center of the tetromino
|
|
||||||
val centerX = tetromino.shape.size * cellSize / 2
|
|
||||||
val centerY = tetromino.shape.size * cellSize / 2
|
|
||||||
canvas.rotate(tetromino.rotation.toFloat(), centerX, centerY)
|
|
||||||
|
|
||||||
// Draw the tetromino
|
// Draw the tetromino
|
||||||
for (row in tetromino.shape.indices) {
|
for (y in 0 until tetromino.shape.size) {
|
||||||
for (col in 0 until tetromino.shape[row].size) {
|
for (x in 0 until tetromino.shape.size) {
|
||||||
if (tetromino.shape[row][col] == 1) {
|
if (tetromino.shape[y][x] == 1) {
|
||||||
// Draw larger glow effect
|
val left = x * cellSize
|
||||||
glowPaint.alpha = 30
|
val top = y * cellSize
|
||||||
canvas.drawRect(
|
val right = left + cellSize
|
||||||
col * cellSize - 8,
|
val bottom = top + cellSize
|
||||||
row * cellSize - 8,
|
|
||||||
(col + 1) * cellSize + 8,
|
|
||||||
(row + 1) * cellSize + 8,
|
|
||||||
glowPaint
|
|
||||||
)
|
|
||||||
|
|
||||||
// Draw medium glow
|
// Draw block with glow effect
|
||||||
glowPaint.alpha = 60
|
canvas.withTranslation(tetromino.x, tetromino.y) {
|
||||||
canvas.drawRect(
|
withScale(tetromino.scale, tetromino.scale) {
|
||||||
col * cellSize - 4,
|
withRotation(tetromino.rotation.toFloat(),
|
||||||
row * cellSize - 4,
|
tetromino.shape.size * cellSize / 2,
|
||||||
(col + 1) * cellSize + 4,
|
tetromino.shape.size * cellSize / 2) {
|
||||||
(row + 1) * cellSize + 4,
|
// Draw glow
|
||||||
glowPaint
|
canvas.drawRect(left - 8f, top - 8f, right + 8f, bottom + 8f, glowPaint)
|
||||||
)
|
// Draw block
|
||||||
|
canvas.drawRect(left, top, right, bottom, paint)
|
||||||
// Draw main block
|
}
|
||||||
canvas.drawRect(
|
}
|
||||||
col * cellSize,
|
}
|
||||||
row * cellSize,
|
|
||||||
(col + 1) * cellSize,
|
|
||||||
(row + 1) * cellSize,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore canvas state
|
|
||||||
canvas.restore()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("TitleScreen", "Error drawing tetromino", e)
|
Log.e("TitleScreen", "Error drawing tetromino", e)
|
||||||
}
|
}
|
||||||
|
@ -252,8 +237,7 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
val titleY = height * 0.4f
|
val titleY = height * 0.4f
|
||||||
canvas.drawText("mintris", width / 2f, titleY, titlePaint)
|
canvas.drawText("mintris", width / 2f, titleY, titlePaint)
|
||||||
|
|
||||||
// Draw high scores
|
// Draw high scores using pre-allocated manager
|
||||||
val highScoreManager = HighScoreManager(context)
|
|
||||||
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
||||||
val highScoreY = height * 0.5f
|
val highScoreY = height * 0.5f
|
||||||
if (highScores.isNotEmpty()) {
|
if (highScores.isNotEmpty()) {
|
||||||
|
@ -274,10 +258,50 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
when (event.action) {
|
||||||
onStartGame?.invoke()
|
MotionEvent.ACTION_DOWN -> {
|
||||||
return true
|
startX = event.x
|
||||||
|
startY = event.y
|
||||||
|
lastTouchX = event.x
|
||||||
|
lastTouchY = event.y
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTouchX = event.x
|
||||||
|
lastTouchY = event.y
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
val deltaX = event.x - startX
|
||||||
|
val deltaY = event.y - startY
|
||||||
|
|
||||||
|
// If the movement was minimal, treat as a tap
|
||||||
|
if (abs(deltaX) < maxTapMovement && abs(deltaY) < maxTapMovement) {
|
||||||
|
performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.onTouchEvent(event)
|
return super.onTouchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun performClick(): Boolean {
|
||||||
|
// Call the superclass's performClick
|
||||||
|
super.performClick()
|
||||||
|
|
||||||
|
// Handle the click event
|
||||||
|
onStartGame?.invoke()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,8 @@ class GameBoard(
|
||||||
var level = 1
|
var level = 1
|
||||||
var lines = 0
|
var lines = 0
|
||||||
var isGameOver = false
|
var isGameOver = false
|
||||||
|
var isHardDropInProgress = false // Make public
|
||||||
|
var isPieceLocking = false // Make public
|
||||||
|
|
||||||
// Scoring state
|
// Scoring state
|
||||||
private var combo = 0
|
private var combo = 0
|
||||||
|
@ -150,6 +152,9 @@ class GameBoard(
|
||||||
* Move the current piece down (soft drop)
|
* Move the current piece down (soft drop)
|
||||||
*/
|
*/
|
||||||
fun moveDown(): Boolean {
|
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)) {
|
return if (canMove(0, 1)) {
|
||||||
currentPiece?.y = currentPiece?.y?.plus(1) ?: 0
|
currentPiece?.y = currentPiece?.y?.plus(1) ?: 0
|
||||||
onPieceMove?.invoke()
|
onPieceMove?.invoke()
|
||||||
|
@ -164,9 +169,19 @@ class GameBoard(
|
||||||
* Hard drop the current piece
|
* Hard drop the current piece
|
||||||
*/
|
*/
|
||||||
fun hardDrop() {
|
fun hardDrop() {
|
||||||
while (moveDown()) {
|
if (isHardDropInProgress || isPieceLocking) return // Prevent multiple hard drops
|
||||||
// Keep moving down until blocked
|
|
||||||
|
isHardDropInProgress = true
|
||||||
|
val piece = currentPiece ?: return
|
||||||
|
|
||||||
|
// Move piece down until it can't move anymore
|
||||||
|
while (canMove(0, 1)) {
|
||||||
|
piece.y++
|
||||||
|
onPieceMove?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock the piece immediately
|
||||||
|
lockPiece()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,9 +264,12 @@ class GameBoard(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock the current piece in place and check for completed lines
|
* Lock the current piece in place
|
||||||
*/
|
*/
|
||||||
fun lockPiece() {
|
private fun lockPiece() {
|
||||||
|
if (isPieceLocking) return // Prevent recursive locking
|
||||||
|
isPieceLocking = true
|
||||||
|
|
||||||
val piece = currentPiece ?: return
|
val piece = currentPiece ?: return
|
||||||
|
|
||||||
// Add the piece to the grid
|
// Add the piece to the grid
|
||||||
|
@ -275,11 +293,15 @@ class GameBoard(
|
||||||
// Find and clear lines immediately
|
// Find and clear lines immediately
|
||||||
findAndClearLines()
|
findAndClearLines()
|
||||||
|
|
||||||
// Spawn new piece
|
// Spawn new piece immediately
|
||||||
spawnPiece()
|
spawnPiece()
|
||||||
|
|
||||||
// Allow holding piece again after locking
|
// Allow holding piece again after locking
|
||||||
canHold = true
|
canHold = true
|
||||||
|
|
||||||
|
// Reset both states after everything is done
|
||||||
|
isPieceLocking = false
|
||||||
|
isHardDropInProgress = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
175
app/src/main/java/com/mintris/model/StatsManager.kt
Normal file
175
app/src/main/java/com/mintris/model/StatsManager.kt
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,10 +135,92 @@
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/finalScoreText"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:text="@string/session_stats"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionScoreText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionLinesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionPiecesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionTimeText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionLevelText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/line_clears"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionSinglesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionDoublesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionTriplesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionTetrisesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
@ -209,6 +291,16 @@
|
||||||
android:text="@string/high_scores"
|
android:text="@string/high_scores"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/statsButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="@string/stats"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/levelSelectorContainer"
|
android:id="@+id/levelSelectorContainer"
|
||||||
|
|
147
app/src/main/res/layout/activity_stats.xml
Normal file
147
app/src/main/res/layout/activity_stats.xml
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/lifetime_stats"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalGamesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalScoreText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalLinesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalPiecesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalTimeText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/line_clears"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalSinglesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalDoublesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalTriplesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/totalTetrisesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/best_performance"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/maxLevelText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/maxScoreText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/maxLinesText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/back"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:layout_marginTop="16dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
|
@ -20,4 +20,28 @@
|
||||||
<string name="new_high_score">New High Score!</string>
|
<string name="new_high_score">New High Score!</string>
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
|
|
||||||
|
<!-- Stats Screen -->
|
||||||
|
<string name="lifetime_stats">Lifetime Stats</string>
|
||||||
|
<string name="best_performance">Best Performance</string>
|
||||||
|
<string name="total_games">Total Games: %d</string>
|
||||||
|
<string name="total_score">Total Score: %d</string>
|
||||||
|
<string name="total_lines">Total Lines: %d</string>
|
||||||
|
<string name="total_pieces">Total Pieces: %d</string>
|
||||||
|
<string name="total_time">Total Time: %s</string>
|
||||||
|
<string name="max_level">Max Level: %d</string>
|
||||||
|
<string name="max_score">Max Score: %d</string>
|
||||||
|
<string name="max_lines">Max Lines: %d</string>
|
||||||
|
<string name="stats">Stats</string>
|
||||||
|
<string name="session_stats">Session Stats</string>
|
||||||
|
<string name="session_score">Score: %d</string>
|
||||||
|
<string name="session_lines">Lines: %d</string>
|
||||||
|
<string name="session_pieces">Pieces: %d</string>
|
||||||
|
<string name="session_time">Time: %s</string>
|
||||||
|
<string name="session_level">Level: %d</string>
|
||||||
|
<string name="line_clears">Line Clears</string>
|
||||||
|
<string name="singles">Singles: %d</string>
|
||||||
|
<string name="doubles">Doubles: %d</string>
|
||||||
|
<string name="triples">Triples: %d</string>
|
||||||
|
<string name="tetrises">Tetrises: %d</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue