Files
cookiecode/dist/cookiecode.js
T

663 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* CookieCode - apprendre l'algorithmique avec des recettes de cuisine.
* Microlibrairie sans dépendance, compatible navigateur, CDN et Node/CommonJS.
*/
((root, factory) => {
if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.CookieCode = factory();
}
})(typeof globalThis !== "undefined" ? globalThis : this, () => {
var configuration = {
sortie: "console",
afficherNotions: true,
};
function configurer(options) {
options = options || {};
if (Object.hasOwn(options, "sortie")) {
configuration.sortie = options.sortie;
}
if (Object.hasOwn(options, "afficherNotions")) {
configuration.afficherNotions = Boolean(options.afficherNotions);
}
return CookieCode;
}
function texte(valeur, defaut) {
if (valeur === undefined || valeur === null || valeur === "") {
return defaut || "";
}
return String(valeur);
}
function echapperHTML(valeur) {
return texte(valeur)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function creerElement(type, nom, quantite, unite, valeur) {
if (!nom && nom !== 0) {
throw new Error(
"CookieCode : un nom est obligatoire pour creer un " + type + ".",
);
}
return {
type: type,
nom: texte(nom),
quantite: quantite,
unite: unite,
valeur: valeur,
};
}
function ingredient(nom, quantite, unite) {
return creerElement("ingredient", nom, quantite, unite);
}
function materiel(nom) {
return creerElement("materiel", nom);
}
function constante(nom, valeur) {
return creerElement("constante", nom, undefined, undefined, valeur);
}
function preparation(nom) {
return {
type: "preparation",
nom: texte(nom, "preparation"),
etat: {},
definir: function (cle, valeur) {
this.etat[cle] = valeur;
return this;
},
est: function (valeur) {
return (
this.etat.texture === valeur ||
this.etat.etat === valeur ||
this.etat[valeur] === true
);
},
};
}
function nomDe(element) {
if (element && typeof element === "object" && element.nom !== undefined) {
return texte(element.nom);
}
return texte(element);
}
function quantiteDe(element, quantite, unite) {
var q = quantite !== undefined ? quantite : element?.quantite;
var u = unite !== undefined ? unite : element?.unite;
if (q === undefined || q === null || q === "") {
return "";
}
return texte(q) + (u ? " " + texte(u) : "");
}
function formatElement(element, quantite, unite) {
var quantiteTexte = quantiteDe(element, quantite, unite);
var nom = nomDe(element);
return quantiteTexte ? quantiteTexte + " de " + nom : nom;
}
function conditionLisible(condition) {
if (typeof condition === "string") {
return condition;
}
if (typeof condition === "boolean") {
return condition ? "condition vraie" : "condition fausse";
}
if (typeof condition === "function") {
return "condition fournie";
}
return "condition";
}
function evaluerCondition(condition) {
if (typeof condition === "function") {
return Boolean(condition());
}
if (typeof condition === "boolean") {
return condition;
}
return true;
}
function creerEtape(texteEtape, notion, enfants) {
return {
texte: texteEtape,
notion: notion,
enfants: enfants || [],
};
}
function Recette(nom) {
if (!nom && nom !== 0) {
throw new Error("CookieCode : une recette doit avoir un nom.");
}
this.nom = texte(nom);
this.etapes = [];
this.notions = {
programme: true,
variables: [],
constantes: [],
fonctions: [],
boucles: false,
conditions: false,
sequence: true,
};
}
Recette.prototype._noterFonction = function (nom) {
if (this.notions.fonctions.indexOf(nom) === -1) {
this.notions.fonctions.push(nom);
}
};
Recette.prototype._noterVariable = function (nom) {
if (nom && this.notions.variables.indexOf(nom) === -1) {
this.notions.variables.push(nom);
}
};
Recette.prototype._noterConstante = function (nom) {
if (nom && this.notions.constantes.indexOf(nom) === -1) {
this.notions.constantes.push(nom);
}
};
Recette.prototype._ajouterEtape = function (texteEtape, notion, enfants) {
this.etapes.push(creerEtape(texteEtape, notion, enfants));
return this;
};
Recette.prototype.prendre = function (nom, quantite, unite) {
this._noterVariable(nomDe(nom));
this._noterFonction("prendre");
return this._ajouterEtape(
"Prendre " + formatElement(nom, quantite, unite) + ".",
"variable",
);
};
Recette.prototype.utiliser = function (outil) {
this._noterConstante(nomDe(outil));
this._noterFonction("utiliser");
return this._ajouterEtape("Utiliser " + nomDe(outil) + ".", "constante");
};
Recette.prototype.ajouter = function (element, quantite, unite) {
this._noterFonction("ajouter");
if (element && element.type === "ingredient") {
this._noterVariable(element.nom);
}
return this._ajouterEtape(
"Ajouter " + formatElement(element, quantite, unite) + ".",
"fonction",
);
};
Recette.prototype.retirer = function (element) {
this._noterFonction("retirer");
return this._ajouterEtape("Retirer " + nomDe(element) + ".", "fonction");
};
Recette.prototype.couper = function (element, forme) {
this._noterFonction("couper");
return this._ajouterEtape(
"Couper " + nomDe(element) + (forme ? " en " + texte(forme) : "") + ".",
"fonction",
);
};
Recette.prototype.melanger = function () {
this._noterFonction("melanger");
return this._ajouterEtape("Melanger.", "fonction");
};
Recette.prototype.verser = function (contenu, contenant) {
this._noterFonction("verser");
return this._ajouterEtape(
"Verser " +
nomDe(contenu) +
(contenant ? " dans " + nomDe(contenant) : "") +
".",
"fonction",
);
};
Recette.prototype.chauffer = function (element, temperature) {
this._noterFonction("chauffer");
return this._ajouterEtape(
"Chauffer " +
nomDe(element) +
(temperature ? " a " + nomDe(temperature) : "") +
".",
"fonction",
);
};
Recette.prototype.cuire = function (duree, unite) {
this._noterFonction("cuire");
return this._ajouterEtape(
"Cuire" +
(duree
? " pendant " + texte(duree) + (unite ? " " + texte(unite) : "")
: "") +
".",
"fonction",
);
};
Recette.prototype.attendre = function (duree, unite) {
this._noterFonction("attendre");
return this._ajouterEtape(
"Attendre " + texte(duree) + (unite ? " " + texte(unite) : "") + ".",
"fonction",
);
};
Recette.prototype.repeter = function (nombre, action) {
this.notions.boucles = true;
this._noterFonction("repeter");
var bloc = new Recette("bloc repeter");
if (typeof action === "function") {
action(bloc);
}
this._fusionnerNotions(bloc);
return this._ajouterEtape(
"Repeter " + texte(nombre) + " fois :",
"boucle",
bloc.etapes,
);
};
Recette.prototype.si = function (condition, action, sinonAction) {
this.notions.conditions = true;
this._noterFonction("si");
var bloc = new Recette("bloc si");
var blocSinon = new Recette("bloc sinon");
var conditionVraie = evaluerCondition(condition);
if (conditionVraie && typeof action === "function") {
action(bloc);
}
if (!conditionVraie && typeof sinonAction === "function") {
sinonAction(blocSinon);
}
this._fusionnerNotions(bloc);
this._fusionnerNotions(blocSinon);
var enfants = bloc.etapes;
if (blocSinon.etapes.length > 0) {
enfants = enfants.concat([
creerEtape("Sinon :", "condition", blocSinon.etapes),
]);
}
return this._ajouterEtape(
"Si " + conditionLisible(condition) + " :",
"condition",
enfants,
);
};
Recette.prototype.servir = function (element) {
this._noterFonction("servir");
this._ajouterEtape(
"Servir" + (element ? " " + nomDe(element) : "") + ".",
"fonction",
);
return this.afficher();
};
Recette.prototype.afficher = function () {
var cible;
if (configuration.sortie === "console" || !configuration.sortie) {
if (typeof console !== "undefined" && console.log) {
console.log(this.toText());
}
return this;
}
if (
typeof configuration.sortie === "string" &&
typeof document !== "undefined"
) {
cible = document.querySelector(configuration.sortie);
if (cible) {
cible.innerHTML = this.toHTML();
}
}
return this;
};
Recette.prototype._fusionnerNotions = function (autreRecette) {
autreRecette.notions.variables.forEach((nom) => {
this._noterVariable(nom);
});
autreRecette.notions.constantes.forEach((nom) => {
this._noterConstante(nom);
});
autreRecette.notions.fonctions.forEach((nom) => {
this._noterFonction(nom);
});
this.notions.boucles = this.notions.boucles || autreRecette.notions.boucles;
this.notions.conditions =
this.notions.conditions || autreRecette.notions.conditions;
};
Recette.prototype._lignesEtapes = function (etapes, prefixe, indentation) {
var lignes = [];
etapes.forEach(function (etape, index) {
var numero = prefixe ? prefixe + "." + (index + 1) : String(index + 1);
lignes.push(indentation + numero + ". " + etape.texte);
if (etape.enfants && etape.enfants.length > 0) {
lignes = lignes.concat(
this._lignesEtapes(etape.enfants, numero, indentation + " "),
);
}
}, this);
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 () {
var lignes = [
"",
"Notions utilisees :",
"- Recette = programme",
"- Sequence = les etapes sont executees dans l'ordre",
];
if (this.notions.variables.length > 0) {
lignes.push(
"- Ingredients = variables : " + this.notions.variables.join(", "),
);
}
if (this.notions.constantes.length > 0) {
lignes.push(
"- Materiel / valeurs fixes = constantes : " +
this.notions.constantes.join(", "),
);
}
if (this.notions.fonctions.length > 0) {
lignes.push(
"- Actions = fonctions : " + this.notions.fonctions.join(", "),
);
}
if (this.notions.boucles) {
lignes.push("- Repeter = boucle");
}
if (this.notions.conditions) {
lignes.push("- Si = condition");
}
return lignes;
};
Recette.prototype.toText = function () {
var lignes = ["Recette : " + this.nom, "", "Etapes :"];
lignes = lignes.concat(this._lignesEtapes(this.etapes, "", ""));
if (configuration.afficherNotions) {
lignes = lignes.concat(this._lignesNotions());
}
return lignes.join("\n");
};
Recette.prototype.toHTML = function () {
var html = '<section class="cookiecode-recette">';
html += "<h2>Recette : " + echapperHTML(this.nom) + "</h2>";
html += "<h3>Etapes</h3>";
html += etapesHTML(this.etapes);
if (configuration.afficherNotions) {
html += "<h3>Notions utilisees</h3><ul>";
this._lignesNotions()
.slice(2)
.forEach((ligne) => {
html += "<li>" + echapperHTML(ligne.replace(/^- /, "")) + "</li>";
});
html += "</ul>";
}
html += "</section>";
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) {
var html = "<ol>";
etapes.forEach((etape) => {
html += "<li>" + echapperHTML(etape.texte);
if (etape.enfants && etape.enfants.length > 0) {
html += etapesHTML(etape.enfants);
}
html += "</li>";
});
html += "</ol>";
return html;
}
function recette(nom) {
return new Recette(nom);
}
var CookieCode = {
configurer: configurer,
recette: recette,
ingredient: ingredient,
materiel: materiel,
constante: constante,
preparation: preparation,
defi: defi,
verifier: verifier,
version: "0.2.0",
};
return CookieCode;
});