feat: types de questions et générateur (tables 1..maxTable)

This commit is contained in:
herel committed 2026-08-28 14:48:14 +02:00
1 parent 135947be3f
commit 21353aac41
4 files changed
+119

No files matched your search

@@ -0,0 +1,10 @@
package com.livrets.game
import kotlinx.serialization.Serializable
@Serializable
data class GameConfig(
val questionCount: Int, // 5..50
val secondsPerQuestion: Int, // 3..30
val maxTable: Int, // 1..12 : les deux facteurs sont tirés dans 1..maxTable
)
@@ -0,0 +1,29 @@
package com.livrets.game
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
sealed interface Question {
val table: Int
@Serializable
@SerialName("direct")
data class Direct(val a: Int, val b: Int) : Question {
override val table: Int get() = maxOf(a, b)
override fun toString(): String = "$a × $b = ?"
}
@Serializable
@SerialName("missing_factor")
data class MissingFactor(val a: Int, val b: Int) : Question {
override val table: Int get() = maxOf(a, b)
override fun toString(): String = "$a × ? = ${a * b}"
}
}
val Question.correctAnswer: Int
get() = when (this) {
is Question.Direct -> a * b
is Question.MissingFactor -> b
}
@@ -0,0 +1,19 @@
package com.livrets.game
import kotlin.random.Random
fun interface QuestionSource {
fun next(previous: Question?, maxTable: Int): Question
}
class QuestionGenerator(private val random: Random = Random.Default) : QuestionSource {
override fun next(previous: Question?, maxTable: Int): Question {
while (true) {
val a = random.nextInt(1, maxTable + 1)
val b = random.nextInt(1, maxTable + 1)
val question =
if (random.nextBoolean()) Question.Direct(a, b) else Question.MissingFactor(a, b)
if (question != previous) return question
}
}
}
@@ -0,0 +1,61 @@
package com.livrets.game
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class QuestionGeneratorTest {
@Test
fun `tous les facteurs restent dans 1 a maxTable`() {
val generator = QuestionGenerator(Random(42))
repeat(500) {
val q = generator.next(previous = null, maxTable = 6)
val (a, b) = when (q) {
is Question.Direct -> q.a to q.b
is Question.MissingFactor -> q.a to q.b
}
assertTrue(a in 1..6, "a=$a hors bornes")
assertTrue(b in 1..6, "b=$b hors bornes")
}
}
@Test
fun `les deux formes de question sont produites`() {
val generator = QuestionGenerator(Random(7))
val questions = List(200) { generator.next(previous = null, maxTable = 12) }
assertTrue(questions.any { it is Question.Direct })
assertTrue(questions.any { it is Question.MissingFactor })
}
@Test
fun `pas de doublon consecutif`() {
val generator = QuestionGenerator(Random(123))
var previous: Question? = null
repeat(300) {
val q = generator.next(previous, 12)
assertNotEquals(previous, q)
previous = q
}
}
@Test
fun `table vaut le plus grand facteur`() {
assertEquals(8, Question.Direct(3, 8).table)
assertEquals(9, Question.MissingFactor(9, 4).table)
}
@Test
fun `bonne reponse selon la forme`() {
assertEquals(56, Question.Direct(7, 8).correctAnswer)
assertEquals(8, Question.MissingFactor(7, 8).correctAnswer)
}
@Test
fun `affichage des deux formes`() {
assertEquals("7 × 8 = ?", Question.Direct(7, 8).toString())
assertEquals("7 × ? = 56", Question.MissingFactor(7, 8).toString())
}
}