TD15 : Interface Web

L'objectif de ce TD est de réaliser une interface Web pour notre jeu, en s'appuyant sur les technologies web frontend : HTML5, CSS3 et JavaScript (ES6). Comme il serait fastidieux de re-coder notre bibliothèque game en JavaScript, nous allons utiliser la technologie Web Assembly (ou Wasm) pour traduire automatiquement notre bibliothèque game en JavaScript.

Technologies Web

Afin de bien commencer, voici une présentation rapide des technologies Web, dont nous allons avoir besoin pour réaliser ce TP.

HTML, CSS et JavaScript

Commencez par lire attentivement le tutoriel ci-dessous sur les technologies HTML, CSS et JavaScript qui sont le cœur du Web.

👉 Réalisez les exercices préliminaires présents dans ce tutoriel, afin de vérifier votre bonne compréhension de ces technologies.

Canvas

Afin de faire un dessin 2D dans une page Web, il existe une balise HTML particulière appelée <canvas>. Pour dessiner dans un canvas (et interagir avec), il faut programmer en JavaScript...

La première étape consiste à ajouter un élément <canvas> de la taille souhaitée dans sa page HTML.

<canvas id='mycanvas' width="400px" height="400px"></canvas>

On donnera à cet élément un identifiant unique, comme mycanvas, afin de faire référence à cet élément dans le code JavaScript. Dessinons par exemple un carré jaune de taille 40x40 pixels, à la position (100,100) dans le canvas :

var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'yellow';
ctx.fillRect(100, 100, 40, 40);
ctx.restore();

Plutôt qu'un long discours, voici le code d'une petite démo : canvas/ que vous pouvez tester en chargeant le fichier demo.html.

Vous noterez que cette démo est interactive et redessine l'image à chaque clic souris gauche ou droit, en changeant la position de l'image mario ou du carré jaune... Le dessin est effectué par la fonction drawCanvas(), qui utilise le contexte ctx, pour définir le style courant à appliquer au dessin d'une figure (couleur, épaisseur trait, ...). Il convient de restaurer ce contexte après le dessin de chaque figure. Notre fonction drawCanvas() est appelée une première fois au chargement de la page web (event load de window), puis à nouveau après chaque évènement souris (event click et contextmenu de canvas). Vous remarquerez que la fonction drawCanvas() prend soin d'effacer le canvas entièrement à chaque nouvel appel : ctx.clearRect(0, 0, width, height).

👉 Reprenez votre page demo.html et ajoutez un canvas avec un petit dessin personnel.

Un peu de documentation sur les canvas :

Web Assembly

Nous allons maintenant nous intéresser à la technologie WebAssembly (ou Wasm), et en particulier à l'outil Emscripten afin de traduire automatiquement notre bibliothèque game (écrite en C) en un module JavaScript portable, et utilisable sur le web !

Nous allons commencer par considérer le petit exemple wasm/, qui définit deux fonctions en langages C :

int int_add(int x, int y)
{
    return x + y;
}

int int_mul(int x, int y)
{
    return x * y;
}

Nota Bene : Même s'il est possible d'utiliser des types complexes, nous nous limiterons ici à l'usage de types simples.

Afin de pouvoir utiliser ces fonctions dans une page Web, il faut commencer par annoter ce code avec le mot-clé EMSCRIPTEN_KEEPALIVE pour déclarer les fonctions que l'on souhaite exporter dans notre module JavaScript (cf. math.c).

Vous pouvez ensuite compiler ce code avec le compilateur emcc, ce qui va ainsi produire notre module math.js (ainsi que le fichier associé math.wasm).

emcc math.c -o math.js -s EXPORTED_RUNTIME_METHODS=ccall,cwrap

Avertissement : Hélas, le compilateur emcc n'est pas disponible sur les machines du CREMI (actuellement en Debian 10). Pour compiler ce module, il vous faudra utiliser le serveur boursouflet du CREMI, qui dispose d'un système Ubuntu plus récent (22.04) avec ce compilateur.

Vous pouvez néanmoins installer emcc sur les systèmes Debian/Ubuntu récents, comme ceci :

sudo apt install emscripten

Afin d'utiliser nos deux fonctions int_add() et int_mul() dans une page web, nous allons devoir charger le script math.js généré précédemment. Les fonctions sont alors accessibles en JavaScript via la variable globale Module, sous le nom _int_add() et _int_mul(), comme ceci :

