---
# System prepended metadata

title: Aide pour écrire des QCMs et des applications

---

---
title: Aide pour écrire des QCMs et des applications
description: Aide pour écrire des QCMs et des applications
---
{%hackmd wuwpIu8NRLWh7rcpKjpyrQ %}

# Aide pour écrire des QCMs et des applications

## Application

Pour avoir une idée de ce à quoi, ca ressemble, voici la toute première application du cours sur les fractions :

```kotlin=
val application = application(
    mathExercise(
        labelExpression = "A" assign "frac(a, c) + frac(b, c)",
        mathRuleSet = MathRuleSet.ReduceFraction,
        possibleValues = define(
            -50..50 coPrimesInto ("a" and "c"),
            -50..50 coPrimesOf "c" into "b"
        )
    )
)
```

D'abord, il y a une partie qui ne change absolument jamais :

```kotlin=
val application = application(
    mathExercise(
        ...
    )
)
```

Ensuite, dans cette ligne : `"A" assign "frac(a, c) + frac(b, c)"`

- on définit la lettre qui sera utilisée pour cette application (ici A)
- on définit l'algorithme qui sera utilisé pour cette application
- on définit la forme de l'expression que l'on veut générer

Pour finir, on définit les paramètres que l'on a utilisés dans l'expression : a, b et c

Si on oublie de les définir, ça va faire planter l’application mobile !
Par contre, s'il y a plus de paramètres définit que de paramètres utilisés : ca ne pose aucun souci. Au contraire, on peut s'en servir pour avoir des variables temporaires permettant d'en créer d'autres.

La définition des paramètres étant commune aux applications et aux QCMs : voir section 'Définir les paramètres'

#### Question différente pour les applications

Pour poser une question différente de "Calculer : ..." on peut la définir dans mathExercise :

```kotlin=
val application = application(
    mathExercise(
        instruction = "Arrondir à 2 chiffres après la virgule : {{a}}"
        labelExpression = "A" assign "round(a, 2)",
        mathRuleSet = MathRuleSet.ReduceInteger,
        possibleValues = define(
            0.0..1000.0 precision 3 into "a" except 0.0
        )
    )
)
```


## QCM

Pour avoir une idée de ce à quoi, ca ressemble, voici le tout premiers QCM des fractions (un peu modifié) :

```kotlin=
val one = mathMcq(
    define(
        -10..10 coPrimesInto ("a" and "c"),
        -10..10 coPrimesOf "c" into "b"
    ),
    variant(
        correctAnswer = "frac(a, c) + frac(b, c)".reduce(),
        wrongAnswers = wrongAnswers(
            30 wrong "-(frac(a, c) + frac(b, c))".reduce(),
            30 wrong "frac(a, c) + frac(b, c)".reduceWithoutSimplifying(),
            -1 wrong "frac(a + b, c + c)".reduce(),
            20 wrong "-frac(a + b, c + c)".reduce(),
            10 wrong "-(frac(a, c) + frac(b, c))".reduceWithoutSimplifying(),
            10 wrong "frac(a * b, c * c)".reduce() ifPositive "a" ifNegative "c"
        )
    )
)
```

D'abord, il y a une forme générale qui ne change absolument jamais :

```kotlin=
val one = mathMcq(
    define(
        ...
    ),
    variant(
        correctAnswer = "...".reduce(),
        wrongAnswers = wrongAnswers(
            ...
        )
    )
)
```

Le bloc define est là pour générer les paramètres : La définition des paramètres étant commune aux applications et aux QCMs : voir section 'Définir les paramètres'

### Variant

Parfois, pour la même question, on veut pouvoir avoir des petites variantes sur l'expression :
Pour `frac(a, c) + frac(b, c)` on pourrait vouloir aussi `frac(a, c) - frac(b, c)`. Sauf que très souvent, cela change aussi les mauvaises réponses proposées.
C'est pourquoi on peut définir plusieurs blocs `variant` les uns à la suite des autres séparés par une virgule :


