Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice pratique : le MEMORY

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 12 janvier 2013

Bonjour,

Le jeu du jour sera un Memory ;-)

Tout d'abord le résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Les sources sont disponibles en fin d'exercice.

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

Je vous recommande fortement d'avoir au moins lu les exercices suivants : PONG, SNAKE, TAQUIN Les exercices sont courts mais de nombreuses astuces y sont proposées.

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.

Le code

Voyons d'abord tout le code d'un coup.

var C:int = 6;
var T:int = 80;
var reste:int = 0;
var stock:Array = [];
var i:int;
var c1:Cartes;
var c2:Cartes;
 
// interface
var panneaux:Panneaux = new Panneaux();
panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init);
panneaux.buttonMode = true;
addChild(panneaux);
 
function init(e:Event):void{
	removeChild(panneaux);
	for (i=0; i<C*C*.5; i++){
		stock.push(i);
		stock.push(i);
	}
	for (i=0; i<C*C; i++){
		var c:Cartes = new Cartes();
		c.x = int(i%C)*T;
		c.y = int(i/C)*T;
		var r:int = Math.random()*stock.length;
		c.ref = stock[r];
		stock.splice(r,1);
		c.addEventListener(MouseEvent.CLICK,choisir);
		c.buttonMode = true;
		addChild(c);
		reste++;
	}
}
 
function choisir(e:MouseEvent){
	var c:Cartes = (e.target as Cartes);
	if (c1 == null){
		c1 = c;
		c.gotoAndStop(c.ref+2);
	} else if (c1 == c){
		c1.gotoAndStop(1);
		c1 = null;
	} else if (c2 == null) {
		c2 = c;
		c.gotoAndStop(c.ref+2);
		if (c1.ref == c2.ref) {
			c1.removeEventListener(MouseEvent.CLICK,choisir);
			c2.removeEventListener(MouseEvent.CLICK,choisir);
			removeChild(c1);
			removeChild(c2);
			c1 = null;
			c2 = null;
			reste -=  2;
			if (reste == 0)	finPartie(3);
		} 
	} else {
		if (c1 != null)	c1.gotoAndStop(1);
		if (c2 != null)	c2.gotoAndStop(1);
		c1 = null;
		c2 = null;
		c1 = c;
		c1.gotoAndStop(c.ref+2);
	}
}
 
function finPartie(G:int):void{
	addChild(panneaux);
	panneaux.gotoAndStop(G);
}

Etude du programme

Comme d'habitude j'utilise la bibliothèque de Flash pour gérer les graphismes, ce qui me permet d'alléger le code et de ne conserver que ce qui est utile pour l'exercice, si vous n'utilisez pas Flash voici les modifications à faire dans le programme :

Panneaux :

  • un clip qui regroupe tous les panneaux d'interface
  • un panneau différent par frame

Cartes :

  • un clip qui regroupe toutes les cartes
  • une carte sur chaque frame
  • la première frame représente le dos des cartes

Allez c'est parti pour l'étude pas à pas :

var C:int = 6;
var T:int = 80;
var reste:int = 0;
var stock:Array = [];
var i:int;
var c1:Cartes;
var c2:Cartes;
 
// interface
var panneaux:Panneaux = new Panneaux();
panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init);
panneaux.buttonMode = true;
addChild(panneaux);

Je commence par déclarer toutes mes variables globales et tableaux, puis je pose l'interface, notons simplement :

C = nombre de colonnes et de lignes
T = largeur et hauteur des cartes
reste = le nombre de cartes restant à découvrir
stock = le tableau regroupant les paires de cartes possibles
i = pointeur qui sert dans diverses boucles
c1 = la première carte découverte
c2 = la deuxième carte découverte

Le jeu étant carré il y a autant de lignes que de colonnes, pensez à ajouter une hauteur séparée si vous changez la forme de la grille.

function init(e:Event):void{
	removeChild(panneaux);
	for (i=0; i<C*C*.5; i++){
		stock.push(i);
		stock.push(i);
	}
	for (i=0; i<C*C; i++){
		var c:Cartes = new Cartes();
		c.x = int(i%C)*T;
		c.y = int(i/C)*T;
		var r:int = Math.random()*stock.length;
		c.ref = stock[r];
		stock.splice(r,1);
		c.addEventListener(MouseEvent.CLICK,choisir);
		c.buttonMode = true;
		addChild(c);
		reste++;
	}
}

Comme pour la plupart des exercices que je propose, la première chose à faire est d'initialiser le jeu. La première boucle sert à créer des paires de cartes, le jeu étant basé sur le principe des paires à découvrir. Je boucle sur le nombre de cartes à afficher divisé par deux, je sais que j'ai C*C cartes en tout à afficher (le nombre de lignes multiplié par le nombre de colonnes). En divisant le nombre de colonnes par deux j'obtiens le nombre de paires disponibles. Pour chaque itération de la boucle je viens placer deux références identiques dans le stock, il s'agit de ma paire de cartes.

La seconde boucle me permet de créer toutes les cartes, de les placer, de les afficher et d'y ajouter un écouteur. D'habitude je ne place pas un écouteur sur chaque objet d'une grille, mais un écouteur global qui regarde où j'ai cliqué dans la grille. Mais pour cet exercice j'ai choisi de modifier un peu la donne de départ pour vous montrer ce que cela impose de placer autant d'écouteurs que d'objets, on y reviendra par la suite. Notez que pour chaque carte j'enregistre la référence de la carte, ceci me permettra de retrouver les paires en cours de jeu. Le tirage des références est aléatoire par rapport au nombre d'index du stock, a chaque tirage je retire l'index du stock correspondant. Enfin, j'incrémente le nombre de cartes restantes à chaque itération de la boucle, en fait c'est un peu inutile car je connaît le nombre de cartes total distribuées, C*C, mais ça revient exactement au même.

