Adds the full Android source tree with unit tests, fastlane store metadata and the Gradle wrapper. The archive shipped without gradle-wrapper.jar, so the official Gradle 9.7.1 wrapper jar is included to make ./gradlew work from a fresh clone. .gitignore now also excludes build output and keystores. README gains build instructions and an updated structure tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015g6ECVSKcBG8CJPrqC3G5o
77 lines
2.8 KiB
Kotlin
77 lines
2.8 KiB
Kotlin
package cc.skysparkle.matewave.stats
|
|
|
|
import android.content.Context
|
|
|
|
object Stats {
|
|
private const val PREFS = "chess_stats_prefs"
|
|
private const val KEY_WINS = "wins_vs_ai"
|
|
private const val KEY_GAMES = "games_played_vs_ai"
|
|
private const val KEY_STREAK = "current_streak"
|
|
private const val KEY_RATINGS = "rating_history"
|
|
private const val KEY_SOLVED = "solved_puzzles"
|
|
private const val RATING_LIMIT = 40
|
|
|
|
private var appContext: Context? = null
|
|
|
|
fun init(context: Context) {
|
|
appContext = context.applicationContext
|
|
}
|
|
|
|
private fun prefs() = appContext?.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
|
|
fun wins(): Int = prefs()?.getInt(KEY_WINS, 0) ?: 0
|
|
|
|
fun gamesPlayed(): Int = prefs()?.getInt(KEY_GAMES, 0) ?: 0
|
|
|
|
fun winPercent(): Int {
|
|
val played = gamesPlayed()
|
|
return if (played == 0) 0 else ((wins() * 100f) / played).let { Math.round(it) }
|
|
}
|
|
|
|
fun streak(): Int = prefs()?.getInt(KEY_STREAK, 0) ?: 0
|
|
|
|
/** Result of a rated game against the AI or another player: 1.0 win, 0.5 draw, 0.0 loss. */
|
|
fun recordResult(result: Double) {
|
|
val p = prefs() ?: return
|
|
val win = result >= 1.0
|
|
p.edit()
|
|
.putInt(KEY_WINS, p.getInt(KEY_WINS, 0) + if (win) 1 else 0)
|
|
.putInt(KEY_GAMES, p.getInt(KEY_GAMES, 0) + 1)
|
|
.putInt(KEY_STREAK, if (win) p.getInt(KEY_STREAK, 0) + 1 else 0)
|
|
.apply()
|
|
}
|
|
|
|
/** Rating change after a rated game; the statistics graph shows this history. */
|
|
fun recordRating(before: Int, after: Int) {
|
|
val p = prefs() ?: return
|
|
val history = readRatings().ifEmpty { listOf(before) }
|
|
val ratings = (history + after).takeLast(RATING_LIMIT)
|
|
p.edit().putString(KEY_RATINGS, ratings.joinToString(",")).apply()
|
|
}
|
|
|
|
private fun readRatings(): List<Int> {
|
|
val raw = prefs()?.getString(KEY_RATINGS, "") ?: ""
|
|
return if (raw.isBlank()) emptyList() else raw.split(",").mapNotNull { it.toIntOrNull() }
|
|
}
|
|
|
|
fun solvedPuzzles(): Set<Int> =
|
|
prefs()?.getStringSet(KEY_SOLVED, emptySet())?.mapNotNull { it.toIntOrNull() }?.toSet() ?: emptySet()
|
|
|
|
fun markPuzzleSolved(id: Int) {
|
|
val p = prefs() ?: return
|
|
val set = (p.getStringSet(KEY_SOLVED, emptySet()) ?: emptySet()).toMutableSet()
|
|
set.add(id.toString())
|
|
p.edit().putStringSet(KEY_SOLVED, set).apply()
|
|
}
|
|
|
|
/** Rating history for the graph; with fewer than two games it starts from the current rating. */
|
|
fun ratingCurve(currentRating: Int): List<Int> {
|
|
val ratings = readRatings()
|
|
return when {
|
|
ratings.isEmpty() -> listOf(currentRating, currentRating)
|
|
ratings.size == 1 -> listOf(ratings.first(), ratings.first())
|
|
else -> ratings
|
|
}
|
|
}
|
|
}
|