```kotlin=
val one = mathMcq(
    define(
        ...
    ),
    variant(
        correctAnswer = "frac(a, c) + frac(b, c)".reduce(),
        wrongAnswers = wrongAnswers(
            ...
        )
    ),
    variant(
        correctAnswer = "frac(a, c) - frac(b, c)".reduce(),
        wrongAnswers = wrongAnswers(
            ...
        )
    )
)
```

#### Question différente pour les QCMs

Pour poser une question différente de "Calculer : ..." on peut la définir dans le variant :

```kotlin=
val one = mathMcq(
    define(
        2..50 into "a"
    ),
    variant(
        instruction = "Calculer l'inverse de : {{a}}",
        correctAnswer = "frac(1, a)".reduce(),
        wrongAnswers = wrongAnswers(
            -1 wrong "-a".reduce(),
            -1 wrong "frac(-1, a)".reduce(),
            -1 wrong "frac(1, -a)".reduce()
        )
    )
)
```

Un autre exemple :
```kotlin=
val one = mathMcq(
    define(
        0.0..1000.0 precision 3 into "a" except 0.0
    ),
    variant(
        instruction = "Arrondir à 2 chiffres après la virgule : {{a}}",
        correctAnswer = "round(a, 2)".reduce(),
        wrongAnswers = wrongAnswers(
            -1 wrong "round(a, 1)".reduce(),
            -1 wrong "round(a, 0)".reduce(),
            -1 wrong "ceil(a, 2)".reduce(),
            -1 wrong "floor(a, 2)".reduce()
        )
    )
)
```

#### Réponse correct

