feat: score leaderboard pondéré et id de board
This commit is contained in:
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())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user