Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice 03 - MEMORY

Compatible JavaScript. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 10 octobre 2015

Bonjour,

Troisième exercice de la série, cette fois nous allons nous attaquer au MEMORY, et comme vous allez pouvoir le constater les jeux les plus simples ont aussi un code des plus simples.
En lisant les retours sur le forum, je me suis tout d'abord décidé à utiliser un Framework pour simplifier l'écriture, mais je me suis rapidement rendu compte que cela reviendrait à utiliser un tank pour chasser une mouche, et quand vous allez voir le code je pense que nous seront du même avis, on va donc réserver les surcouches de langage pour la suite.

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 MEMORY c'est quoi ? (merci Wikipedia)

Le jeu se compose de paires de cartes portant des illustrations identiques. L'ensemble des cartes est mélangé, puis étalé face contre table. À son tour, chaque joueur retourne deux cartes de son choix. S'il découvre deux cartes identiques, il les ramasse et les conserve, ce qui lui permet de rejouer. Si les cartes ne sont pas identiques, il les retourne faces cachées à leur emplacement de départ.

Le jeu se termine quand toutes les paires de cartes ont été découvertes et ramassées. Le gagnant est le joueur qui possède le plus de paires.

Un jeu très simple qui, pour notre exercice, se jouera en solo.

Les pré-requis

Pour ce programme vous devez connaître :

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.

<!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>

L'habillage

J'ajoute une bordure à la balise canvas :

canvas {
    border: 1px solid black;
}

Les images

Je prépare tous mes assets, c'est à dire toutes les images prédécoupées qui vont servir à mon jeu.

Je range le tout dans le dossier “assets”, chaque image est nommée “imgX.jpg” où X correspond au numéro de l'image.

Vous noterez que j'utilise une image noire pour le dos des cartes et une blanche pour les paires déjà trouvées, mais nous allons détailler ça un peu plus bas.

Le code Javascript

Allez c'est parti pour un peu de littérature…

// variables
var canvas,ctx,posX,posY,c1,c2,i,reste;					// globales
var images, tuiles, paires;						// tableaux
 
// paramètres
var C = 	6;
var T = 	80;
var W = 	480;
var H = 	480;
var nbImg = 	20;
var dos = 	19;
var vide = 	20;
 
window.onload = function() {						// préparation du jeu
    canvas = 		document.getElementById('canvas');
    ctx = 			canvas.getContext('2d');
	posX = 			canvas.offsetLeft;
	posY = 			canvas.offsetTop;
	canvas.width = 	W;
	canvas.height = H
	loadImages();
}
 
function loadImages(){							// chargement des images
	images = [];
	for(var i=1; i<nbImg+1; i++){
		var b = new Image();
		b.src = "assets/img"+i+".jpg";
		b.onload = function() {
			images.push(this);
			if(--nbImg==0) init();
		};
	}
}
 
function init() {							// initialisation de la partie
	tuiles = 	[];
	paires = 	[];
	total = 	C*C;
	for (i=0; i<total*.5; i++){
		paires.push(i,i);
	}
	for (i=0; i<total; i++){
		var c = {};
		c.x = parseInt(i%C)*T;
		c.y = parseInt(i/C)*T;
		var r = parseInt(Math.random()*paires.length);
		c.ref = paires[r]+1;
		paires.splice(r,1);
		c.frame = dos;
		tuiles.push(c);
	}
	render();
	canvas.addEventListener("click", choisir, false);
}
 
function choisir(e){												// cliquer sur une case
 
	var c = tuiles[parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*C];				// trouve la case sur laquelle on a cliqué
 
	if(c.frame != vide) {											// si ce n'est pas une case vide
		c.frame = c.ref;										// enregistre l'image à afficher
		if (!c1){											// si aucune carte n'a été retournée
			c1 = c;											// la carte choisie est la première 
		} else if (c1 == c){										// sinon si la carte choisie est déjà retournée
			c1.frame = dos;										// masque la carte
			c1 = null;										// la première carte n'est plus sélectionnée
		} else if (!c2) {										// sinon si la deuxième carte n'est pas choisie
			c2 = c;											// la carte choisie est la deuxième carte
			if (c1.ref == c2.ref) {									// si les références des deux cartes est identique
				c1.frame = c2.frame = vide;							// supprime les deux cartes de l'affichage
				c1 = c2 = null;									// déselectionne les deux cartes
				total -=  2;									// décrémente le total des cartes
				if (total == 0)	finPartie();							// si il n'y a plus de carte à découvrir la partie est terminée
			} 
		} else {											// sinon la paire n'est pas bonne
			c1.frame = c2.frame = dos;								// masque les deux cartes
			c2 = null;										// déslectionne la deuxième carte
			c1 = c;											// enregistre la première carte
		}
	}
	render();												// mise à jour de l'affichage
}
 
function finPartie(){												// fin de partie
	alert("Fin de partie, cliquez pour rejouer.");
	init();
}
 
function render() {												// Dessine le jeu
	for(var i=0; i<tuiles.length; i++){
		ctx.drawImage(images[tuiles[i].frame-1], tuiles[i].x, tuiles[i].y);
	}
}

Etude du programme

Allez c'est parti pour une étude rapide du programme, vous êtes sensés avoir fait les deux exercices précédents, à savoir PONG et TAQUIN, il ne va donc pas y avoir grand chose à dire sur cet exercice précis.

// variables
var canvas,ctx,posX,posY,c1,c2,i,reste;					// globales
var images, tuiles, paires;						// tableaux
 
// paramètres
var C = 	6;
var T = 	80;
var W = 	480;
var H = 	480;
var nbImg = 	20;
var dos = 	19;
var vide = 	20;

