Montrer la tortue
Dans la partie précédente, nous avions donné vie à notre tortue, mais nous ne lui avons donné aucune apparence.
Sur le module python, les tortues sont modélisées par des flèches dont la pointe est au niveau des coordonnées.
On rajoute donc la fonction qui dessine cette flèche :
blitturtle(color) {
context.fillStyle = color;
context.beginPath();
context.moveTo(Math.round(this.x), Math.round(this.y));
context.lineTo(Math.round(this.x - 12 * Math.cos(Math.PI / 6 + Math.PI * (this.angle / 180))), Math.round(this.y + 12 * Math.sin(Math.PI / 6 + Math.PI * (this.angle / 180))));
context.lineTo(Math.round(this.x - 7 * Math.cos(Math.PI * (this.angle / 180))), Math.round(this.y + 7 * Math.sin(Math.PI * (this.angle / 180))));
context.lineTo(Math.round(this.x - 12 * Math.cos(Math.PI * (this.angle / 180) - Math.PI/6)), Math.round(this.y + 12 * Math.sin(Math.PI * (this.angle / 180) - Math.PI / 6)));
context.closePath();
context.fill();
}
showturtle() {
this.is_visible = true;
}
st() { return this.showturtle(); }
hideturtle() {
this.is_visible = false;
}
ft() { return this.hideturtle(); }
isvisible() { return this.is_visible; }
La fleche est faite a partir du schéma ci-dessous : (Notez que les angles bleu et vert sont identiques)
On utilise deux cercles concentriques pour déterminer les coordonnées des sommets de la fleche.
Il suffit maintenant d'implementer le bout de code qui affichera notre tortue :
// Pour rafraichir l'ecran
function rafraichir() {
context.fillStyle = background_color;
context.fillRect(0, 0, screen.width, screen.height);
for (tortue of tortues) {
if (tortue.show_drawing) {
for (chemins of tortue.all_shapes) {
context.strokeStyle = chemins.color;
context.lineWidth = chemins.width;
context.stroke(chemins);
}
}
if (tortue.isvisible()) {
tortue.blitturtle(tortue.turtle_color);
}
}
}
La console
Vous êtes vous déjà demandé comment on crée un élément html ?
Et bien, ne vous demandez plus, car nous allons le faire !
Les éléments HTML sont caractérisé par le DOM et il existe une classe JS qui permet d'accéder à leur propriétés.
En executant la commande :
document.getElementById("mon-id").style.color = "red";
Vous modifiez un élément grâce au DOM.
Ainsi, tout les éléments du DOM sont des sous-classes de HTMLElement.
Il parait donc cohérent de créer une classe qui prend l'hérédité de HTMLElement :
class TurtleElement extends HTMLElement {
constructor() {
// On laisse le navigateur comprendre qu'il s'agit d'une balise html
super();
}
}
Ce code n'est cependant pas suffisant, en effet, il faut signifier au DOM que notre balise <turtle-js> est bien définie, pour cela, on utilise notre classe :
customElements.define("turtle-js", TurtleElement);
Et voila ! Vous avez désormais créé une balise HTML, c'est simple, n'est-ce pas ?
Comme vous pouvez vous y attendre, il est possible de rajouter du code à la classe définissant notre balise et donc d'exécuter du code au chargement de la page.
C'est comme ca que la balise <nsi-xyz> fonctionne, l'API nsixyz.js définit la balise et lit son contenu pour ajouter le code nécessaire. Bien évidemment, le code d'ajout est plus complexe, mais vous devriez être capable de pouvoir refaire le menu avec une BDD locale si vous comprenez ce tutoriel.
Il ne reste plus qu'à trouver une utilité à notre balise, nous allons nous en servir pour créer un canvas et une textarea qui nous servira de console d'exécution :
class TurtleElement extends HTMLElement {
constructor() {
// On laisse le navigateur comprendre qu'il s'agit d'une balise html
super();
// On recupere le contenu de la balise et on l'efface
var code = this.innerHTML;
this.innerHTML = "";
// On crée un élément canvas
var child = document.createElement("canvas");
child.id = 'screen';
child.height = this.getAttribute("canvas-height") || "400";
child.width = this.getAttribute("canvas-width") || "400";
child.style.display = "block";
this.appendChild(child);
// On crée un élément textarea qui contient le code de départ
child = document.createElement("textarea");
child.innerHTML = code;
child.style.height = (this.getAttribute("textarea-height") || "100") + "px";
child.style.width = (this.getAttribute("textarea-width") || "400") + "px";
child.style.resize = "none";
this.appendChild(child);
}
}
Vous remarquerez certainement ces lignes étranges :
child.height = this.getAttribute("canvas-height") || "400";
Cette syntaxe définie par EcmaScript2020, sont équivalentes à ce bloc de code :
// Premiere version :
if (this.getAttribute("canvas-height") === undefined) {
child.height = "400";
} else {
child.height = this.getAttribute("canvas-height");
}
// Deuxième version, plus compacte :
if (this.getAttribute("canvas-height") === undefined) child.height = "400";
else child.height = this.getAttribute("canvas-height");
// Troisième version, encore plus compacte :
child.height = (this.getAttribute("canvas-height") === undefined) ? "400" : this.getAttribute("canvas-height");
Cette syntaxe se comprend assez facilement, car || signifie ou. Un "équivalent" python serait :
# Toute variable non définie en js vaut undefined qui a la meme valeur logique que null (None en py)
ma_var_1 = None
ma_var_2 = 666
ma_var = ma_var_1 or 42
print(ma_var) # output -> 42
ma_var = ma_var_2 or ma_var
print(ma_var) # output -> 666
Le style de la console
On peut ajouter du style à notre console grâce au JS. Ici, j'opte pour un sobre blanc sur noir.
child.style.color = "white";
child.style.backgroundColor = "black";
child.style.fontFamily = "monospace";
Mais la puissance du DOM ne s'arrête pas là.
Les évènements
En effet, on peut assigner des évènements à certains éléments lorsqu'ils gagnent le focus utilisateur (comme les textarea).
On peut donc ajouter un évènement pour la pression des touches du clavier dans notre console.
On utilise une fonction anonyme :
child.onkeydown = (event) => {
switch (event.key) {
case "Tab":
var v = child.value, s = child.selectionStart, e = child.selectionEnd;
child.value = v.substring(0, s) + ' ' + v.substring(e);
child.selectionStart = child.selectionEnd = s + 4;
return false;
case "Enter":
if (!event.shiftKey) {
var code = child.value;
child.value = "";
window.eval(code); // Where magic happens
event.preventDefault();
return false;
}
break;
}
}
Pour faire court : quand une touche est enfoncée, on regarde de quelle touche il s'agit.
Si c'est une tabulation, on insère 4 espaces au niveau du curseur de façon astucieuse (car on ne peut pas simuler d'input clavier).
Si c'est la touche Entrer, on regarde si la touche Shift est préssée, si non, on envoie le code et on l'execute de façon globale.
Cependant, dans l'état, la tortue ne dessine pas en temps réel, il nous faut donc y remédier. On crée donc la fonction suivante :
function blitOnScreen () {
for (tortue of tortues) {
if (tortue.is_drawing) {
tortue.pu();
tortue.pd();
}
}
}
Puis on ajoute un intervalle pour palier au problème :
setInterval(blitOnScreen, 50);
Et voilà notre tortue qui dessine librement !
Passer à la fin du tutoriel