🚧 Attention, peinture fraîche !

Cette page a été traduite par une seule personne et n'a pas été relue et vérifiée par quelqu'un d'autre ! Les informations peuvent par exemple être erronées, être formulées maladroitement, ou contenir d'autres types de fautes.

Ajouter de l'interactivité

Nous allons continuer à explorer l'interfaçage entre le JavaScript et le WebAssembly en ajouter des fonctionnalités d'interaction avec notre implémentation du jeu de la vie. Nous allons permettre aux utilisateurs de changer le statut vivant ou mort d'une cellule en cliquant dessus, et aussi leur permettre de mettre en pause le jeu, ce qui va faciliter le dessin de certains schémas.

Mettre en pause et reprendre le jeu

Ajoutons un bouton pour changer si le jeu est en cours de lecture ou en pause. Dans wasm-jeu-de-la-vie/www/index.html, ajoutez le bouton juste au-dessus le <canvas> :

<button id="lecture-pause"></button>

Dans le JavaScript wasm-game-of-life/www/index.js, nous allons faire les changements suivants :

  • Conserver l'identifiant retourné par le dernier appel à requestAnimationFrame, pour que nous puissions annuler l'animation en faisant appel à cancelAnimationFrame avec cet identifiant.
  • Lorsque le bouton lecture/pause sera actionné, on va regarder si nous avons un identifiant pour un calcul d'une génération de l'animation. Si c'est le cas, alors le jeu est actuellement en cours de lecture, et cela veut donc dire que nous souhaitons annuler le calcul pour éviter que boucleDeRendu soit appelé à nouveau, ce qui aura pour effet de mettre en pause le jeu. Si nous n'avons pas d'identifiant pour un calcul d'une génération de l'animation, alors cela veut dire que nous sommes actuellement en pause, et nous devons faire appel à requestAnimationFrame pour reprendre le jeu.

Comme le JavaScript pilote le Rust via le WebAssembly, c'est tout ce que nous avons besoin de faire, et nous n'avons pas besoin de changer le code source Rust.

Nous ajoutons la variable animationId pour conserver l'identifiant retourné par requestAnimationFrame. Lorsqu'il n'y a pas de calcul d'une génération de l'animation, nous donnons assignons la valeur null à cette variable.

let animationId = null;

// Cette fonction est la même qu'avant, sauf que le résultat de
// `requestAnimationFrame` est assigné à `animationId`.
const boucleDeRendu = () => {
  dessinerGrille();
  dessinerCellules();

  univers.tick();

  animationId = requestAnimationFrame(boucleDeRendu);
};

A n'importe quel moment, nous pouvons savoir si le jeu est en pause ou non en vérifiant la valeur de animationId :

const estEnPause = () => {
  return animationId === null;
};

Maintenant lorsque le bouton lecture/pause est cliqué, nous vérifions si le jeu est en pause ou en lecture, et respectivement nous reprenons l'animation boucleDeRendu ou nous arrêtons le calcul de la prochaine génération de l'animation. De plus, nous allons changer le texte du bouton pour refléter l'action que le bouton va faire lors du prochain clic.

const boutonLecturePause = document.getElementById("lecture-pause");

const lecture = () => {
  boutonLecturePause.textContent = "⏸";
  boucleDeRendu();
};

const pause = () => {
  boutonLecturePause.textContent = "▶";
  cancelAnimationFrame(animationId);
  animationId = null;
};

boutonLecturePause.addEventListener("click", event => {
  if (estEnPause()) {
    lecture();
  } else {
    pause();
  }
});

Enfin, jusqu'ici nous avons démarré le jeu et son animation en faisant directement appel à requestAnimationFrame(boucleDeRendu), mais nous voulons remplacer cela par l'appel à lecture pour que le bouton ait la bonne icone initiale.

// On utilise cela à la place de `requestAnimationFrame(boucleDeRendu)`.
lecture();

Rafraîchissez http://localhost:8080/ et vous devriez maintenant mettre en pause et reprendre le jeu en cliquant sur le bouton !

Changer l'état d'une cellule avec un évènement "click"

Maintenant que nous pouvons mettre le jeu en pause, nous pouvons ajouter la possibilité de muter les cellules en cliquant sur elles.

Pour basculer le statut d'une cellule de vivante à morte, ou de morte à vivante. Ajoutez une méthode basculer à Cellule dans wasm-jeu-de-la-vie/src/lib.rs :


#![allow(unused)]
fn main() {
impl Cellule {
    fn basculer(&mut self) {
        *self = match *self {
            Cellule::Morte => Cellule::Vivante,
            Cellule::Vivante => Cellule::Morte,
        };
    }
}
}

Pour basculer l'état d'une cellule à une ligne et colonne donnée, nous traduisons le couple ligne et colonne en indice du vecteur de toutes les cellules et nous faisons appel à la méthode basculer sur la cellule à cet indice :


#![allow(unused)]
fn main() {
/// Méthodes publiques, exportées en JavaScript.
#[wasm_bindgen]
impl Univers {
    // ...

    pub fn basculer_cellule(&mut self, ligne: u32, colonne: u32) {
        let indice = self.calculer_indice(ligne, colonne);
        self.cellules[indice].basculer();
    }
}
}

Cette méthode est définie dans le bloc impl qui est annoté avec #[wasm_bingen] afin qu'il puisse être appelé en JavaScript.

Dans wasm-jeu-de-la-vie/www/index.js, nous écoutons les évènements de clics sur le noeud <canvas>, puis nous traduisons les coordonnées du clic dans le contexte de la page en coordonnées dans le canvas, et ensuite ces coordonnées en ligne et colonne, puis nous évoquons la méthode basculer_cellule, et enfin nous redessinons la scène.

canvas.addEventListener("click", evenement => {
  const zoneRectangulaire = canvas.getBoundingClientRect();

  const echelleX = canvas.width / zoneRectangulaire.width;
  const echelleY = canvas.height / zoneRectangulaire.height;

  const distanceGaucheDuCanvas = (evenement.clientX - zoneRectangulaire.left) * echelleX;
  const distanceHautDuCanvas = (evenement.clientY - zoneRectangulaire.top) * echelleY;

  const ligne = Math.min(Math.floor(distanceHautDuCanvas / (CELL_SIZE + 1)), hauteur - 1);
  const colonne = Math.min(Math.floor(distanceGaucheDuCanvas / (CELL_SIZE + 1)), largeur - 1);

  univers.basculer_cellule(ligne, colonne);

  dessinerGrille();
  dessinerCellules();
});

Recompilez avec wasm-pack build dans le dossier wasm-jeu-de-la-vie, ensuite rafraîchissez à nouveau la page http://localhost:8080/ et vous devriez pouvoir dessiner vos propres schémas en cliquant sur les cellules pour pouvoir changer leur état.

Exercices

  • Ajouter un composant <input type="range"> pour pouvoir régler combien de ticks se produisent à chaque séquence de l'animation.
  • Ajouter un bouton qui réinitialise l'univers dans un état initial aléatoire lorsqu'on clique dessus. Et un autre bouton qui réinitialise l'univers avec uniquement des cellules mortes.
  • Lors d'un Ctrl + Clic, insérez un planeur centré sur la cellule cible. Lors d'un Shift + Clic, insérez un pulsar.