Exercice 02 - TAQUIN
Bonjour,
Cher ami lecteur, aujourd'hui nous allons apprendre à faire un jeu de TAQUIN à l'aide de quelques astuces simples. Il fait directement suite à l'exercice sur PONG ( http://forums.mediabox.fr/wiki/tutoriaux/javascript/divers/exercice_pong ) que je vous recommande grandement d'avoir lu avant de commencer celui-ci, si vous êtes débutant.
Vous pouvez également retrouver la version ActionScript pour Flash de cet exercice, ici : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/exercice_-_le_taquin
Avant de commencer, voici à quoi tout ceci va ressembler.
*pour des raisons de sécurité il n'est pas possible de vous présenter le jeu au sein d'une page du wiki, reportez-vous à la source située en bas de cette page pour voir comment ça marche.
Etude préliminaire
Tout d'abord le TAQUIN c'est quoi ? (merci Wikipedia)
Il s'agit d'un jeu solitaire en forme de damier, qui découle d'une théorie mathématique. Le jeu original est composé de 15 carreaux numérotés de 1 à 15 qui glissent dans un cadre prévu pour 16 pièces (il y a donc un emplacement vide pour permettre aux 15 pièces de coulisser). Le but du jeu est de remettre dans l'ordre les 15 carreaux à partir d'une configuration quelconque.
Le principe du jeu à été ensuite étendu à de nombreuses variantes, dont le Rubik's Cube.
Pour un jeu composé de 15 pièces (c'est important pour la suite), parmi toutes les dispositions initiales, il existe 10 461 394 944 000 dispositions dont la résolution est possible et autant dont la résolution est impossible.
Il est possible de dire à l'avance si le problème posé est soluble ou non. En effet, la configuration initiale d'un taquin est une permutation de sa configuration finale. Cette permutation est dite paire si elle peut être obtenue par un nombre pair d'échanges successifs de deux cases, adjacentes ou non, vide ou non, appelés également transpositions. On montre que cette notion ne dépend pas du choix de la suite des échanges. Elle est impaire sinon. On associe également à la case vide une parité : la case vide est paire si l'on peut se rendre de la position initiale de la case vide à la position finale en un nombre pair de déplacements, impair sinon.
Le problème sera soluble si la parité de la permutation est identique à la parité de la case vide.
Bon courage……..
Mélanger les pièces du jeu aléatoirement nous expose donc à un nombre considérable de configurations insoluble, or pour un joueur il faut impérativement que le jeu soit soluble, sinon il perd tout son attrait. De là on dispose de deux solutions, soit on fait un tirage aléatoire et on demande au programme de le résoudre avant de laisser le joueur se lancer, ce qui impose de connaître les algorithmes de résolution, que je ne connais pas…. Soit on prend en compte que ce jeu a été inventé en 1870, et qu'à cette époque il n'y avait pas d'ordinateurs pour mélanger le jeu, il fallait qu'une personne déplace les pièces de manière aléatoire avant qu'un autre joueur puisse essayer de résoudre le défi. C'est une solution non mathématique, mais qui fonctionne parfaitement et qui fera un très bon exercice pour démarrer la programmation de jeux vidéo et aborder quelques notions essentielles.
Les pré-requis
Pour ce programme vous devez connaître :
Variables et fonctions en Javascript : http://forums.mediabox.fr/wiki/tutoriaux/javascript/language/notions_base
Ecouteurs d'événements en Javascript : https://developer.mozilla.org/fr/docs/Web/API/EventTarget/addEventListener
Manipulation de tableaux en Javascript : http://www.commentcamarche.net/contents/587-javascript-les-tableaux
Exercice PONG en Javascript : http://forums.mediabox.fr/wiki/tutoriaux/javascript/divers/exercice_pong
Si vous souhaitez plus de précisions sur ces points, je vous encourage à parcourir le Wiki de Mediabox où vous trouverez de nombreux tutoriaux détaillés.
La structure
Le support principal est une page HTML classique utilisant une simple balise canvas et intégrant une feuille de style et le script du jeu.
Note à ceux qui ont lu PONG, vous commencez à voir ce que je veux dire quand je dis : “me répéter à chaque exercice” ?
<!DOCTYPE html> <html> <head> <title>Pong</title> <link rel="stylesheet" type="text/css" href="css/styles.css" /> <script type="text/javascript" src="js/jeu.js"></script> <!--[if lt IE 9]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]--> </head> <body> <canvas id="canvas">Votre navigateur ne supporte pas HTML5.</canvas> <audio id="audio" src="Assets/click.wav" ></audio> </body> </html>
Pour faire simple, dans la balise <head> on intégre la feuille de style et le script du jeu, et dans le <body> on crée une balise <canvas> qui va servir d'écran d'affichage pour le jeu. Canvas est en fait une zone de dessin, pour les flasheux c'est un peu comme un SWF, on va dessiner notre jeu dedans… Pour l'arborescence des dossiers vous faites comme vous le souhaitez, moi j'ai créé un dossier par type de fichiers, je vous recommande de le faire pour plus de clarté. J'ai pour ma part un dossier CSS dans lequel je met mon fichier styles.css, un dossier ASSETS dans lequel je met les images et autres médias, et un dossier JS dans lequel je met tout le code JS, la page HTML est à la racine.
Note pour tout le monde, dans les prochains exercice on ne vous le redira pas… on vous dira juste ce qui change.
Et ici on quelque chose qui change, j'ai ajouté une balise <audio> !!!
Je me suis dit qu'un bruitage ne ferait pas de mal, j'ai donc ajouté un fichier audio à mes Assets et fais le lien dans la balise “audio”.
On verra plus tard comment ça fonctionne, mais voici déjà un gros plus à nos jeux, du son !
L'habillage
J'ajoute une bordure à la balise canvas et je n'y affiche pas la souris :
canvas { border: 1px solid black; cursor: none; }
Les images
Je prépare tous mes assets, à savoir une grande image de la taille de mon jeu, qui servira de base au TAQUIN, un PNG tranparent qui le sert de repère de case quand on passe la souris dessus, et un son qui va se jouer quand on déplace une case.
Je vous prépare une fiche pour les formats acceptés et lus en HTML5/JS, mais en attendant du JPG à 72 Dpi, du PNG 24 bits transparent et du av 44Kh 16bits feront largement l'affaire.
Le code Javascript
Allez c'est parti pour un peu de littérature…
// charger les images du jeu var pieces = new Image(); var repereImg = new Image(); pieces.src = "Assets/pieces.jpg"; repereImg.src = "Assets/repere.png"; window.onload = function() { // récupère le canva et son contexte var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var audio = document.getElementById("audio"); // variables var W = 480; var H = 480; var T = 120; // Taille des pièces var L = H/T; // Lignes var C = W/T; // Colonnes var posX = canvas.offsetLeft; var posY = canvas.offsetTop; var repere; var stockPieces; // tableau de stockage des pièces var timer; init(); // initialisation du jeu function init() { canvas.width = W; canvas.height = H; stockPieces = []; // boucle sur le nombre de pièces for (var i=0; i<L*C;i++){ var piece = {x:i%C*T, y:parseInt(i/C)*T, width:T, height:T, place:i, depart:false, alpha:1}; stockPieces.push(piece); // ajout du conteneur au stock if(!i) { // si la pièce est la première piece.alpha=0; // elle est transparente piece.depart=true; // elle est déjà mélangée } } repere = {x:0,y:0}; timer = setInterval(melange,15); render(); canvas.addEventListener("click", choisir, false); canvas.addEventListener("mousemove", reperePos, false); } // mélanger les pièces function melange(e){ var P; var X; var Y; var E = 0; var i; var S = false; for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i].alpha==0) { P = stockPieces[i]; E = i; X = parseInt(i%C); Y = parseInt(i/C); break; } } for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i]!=P && stockPieces[i].depart==false) { var D = parseInt(Math.random()*4)+1;// choisi une direction if (D==1 && X-1>=0 && E-1>0) deplace(stockPieces[E-1]); if (D==2 && X+1<=C && E+1<stockPieces.length) deplace(stockPieces[E+1]); if (D==3 && Y+1<=L && E+L<stockPieces.length) deplace(stockPieces[E+L]); if (D==4 && Y-1>=0 && E-L>0) deplace(stockPieces[E-L]); } } repere.x = P.x; repere.y = P.y; render(); for (var i=0; i<stockPieces.length; i++) { if(!stockPieces[i].depart) return; } clearInterval(timer); } // déplacement de la pièce function deplace(P){ // recherche le clip invisible var I = 0; var V; for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i].alpha==0) { I = i; V = stockPieces[i]; } } // vérifie si la tuile est à coté et inverse les index var D = stockPieces.indexOf(P); if((parseInt(V.x/T)==C-1 && D==I+1)) return; if((parseInt(V.x/T)==0 && D==I-1)) return; if(D==I+1 || D==I+L || D==I-L || D==I-1){ stockPieces[D] = stockPieces[I]; stockPieces[I] = P; if (stockPieces[I].depart==false) stockPieces[I].depart=true; } audio.play(); gagne(); } // cliquer sur une case function choisir(e){ var id = parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*L; var P = stockPieces[id]; // recherche le clip invisible var I = 0; var V; for (var i=0; i<stockPieces.length;i++) { if(stockPieces[i].alpha==0) { I = i; V = stockPieces[i]; } } // vérifie si la tuile est à coté et inverse les index var D = stockPieces.indexOf(P); if((parseInt(I%C)==C-1 && D==I+1)) return; if((parseInt(I%C)==0 && D==I-1)) return; if(D==I+1 || D==I+L || D==I-L || D==I-1){ stockPieces[D] = stockPieces[I]; stockPieces[I] = P; if (!stockPieces[I].depart) stockPieces[I].depart=true; } audio.play(); render(); } // vérifie si le joueur a gagné function gagne(){ for (var i=0; i<stockPieces.length; i++) { if(i!=stockPieces[i].place) return; } repere.x = 0; repere.y = 0; finPartie(); } // position du repère function reperePos(e){ repere.x = parseInt((e.clientX-posX)/T)*T; repere.y = parseInt((e.clientY-posY)/T)*T; render(); } // fin de partie function finPartie(){ render(); alert("Fin de partie, cliquez pour rejouer."); init(); } // Dessine le jeu function render() { ctx.fillStyle = "rgb(256,256,256)"; ctx.fillRect(0, 0, W, H); for(var i=0; i<stockPieces.length; i++){ var p = stockPieces[i]; if(p.alpha!=0){ ctx.drawImage(pieces, p.x, p.y,p.width,p.height, parseInt(i%C)*T,parseInt(i/C)*T,p.width,p.height); } } ctx.drawImage(repereImg, repere.x,repere.y); } }
“PONG”, “pré-requis”, on va aller vite je crois
Etude du programme
Bien, ceux qui ont été attentifs et on déjà fait le premier exercice de la série : Pong (je suis pas un peu lourd à vous le rabâcher sans cesse ?), ont déjà compris pas mal de chose, mais pour les autres on va détailler un peu.
// charger les images du jeu var pieces = new Image(); var repereImg = new Image(); pieces.src = "Assets/pieces.jpg"; repereImg.src = "Assets/repere.png";
On charge les images utiles.
On va avoir besoin de deux images, une grande qui sera notre image à découvrir et qu'on appelle “pieces” puisqu'elle sera découpée en petites tuiles, et une pour le repère affiché au survol des pièces (un PNG transparent).
window.onload = function() { ... }
Quand la page est chargée…
// récupère le canva et son contexte var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var audio = document.getElementById("audio"); // variables var W = 480; var H = 480; var T = 120; // Taille des pièces var L = H/T; // Lignes var C = W/T; // Colonnes var posX = canvas.offsetLeft; var posY = canvas.offsetTop; var repere; var stockPieces; // tableau de stockage des pièces var timer; init();
On récupère le canvas et son contexte (la zone de dessin et les outils pour dessiner), et petite nouveauté, on ajoute une variable “audio”.
// initialisation du jeu function init() { canvas.width = W; canvas.height = H; stockPieces = []; // boucle sur le nombre de pièces for (var i=0; i<L*C;i++){ var piece = {x:i%C*T, y:parseInt(i/C)*T, width:T, height:T, place:i, depart:false, alpha:1}; stockPieces.push(piece); // ajout du conteneur au stock if(!i) { // si la pièce est la première piece.alpha=0; // elle est transparente piece.depart=true; // elle est déjà mélangée } } repere = {x:0,y:0}; timer = setInterval(melange,15); render(); canvas.addEventListener("click", choisir, false); canvas.addEventListener("mousemove", reperePos, false); }
On initialise le jeu.
On fixe la largeur et la hauteur de la zone de dessin.
On vide le tableau de stockage des pièces.
On fait une boucle pour créer toutes les pièces (objets) du jeu.
On place le repère de case en haut à gauche de la zone de jeu.
On lance la fonction “mélange” à intervalle de 15 millisecondes.
On lance le rendu.
Et on termine en ajoutant deux écouteurs, un pour le clic sur une pièce et l'autre pour le positionnement du repère quand on bouge la souris.
Le truc à noter ici c'est la boucle pour créer les pièces, on compte le nombre de lignes et de colonnes, ça nous donne le nombre de pièces total (le jeu est donc facilement paramètrable). On va donc créer un nouvel objet par pas de la boucle, pour le positionner on se sert de la formule suivante :
Position x : i%C*T
Position y : int(i/L)*T
“T” étant la largeur et la hauteur de chaque case.
Pour faire simple elle permet de passer de ça :
1,2,3,4,5,6,7,8,9
A ça :
1,2,3
4,5,6
7,8,9
On enregistre également sa place d'origine dans la grille ainsi formée, si il s'agit de la pièce de départ et son opacité, qui nous servirons un peu plus tard.
Il faut une case vide pour pouvoir déplacer les autres pièces. Je prend la première pièce de la liste, celle qui représente la case haut gauche de la grille, elle est considérée comme étant déjà mélangée (vous verrez pourquoi par la suite) et elle est transparente, elle existe donc bien, mais on ne la vois pas.
Notez une chose, il y a une différence entre les objets “pièces” et les images qui les réprésentent.
En fait on sépare complétement calculs (avec les objets) et affichage (à partir des résultats des calculs), mais on y reviendra plus tard.
Tout est prêt, on mélange les pièces :
// mélanger les pièces function melange(e){ var P; var X; var Y; var E = 0; var i; var S = false; for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i].alpha==0) { P = stockPieces[i]; E = i; X = parseInt(i%C); Y = parseInt(i/C); break; } } for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i]!=P && stockPieces[i].depart==false) { var D = parseInt(Math.random()*4)+1;// choisi une direction if (D==1 && X-1>=0 && E-1>0) deplace(stockPieces[E-1]); if (D==2 && X+1<=C && E+1<stockPieces.length) deplace(stockPieces[E+1]); if (D==3 && Y+1<=L && E+L<stockPieces.length) deplace(stockPieces[E+L]); if (D==4 && Y-1>=0 && E-L>0) deplace(stockPieces[E-L]); } } repere.x = P.x; repere.y = P.y; render(); for (var i=0; i<stockPieces.length; i++) { if(!stockPieces[i].depart) return; } clearInterval(timer); }
Cette méthode est appelée par un timer, elle tourne en boucle à 15 millisecondes, le programme mélange donc les pièces en continu et on ne sort de la boucle que lorsque toutes les pièces ont été mélangées.
La première boucle va nous servir à repérer la pièce maquante, elle existe mais on ne la vois pas, on cherche donc la pièce qui ne se vois pas… on enregistre son index dans le stock, et sa position dans la grille.
La seconde boucle est notre grosse astuce pour avoir un jeu soluble.
Le but est de prendre la pièce vide comme référence, de tirer une direction aléatoire (haut, bas, droite ou gauche) et de vérifier si la pièce suivante qui se trouve dans cette direction peut bouger et n'a pas déjà été mélangée. Auquel cas on la déplace.
Nous demandons donc au programme de mélanger les pièces à partir de la pièce vide, jusqu'à ce que toutes les pièces aient été mélangées, c'est ça qui nous assure qu'il est possible de résoudre le problème, le tri n'est pas fait aléatoirement, mais en fonction d'un chemin, que le joueur peut choisir de suivre ou non, mais dans tous les cas la solution existe.
Voyons comment fonctionne cette boucle.
On commence par exclure la pièce vide de la boucle, normal c'est à partir d'elle qu'on déplace les autres, on ne la mélange pas avec elle même, et on regarde si la pièce à bouger est déjà mélangée. Si tout est ok, on tire une direction aléatoirement, puis on lance une série de tests.
Selon la direction choisie, à partir de la case vide on trouve la pièce adjacente dans la grille, elle peut être dans la case immédiatement à côté, ou dans la case située au dessus ou en dessous (donc la ligne précédente, ou à la ligne suivante). Il faut également faire attention à ce que la case testée ne sorte jamais de la grille.
Si tout est ok, on essayes de déplacer la pièce, on utilise la fonction “deplace” que nous détaillerons plus loin, à laquelle on passe la référence de la pièce.
Je dois également vous mettre en garde, ce procédé fonctionne quel que soit le nombre de cases que vous avez dans votre grille, mais plus ce nombre est important plus le mélange prendra du temps, car le programme doit passer sur toutes les pièces en suivant un chemin aléatoire, au delà de 200 pièces cela peut prendre plusieurs minutes pour effectuer un mélange correct. Afin d'ajouter un petit effet visuel j'ai fait en sorte que le mélange se voie à l'écran, si vous utilisez de grandes grilles pensez à ne pas afficher cette étape et à optimiser le mélange pour que le joueur n'ai pas à attendre trop longtemps avant de jouer.
Continuons, car la fonction n'est pas encore terminée, vous vous rappelez qu'elle tourne en boucle tant que toutes les cases n'ont pas été mélangées. La boucle suivante, parcours une nouvelle fois tout le stock et regarde le paramètre “depart” de toutes les cases, si jamais une des cases n'est pas encore mélangée on stoppe la fonction à cet endroit.
Passons au déplacement des pièces :
// déplacement de la pièce function deplace(P){ // recherche le clip invisible var I = 0; var V; for (var i=0; i<stockPieces.length; i++) { if(stockPieces[i].alpha==0) { I = i; V = stockPieces[i]; } } // vérifie si la tuile est à coté et inverse les index var D = stockPieces.indexOf(P); if((parseInt(V.x/T)==C-1 && D==I+1)) return; if((parseInt(V.x/T)==0 && D==I-1)) return; if(D==I+1 || D==I+L || D==I-L || D==I-1){ stockPieces[D] = stockPieces[I]; stockPieces[I] = P; if (stockPieces[I].depart==false) stockPieces[I].depart=true; } audio.play(); gagne(); }
Lorsqu'on veut déplacer une pièce, que ce soit lorsqu'on les mélange ou lorsque le joueur clique dessus, cela se passe en deux temps.
Une première boucle sur le stock repère la pièce vide, on enregistre son index dans le stock.
On repère l'index dans le stock de la pièce passée en paramètre.
Puis on utilise une bride que je vais détailler tout de suite.
Rappelez-vous que notre stock est une liste, et non une grille. Que se passe t'il si ma pièce vide se trouve au bord d'une colonne, par exemple à droite ? Dans une grille à deux dimensions, par construction la case adjacente de droite n'existe pas, elle est en dehors du tableau. Mais dans une liste à une dimension, la case suivante existe, elle est bien dans le tableau et elle correspond à la case qui se trouve à l'autre bout de la colonne sur la ligne inférieure de la grille. Nous sommes donc obligés de le prendre en compte et de repérer si la pièce vide est au bout d'une colonne et si le déplacement à la pièce suivante ou précédente peut se faire, sinon on interdit le mouvement de la pièce.
Lorsque la pièce peut se déplacer, on intervertis simplement les index entre la pièce vide et la pièce à déplacer au sein du stock, puis refait une passe d'affichage pour le damier. Notez au passage que nous signalons à la pièce, si elle n'est pas déjà au courant, qu'elle a été mélangée. C'est important car c'est ici que chaque pièce apprend qu'elle est considérée comme étant mélangée, sans cette ligne le mélange ne finirait jamais.
A chaque déplacement on lance le buitage…
Oui, oui, il suffit de lui demander de jouer (play).
Et on termine en regardant si le joueur à gagné, mais avant nous allons regarder comment le joueur peut jouer…
// cliquer sur une case function choisir(e){ var id = parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*L; var P = stockPieces[id]; // recherche le clip invisible var I = 0; var V; for (var i=0; i<stockPieces.length;i++) { if(stockPieces[i].alpha==0) { I = i; V = stockPieces[i]; } } // vérifie si la tuile est à coté et inverse les index var D = stockPieces.indexOf(P); if((parseInt(I%C)==C-1 && D==I+1)) return; if((parseInt(I%C)==0 && D==I-1)) return; if(D==I+1 || D==I+L || D==I-L || D==I-1){ stockPieces[D] = stockPieces[I]; stockPieces[I] = P; if (!stockPieces[I].depart) stockPieces[I].depart=true; } audio.play(); render(); }
Pour jouer, le joueur doit cliquer sur des cases pour les déplacer. Rappelez-vous, dans la fonction “init()” nous avons placé un écouteur qui regarde si le joeur clique sur la zone de dessin (canvas). Au clic on lance la fonction “choisir” que voici.
On récupère l'index de la case sur laquelle on viens de cliquer, rappelez-vous (encore) que plus haut on à utilisé une formule pour passer d'une liste à une grille, ici on fait l'inverse avec :
var id = parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*L;
Pour simplifier en pseudo code :
index = entier(souris.x/T)+entier(souris.y/T)*L;
Bien, on sait sur quelle case on a cliqué, maintenant on cherche la case vide.
Puis on vérifie si les deux sont côté à côte et si c'est le cas on inverse leur position dans le stock.
Il ne faut pas oublier de mettre à jour l'affichage après avoir déplacé une pièce et éventuellement jouer un bruitage si on veut.
Le joueur à déplacé ses pièces, voyons à présent si il a gagné.
// vérifie si le joueur a gagné function gagne(){ for (var i=0; i<stockPieces.length; i++) { if(i!=stockPieces[i].place) return; } repere.x = 0; repere.y = 0; finPartie(); }
Si une pièce n'est pas à sa place de départ ce n'est pas gagné, sinon on remet le repère au départ et on lance la fonction “finPartie”.
// fin de partie function finPartie(){ render(); alert("Fin de partie, cliquez pour rejouer."); init(); }
A la fin de la partie, on met à jour l'affichage, on alerte le joueur qu'il doit cliquer pour rejouer et on relance la fonction “init” qui va se charger de réinitialiser le jeu
Mais on a pas encore tout à fait fini, il faut encore déplacer le repère de position
// position du repère function reperePos(e){ repere.x = parseInt((e.clientX-posX)/T)*T; repere.y = parseInt((e.clientY-posY)/T)*T; render(); }
On replace donc le repère de position sur la case où se trouve la souris et on met à jour l'affichage.
Et… on met à jour l'affichage.
Les curieux ont sans doute remarqué que j'ai tendance à me répéter…
// Dessine le jeu function render() { ctx.fillStyle = "rgb(256,256,256)"; ctx.fillRect(0, 0, W, H); for(var i=0; i<stockPieces.length; i++){ var p = stockPieces[i]; if(p.alpha!=0){ ctx.drawImage(pieces, p.x, p.y,p.width,p.height, parseInt(i%C)*T,parseInt(i/C)*T,p.width,p.height); } } ctx.drawImage(repereImg, repere.x,repere.y); }
On dessine d'abord tout le fond, un rectangle noir.
Puis pour chaque pièce, différente de celle qui est vide, on dessine une partie de l'image de référence.
Pour éclairer votre lanterne, quelques détails :
ctx.drawImage( pieces, // l'image à découper p.x, // position de départ de ma pièce sur x p.y, // position de départ de ma pièce sur y p.width, // largeur de ma pièce p.height, // hauteur de ma pièce parseInt(i%C)*T, // position de départ de la zone à tracer depuis l'image sur x parseInt(i/C)*T, // position de départ de la zone à tracer depuis l'image sur y p.width, // largeur de la zone à tracer p.height // hauteur de la zone à tracer );
On va donc dessinner chaque pièce l'une après l'autre en fonction de sa position dans la grille et de la zone qu'elle est sensée représenter au départ.
Et pour terminer on dessine le repère de position des pièces, et on a fini
Conclusion
Mine de rien, nous avons survolé pas mal de notions intéressantes qui vous servirons pour beaucoup de jeux : afficher une grille à partir d'une liste et assurer la conversion de l'une vers l'autre, découper une planche de sprites, jongler avec les index des tableaux, simuler un comportement…
TAQUIN apporte son lot d'astuces et nous fait réfléchir à diverses solutions pour contourner un problème (algorithme de résolution du plateau), et nous apprend également à jongler avec les grilles et les listes, ce qui est essentiel pour la suite de nos aventures.
Les sources
