Aller au contenu

Indices et progression

Les indices (appelés hints dans le code) sont des messages courts que l'étudiant peut afficher à la demande en cliquant sur un bouton 💡 dans l'overlay. Ils sont pensés comme une roue de secours pédagogique : l'étudiant résout l'énigme sans eux tant qu'il y arrive ; s'il bloque, le formateur peut l'y autoriser. Cette page décrit le mécanisme à trois niveaux qui pilote la visibilité des indices : le champ hintAvailable de chaque énigme, le toggle formateur en temps réel, et l'affichage conditionnel côté React.

Les trois leviers de visibilité

Un bouton 💡 n'apparaît sur une énigme que si les trois conditions suivantes sont satisfaites en même temps :

flowchart LR
    A["enigme.hintAvailable<br/>(booléen défini en Java)"] --> AND{ET}
    B["pedagogiqueHintsEnabled<br/>(toggle formateur)"] --> AND
    C["enigme.hint != null<br/>(texte résolu i18n)"] --> AND
    AND --> OUI[💡 Bouton affiché]
    AND -.->|manque 1|NON[Aucun bouton]
Niveau de contrôle Portée Qui décide Quand
Par énigmehintAvailable Une seule étape Concepteur du catalogue (code Java) À la définition du record Enigme
Global sessionpedagogiqueHintsEnabled Toutes les étapes du parcours actif Formateur À chaud, pendant le parcours, via un bouton chaos-admin
Rendu client — absence de hint Une seule étape Configuration i18n Au chargement de la session React

Les deux premiers leviers sont indépendants : désactiver le toggle global masque les boutons pour toutes les énigmes, y compris celles qui ont hintAvailable = true. Inversement, le toggle ne fait rien apparaître pour une énigme dont hintAvailable = false.

Le champ hintAvailable par énigme

Dans le record Enigme, hintAvailable est un boolean défini à la construction :

new Enigme("BAC3-17", "bac3", 17,
    t("bac3", "BAC3-17.text"), "right",
    t("bac3", "BAC3-17.hint"),
    true,                     // ← hintAvailable
    h("…"),
    t("bac3", "BAC3-17.culturalNote"),
    "checkout"
);

Aujourd'hui, toutes les 100 énigmes des cinq niveaux ont hintAvailable = true. Le champ est conservé comme point d'extension : un concepteur pourrait désactiver l'indice sur une énigme spécifique (par exemple une épreuve finale chronométrée de concours) sans toucher à la logique globale.

L'indice lui-même (enigme.hint) est résolu depuis le JSON i18n via t(level, "BACx-n.hint"). Si la clé est absente du fichier, t() retourne [BACx-n.hint] — visible à l'écran, ce qui signale immédiatement la clé manquante au concepteur sans empêcher l'énigme de fonctionner.

Le toggle formateur

Le toggle global est porté par le champ pedagogiqueHintsEnabled de ChaosStudentController, déclaré volatile pour garantir la visibilité entre threads :

private volatile boolean pedagogiqueHintsEnabled = true;

Il est initialisé à true au démarrage de l'application et réinitialisé à true à chaque deactivate ou changement de niveau — chaque parcours commence donc avec les indices activés. Le formateur peut basculer ce flag à tout moment via un endpoint dédié.

Endpoint POST /pedagogique/hints

@PostMapping("/pedagogique/hints")
public ResponseEntity<?> setHintsEnabled(@RequestBody Map<String, Object> body,
        HttpSession session, @RequestHeader(value = "X-Admin-Token", required = false) String tok) {
    if (!AdminAuth.isAdmin(session, tok)) return unauth();
    Object val = body.get("enabled");
    if (!(val instanceof Boolean))
        return ResponseEntity.badRequest().body(Map.of("error",
            i18n.t("student.pedagogique.error.hints_enabled_required")));
    pedagogiqueHintsEnabled = (Boolean) val;
    log.info("[Pédagogique] Indices {} par le formateur",
        pedagogiqueHintsEnabled ? "activés" : "masqués");
    return ResponseEntity.ok(Map.of("success", true,
        "hintsEnabled", pedagogiqueHintsEnabled));
}
Propriété Valeur
Méthode POST
URL /api/chaos/student/pedagogique/hints
Authentification Header X-Admin-Token ou session admin
Body {"enabled": true|false}
Réponse 200 {"success": true, "hintsEnabled": <bool>}
Réponse 400 Si enabled est absent ou pas un booléen
Réponse 401 Si le token admin est manquant ou invalide

La validation du body refuse explicitement tout ce qui n'est pas un booléen — un client envoyant {"enabled": "true"} (chaîne) ou {"enabled": 1} (entier) reçoit un 400.

Propagation aux étudiants

Contrairement à d'autres paramètres, le toggle n'est pas push vers les étudiants : il est relu à chaque poll dans la réponse /status. Cette stratégie « pull » simplifie l'architecture (pas de WebSocket, pas de SSE) et reste suffisamment réactive puisque le polling s'exécute toutes les 15 secondes.

