Projet

Général

Profil

AbulEduCommonStatesV1

Cette classe s'utilise avec la AbulEduStateMachineV1, elle permet de personnaliser quelques aspects de la machine à états finis. Entre autre, les animations associées aux transitions et la visibilité globale de certains widgets.

Une bonne pratique serait de modifier aussi peu que possible cette classe (sinon vous verrez bien !)

Les sources

Elles sont accessibles ci-dessous.

git submodule add http://redmine.abuledu.org/ryxeo/leterrier-developpeurs/abuleducommonstatesv1.git src/lib/abuleducommonstatesv1

Mainteneur de la lib:

git clone ssh://gitolite3@redmine.abuledu.org/repositories-redmine/ryxeo/leterrier-developpeurs/abuleducommonstatesv1.git

Modification du fichier .pro

Pensez à modifier votre fichier .pro pour ajouter cette nouvelle lib

#AbulEduCommonStatesV1
include(lib/abuleducommonstatesv1/abuleducommonstatesv1.pri)

Utilisation

Création d'une classe qui hérite d'AbulEduCommonStatesV1

  • Votre classe doit hériter de AbulEduCommonStatesV1 (exemple avec ExerciceParagraphesMelanges du projet Aller 5)
    • dans votre fichier .h
      class ExerciceParagraphesMelanges : public AbulEduCommonStatesV1
      
    • et le fichier .cpp
      ExerciceParagraphesMelanges::ExerciceParagraphesMelanges(QWidget *parent) : AbulEduCommonStatesV1(parent)
      

Généralités

Au niveau de la machine à état l'imbrication des états est la suivante: séquence -> exercice(s) -> question(s)

Codes couleurs

Face à une question, l'utilisateur peut soit proposer une réponse (Bouton Vérifier), soit demander quelle était la bonne réponse (Bouton Solution)
On va utiliser un code de couleurs pour indiquer dans quel cas on se trouve. Les fonctions verifieReponse(), appelées dans l' état afficheVerificationQuestion, et donneReponse(), appelées dans l'état afficheCorrectionQuestion, implémenteront les mises en couleur.
Voici les couleurs choisies par Arnaud pour les cas suivants (RGB) :
Vérifier -> la réponse était exacte : 3,151,3 (Vert)
Vérifier -> la réponse était inexacte : 234,33,0 (Rouge)
Solution -> aucune réponse n'était proposée : 0,108,192 (Bleu)
Solution -> la réponse proposée au moment où la solution est demandée était inexacte : 134,45,176 (Violet)
Solution -> la réponse proposée au moment où la solution est demandée était exacte : 1,125,116 (Turquoise)

Examiner des réponses

