Add challenge feedback API

This commit is contained in:
2026-07-21 22:29:22 +02:00
parent 5a388d3d60
commit f4fea7324a
7 changed files with 500 additions and 6 deletions
+58
View File
@@ -60,6 +60,7 @@ CookieCode.ingredient("farine", 200, "g");
CookieCode.materiel("saladier"); CookieCode.materiel("saladier");
CookieCode.constante("temperature", 180); CookieCode.constante("temperature", 180);
CookieCode.preparation("pate"); CookieCode.preparation("pate");
CookieCode.defi("Cookie avec boucle", { ingredientsMinimum: 3 });
``` ```
### Methodes d'une recette ### Methodes d'une recette
@@ -81,6 +82,7 @@ CookieCode.preparation("pate");
.afficher() .afficher()
.toText() .toText()
.toHTML() .toHTML()
.analyser()
``` ```
## Exemple avec variables ## Exemple avec variables
@@ -117,6 +119,62 @@ CookieCode.recette("Pate a cookies")
.servir(); .servir();
``` ```
## Feedback ludique
CookieCode peut analyser une recette et verifier si elle respecte un defi.
```js
const recette = CookieCode.recette("Cookie")
.prendre("farine", 200, "g")
.prendre("sucre", 100, "g")
.prendre("chocolat", 150, "g")
.ajouter("farine")
.ajouter("sucre")
.ajouter("chocolat")
.melanger()
.repeter(2, function (recette) {
recette.melanger();
})
.cuire(12, "minutes")
.servir();
const defi = CookieCode.defi("Cookie avec boucle", {
ingredientsMinimum: 3,
actionsObligatoires: ["melanger", "cuire", "servir"],
boucleObligatoire: true,
terminerParServir: true,
interdireErreurs: true
});
const verification = CookieCode.verifier(recette, defi);
console.log(verification.toText());
```
Sortie possible :
```txt
Defi : Cookie avec boucle
Resultat : reussi
✅ Tu as utilise 3 ingredient(s).
✅ Action trouvee : melanger.
✅ Action trouvee : cuire.
✅ Action trouvee : servir.
✅ Tu as utilise une boucle avec repeter.
✅ Ta recette se termine par servir.
✅ Aucune erreur logique detectee.
```
Une recette peut aussi etre analysee directement :
```js
const rapport = recette.analyser();
console.log(rapport.ingredients);
console.log(rapport.actions);
console.log(rapport.utiliseBoucle);
console.log(rapport.erreurs);
```
## Cartes pedagogiques conseillees ## Cartes pedagogiques conseillees
| Type de carte | Mots cles | Notion | | Type de carte | Mots cles | Notion |
+186 -1
View File
@@ -381,6 +381,47 @@
return lignes; return lignes;
}; };
Recette.prototype.analyser = function () {
var actions = this.notions.fonctions.slice();
var etapesAPlates = aplatirEtapes(this.etapes);
var derniereAction =
etapesAPlates.length > 0
? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte)
: "";
var erreurs = [];
if (this.etapes.length === 0) {
erreurs.push("Ta recette est vide : ajoute au moins une etape.");
}
if (this.etapes.length > 0 && derniereAction !== "servir") {
erreurs.push("Ta recette ne se termine pas par servir.");
}
etapesAPlates.forEach((etape, index) => {
if (
actionDeTexte(etape.texte) === "servir" &&
index !== etapesAPlates.length - 1
) {
erreurs.push("Tu as servi avant la fin de la recette.");
}
});
return {
nom: this.nom,
ingredients: this.notions.variables.slice(),
variables: this.notions.variables.slice(),
materiels: this.notions.constantes.slice(),
constantes: this.notions.constantes.slice(),
actions: actions,
nombreEtapes: etapesAPlates.length,
utiliseBoucle: this.notions.boucles,
utiliseCondition: this.notions.conditions,
termineParServir: derniereAction === "servir",
erreurs: erreurs,
};
};
Recette.prototype._lignesNotions = function () { Recette.prototype._lignesNotions = function () {
var lignes = [ var lignes = [
"", "",
@@ -446,6 +487,148 @@
return html; return html;
}; };
function aplatirEtapes(etapes) {
var resultat = [];
etapes.forEach((etape) => {
resultat.push(etape);
if (etape.enfants && etape.enfants.length > 0) {
resultat = resultat.concat(aplatirEtapes(etape.enfants));
}
});
return resultat;
}
function actionDeTexte(texteEtape) {
var index;
var valeur = texte(texteEtape).toLowerCase();
var actions = [
"prendre",
"utiliser",
"ajouter",
"retirer",
"couper",
"melanger",
"verser",
"chauffer",
"cuire",
"attendre",
"repeter",
"si",
"servir",
];
for (index = 0; index < actions.length; index += 1) {
if (valeur.indexOf(actions[index]) === 0) {
return actions[index];
}
}
return "";
}
function defi(nom, regles) {
if (!nom && nom !== 0) {
throw new Error("CookieCode : un defi doit avoir un nom.");
}
return {
type: "defi",
nom: texte(nom),
regles: regles || {},
};
}
function verifier(recetteAVerifier, defiAVerifier) {
var rapport = recetteAVerifier.analyser();
var regles = defiAVerifier?.regles ? defiAVerifier.regles : {};
var messages = [];
var reussi = true;
function valider(condition, succes, echec) {
if (condition) {
messages.push("✅ " + succes);
} else {
reussi = false;
messages.push("❌ " + echec);
}
}
if (regles.ingredientsMinimum !== undefined) {
valider(
rapport.ingredients.length >= regles.ingredientsMinimum,
"Tu as utilise " + rapport.ingredients.length + " ingredient(s).",
"Il faut au moins " + regles.ingredientsMinimum + " ingredient(s).",
);
}
if (regles.materielsMinimum !== undefined) {
valider(
rapport.materiels.length >= regles.materielsMinimum,
"Tu as utilise " + rapport.materiels.length + " materiel(s).",
"Il faut au moins " + regles.materielsMinimum + " materiel(s).",
);
}
(regles.actionsObligatoires || []).forEach((action) => {
valider(
rapport.actions.indexOf(action) !== -1,
"Action trouvee : " + action + ".",
"Action manquante : " + action + ".",
);
});
if (regles.boucleObligatoire) {
valider(
rapport.utiliseBoucle,
"Tu as utilise une boucle avec repeter.",
"Il manque une boucle : essaie avec .repeter().",
);
}
if (regles.conditionObligatoire) {
valider(
rapport.utiliseCondition,
"Tu as utilise une condition avec si.",
"Il manque une condition : essaie avec .si().",
);
}
if (regles.terminerParServir) {
valider(
rapport.termineParServir,
"Ta recette se termine par servir.",
"Ta recette doit se terminer par .servir().",
);
}
if (regles.interdireErreurs) {
valider(
rapport.erreurs.length === 0,
"Aucune erreur logique detectee.",
rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.",
);
}
if (messages.length === 0) {
messages.push("️ Aucun critere de defi a verifier.");
}
return {
reussi: reussi,
nom: defiAVerifier ? defiAVerifier.nom : "Defi",
messages: messages,
rapport: rapport,
toText: function () {
return [
"Defi : " + this.nom,
this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer",
]
.concat(this.messages)
.join("\n");
},
};
}
function etapesHTML(etapes) { function etapesHTML(etapes) {
var html = "<ol>"; var html = "<ol>";
etapes.forEach((etape) => { etapes.forEach((etape) => {
@@ -470,7 +653,9 @@
materiel: materiel, materiel: materiel,
constante: constante, constante: constante,
preparation: preparation, preparation: preparation,
version: "0.1.0", defi: defi,
verifier: verifier,
version: "0.2.0",
}; };
return CookieCode; return CookieCode;
+11 -1
View File
@@ -3,7 +3,7 @@ CookieCode.configurer({
afficherNotions: true, afficherNotions: true,
}); });
CookieCode.recette("Cookie au chocolat") const recette = CookieCode.recette("Cookie au chocolat")
.prendre("farine", 200, "g") .prendre("farine", 200, "g")
.prendre("sucre", 100, "g") .prendre("sucre", 100, "g")
.prendre("chocolat", 150, "g") .prendre("chocolat", 150, "g")
@@ -17,3 +17,13 @@ CookieCode.recette("Cookie au chocolat")
}) })
.cuire(12, "minutes") .cuire(12, "minutes")
.servir(); .servir();
const defi = CookieCode.defi("Cookie avec boucle", {
ingredientsMinimum: 3,
actionsObligatoires: ["melanger", "cuire", "servir"],
boucleObligatoire: true,
terminerParServir: true,
interdireErreurs: true
});
console.log(CookieCode.verifier(recette, defi).toText());
+12
View File
@@ -24,3 +24,15 @@ Feature: Apprendre l'algorithmique avec CookieCode
Then la recette affiche l'etape conditionnelle Then la recette affiche l'etape conditionnelle
And les actions du bloc conditionnel sont affichees And les actions du bloc conditionnel sont affichees
And la lecture algorithmique indique que si est une condition And la lecture algorithmique indique que si est une condition
Scenario: Verifier un defi ludique
Given un defi demande au moins trois ingredients, une boucle et une fin avec servir
When l'apprenant verifie une recette qui respecte ces criteres
Then CookieCode indique que le defi est reussi
And CookieCode affiche des messages de validation positifs
Scenario: Aider a ameliorer une recette incomplete
Given un defi demande au moins trois ingredients, une boucle et une fin avec servir
When l'apprenant verifie une recette incomplete
Then CookieCode indique que le resultat est a ameliorer
And CookieCode explique les criteres manquants
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "cookiecode", "name": "cookiecode",
"version": "0.1.0", "version": "0.2.0",
"description": "Microlibrairie educative pour apprendre l'algorithmique avec des recettes de cuisine.", "description": "Microlibrairie educative pour apprendre l'algorithmique avec des recettes de cuisine.",
"main": "dist/cookiecode.js", "main": "dist/cookiecode.js",
"scripts": { "scripts": {
+186 -1
View File
@@ -381,6 +381,47 @@
return lignes; return lignes;
}; };
Recette.prototype.analyser = function () {
var actions = this.notions.fonctions.slice();
var etapesAPlates = aplatirEtapes(this.etapes);
var derniereAction =
etapesAPlates.length > 0
? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte)
: "";
var erreurs = [];
if (this.etapes.length === 0) {
erreurs.push("Ta recette est vide : ajoute au moins une etape.");
}
if (this.etapes.length > 0 && derniereAction !== "servir") {
erreurs.push("Ta recette ne se termine pas par servir.");
}
etapesAPlates.forEach((etape, index) => {
if (
actionDeTexte(etape.texte) === "servir" &&
index !== etapesAPlates.length - 1
) {
erreurs.push("Tu as servi avant la fin de la recette.");
}
});
return {
nom: this.nom,
ingredients: this.notions.variables.slice(),
variables: this.notions.variables.slice(),
materiels: this.notions.constantes.slice(),
constantes: this.notions.constantes.slice(),
actions: actions,
nombreEtapes: etapesAPlates.length,
utiliseBoucle: this.notions.boucles,
utiliseCondition: this.notions.conditions,
termineParServir: derniereAction === "servir",
erreurs: erreurs,
};
};
Recette.prototype._lignesNotions = function () { Recette.prototype._lignesNotions = function () {
var lignes = [ var lignes = [
"", "",
@@ -446,6 +487,148 @@
return html; return html;
}; };
function aplatirEtapes(etapes) {
var resultat = [];
etapes.forEach((etape) => {
resultat.push(etape);
if (etape.enfants && etape.enfants.length > 0) {
resultat = resultat.concat(aplatirEtapes(etape.enfants));
}
});
return resultat;
}
function actionDeTexte(texteEtape) {
var index;
var valeur = texte(texteEtape).toLowerCase();
var actions = [
"prendre",
"utiliser",
"ajouter",
"retirer",
"couper",
"melanger",
"verser",
"chauffer",
"cuire",
"attendre",
"repeter",
"si",
"servir",
];
for (index = 0; index < actions.length; index += 1) {
if (valeur.indexOf(actions[index]) === 0) {
return actions[index];
}
}
return "";
}
function defi(nom, regles) {
if (!nom && nom !== 0) {
throw new Error("CookieCode : un defi doit avoir un nom.");
}
return {
type: "defi",
nom: texte(nom),
regles: regles || {},
};
}
function verifier(recetteAVerifier, defiAVerifier) {
var rapport = recetteAVerifier.analyser();
var regles = defiAVerifier?.regles ? defiAVerifier.regles : {};
var messages = [];
var reussi = true;
function valider(condition, succes, echec) {
if (condition) {
messages.push("✅ " + succes);
} else {
reussi = false;
messages.push("❌ " + echec);
}
}
if (regles.ingredientsMinimum !== undefined) {
valider(
rapport.ingredients.length >= regles.ingredientsMinimum,
"Tu as utilise " + rapport.ingredients.length + " ingredient(s).",
"Il faut au moins " + regles.ingredientsMinimum + " ingredient(s).",
);
}
if (regles.materielsMinimum !== undefined) {
valider(
rapport.materiels.length >= regles.materielsMinimum,
"Tu as utilise " + rapport.materiels.length + " materiel(s).",
"Il faut au moins " + regles.materielsMinimum + " materiel(s).",
);
}
(regles.actionsObligatoires || []).forEach((action) => {
valider(
rapport.actions.indexOf(action) !== -1,
"Action trouvee : " + action + ".",
"Action manquante : " + action + ".",
);
});
if (regles.boucleObligatoire) {
valider(
rapport.utiliseBoucle,
"Tu as utilise une boucle avec repeter.",
"Il manque une boucle : essaie avec .repeter().",
);
}
if (regles.conditionObligatoire) {
valider(
rapport.utiliseCondition,
"Tu as utilise une condition avec si.",
"Il manque une condition : essaie avec .si().",
);
}
if (regles.terminerParServir) {
valider(
rapport.termineParServir,
"Ta recette se termine par servir.",
"Ta recette doit se terminer par .servir().",
);
}
if (regles.interdireErreurs) {
valider(
rapport.erreurs.length === 0,
"Aucune erreur logique detectee.",
rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.",
);
}
if (messages.length === 0) {
messages.push("️ Aucun critere de defi a verifier.");
}
return {
reussi: reussi,
nom: defiAVerifier ? defiAVerifier.nom : "Defi",
messages: messages,
rapport: rapport,
toText: function () {
return [
"Defi : " + this.nom,
this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer",
]
.concat(this.messages)
.join("\n");
},
};
}
function etapesHTML(etapes) { function etapesHTML(etapes) {
var html = "<ol>"; var html = "<ol>";
etapes.forEach((etape) => { etapes.forEach((etape) => {
@@ -470,7 +653,9 @@
materiel: materiel, materiel: materiel,
constante: constante, constante: constante,
preparation: preparation, preparation: preparation,
version: "0.1.0", defi: defi,
verifier: verifier,
version: "0.2.0",
}; };
return CookieCode; return CookieCode;
+46 -2
View File
@@ -1,7 +1,7 @@
const assert = require("assert"); const assert = require("assert");
const CookieCode = require("../dist/cookiecode.js"); const CookieCode = require("../dist/cookiecode.js");
CookieCode.configurer({ sortie: null, afficherNotions: true }); CookieCode.configurer({ sortie: "test", afficherNotions: true });
const texteRecette = CookieCode.recette("Cookie au chocolat") const texteRecette = CookieCode.recette("Cookie au chocolat")
.prendre("farine", 200, "g") .prendre("farine", 200, "g")
@@ -48,6 +48,50 @@ assert.ok(!html.includes("<img src=x"));
assert.ok(html.includes("&lt;script&gt;alert")); assert.ok(html.includes("&lt;script&gt;alert"));
assert.ok(html.includes("&lt;img src=x")); assert.ok(html.includes("&lt;img src=x"));
assert.strictEqual(CookieCode.version, "0.1.0"); const recetteDefi = CookieCode.recette("Cookie defi")
.prendre("farine", 200, "g")
.prendre("sucre", 100, "g")
.prendre("chocolat", 150, "g")
.ajouter("farine")
.ajouter("sucre")
.ajouter("chocolat")
.melanger()
.repeter(2, (recette) => {
recette.melanger();
})
.cuire(12, "minutes")
.servir();
const rapport = recetteDefi.analyser();
assert.deepStrictEqual(rapport.ingredients, ["farine", "sucre", "chocolat"]);
assert.ok(rapport.actions.includes("repeter"));
assert.strictEqual(rapport.utiliseBoucle, true);
assert.strictEqual(rapport.termineParServir, true);
assert.deepStrictEqual(rapport.erreurs, []);
const defiCookie = CookieCode.defi("Cookie avec boucle", {
ingredientsMinimum: 3,
actionsObligatoires: ["melanger", "cuire", "servir"],
boucleObligatoire: true,
terminerParServir: true,
interdireErreurs: true,
});
const verification = CookieCode.verifier(recetteDefi, defiCookie);
assert.strictEqual(verification.reussi, true);
assert.match(verification.toText(), /Resultat : reussi/);
assert.match(verification.toText(), /✅ Tu as utilise 3 ingredient\(s\)\./);
const recetteIncomplete = CookieCode.recette("Cookie incomplet")
.prendre("farine", 200, "g")
.melanger();
const verificationIncomplete = CookieCode.verifier(recetteIncomplete, defiCookie);
assert.strictEqual(verificationIncomplete.reussi, false);
assert.match(verificationIncomplete.toText(), /❌ Il faut au moins 3 ingredient\(s\)\./);
assert.match(verificationIncomplete.toText(), /❌ Il manque une boucle : essaie avec \.repeter\(\)\./);
assert.match(verificationIncomplete.toText(), /❌ Ta recette doit se terminer par \.servir\(\)\./);
assert.strictEqual(CookieCode.version, "0.2.0");
console.log("CookieCode tests: OK"); console.log("CookieCode tests: OK");