feat: résultats de partie, enregistrements de réponses et stats par table

This commit is contained in:
herel committed 2026-08-28 15:01:22 +02:00
1 parent 21353aac41
commit ba977880ed
4 files changed
+143

No files matched your search

@@ -0,0 +1,12 @@
package com.livrets.game
import kotlinx.serialization.Serializable
@Serializable
data class AnswerRecord(
val question: Question,
val givenAnswer: Int?,
val correctAnswer: Int,
val isCorrect: Boolean,
val durationMs: Long,
)
@@ -0,0 +1,24 @@
package com.livrets.game
import kotlin.time.Instant
import kotlinx.serialization.Serializable
@Serializable
data class GameResult(
val date: Instant,
val config: GameConfig,
val records: List<AnswerRecord>,
) {
val correctCount: Int get() = records.count { it.isCorrect }
val successRate: Double
get() = if (records.isEmpty()) 0.0 else correctCount.toDouble() / records.size
val averageResponseMs: Long
get() = records.filter { it.givenAnswer != null }
.takeIf { it.isNotEmpty() }
?.map { it.durationMs }
?.average()
?.toLong()
?: 0L
}
@@ -0,0 +1,19 @@
package com.livrets.game
data class TableStats(
val table: Int,
val attempts: Int,
val correctCount: Int,
) {
val successRate: Double get() = if (attempts == 0) 0.0 else correctCount.toDouble() / attempts
}
fun List<GameResult>.tableStats(): List<TableStats> =
(1..12).map { table ->
val records = flatMap { it.records }.filter { it.question.table == table }
TableStats(
table = table,
attempts = records.size,
correctCount = records.count { it.isCorrect },
)
}
@@ -0,0 +1,88 @@
package com.livrets.game
import kotlin.time.Instant
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class GameResultTest {
private fun record(table: Int, correct: Boolean) = AnswerRecord(
question = Question.Direct(table, table),
givenAnswer = if (correct) table * table else table * table + 1,
correctAnswer = table * table,
isCorrect = correct,
durationMs = 1000,
)
@Test
fun `stats par table avec tables sans donnees`() {
val results = listOf(
GameResult(
date = Instant.parse("2026-01-01T00:00:00Z"),
config = GameConfig(4, 10, 12),
records = listOf(
record(7, true), record(7, true), record(7, false), record(9, false),
),
),
)
val stats = results.tableStats()
assertEquals(12, stats.size)
assertEquals(7, stats[6].table)
assertEquals(3, stats[6].attempts)
assertEquals(2, stats[6].correctCount)
assertEquals(1, stats[8].attempts)
assertEquals(0, stats[3].attempts)
assertEquals(0.0, stats[3].successRate)
}
@Test
fun `derivees de GameResult`() {
val result = GameResult(
date = Instant.parse("2026-01-01T00:00:00Z"),
config = GameConfig(3, 10, 12),
records = listOf(
record(2, true),
record(3, false),
AnswerRecord(Question.MissingFactor(4, 6), null, 6, false, 5000),
),
)
assertEquals(1, result.correctCount)
assertEquals(1.0 / 3, result.successRate, 1e-9)
assertTrue(result.averageResponseMs > 0) // seules les réponses non-nulles comptent
}
@Test
fun `seuil table faible - en dessous de 60 pourcent`() {
val stats = listOf(
GameResult(
date = Instant.parse("2026-01-01T00:00:00Z"),
config = GameConfig(2, 10, 12),
records = listOf(record(8, false), record(8, false)),
),
).tableStats()
assertTrue(stats[7].successRate < 0.6)
}
@Test
fun `serialisation aller-retour de GameResult`() {
val result = GameResult(
date = Instant.parse("2026-08-28T10:00:00Z"),
config = GameConfig(10, 10, 6),
records = listOf(
record(7, true),
AnswerRecord(
question = Question.MissingFactor(3, 8),
givenAnswer = null,
correctAnswer = 8,
isCorrect = false,
durationMs = 10_000,
),
),
)
val json = Json.encodeToString(GameResult.serializer(), result)
val decoded = Json.decodeFromString(GameResult.serializer(), json)
assertEquals(result, decoded)
}
}