Bien, le jeu est prêt, reste à savoir comment jouer.

function choisir(e:MouseEvent){
	var c:Cartes = (e.target as Cartes);
	if (c1 == null){
		c1 = c;
		c.gotoAndStop(c.ref+2);
	} else if (c1 == c){
		c1.gotoAndStop(1);
		c1 = null;
	} else if (c2 == null) {
		c2 = c;
		c.gotoAndStop(c.ref+2);
		if (c1.ref == c2.ref) {
			c1.removeEventListener(MouseEvent.CLICK,choisir);
			c2.removeEventListener(MouseEvent.CLICK,choisir);
			removeChild(c1);
			removeChild(c2);
			c1 = null;
			c2 = null;
			reste -=  2;
			if (reste == 0)	finPartie(3);
		} 
	} else {
		if (c1 != null)	c1.gotoAndStop(1);
		if (c2 != null)	c2.gotoAndStop(1);
		c1 = null;
		c2 = null;
		c1 = c;
		c1.gotoAndStop(c.ref+2);
	}
}

Chaque carte dispose d'un écouteur, lorsque le joueur clique sur une carte il y a plusieurs options possibles.

Je commence par regarder si la carte est la première à être retournée, si la carte 1 n'a pas de valeur c'est que le joueur n'a pas encore découvert de carte, a ce moment là la carte 1 prend la valeur de la carte que le joueur vient de retourner et on affiche l'image correspondante. Notez que l'image correspond à une frame de l'objet “Cartes”, que la première frame de cet objet représente le dos des cartes et que notre tableau de référence commence à 0, donc pour trouver la bonne image à afficher je dois ajouter 2 à la référence de la carte. J'aurais très bien pu ajouter 2 dès le départ, au moment où j'initialise les cartes, cela n'a pas d'incidence ici.

Sinon, si la première carte à déjà été retournée et que c'est la même que celle sur laquelle le joueur à cliqué, la carte est de nouveau masquée et la carte 1 n'a plus de valeur. Ceci permet de ne retourner qu'une carte et d'en choisir une autre.

Sinon, si la première carte à été retournée et le joueur clique sur une autre carte, et que la carte 2 n'a pas encore de valeur, la carte 2 correspond à la carte sur laquelle le joueur à cliqué et on affiche la bonne image. Puis on vérifie tout de suite si les références des deux cartes (carte 1 et carte 2) sont identiques, si c'est le cas c'est que le joueur est tombé sur une paire, on retire donc les écouteurs de chaque carte, on les supprime de l'affichage et on passe les deux cartes de références (carte 1 et carte 2) à “null” (on considère alors que aucune carte n'est à présent retournée), puis on retire deux carte du décompte des cartes restantes, si il ne reste plus de carte à retourner la partie est terminée. Notez qu'ici je retire les écouteurs de chaque carte, ceci n'arriverai pas si je travaillait comme d'habitude avec un seul écouteur global, mais si je tenais à vous montrer cette manipulation c'est pour vous parler très brièvement du “ramasse miettes” (ou Garbage Collector). On ne va pas trop s'étendre dessus mais sachez que le “ramasse miettes” est une fonction interne du lecteur Flash qui a pour but de supprimer de la mémoire tout ce qui n'est pas utile. Il passe un peu quand il veut et son fonctionnement reste obscur, cependant ne sont éligibles à la suppression mémoire que les objets n'ayant aucune référence, c'est à dire des objets qui ne sont pas référencés dans un tableau, ni dans une variable, et qui n'ont pas d'écouteur. C'est pour ça que je dois retirer les écouteurs des objets avant de les passer à “null”, sinon le ramasse miette ne les prendrait jamais en compte. Ici ça ne joue pas vraiment, le jeu est trop simple et en plus on se ressert tout le temps de ces objets, mais dans le cas où vous auriez de nombreux objets à gérer cela deviendrait un impératif. C'est pour cette raison (entre autres) que lorsque je le peut j'utilise plutôt un écouteur global et non un écouteur par objet, cela me fait ça de moins à penser lorsque je souhaite détruire un objet.

Dernière option possible, les deux cartes ont été découvertes mais le joueur n'a pas trouvé de paire, dans ce cas on retourne face cachée les deux cartes, on considère que aucune carte n'est plus retournée, et on affiche la nouvelle carte sur laquelle le joueur viens de cliquer.

function finPartie(G:int):void{
	addChild(panneaux);
	panneaux.gotoAndStop(G);
}

La partie est terminée, on affiche le panneau de victoire.

Conclusion

Voilà, une nouvelle fois un jeu très simple, facile à réaliser en très peu de lignes de code. Il m'a donné l'occasion de vous parler du “ramasse miettes”, cela peut paraître anodin mais c'est très important pour assurer l'optimisation de vos jeux. Nous y reviendrons plus en détail dans un autre exercice, mais vous devez d'ores et déjà comprendre qu'il s'agit là d'un élément clé pour gérer les ressources mémoire que prend votre programme.

Les sources

memory_mb_v2.fla version CS6
memory_mb_cs5_v2.fla version CS5