Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
meikpiep committed Jan 26, 2024
1 parent ca99a14 commit ef7edae
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 129 deletions.
1 change: 1 addition & 0 deletions gauguin-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ dependencies {
implementation(libs.thirdparty.ferriswheel)
implementation(libs.thirdparty.navigationdrawer)
implementation(libs.thirdparty.balloon)
implementation(libs.thirdparty.vico)

implementation(libs.bundles.koin)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MainApplication : Application() {
} withOptions { binds(listOf(ApplicationPreferences::class)) }
single {
StatisticsManagerImpl(
filesDir,
this@MainApplication.getSharedPreferences("stats", Context.MODE_PRIVATE),
)
} withOptions {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.piepmeyer.gauguin.preferences

import android.content.SharedPreferences
import androidx.core.content.edit
import org.piepmeyer.gauguin.Utils
import org.piepmeyer.gauguin.grid.Grid
import kotlin.time.Duration.Companion.ZERO
import kotlin.time.Duration.Companion.milliseconds

class LegacyStatisticsManager(
private val stats: SharedPreferences,
) {
fun storeStatisticsAfterFinishedGame(grid: Grid): String? {
if (grid.countCheated() > 0) {
return null
}

val gridsize = grid.gridSize

val timestat = stats.getLong("solvedtime$gridsize", 0).milliseconds
val editor = stats.edit()
val recordTime =
if (timestat == ZERO || timestat > grid.playTime) {
editor.putLong("solvedtime$gridsize", grid.playTime.inWholeMilliseconds)
Utils.displayableGameDuration(grid.playTime)
} else {
null
}
editor.apply()
return recordTime
}

fun currentStreak() = stats.getInt("solvedstreak", 0)

fun longestStreak() = stats.getInt("longeststreak", 0)

fun totalStarted() = stats.getInt("totalStarted", 0)

fun totalSolved() = stats.getInt("totalSolved", 0)

fun totalHinted() = stats.getInt("totalHinted", 0)

fun clearStatistics() {
stats.edit { clear() }
}
}
Original file line number Diff line number Diff line change
@@ -1,87 +1,126 @@
package org.piepmeyer.gauguin.preferences

import android.content.SharedPreferences
import androidx.core.content.edit
import org.piepmeyer.gauguin.Utils
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.piepmeyer.gauguin.difficulty.GridDifficultyCalculator
import org.piepmeyer.gauguin.grid.Grid
import kotlin.time.Duration.Companion.ZERO
import kotlin.time.Duration.Companion.milliseconds
import org.piepmeyer.gauguin.statistics.Statistics
import java.io.File
import java.nio.charset.StandardCharsets

private val logger = KotlinLogging.logger {}

class StatisticsManagerImpl(
private val stats: SharedPreferences,
directory: File,
sharedPreferences: SharedPreferences,
) : StatisticsManager {
private val statisticsFile = File(directory, "statistics.yaml")
private val legacyManager = LegacyStatisticsManager(sharedPreferences)
private var statistics: Statistics = loadStatistics()

override fun puzzleStartedToBePlayed() {
stats.edit {
putInt("totalStarted", totalStarted() + 1)
}
statistics.overall.gamesStarted++

saveStatistics()
}

override fun puzzleSolved(grid: Grid) {
stats.edit {
putInt("totalSolved", totalSolved() + 1)
statistics.overall.gamesSolvedWithoutHints++

if (grid.isCheated()) {
putInt("totalHinted", totalHinted() + 1)
}
if (grid.isCheated()) {
statistics.overall.gamesSolvedWithHints++
}
}

override fun storeStatisticsAfterNewGame(grid: Grid) {
val gamestat = stats.getInt("playedgames" + grid.gridSize, 0)
statistics.overall.solvedDifficulty.add(GridDifficultyCalculator(grid).calculate())
statistics.overall.solvedDuration.add(grid.playTime.inWholeSeconds.toInt())

stats.edit {
putInt("playedgames" + grid.gridSize, gamestat + 1)
}
saveStatistics()
}

override fun storeStatisticsAfterFinishedGame(grid: Grid): String? {
val gridsize = grid.gridSize

// assess hint penalty - gridsize^2/2 seconds for each cell
val penalty = grid.countCheated().toLong() * 500 * gridsize.surfaceArea
grid.playTime = grid.playTime + penalty.milliseconds
val solvetime = grid.playTime

val timestat = stats.getLong("solvedtime$gridsize", 0).milliseconds
val editor = stats.edit()
val recordTime =
if (timestat == ZERO || timestat > solvetime) {
editor.putLong("solvedtime$gridsize", solvetime.inWholeMilliseconds)
Utils.displayableGameDuration(solvetime)
} else {
null
}
editor.apply()
return recordTime
return legacyManager.storeStatisticsAfterFinishedGame(grid)
}

override fun storeStreak(isSolved: Boolean) {
val solvedStreak = currentStreak()
val longestStreak = longestStreak()

stats.edit {
if (isSolved) {
putInt("solvedstreak", solvedStreak + 1)
if (solvedStreak == longestStreak) {
putInt("longeststreak", solvedStreak + 1)
}
} else {
putInt("solvedstreak", 0)
if (isSolved) {
statistics.overall.streak++
if (solvedStreak == longestStreak) {
statistics.overall.longestStreak = statistics.overall.streak
}
} else {
statistics.overall.solvedDifficulty.add(0.0)
statistics.overall.solvedDuration.add(0)
statistics.overall.streak = 0
}

saveStatistics()
}

private fun loadStatistics(): Statistics {
if (!statisticsFile.exists()) {
return migrateLegacyStatistics()
}

val fileData = statisticsFile.readText(StandardCharsets.UTF_8)

return try {
Json.decodeFromString<Statistics>(fileData)
} catch (e: Exception) {
logger.error(e) { "Error loading statistics: " + e.message }
Statistics()
}
}

private fun migrateLegacyStatistics(): Statistics {
if (legacyManager.totalStarted() == 0) {
return Statistics()
}

val stats = Statistics()

stats.overall.gamesStarted = legacyManager.totalStarted()
stats.overall.gamesSolvedWithHints = legacyManager.totalHinted()
stats.overall.gamesSolvedWithoutHints = legacyManager.totalSolved()
stats.overall.streak = legacyManager.currentStreak()
stats.overall.longestStreak = legacyManager.longestStreak()

return stats
}

private fun saveStatistics() {
try {
val result = Json.encodeToString(statistics)

statisticsFile.writeText(result)
} catch (e: Exception) {
logger.error(e) { "Error saving statistics: " + e.message }
return
}
}

override fun currentStreak() = stats.getInt("solvedstreak", 0)
override fun currentStreak() = statistics.overall.streak

override fun longestStreak() = stats.getInt("longeststreak", 0)
override fun longestStreak() = statistics.overall.longestStreak

override fun totalStarted() = stats.getInt("totalStarted", 0)
override fun totalStarted() = statistics.overall.gamesStarted

override fun totalSolved() = stats.getInt("totalSolved", 0)
override fun totalSolved() = statistics.overall.gamesSolvedWithoutHints

override fun totalHinted() = stats.getInt("totalHinted", 0)
override fun totalHinted() = statistics.overall.gamesSolvedWithHints

override fun clearStatistics() {
stats.edit { clear() }
statistics = Statistics()
saveStatistics()

legacyManager.clearStatistics()
}

override fun statistics(): Statistics {
return statistics
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine
import com.patrykandpatrick.vico.core.chart.layer.LineCartesianLayer
import com.patrykandpatrick.vico.core.component.shape.LineComponent
import com.patrykandpatrick.vico.core.component.shape.shader.ColorShader
import com.patrykandpatrick.vico.core.component.text.textComponent
import com.patrykandpatrick.vico.core.model.CartesianChartModel
import com.patrykandpatrick.vico.core.model.LineCartesianLayerModel
import com.patrykandpatrick.vico.views.chart.CartesianChartView
import org.koin.android.ext.android.inject
import org.piepmeyer.gauguin.R
import org.piepmeyer.gauguin.databinding.ActivityStatisticsBinding
Expand Down Expand Up @@ -32,6 +41,18 @@ class StatisticsActivity : AppCompatActivity() {
}

private fun updateViews() {
val chartsAvailable =
statisticeManager.statistics().overall.solvedDifficulty.isNotEmpty() &&
statisticeManager.statistics().overall.solvedDuration.isNotEmpty()

if (chartsAvailable) {
fillCharts()
binding.labelNoStatisticsAvailableYet.visibility = View.INVISIBLE
} else {
hideCharts()
binding.labelNoStatisticsAvailableYet.visibility = View.VISIBLE
}

binding.startedstat.text = statisticeManager.totalStarted().toString()
binding.hintedstat.text = statisticeManager.totalHinted().toString()
binding.solvedstat.text = statisticeManager.totalSolved().toString() + " (" +
Expand All @@ -43,6 +64,104 @@ class StatisticsActivity : AppCompatActivity() {
binding.longeststreak.text = statisticeManager.longestStreak().toString()
}

private fun fillCharts() {
fillChart(
binding.overallDifficulty,
statisticeManager.statistics().overall.solvedDifficulty,
statisticeManager.statistics().overall.solvedDifficulty.average(),
com.google.android.material.R.attr.colorPrimary,
com.google.android.material.R.attr.colorOnPrimary,
)

fillChart(
binding.overallDuration,
statisticeManager.statistics().overall.solvedDuration,
statisticeManager.statistics().overall.solvedDuration.average(),
com.google.android.material.R.attr.colorSecondary,
com.google.android.material.R.attr.colorOnSecondary,
)
}

private fun <T : Number> fillChart(
chartView: CartesianChartView,
chartData: List<T>,
average: Double,
lineColor: Int,
areaColor: Int,
) {
val wrappedChartData = dublicateIfSingleItem(chartData)

chartView.setModel(
CartesianChartModel(
LineCartesianLayerModel.build {
series(wrappedChartData)
},
),
)

if (wrappedChartData.any { it.toDouble() != average }) {
chartView.chart!!.addDecoration(
ThresholdLine(
thresholdValue = average.toFloat(),
thresholdLabel = "Mittelwert",
lineComponent =
LineComponent(
color = MaterialColors.getColor(binding.root, R.attr.colorCustomColor1),
),
labelComponent =
textComponent {
color = MaterialColors.getColor(binding.root, R.attr.colorCustomColor1)
},
),
)
}

addColorToLine(
chartView,
lineColor,
areaColor,
)
}

private fun <T> dublicateIfSingleItem(items: List<T>): List<T> {
return if (items.size == 1) {
listOf(
items.first(),
items.first(),
)
} else {
items
}
}

private fun hideCharts() {
binding.labelOverallDifficulty.visibility = View.GONE
binding.overallDifficulty.visibility = View.GONE
binding.labelOverallDuration.visibility = View.GONE
binding.overallDuration.visibility = View.GONE
}

private fun addColorToLine(
chartView: CartesianChartView,
foregroundColor: Int,
backgroundColor: Int,
) {
val lineLayer = chartView.chart!!.layers.first() as LineCartesianLayer
val line = lineLayer.lines.first()

line.shader =
ColorShader(
MaterialColors.getColor(binding.root, foregroundColor),
)
line.backgroundShader =
ColorShader(
MaterialColors.compositeARGBWithAlpha(
MaterialColors.getColor(binding.root, backgroundColor),
128,
),
)
}

private fun solveRate(): Double {
return if (statisticeManager.totalStarted() == 0) {
0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ class MainActivity : AppCompatActivity(), GridCreationListener {

if (newGame) {
game.grid.isActive = true
statisticsManager.storeStatisticsAfterNewGame(game.grid)
gameLifecycle.startNewGrid()
}
}
Expand Down
Loading

0 comments on commit ef7edae

Please sign in to comment.