sequenceDiagram
    autonumber
    actor F as Formateur
    participant ADM as chaos-admin
    participant CTR as ChaosStudentController
    actor E1 as Étudiant 1
    actor E2 as Étudiant 2

    Note over E1,E2: Polling en cours<br/>(15s d'intervalle)

    F->>ADM: Clique "Masquer les indices"
    ADM->>CTR: POST /pedagogique/hints {enabled:false}
    CTR->>CTR: pedagogiqueHintsEnabled = false
    CTR-->>ADM: 200 {hintsEnabled:false}

    Note over ADM: UI confirme "masqué"

    par Prochains polls étudiants
      E1->>CTR: GET /status
      CTR-->>E1: pedagogique.hintsEnabled = false
      E1->>E1: Bouton 💡 disparaît
    and
      E2->>CTR: GET /status
      CTR-->>E2: pedagogique.hintsEnabled = false
      E2->>E2: Bouton 💡 disparaît
    end

    Note over F: Formateur voit que<br/>tous les élèves ont compris
    F->>ADM: Clique "Afficher les indices"
    ADM->>CTR: POST /pedagogique/hints {enabled:true}
    CTR->>CTR: pedagogiqueHintsEnabled = true

    par Polls suivants
      E1->>CTR: GET /status
      CTR-->>E1: pedagogique.hintsEnabled = true
      E1->>E1: Bouton 💡 réapparaît
    and
      E2->>CTR: GET /status
      CTR-->>E2: pedagogique.hintsEnabled = true
    end

Latence observée

La latence entre le clic formateur et la disparition du bouton 💡 chez l'étudiant est au pire de 15 secondes (intervalle de polling) plus le temps de réponse du backend (quelques millisecondes). En pratique, comme les polls de plusieurs étudiants ne sont pas synchronisés entre eux, la disparition est progressive sur toute la classe en moins d'un cycle complet.

Cette asynchronicité est un avantage pédagogique : si le formateur active les indices pour « débloquer » un étudiant précis, il n'y a pas d'effet visible immédiat sur l'ensemble de la classe qui pourrait parasiter la concentration des autres.

Affichage conditionnel côté React

Le champ hintsEnabled est propagé jusqu'à PedagogiqueOverlay via la prop :

<PedagogiqueOverlay
  enigme={state.enigme}
  step={state.step}
  totalSteps={state.totalSteps}
  onValidated={refresh}
  timerExpired={timerExpired}
  skipPollingRef={skipPollingRef}
  hintsEnabled={state.hintsEnabled !== false}
/>

Le test !== false est volontaire : il traite undefined et null comme « activé » par défaut, afin d'être rétrocompatible avec les sessions qui n'incluraient pas encore le champ (par exemple pendant un rollback partiel frontend/backend).

L'overlay rend le bouton 💡 uniquement si les trois conditions sont remplies :

{enigme.hintAvailable && hintsEnabled && (
  <div style={{ marginBottom: '10px' }}>
    <button onClick={() => setHintVisible(v => !v)}>
      💡 {hintVisible ? t('ped.hideHint') : t('ped.showHint')}
    </button>
    {hintVisible && enigme.hint && (
      <div>{enigme.hint}</div>
    )}
  </div>
)}

Le bouton affiche ou masque l'indice en basculant un état local (hintVisible) — le texte n'est jamais masqué côté serveur une fois envoyé, c'est uniquement un état d'affichage. Le state hintVisible est remis à false automatiquement à chaque changement d'étape via le useEffect([enigme?.id]) de l'overlay.

Interaction avec le thème Logique

Le thème final Logique & Mathématiques (voir themes.md) dispose de son propre mécanisme d'indices indépendant du toggle formateur. Chaque question du pool de 25 est accompagnée d'un indice court, stocké dans le champ hint du fichier logique_fr.json, et affiché via un bouton 💡 local à la question. Le toggle global pedagogiqueHintsEnabled n'affecte pas cette page.

La raison est fonctionnelle : la page de succès et le thème final s'affichent après la fin du parcours principal. À ce stade, la notion de « classe en cours » a perdu son sens — l'étudiant est sur son écran de fin, le formateur ne pilote plus rien. Lui couper l'accès aux indices du thème final serait un faux signal.

Clés i18n concernées

Pour traduire ou adapter les messages liés aux indices, cibler :

Clé Fichier Usage
ped.showHint frontend/src/i18n/fr.json et en.json Libellé du bouton quand l'indice est masqué
ped.hideHint frontend/src/i18n/fr.json et en.json Libellé quand l'indice est visible
BACx-n.hint backend/src/main/resources/i18n/enigmes/bacX/enigmes_XX.json Texte de chaque indice individuel
student.pedagogique.error.hints_enabled_required messages_fr.properties et messages_en.properties Erreur 400 si body mal formé

Pages suivantes : ← Système d'étoiles · Page de succès → · Thèmes finaux →