diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/game/Question.kt b/sharedUI/src/commonMain/kotlin/com/livrets/game/Question.kt index 264ba34..e083a45 100644 --- a/sharedUI/src/commonMain/kotlin/com/livrets/game/Question.kt +++ b/sharedUI/src/commonMain/kotlin/com/livrets/game/Question.kt @@ -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 + } diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/games/LeaderboardScore.kt b/sharedUI/src/commonMain/kotlin/com/livrets/games/LeaderboardScore.kt new file mode 100644 index 0000000..86b0a9d --- /dev/null +++ b/sharedUI/src/commonMain/kotlin/com/livrets/games/LeaderboardScore.kt @@ -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() +} diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt b/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt new file mode 100644 index 0000000..bada4ea --- /dev/null +++ b/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt @@ -0,0 +1,5 @@ +package com.livrets.games + +import com.livrets.game.GameConfig + +fun GameConfig.leaderboardId(): String = "questions_$questionCount" diff --git a/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt b/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt new file mode 100644 index 0000000..4d619a6 --- /dev/null +++ b/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt @@ -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) = + 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()) + } +}