Files
livrets/docs/superpowers/plans/2026-08-31-generation-ponderee-questions.md
T

6.6 KiB
Raw Blame History

Génération pondérée des questions — Plan d'implémentation

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Remplacer le tirage uniforme des facteurs par une pondération progressive (P(k) ∝ k) pour éviter les questions trop faciles (1 × 2, 3 × ? = 3).

Architecture: Un seul fichier de production modifié : QuestionGenerator.kt. Chaque facteur est tiré indépendamment avec un poids linéaire sur 1..maxTable (tirage par cumul de poids, aucune boucle de rejet sur la difficulté). Formes Direct/MissingFactor et anti-doublon consécutif inchangés.

Tech Stack: Kotlin Multiplatform (commonMain/commonTest), kotlin.random.Random, kotlin.test, Gradle.

Spec: docs/superpowers/specs/2026-08-31-generation-ponderee-questions-design.md

Global Constraints

  • Commande de test : ./gradlew :sharedUI:jvmTest (logique de jeu dans commonTest, kotlin.test)
  • Fichier de production modifié : uniquement sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt
  • Comportements conservés : facteurs dans 1..maxTable ; formes Direct/MissingFactor ~50/50 ; pas de doublon consécutif ; terminaison garantie même pour maxTable = 1 (poids > 0 pour tout facteur)
  • Pas de dépendance nouvelle ; pas de commentaire dans le code
  • Interface et docs du projet en français

Task 1: Générateur pondéré + tests

Files:

  • Modify: sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt:10-18
  • Test: sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt
  • Modify: docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md:87 (amendement de la règle de génération)

Interfaces:

  • Consumes: Question.Direct(a, b), Question.MissingFactor(a, b) (existants, inchangés)

  • Produces: QuestionGenerator(random: Random) : QuestionSource — signature inchangée, seule la distribution change

  • Step 1: Write the failing tests

Ajouter à la fin de la classe QuestionGeneratorTest (après le test affichage des deux formes) :

    private fun factors(q: Question): Pair<Int, Int> = 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)
        }
    }
  • Step 2: Run tests to verify they fail

Run: ./gradlew :sharedUI:jvmTest Expected: FAIL — biais progressif - les facteurs eleves reviennent plus souvent échoue (tirage uniforme : with1 ≈ with10 ≈ 19 % chacun, l'assertion with1 < with10 / 5 est fausse). Les autres tests (existants, les petits facteurs restent possibles, maxTable egale a 1) passent.

  • Step 3: Write the implementation

Remplacer tout le contenu de sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt par :

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 = 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
    }
}

La boucle for consomme les poids cumulés (1, 2, …, maxTable) : chaque facteur k est retourné avec une probabilité k / (maxTable·(maxTable+1)/2). Le return maxTable final est un garde-fou contre l'arrondi flottant (roll ≥ 0 après soustraction de tous les poids). Aucun rejet sur la difficulté : terminaison garantie, y compris maxTable = 1.

  • Step 4: Run the full test suite

Run: ./gradlew :sharedUI:jvmTest Expected: PASS — les tests existants (bornes, formes produites, pas de doublon consécutif, engine, persistance) et les deux nouveaux tests passent.

  • Step 5: Amend the original spec's generation rule

Dans docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md, ligne 87, remplacer :

- 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.

par :

- 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.
  • Step 6: Commit
git add sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md docs/superpowers/specs/2026-08-31-generation-ponderee-questions-design.md
git commit -m "feat: génération pondérée des questions (les grands facteurs sortent plus souvent)"