feat: score leaderboard pondéré et id de board

This commit is contained in:
herel committed 2026-08-29 08:28:52 +02:00
1 parent 2b8dd19e72
commit 285d92b1b2
4 files changed
+112

No files matched your search

@@ -27,3 +27,9 @@ val Question.correctAnswer: Int
is Question.Direct -> a * b
is Question.MissingFactor -> b
}
val Question.product: Int
get() = when (this) {
is Question.Direct -> a * b
is Question.MissingFactor -> a * b
}
@@ -0,0 +1,11 @@
package com.livrets.games
import com.livrets.game.GameResult
import com.livrets.game.product
import kotlin.math.roundToLong
fun GameResult.leaderboardScore(): Long {
if (correctCount == 0) return 0L
val products = records.filter { it.isCorrect }.sumOf { it.question.product }
return (products * 30.0 / config.secondsPerQuestion).roundToLong()
}
@@ -0,0 +1,5 @@
package com.livrets.games
import com.livrets.game.GameConfig
fun GameConfig.leaderboardId(): String = "questions_$questionCount"
@@ -0,0 +1,90 @@
package com.livrets.games
import com.livrets.game.AnswerRecord
import com.livrets.game.GameConfig
import com.livrets.game.GameResult
import com.livrets.game.Question
import com.livrets.game.product
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Instant
class LeaderboardScoreTest {
private fun result(config: GameConfig, records: List<AnswerRecord>) =
GameResult(date = Instant.parse("2026-08-28T10:00:00Z"), config = config, records = records)
private fun direct(a: Int, b: Int, correct: Boolean) = AnswerRecord(
question = Question.Direct(a, b),
givenAnswer = if (correct) a * b else a * b + 1,
correctAnswer = a * b,
isCorrect = correct,
durationMs = 1000,
)
private fun missing(a: Int, b: Int, correct: Boolean) = AnswerRecord(
question = Question.MissingFactor(a, b),
givenAnswer = if (correct) b else b + 1,
correctAnswer = b,
isCorrect = correct,
durationMs = 1000,
)
@Test
fun `produit d'une question directe et a facteur manquant`() {
assertEquals(56, Question.Direct(7, 8).product)
assertEquals(10, Question.MissingFactor(2, 5).product)
}
@Test
fun `score nul sans bonne reponse`() {
val r = result(GameConfig(5, 5, 12), listOf(direct(7, 8, false), missing(2, 5, false)))
assertEquals(0L, r.leaderboardScore())
}
@Test
fun `score = somme des produits reussis fois le facteur vitesse`() {
val r = result(
GameConfig(10, 5, 12),
listOf(direct(7, 8, true), direct(3, 4, false), missing(2, 5, true)),
)
// (56 + 10) × 30 / 5 = 396
assertEquals(396L, r.leaderboardScore())
}
@Test
fun `toutes les questions reussies`() {
val r = result(
GameConfig(5, 3, 12),
listOf(
direct(4, 6, true),
direct(7, 8, true),
direct(9, 9, true),
direct(2, 12, true),
direct(5, 5, true),
),
)
// (24 + 56 + 81 + 24 + 25) × 30 / 3 = 210 × 10 = 2100
assertEquals(2100L, r.leaderboardScore())
}
@Test
fun `arrondi au point le plus proche`() {
// 8 × 30 / 4 = 60 (exact)
assertEquals(
60L,
result(GameConfig(10, 4, 12), listOf(direct(2, 4, true))).leaderboardScore(),
)
// 7 × 30 / 4 = 52,5 → 53
assertEquals(
53L,
result(GameConfig(10, 4, 12), listOf(direct(7, 1, true))).leaderboardScore(),
)
}
@Test
fun `id de board par nombre de questions`() {
assertEquals("questions_10", GameConfig(10, 5, 12).leaderboardId())
assertEquals("questions_5", GameConfig(5, 3, 1).leaderboardId())
}
}