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