feat: résultats de partie, enregistrements de réponses et stats par table
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user