Quand un utilisateur propose une réponse à une question, il s'agit maintenant de gérer cette réponse :
  • Examen de la réponse
    La fonction int verifieReponse() doit retourner le nombre d'erreurs à l'exercice, la fonction int donneReponse() doit afficher la bonne réponse à l'utilisateur qui renonce à la trouver et retourner (arbitrairement) -1.
    Ces fonctions sont virtuelles pures dans l'AbulEduStateMachineV1. Il est en effet impossible de les implémenter au niveau de l'AbulEduStateMachineV1 puisque qu'on ne sait pas à ce niveau ce que va demander (et donc attendre) l'exercice.
    Une variable de l'AbulEduStateMachineV1 va stocker ce nombre d'erreurs : m_verifieReponse
    On y accède depuis les exercices qui héritent d'AbulEduStateMachineV1 au travers de deux accesseurs : void abeStateMachineSetVerifieReponse(int nombreErreurs) pour l'écriture, int abeStateMachineGetVerifieReponse() pour la lecture.
    Le clic sur le bouton Vérifier ou l'émission de l'événement QuestionVerifie
    sequenceMachine->postEvent(new StringEvent("QuestionVerifie"));
    

    met l'AbulEduStateMachineV1 dans l'état afficheVerificationQuestion, tandis que le clic sur le bouton Solution met l'AbulEduStateMachineV1 dans l'état afficheCorrectionQuestion
    Il faut donc dans slotAfficheVerificationQuestionEntered() examiner la réponse et écrire le nombre d'erreurs en faisant abeStateMachineSetVerifieReponse(verifieReponse());,
    et dans slotAfficheCorrectionQuestionEntered() donner la réponse et écrire comme nombre d'erreurs ce -1 qui indique l'abandon en faisant abeStateMachineSetVerifieReponse(donneReponse());
  • Aiguillage dans l'AbulEduStateMachineV1
    Pour que l'aiguillage fonctionne, il faut que le nombre de questions dans l'exercice soit renseigné, y compris s'il n'y a qu'une question. On utilise pour ce faire l'accesseur setAbeNbTotalQuestions
    Quand l'AbulEduStateMachineV1 est dans un des états afficheVerificationQuestion ou afficheCorrectionQuestion, un clic sur le bouton Suivant ou l'attente de 7 secondes passe la machine soit dans l'état finVerificationQuestion, soit dans l'état finCorrectionQuestion. L'aiguillage se fait dans les slots correspondant à l'entrée dans ces états.
    • une réponse a été soumise (clic sur Vérifier)
      on est dans l'état finVerificationQuestion.
      • la réponse est juste
        • c'était la dernière question
          l'événement Questionsdone est émis, ce qui a pour effet d'amener la machine à l'état finQuestion (qui devrait s'appeler en fait finQuestionS) qui conduit automatiquement vers l'état testJouerBilanExercices puis, éventuellement après présentation du bilan d'exercice, à l'état finExercice.
        • il reste des questions
          l'événement Questionsloop est émis, ce qui a pour effet d'amener la machine à l'état initQuestion qui conduit à la question suivante.
      • la réponse était fausse
        l'événement Questioncontinue est émis, ce qui a pour effet d'amener la machine à l'état continueQuestion qui permet de proposer une autre réponse. (ou pas ? à voir, mais c'est pour l'instant le comportement)
    • la solution a été demandée par clic sur Solution (cas de l'"abandon")
      on est dans l'état finCorrectionQuestion.
      • c'était la dernière question
        l'événement QuestionsDoneCorrect est émis, ce qui a pour effet d'amener la machine à l'état finQuestion (qui devrait s'appeler en fait finQuestionS) qui conduit automatiquement vers l'état testJouerBilanExercices puis, éventuellement après présentation du bilan d'exercice, à l'état finExercice.
      • il reste des questions
        l'événement QuestionsLoopCorrect est émis, ce qui a pour effet d'amener la machine à l'état initQuestion qui conduit à la question suivante.
  • Implémentation dans les exercices
    Vous devez donc
    • Implémenter vos propres méthodes verifieReponse() et _donneReponse(), après les avoir déclarées en "protected" dans le .h de votre exercice
    • Affecter leurs retours à la variable m_verifieReponse en utilisant les accesseurs dans slotAfficheVerificationQuestionEntered() et slotAfficheCorrectionQuestionEntered(), PUIS appeler dans ces slots ceux d'AbulEduStateMachineV1 ex :
      void ExerciceSuperTemps::slotAfficheVerificationQuestionEntered()
      {
          abeStateMachineSetVerifieReponse(verifieReponse());
          setHelpText(trUtf8("Message à afficher par l'aide suite à la vérification, plusieurs cas éventuellement possibles..."));
          AbulEduStateMachineV1::slotAfficheVerificationQuestionEntered();
      }
      

      et faire des choses supplémentaires si vous le souhaitez...
    • Appeler dans les slotFinVerificationQuestionEntered() et slotFinCorrectionQuestionEntered() ceux d'AbulEduStateMachineV1, et faire autre chose en plus si ça vous est utile...