<script>
var x = 10; var y = 20;
var result = Module._int_add(x, y);
</script>
<script src="math.js"></script>

Afin de rendre notre exemple plus interactif, nous allons utiliser des balises <input> de types number et button, pour respectivement saisir les entiers x et y et choisir la fonction add ou mul à invoquer.

Vous pouvez consulter le code de cette démo dans wasm/, et la tester en chargeant le fichier test.html. Il ne faut pas oublier de copier les fichiers math.js et math.wasm dans le même répertoire que la page web qui les utilise. En revanche, il n'est pas utile de mettre sur le web le code source math.c. De plus, il n'est pas nécessaire de recompiler ces fichiers si vous changer de machine, car JavaScript et Wasm sont entièrement portables.

Avertissement : Attention, si vous testez cette démo en local, il faut absolument utiliser le protocole http:// et non file://, ce qui provoquerait sinon un échec !

Retrouvez le code cette démo sur GitHub : https://github.com/orel33/demo-wasm.

👉 Reprenez votre page demo.html et complétez-la d'une petite fonction en C, qui calcule le terme N de la suite de Fibbonnaci.

Un peu de documentation en complémentaire :

Interface Web pour notre jeu

Voici une proposition de plan de travail pour réaliser une interface web à notre jeu :

  • Considérons la démo suivante : game-web/, qui va servir de point de départ pour votre interface web.
  • Dans votre dépôt GitLab, créez un répertoire web et recopiez les fichiers disponibles dans l'archive make-game-web.zip.
  • Étudiez le code fourni... En particulier, vous remarquerez dans le fichier demo.js le code ci-dessous qui indique d'appeler la fonction start() lorsque l'évènement onRuntimeInitialized survient, c'est-à-dire lorsque notre module Wasm est entièrement chargé. Il ne convient pas d'appeler les fonctions du Module avant.
Module.onRuntimeInitialized = () => { start(); }

function start() {
  console.log("call start routine");
  /* ... */
}
  • Dans le sous-répertoire web/src/, vous trouvez la version enseignante (V2) du jeu, sans le fichier game_tools.h.
  • Si vous le souhaitez, vous pouvez remplacer les fichiers sources par ceux de votre propre bibliothèque game. Dans ce cas supprimez les fichiers dans src/ et ajoutez des liens symboliques (ln -s) vers vos propres fichiers sources (.c et .h).
  • Si besoin, éditez le fichier wrapper.c pour définir précisément les fonctions que vous souhaitez exposer dans le module JavaScript... En particulier, il est utile de rajouter les fonctions game_random() et game_solve() pour rendre votre jeu web plus intéressant !
  • Vous pouvez ensuite compiler ce module sur le serveur boursouflet du CREMI en utilisant le Makefile fourni.
$ ssh boursouflet
$ make
emcc -I src -c wrapper.c -o wrapper.o
emcc -I src -c src/game_aux.c -o src/game_aux.o
emcc -I src -c src/game.c -o src/game.o
emcc -I src -c src/game_ext.c -o src/game_ext.o
emcc -I src -c src/game_private.c -o src/game_private.o
emcc -I src -c src/queue.c -o src/queue.o
emar rcs libgame.a src/game_aux.o src/game.o src/game_ext.o src/game_private.o src/queue.o
emcc wrapper.o libgame.a -o game.js -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=ccall,cwrap
  • Vérifiez que la démo game-web fonctionne sur le serveur web du CREMI, comme ceci : demo.
  • Renommez le fichier demo.html en game.html, et modifiez-le pour afficher la grille du jeu par défaut dans un canvas HTML. On se limitera pour commencer à un affichage simple pour une grille carrée de taille fixe, qui occupe tout le canvas.
  • Prenez ensuite en compte les événements clicks sur le canvas pour permettre à un utilisateur d'interagir et de jouer à notre jeu...
  • Pour améliorer votre jeu, ajoutez des boutons restart, undo, redo, solve et random.
  • Vous pouvez également améliorer l'affichage de votre interface web, en ajustant dynamiquement la taille du canvas en fonction de la taille de la fenêtre. Dans ce cas, il vous sera nécessaire de réagir à l'évènement resize de l'objet window...
  • Pour aller plus loin, rendez votre interface web responsive afin qu'elle s'affiche correctement à la fois sur votre écran d'ordinateur et sur votre smartphone...

Rendu Moodle : Finalement, effectuez le rendu de votre travail sur Moodle, en respectant les consignes indiquées.