From 691c98f86237ab0fbf6d37e5f1ccbbe055ab057a Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Mon, 31 Aug 2026 09:35:11 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20g=C3=A9n=C3=A9ration=20pond=C3=A9r?= =?UTF-8?q?=C3=A9e=20des=20questions=20(les=20grands=20facteurs=20sortent?= =?UTF-8?q?=20plus=20souvent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...8-28-livrets-multiplication-game-design.md | 2 +- .../com/livrets/game/QuestionGenerator.kt | 14 ++++++- .../com/livrets/game/QuestionGeneratorTest.kt | 39 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md b/docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md index 33de278..99b890b 100644 --- a/docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md +++ b/docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md @@ -84,7 +84,7 @@ data class GameResult( Événements : `AnswerSubmitted(n)`, `Timeout`, `Tick`, `FeedbackElapsed`. Règles : -- Génération : `a`, `b` tirés uniformément dans 1..`config.maxTable` (le joueur limite la difficulté en réduisant la table max) ; forme `Direct` ou `MissingFactor` au hasard (~50/50) ; interdiction de répéter exactement la même question deux fois de suite. +- Génération : `a`, `b` tirés dans 1..`config.maxTable` avec poids ∝ facteur (amendé le 2026-08-31 : cf. spec « Génération pondérée des questions ») ; forme `Direct` ou `MissingFactor` au hasard (~50/50) ; interdiction de répéter exactement la même question deux fois de suite. - Pour `MissingFactor(a, b)` : la question s'affiche `a × ? = a*b`, la réponse attendue est `b`. - Réponse correcte → `Feedback` vert 1,5 s → question suivante. - Réponse fausse → `Feedback` rouge 1,5 s (bonne réponse affichée) → question suivante. diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt b/sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt index 282e77c..9b1d436 100644 --- a/sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt +++ b/sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt @@ -9,11 +9,21 @@ fun interface QuestionSource { 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 a = nextFactor(maxTable) + val b = nextFactor(maxTable) val question = if (random.nextBoolean()) Question.Direct(a, b) else Question.MissingFactor(a, b) if (question != previous) return question } } + + private fun nextFactor(maxTable: Int): Int { + val totalWeight = maxTable * (maxTable + 1) / 2 + var roll = random.nextDouble() * totalWeight + for (k in 1..maxTable) { + roll -= k + if (roll < 0) return k + } + return maxTable + } } diff --git a/sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt b/sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt index 235b55d..6bbde8e 100644 --- a/sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt +++ b/sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt @@ -58,4 +58,43 @@ class QuestionGeneratorTest { assertEquals("7 × 8 = ?", Question.Direct(7, 8).toString()) assertEquals("7 × ? = 56", Question.MissingFactor(7, 8).toString()) } + + private fun factors(q: Question): Pair = when (q) { + is Question.Direct -> q.a to q.b + is Question.MissingFactor -> q.a to q.b + } + + @Test + fun `biais progressif - les facteurs eleves reviennent plus souvent`() { + val generator = QuestionGenerator(Random(42)) + var with1 = 0 + var with10 = 0 + repeat(5000) { + val (a, b) = factors(generator.next(previous = null, maxTable = 10)) + if (a == 1 || b == 1) with1++ + if (a == 10 || b == 10) with10++ + } + assertTrue(with1 < with10 / 5, "with1=$with1, with10=$with10") + } + + @Test + fun `les petits facteurs restent possibles`() { + val generator = QuestionGenerator(Random(42)) + var with1 = 0 + repeat(5000) { + val (a, b) = factors(generator.next(previous = null, maxTable = 10)) + if (a == 1 || b == 1) with1++ + } + assertTrue(with1 > 0, "with1=$with1") + } + + @Test + fun `maxTable egale a 1 - seule question possible sans boucle infinie`() { + val generator = QuestionGenerator(Random(42)) + repeat(200) { + val (a, b) = factors(generator.next(previous = null, maxTable = 1)) + assertEquals(1, a) + assertEquals(1, b) + } + } }