Enregistrer des résultats (Exemple d'Aller-5.0)

Dans le slotAfficheVerificationQuestionEntered(), on fait appel à la méthode verifieReponse().
Comme son nom l'indique, cette fonction compare la réponse fournie par l'utilisateur à la réponse attendue.
Dans cette fonction, on ajoute une entrée à une structure dédiée héritée d'AbulEduExerciceV1

QList<QPair<QVariant,QVariant> > m_answers;

On va donc ajouter une QPair de QVariant, le premier étant la réponse donnée et le second la réponse attendue.
m_answers.append(QPair<QVariant,QVariant>(reponseDonnee.join(","),reponseAttendue.join(",")));

Je mets à jour également à cet endroit un réel hérité lui aussi, auquel j'affecte le rapport nombre d'erreurs/nombre de questions.
m_percent = setAbeTeteForResult(nbreponsesFausses,m_listeCellulesVides.count());

Remarque : c'est la fonction setAbeTeteForResult, appelée dans verifieReponse(), qui met à jour la variable m_evaluation

Parallèlement, j'aurais pu faire appel dans le slotAfficheCorrectionQuestionEntered() à la méthode donneReponse().
Dans cette fonction, j'indique l'absence de réponse dans m_evaluation

m_evaluation = abe::evalZ;

... et je devrais sans doute enregistrer la réponse cause de l'échec en faisant un truc du genre
m_answers.append(QPair<QVariant,QVariant>(QString(),reponseAttendue.join(",")));

... et peut être même donner une valeur remarquable à m_percent (-1 ?)

Je peux alors dans slotFinVerificationQuestionEntered() et dans slotFinCorrectionQuestionEntered() préparer ma ligne de log

setAbeLineLog(m_answers.last().second.toString(),
              m_answers.last().first.toString(),
              score,
              m_nbClicSurVerifier,
              m_evaluation,
              m_answers.last().second.toString());

score est m_percent arrondi à l'entier le plus proche...

J'envoie au serveur les logs préparés dans le slotBilanSequenceEntered() en invoquant la méthode pushAbulEduLogs()

Variables

La machine à états apporte un certain nombre de variables que vous devrez manipuler pour modifier son comportement:

  • setAbeNbExercices(int) : permet de définir le nombre d'exercices qui composent la séquence
  • abeEnableDureeAnimationsConsigneBilan(bool) : permet d'activer ou pas les animations de consigne et bilan (consigne et bilan qui "tombent du ciel")
  • bool onPeutPresenterSequence : provoque l'affichage de la consigne de la séquence au début de la séquence, cette consigne ne sera affichée qu'une seule fois (cf schéma)
  • bool onPeutPresenterExercice : provoque l'affichage de la consigne de l'exercice au début de chaque exercice
  • bool onPeutPresenterBilanExercice : provoque l'affichage du bilan de l'exercice lors de la fin de chaque exercice

Fonctions qu'on DOIT surcharger

Vous trouverez ci-dessous la liste des méthodes que vous devez surcharger dans votre application pour pouvoir utiliser la machine à états. Un bon support de compréhension est le schéma disponible sur la page suivante: BoiteAOutilsV1

  • void slotSequenceEntered()

ce slot est appelé au lancement de la séquence de la machine à état. C'est par exemple dans ce slot qu'on indique le nombre de questions qui seront posées dans cette séquence, si on affichera le bilan à la fin de la séquence, l'affichage des consignes au début des séquences et exercices etc.

    setAbeNbTotalQuestions(5);
    setAbeNbExercices(10);

    // Determine si on présente la séquence ou pas.
    onPeutPresenterSequence = true;
    // Determine si on présente l'exercice ou pas.
    onPeutPresenterExercice = true;
    // Determine si on affiche le bilan de l'exercice
    onPeutPresenterBilanExercice = true;

    // Appel du slot d'AbulEduCommonStatesV1
    AbulEduCommonStatesV1::slotSequenceEntered();
  • void slotPresenteSequenceEntered()

Affiche la consigne de la séquence si onPeutPresenterSequence est placé à true

    // Appel du slot d'AbulEduCommonStatesV1
    AbulEduCommonStatesV1::slotPresenteSequenceEntered();

    // Création d'un widget qui affichera le message de la consigne
    m_message = new AbulEduExerciceWidgetMessageV1(parent());

    m_message->abeWidgetMessageSetTitre(trUtf8("Paragraphes mélangés"));
    m_message->abeWidgetMessageSetConsigne(trUtf8("La consigne des paragraphes mélangés"));
    m_message->abeWidgetMessageSetZoneTexteVisible(true);
    m_message->abeWidgetMessageSetTexteExercice("Le texte très long a lire");
    m_message->setVisible(true);
  • void slotPresentationExerciceEntered()

Affiche la consigne de l'exercice en cours si onPeutPresenterExercice est placé à true

    // Appel du slot d'AbulEduCommonStatesV1
    AbulEduCommonStatesV1::slotPresentationExerciceEntered();

    m_message->abeWidgetMessageSetTitre(trUtf8("Exercice numéro %1").arg(numero));
    m_message->abeWidgetMessageSetConsigne(trUtf8("La consigne ..."));
    m_message->abeWidgetMessageSetZoneTexteVisible(true);
    m_message->abeWidgetMessageSetTexteExercice("Le texte très long a lire");
    m_message->setVisible(true);
  • void slotInitQuestionEntered()

C'est un des slot les plus importants: on initialise tout ce qui est commun à toutes les questions

    AbulEduCommonStatesV1::slotInitQuestionEntered();
    // Code spécifique pour cet exercice: creation des objets, widgets, étiquettes etc.
  • void slotQuestionEntered()

Ce slot permet de gérer une spécificité d'une question particulière; ou de gérer des questions aléatoires (par exemple);

  if(m_numQuestion == 3) {
    //traitement spécial
  }
  • void slotVerificationQuestionEntered()

C'est le second slot le plus important: il permet de vérifier la réponse fournie à la question posée. Et de provoquer le passage à la question suivante ou pas.
Exemple: dans ordrealphabetique on interdit le passage à la question suivante tant que la bonne réponse n'est pas fournie, alors que dans calculmental on passe à la question suivante dans tous les cas (choix laisse à la liberté du concepteur).

    m_nbClicSurVerifier++;

    //et on passe au parent pour qu'il s'occupe de l'enchaînement
    //si on souhaite beneficier de l'enchainement par defaut, sinon on
    //implemente ici les sequenceMachine->postEvent(new StringEvent("Questionredo"));
    //and co ad-hoc et on commente la ligne ci-dessous
    //AbulEduStateMachineV1::slotVerificationQuestionEntered();
    int score;
    if(verifieReponse()==0) //On a déporté l'évaluation de la réponse dans la fonction verifieReponse
                            //qui retourne le nombre d'erreurs, donc si nombre d'erreurs = 0 c'est que tout est OK
    {
        sequenceMachine->postEvent(new StringEvent("Questionsdone"));
        score = 1;
    }
    else
    {
        sequenceMachine->postEvent(new StringEvent("Questionredo"));
        score = 0;
    }

    //Choix pédagogique de logger toutes les étapes
    //On ajoute une ligne de log
    QStringList liste_mots_reponse;
    QMap<int, AbuleduLabelV1 *> ma_liste = aireDeJeu->abelabelListeAbuleduLabelsByNumPlace();
    foreach(AbuleduLabelV1 *etiq, ma_liste)
    {
        if(etiq != 0)
        {
            liste_mots_reponse.append(etiq->text());
        }
    }
    QStringList liste_mots_bonne_reponse = abeoutilsSortListeMots(m_listeParagraphes);
    setAbeLineLog(m_listeParagraphes.join(","),
                  liste_mots_reponse.join(","),score,m_nbClicSurVerifier,
                  "",liste_mots_bonne_reponse.join(","));
  • void slotBilanExerciceEntered()

Utilisé dans le cas où onPeutPresenterBilanExercice est à true. Ce slot permet d'implémenter le bilan de l'exercice.

    // On affiche la bonne tete en fonction du nb de clics sur btnVerifier
    // C'est un choix du développeur / pédagogue
    switch(m_nbClicSurVerifier)
    {
    case 1:
    case 2:
        boiteTetes->setEtatTete(m_numExercice, abe::evalA);
        break;
    case 3:
    case 4:
        boiteTetes->setEtatTete(m_numExercice, abe::evalB);
        break;
    case 5:
    case 6:
        boiteTetes->setEtatTete(m_numExercice, abe::evalC);
        break;
    default:
        boiteTetes->setEtatTete(m_numExercice, abe::evalD);
    }

    AbulEduCommonStatesV1::slotBilanExerciceEntered();
    QString message;
    if(m_nbClicSurVerifier==1)
    {
        message = trUtf8("Bravo, tu as réussi l'exercice du premier coup !");
    }
    else
    {
        message = trUtf8("Tu as réussi l'exercice en %1 essais","%1 est supérieur à 1").arg(m_nbClicSurVerifier);
    }

    m_message->abeWidgetMessageSetTitre(trUtf8("Bilan de l'exercice n°")+QString::number(m_numExercice + 1));
    QString imagetete = QString(":/"+m_prefixeTetes+"/"+boiteTetes->suffixe->value(boiteTetes->getEtatTete(m_numExercice)));
    m_message->abeWidgetMessageSetConsigne(QString("<center><img src=")+imagetete+QString("/><br>")
                                           + message + QString("</center>"));
    m_message->abeWidgetMessageResize();
    m_message->abeWidgetMessageSetZoneTexteVisible(false);

    m_message->move((getAbeExerciceAireDeTravailV1()->width() - m_message->width())/2,
                    ((getAbeExerciceAireDeTravailV1()->height() - m_message->height())/2) - 200*abeApp->getAbeApplicationDecorRatio());
    m_message->setVisible(true);
  • void slotBilanSequenceEntered()

Idem mais pour la séquence, il semblerait qu'on ne puisse pas le squizzer

    m_message->abeWidgetMessageSetTitre(trUtf8("Bilan des exercices")+"\n");

    AbulEduCommonStatesV1::slotBilanSequenceEntered();

    QString message = "";
    message = "<center>" + message;
    int numExercice = 0;
    int numExerciceAtraiter = 0;
    int numtete = 0;
    QPair<int, int> resultat;
    message = message + QString("<table cellpadding=\"4\" cellspacing=\"0\" border=\"0\" align=\"center\" valign=\"middle\"><tr><td>") + trUtf8("Exercice N°")+QString::number(m_resultats[numExercice].first + 1)+ QString("</td>");
    foreach(resultat, m_resultats){
        numExerciceAtraiter = resultat.first;
        if(numExercice != numExerciceAtraiter){
            numExercice = numExerciceAtraiter;
            message = message +"</tr>"+QString("<tr><td>") + trUtf8("Exercice N°")+QString::number(resultat.first + 1)+ QString("</td>");
        }

        switch(resultat.second)
        {
        case 1:
        case 2:
            numtete = abe::evalA;
            break;
        case 3:
        case 4:
            numtete = abe::evalB;
            break;
        case 5:
        case 6:
            numtete = abe::evalC;
            break;
        default:
            numtete = abe::evalD;
        }
        QString imagetete = "<td> " + QString(" <img src=\":/"+m_prefixeTetes+"/"+boiteTetes->suffixe->value(numtete)+"\"width=48 height=48></td>");
        message = message + imagetete;
    }

    message = message+"</table>";
    m_message->abeWidgetMessageSetTitre(trUtf8("Ordre alphabétique"));
    m_message->abeWidgetMessageSetConsigne(message);
    m_message->abeWidgetMessageResize();
    m_message->abeWidgetMessageSetZoneTexteVisible(false);
    m_message->move((getAbeExerciceAireDeTravailV1()->width() - m_message->width())/2,
                    ((getAbeExerciceAireDeTravailV1()->height() - m_message->height())/2) - 200*abeApp->getAbeApplicationDecorRatio());
    m_message->setVisible(true);
    redimensionneBilan();

  • void slotQuitter()

Pourrait être utilisé pour gérer la sortie de la machine à état


Utilisation de cette classe

Maintenant, dans votre fenêtre d'application principale (QMainWindow) vous pouvez instancier un objet de type ExerciceParagraphesMelanges (exemple en cours):

        ExerciceParagraphesMelanges *b = new ExerciceParagraphesMelanges();
        b->getAbeExerciceAireDeTravailV1()->setParent(gv_Accueil);
        b->getAbeExerciceAireDeTravailV1()->show();

        b->getAbeExerciceTelecommandeV1()->setParent(gv_Accueil);
        b->getAbeExerciceTelecommandeV1()->move(745,100);
        b->getAbeExerciceTelecommandeV1()->show();

Comme vous pouvez le voir vous avez accès à deux fonctions un peu particulières:

  • getAbeExerciceAireDeTravailV1:

Cette fonction vous retourne un widget "Aire de Travail" que vous devez placer dans une GraphicsView, l'astuce est en fait d'utiliser une AbulEduPageAccueilV1 pour ça ...

  • getAbeExerciceTelecommandeV1:

Et celle-ci retourne un widget "Télécommande" que vous devez placer dans une GraphicsView, on utilisera également de préférence notre AbulEduPageAccueilV1 pour ça ...

Note importante: ces objets vont encore évoluer dans les prochaines semaines

Dépendances

Cette classe dépend de

20120211-statemachine_principes_de_base.png (8,3 ko) Eric, 11/02/2012 15:54

warning.png (907 octets) Philippe, 25/01/2013 09:50

Redmine Appliance - Powered by TurnKey Linux