La listes des variables globales utiles pour le code, celle des tableaux, et les variables que je considère comme des paramètres qui vont permettre d'adapter le jeu à volonté.
On retrouve le nombre de colonnes, la taille d'une case, la largeur et la hauteur de la zone de jeu, le nombre d'images à charger, la référence du dos des cartes et celle des paires trouvées.

Quand la page est chargée…

window.onload = function() {						// préparation du jeu	
	canvas = 		document.getElementById('canvas');	
	ctx = 			canvas.getContext('2d');
	posX = 			canvas.offsetLeft;
	posY = 			canvas.offsetTop;
	canvas.width = 	W;
	canvas.height = H;
	loadImages();
}

On prépare le jeu.
On récupère le canvas et son contexte (la zone de dessin et les outils pour dessiner), et petite nouveauté de cet exercice, on écupère le décalage de position de notre canvas.
On fixe la taille de la zone de dessine et on lance le chargement des images (autre nouveauté pour cet exercice).

function loadImages(){							// chargement des images
	images = [];
	for(var i=1; i<nbImg+1; i++){
		var b = new Image();
		b.src = "assets/img"+i+".jpg";
		b.onload = function() {
			images.push(this);
			if(--nbImg==0) init();
		};
	}
}

On se sert tout simplement d'une boucle pour charger une à une toutes les images et les placer dans un tableau.
Notez l'écouteur “onload”, il sert à savoir quand une image à fini d'être chargée, lorsque toutes les images ont été chargées on lance l'initialisation du programme.

function init() {							// initialisation de la partie
	tuiles = 	[];
	paires = 	[];
	total = 	C*C;
	for (i=0; i<total*.5; i++){
		paires.push(i,i);
	}
	for (i=0; i<total; i++){
		var c = {};
		c.x = parseInt(i%C)*T;
		c.y = parseInt(i/C)*T;
		var r = parseInt(Math.random()*paires.length);
		c.ref = paires[r]+1;
		paires.splice(r,1);
		c.frame = dos;
		tuiles.push(c);
	}
	render();
	canvas.addEventListener("click", choisir, false);
}

On vide les tableaux.
On calcule le nombre total de cases du jeu.
On crée une première boucle qui à chaque pas ajoute une paire de références dans le tableau des paires.
Puis une seconde boucle sur la totalité des pièces affichées nous permet de placer et paramètrer chacune.
Création de l'objet (carte ou c).
Position de l'objet (voir TAQUIN pour la formule).
Choisir une référence aléatoirement dans le tableau des paires pour cette carte.
Supprimer la référence choisie du tableau des paires.
Choisir la référence de l'image à afficher par défaut (ici le dos de la carte).
Ajouter cette carte au tableau des tuiles (et c'est là que je m'apperçoit que j'ai mal choisi les noms de mes tableaux, mais arrivé à ce stade vous corrigerez de vous même). Quand tous les objets ont été créés on lance le rendu graphique.
Et on termine par un écouteur souris sur le CANVAS pour déterminer sur quelle carte on vient de cliquer. Le jeu est prêt, il ne reste plus qu'à coder le moteur.

function choisir(e){
 
	var c = tuiles[parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*C];
 
	if(c.frame != vide) {
		c.frame = c.ref;
		if (!c1){
			c1 = c;
		} else if (c1 == c){
			c1.frame = dos;
			c1 = null;
		} else if (!c2) {
			c2 = c;
			if (c1.ref == c2.ref) {
				c1.frame = c2.frame = vide;
				c1 = c2 = null;
				total -=  2;
				if (total == 0)	finPartie();
			} 
		} else {
			c1.frame = c2.frame = dos;
			c2 = null;
			c1 = c;
		}
	}
	render();
}

Tout est là !
Bien que j'aie déjà tout commenté ligne par ligne dans le code, voyons rapidement comment ça marche.

On cherche tout d'abord la carte (ou case) sur laquelle on vient de cliquer (voir le TAQUIN pour la formule).
On vérifie que le joueur n'a pas cliqué sur une case vide, auquel cas on ne fait rien, si ce n'est pas le cas on enregistre l'image qu'il va falloir afficher pour cette carte. On vérifie ensuite si aucune carte n'a déjà été retournée, dans ce cas la carte sur laquelle on vient de cliquer est la première à être retourner, sinon c'est la seconde, ou dans tous les autres cas, on réinitialise les valeurs afin que la prochaine carte cliquée soit la première. Voilà c'est tout, il nous reste à mettre à jour l'affichage.

Ok mais comment sait-on que deux cartes retournées font une paire ?

Si la carte sur laquelle on vient de cliquer est bien la seconde à être retournée, on regarde tout simplement si sa référence est la même que pour la première, si c'est le cas on a une paire, on modifie les affichages et on efface les références, puis on vérifie si il reste encore des paires à découvrir, si ce n'est pas le cas le joueur gagne la partie.

function finPartie(){
	alert("Fin de partie, cliquez pour rejouer.");
	init();
}

Si la partie est terminée on affiche une alerte et on relance l'init du jeu pour la prochaine.

function render() {
	for(var i=0; i<tuiles.length; i++){
		ctx.drawImage(images[tuiles[i].frame-1], tuiles[i].x, tuiles[i].y);
	}
}

Et on termine par le rendu graphique, c'est très simple, on parcours la liste des cartes, on regarde leur référence image, et on dessinne l'image concernée à la bonne position.

Conclusion

Voilà une fois encore un jeu au code très très simple que vous n'aurez sans doute pas de difficulté à prendre en main.

Les sources