La ligne `correctAnswer = "...".reduce(),` définit la forme de la bonne réponse. (On ne s'en serait pas douté dis-donc...)

Il suffit d'écrire le calcul entre guillemets suivi de `.reduce()` : `"frac(a, b)".reduce(),`

Le `.reduce()` signifie que l'on veut réduire l'expression au maximum. Pour d'autres notions que `Fractions`, il est possible que ça ne soit pas la même chose. (Pour l'instant les algos n'existent pas donc on ne sait pas)

#### Les mauvaises réponses

Pour définir les mauvaises réponses, on a le bloc `wrongAnswers`.
Dans ce bloc, chaque ligne définit une mauvaise réponse possible.

C'est une liste donc les définitions sont séparées par des virgules (donc chaque ligne finit par une virgule sauf la dernière)

```kotlin=
wrongAnswers(
    30 wrong "-(frac(a, c) + frac(b, c))".reduce(),
    -1 wrong "frac(a + b, c + c)".reduce(),
    20 wrong "-frac(a + b, c + c)".reduce(),
    -1 wrong "frac(a, c) + frac(b, c)".reduceWithoutSimplifying()
)
```

Pour choisir les mauvaises réponses lors du QCM, on créé un panier dans lequel on met X fois les mauvaises réponses et ensuite, on tire au hasard dans le panier. (on évite évidemment les doublons : on ne peut pas tirer deux fois la même mauvaise réponse)

Cela signifie que pour un QCM avec `4` réponses il faut au minimum `3` mauvaises réponses !

Dans l'exemple ci-dessus, on peut voir que toutes les lignes commencent par `X wrong ...`.
`X` c'est le nombre de fois qu'on va mettre la mauvaise réponse dans le panier.
On remarque que parfois il y à écrit `-1`, cela signifie que cette mauvaise réponse sera toujours choisie !

Ensuite, on a une expression entre guillemets : on peut y mettre ce que l'on veut tant que tous les paramètres utilisés ont été définis dans le bloc `define`.

Pour finir, les définitions finissent soit par `.reduce()` soit par `.reduceWhithoutSimplifying()` :
`.reduce()` signifie qu'on va réduire au maximum puis simplifier le résultat
`.reduceWhithoutSimplifying()` signifie que cette fois le résultat ne sera pas simplifié.

On peut donc écrire la bonne réponse avec `.reduceWhithoutSimplifying()` à la fin dans les mauvaises réponses. (Si jamais il n'y a aucune simplification à faire dans la bonne réponse, cette mauvaise réponse ne sera pas affichée)

##### Les conditions

Parfois, on veut qu'une mauvaise réponse ne soit proposée que lorsqu'elle remplit une condition.
On peut rajouter cette condition à la fin de la définition d'une mauvaise réponse :

```kotlin=
30 wrong "-(frac(a, c) + frac(b, c))".reduce() ifPositive "c"
```

On peut mettre plusieurs conditions les unes à la suite des autres :

```kotlin=
30 wrong "-(frac(a, c) + frac(b, c))".reduce() ifPositive "a" ifNegative "b" ifNotZero "c"
```

Les conditions existantes sont :
- `ifPositive`
- `ifNegative`
- `ifStrictlyPositive`
- `ifStrictlyNegative`
- `ifZero`
- `ifNotZero`

Il est possible de mettre un calcul dans ces conditions. Grâce à ça, on peut par exemple vérifier si `a > c` avec une astuce :

```kotlin=
10 wrong "frac(a * b, c * c)".reduce() ifPositive "a-c"
```


## Définir les paramètres (commun aux QCMs et applications)

### Le bloc `define`

Pour définir les paramètres, on utilise le bloc `define`

Exemple :

```kotlin=
define(
    1 into "a",
    2 into "b",
    3 into "c"
)
```

 - Ce bloc définit les paramètres a, b et c respectivement 1, 2 et 3
 - Chaque définition est sur une ligne.
 - C'est une liste donc les définitions sont séparées par des virgules (donc chaque ligne finit par une virgule sauf la dernière)

### Définitions d'un paramètre

Selon les besoins, il y a plusieurs façons de définir un paramètre :

La définition la plus simple serait de définir `a` tel qu'il soit égal à un entier (ici `1`) :

```kotlin=
1 into "a"
```

Cependant, c'est absolument inutile !

On aurait simplement pu utiliser `1` directement dans l'expression.

On cherche plutôt à définir des paramètres qui pour chaque génération seront différents.

Attention : Il faut faire très attention à ce qu'on écrit et s'assurer que c'est possible !
Si on tente de définir des paramètres et qu'il n'y aucune solution : l'application mobile va planter.

Attention : On ne peut pas espérer que nos définitions soient rétro-compatibles. Si on définit des paramètres, il faut considérer qu'à la ligne d'après, ils sont déjà créés! Donc on ne peut pas définir un autre paramètre qui conditionnerait des paramètres précédents.

#### Les intervalles

Pour définir un paramètre appartenant à un intervalle :

```kotlin=
-20..20 into "a"
```

Ici, on définit `a` tel qu'il soit compris dans l'intervalle `[-20; 20]`

Donc, a chaque génération aléatoire, on aura un nouveau `a` appartenant à `[-20; 20]`

Exemples avec l'expression `frac(a, 2)` :
- `frac(-2, 2)`
- `frac(4, 2)`
- `frac(5, 2)`
- `frac(-7, 2)`
- `frac(20, 2)`

On peut aussi définir un pas pour l'incrémentation :

```kotlin=
-10..10 step 3 into "a"
```

Ici, on aura les valeurs : `-10, -7, -4, -1, 2, 5, 8` comme possibilités. En effet, on part de `-10` et on ajoute `3` tant que c'est inférieur ou égale à `10`.

#### Les calculs

On peut assigner le résultat d'un calcul pour définir un paramètre.

Attention : Il faut nécessairement que tous les paramètres utilisés dans le calcul soient définis AVANT.

```kotlin=
-20..20 into "a",
-20..20 into "b",
"a + b" into "c",
"frac(a, b)" into "d",
"frac(a + b, b) + 2*a*b*c*d + 10" into "e"
```

Ici, on définit `c`, `d` et `e` avec un calcul. On remarque que les paramètres utilisés dans les calculs sont toujours définis dans les lignes au-dessus.

TIPS : Si on désire générer un calcul avec aléatoirement un `+` ou un `-` on peut utiliser le caractère : `±`. Celui-ci sera remplacé soit par un `+` soit par un `-`. Exemples : `a ± b`, `±a`, etc.

#### Générer un couple de paramètres étant premiers entre eux

Il arrive régulièrement d'avoir besoin de valeurs qui ne sont pas totalement aléatoires.
Par exemple, pour créer une fraction avec des valeurs aléatoires en étant certains que la fraction ne se simplifie pas, on pourra écrire :

```kotlin=
1..10 coPrimesInto ("a" and "b")
```

Ici, on génère le couple `a` et `b` tel que `a` et `b` soient premiers entre eux et compris dans l'intervalle `[1; 10]`

Dans certains cas, on a déjà un paramètre et on voudrait en générer un autre à partir du premier en étant certains que les deux soient premiers entre eux :

```kotlin=
1..10 into "a",
1..20 coPrimesOf "a" into "b"
```

Ici, on génère `a` dans l'intervalle `[1; 10]`, puis on génère `b` dans l'intervalle `[1; 20]` tel que `a` et `b` soient premiers entre eux.

Il existe des variantes de `coPrimesOf`:

```kotlin=
1..10 into "a",
1..10 into "b",
1..20 coPrimesOf "a + b" into "c"
```
```kotlin=
1..20 coPrimesOf 5 into "a"
```

#### Générer un couple de paramètres n'étant PAS premiers entre eux

Parfois, on a besoin d'avoir des valeurs aléatoires en étant certains qu'elles vont se simplifier entre elles !

La syntaxe est très similaire à la section du dessus :

```kotlin=
1..10 notCoPrimesInto ("a" and "b")
```
```kotlin=
1..10 into "a",
1..20 notCoPrimesOf "a" into "b"
```
```kotlin=
1..10 into "a",
1..10 into "b",
1..20 notCoPrimesOf "a + b" into "c"
```
```kotlin=
1..20 notCoPrimesOf 5 into "a"
```

#### Les nombres à virgules

Pour les nombres à virgules, il faut absolument écrire sous la forme décimale : `0.1`
Même si on a un chiffre rond : `1.0`, `0.0`

On remarque l'utilisation du point et non de la virgule !

Comme pour les entiers, on peut générer dans un intervalle :

```kotlin=
-20.0..20.0 into "a"
```

Par défaut, la précision est de `2` chiffres après la virgule, on aura donc des valeurs comme :

- `-2.13`
- `2.54`
- `19.83`
- `-17.89`

Cependant, si on veut changer la précision, on peut faire :

```kotlin=
-20.0..20.0 precision 3 into "a"
```
Ici, on aura une précision de `3` chiffres après la virgule.

### Les contraintes sur un paramètre

Parfois, on a besoin de définir des contraintes sur un paramètre.
Rappel : Aucune contrainte ne peut être rétrospective ! Elle n'agit que sur le paramètre courant.

On ajoute les contraintes à la fin de la définition d'un paramètre (sur la même ligne).

Exemple :

```kotlin=
-10..10 into "a" except 0
```

On peut rajouter plusieurs contraintes les unes à la suite des autres :

```kotlin=
-10..10 into "a" except 0 except 1 except -1
```

On peut aussi utiliser des paramètres précédemment définis :

```kotlin=
-10..10 into "a" except 0 except 1 except -1
-10..10 into "b" except "a"
```

Attention, on ne peut pas mettre un calcul ! Il faut mettre un paramètre précédent ou une valeur.
Si on veut absolument avoir une contrainte sur un calcul, on peut définir un paramètre qui ne servira qu'à ça :
```kotlin=
-10..10 into "a" except 0 except 1 except -1
"a + 3" into "i"
-10..10 into "b" except "i"
```

Au-delà de l'égalité, on peut aussi comparer :

Différent de `0` :
```kotlin=
-10..10 into "a" except 0
```
Inférieur à `0` :
```kotlin=
-10..10 into "a" lessThan 0
```
Inférieur ou égale à `0` :
```kotlin=
-10..10 into "a" lessThanOrEqual 0
```
Supérieur à `0` :
```kotlin=
-10..10 into "a" moreThan 0
```
Supérieur ou égale à `0` :
```kotlin=
-10..10 into "a" moreThanOrEqual 0
```