Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice 10 - BEJEWELED

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

Bonjour,

Aujourd'hui on s'attaque au Bejeweled.

Tout d'abord le résultat :

*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 BEJEWELED c'est quoi ?

Ce jeu a porté de nombreux noms mais à la base il s'agit d'un jeu vidéo sur navigateur web développé par PopCap Games et sorti en 2001. Son but est ultra simple, dans un plateau composé de colonnes et de lignes, vous devez aligner verticalement ou horizontalement plus de deux pièces de même type pour les faire disparaître. Toutes les pièces se trouvant au dessus venant combler le trou laissé par les pièces retirées, et de nouvelles pièces étant générées pour combler le vide ainsi laissé par les pièces descendues. La base du jeu est donc extrêmement simple et s'agrémente de nouvelles fonctionnalités selon les versions.

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>BEJEWELED</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>
    </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”.

Le code Javascript

Nous allons travailler avec deux fichiers, comme nous l'avons fait avec l'exercice PROXIMITY, voici le fichier du jeu (“jeu.js”) :

 
// variables
var canvas, ctx, W, H, posX, posY, decalX, decalY, T, i, j, C, L, score, p, choix, rendu, marqueur, images, stock, images;
 
// importer un fichier 
function include(fileName){
	document.write("<script type='text/javascript' src='js/"+fileName+".js'></script>" );
}
include("tuile"); 
 
// quand la page est chargée
window.onload = function() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
	W = 480;
	H = 480;
	posX = canvas.offsetLeft;
	posY = canvas.offsetTop;
	canvas.width = W;
	canvas.height = H;
	decalX = 45;
	decalY = 45;
	T = 45;
	loadImages(9);
}		
 
// chargement des images
function loadImages(nbImg){
	images = [];
	for(i=1; i<nbImg+1; i++){
		var b = new Image();
		b.src = "assets/tuile"+i+".png";
		b.onload = function() {
			images.push(this);
			if(--nbImg==0) init();
		};
	}
}
 
// initialisation du jeu
function init() {	
	stock = 	[];
	supprime = 	[];
	for(i=0; i<8; i++) stock.push([]);
	marqueur = {};
	marqueur.x = -1000;
	marqueur.y = -1000;
	score = 0;
	while(true){
		for (i=0; i<8*8; i++) new Tuile(i%8,parseInt(i/8),T,decalX,decalY,stock);
		if (chercheCombis().length != 0) continue;
		if (!solutions()) continue;
		break;
	}	
	render();
	canvas.addEventListener("click", clickPiece, false);
	setInterval(main, 15);
}
 
// déplacer les pièces
function main(){
	var M = false;
	for (i=0; i<8*8; i++){
		p = stock[i%8][parseInt(i/8)];
		if (p != null)	M = p.move(M);
	}
	if(!M) changePieces();
	render();
}
 
// changer les pieces
function changePieces(){
	var c = chercheCombis();
	for (i=0; i<c.length; i++){
		for (j=0; j<c[i].length; j++){
			p = c[i][j];
			score += (c[i].length-1)*50;
			stock[p.C][p.L] = null;
			for (L=p.L-1; L>=0; L--){
				if (stock[p.C][L] != null){
					stock[p.C][L].L++;
					stock[p.C][L+1] = stock[p.C][L];
					stock[p.C][L] = null;
				}
			}
		}
	}
	for (C=0; C<8; C++){
		for (L=7; L>=0; L--) {
			if (stock[C][L] == null) {
				p = new Tuile(C,L,T,decalX,decalY,stock);
			}
		}
	}
	if (c.length == 0 && !solutions()) finPartie();
}
 
 
// choisir une pièce
function clickPiece(e){
	p = stock[parseInt((e.clientX-posX-decalX)/T)][parseInt((e.clientY-posY-decalY)/T)];
	marqueur.x = -1000;
	marqueur.y = -1000;
	if (choix == null){
		marqueur.x = p.x;
		marqueur.y = p.y;
		choix = p;
	} else {
		if (choix == p) permute(choix,p);
		else if (choix.L == p.L && Math.abs(choix.C-p.C) == 1) permute(choix,p);
		else if (choix.C == p.C && Math.abs(choix.L-p.L) == 1) permute(choix,p);
		else {
			marqueur.x = p.x;
			marqueur.y = p.y;
			choix = p;
		}
	}
	render();
}
 
 
// permuter les pièces
function permute(p1,p2){
	choix = null;
	permutePieces(p1,p2);
	if (chercheCombis().length == 0) permutePieces(p1,p2);
}
function permutePieces(p1,p2){
	C = p1.C;
	L = p1.L;
	p1.C = p2.C;
	p1.L = p2.L;
	p2.C = C;
	p2.L = L;
	stock[p1.C][p1.L] = p1;
	stock[p2.C][p2.L] = p2;
}
 
// cherche les combinaisons
function chercheCombis(){
	var liste = [];
	var combi;
	for (L=0; L<8; L++){
		for (C=0; C<8; C++){
			combi = verifLignes(C,L);
			if (combi.length > 2){
				liste.push(combi);
				C += combi.length-1;
			}
			combi = verifColonnes(C,L);
			if (combi.length > 2){
				liste.push(combi);
				L += combi.length-1;
			}
		}
	}
	return liste;
}
 
// vérifie les lignes
function verifLignes(C,L){
	var combi = [stock[C][L]];
	for (i=1; i+C<8; i++)	{
		if (stock[C][L].type == stock[C+i][L].type)	combi.push(stock[C+i][L]);
		else return combi;
	}
	return combi;
}
// vérifie les Colonnes
function verifColonnes(C,L){
	var combi = [stock[C][L]];
	for (i=1; i+L<8; i++) {
		if (stock[C][L].type == stock[C][L+i].type) combi.push(stock[C][L+i]);
		else return combi;
	}
	return combi;
}
 
// cherche une solution
function solutions() {
	for (C=0; C<8; C++){
		for (L=0; L<8; L++){
			if (trouveCombis(C, L, [1,0], [[-2,0],[-1,-1],[-1,1],[2,-1],[2,1],[3,0]])) return true;// horizontal deux + un
			if (trouveCombis(C, L, [2,0], [[1,-1],[1,1]])) return true;// horizontal milieu
			if (trouveCombis(C, L, [0,1], [[0,-2],[-1,-1],[1,-1],[-1,2],[1,2],[0,3]])) return true;// vertical deux + un
			if (trouveCombis(C, L, [0,2], [[-1,1],[1,1]])) return true;// vertical milieu
		}
	}
	return false;// pas de solution
}	
 
// trouve les combinaisons	
function trouveCombis(C,L, obligatoire, complement) {
 
	var type = stock[C][L].type;
 
	// y a t'il la pièce obligatoire pour créer une combi
	if (!compareType(C+obligatoire[0], L+obligatoire[1], type)) return false; 
 
	// et au moins une pièce pour faire le complément
	for(i=0;i<complement.length;i++) {
		if (compareType(C+complement[i][0], L+complement[i][1], type)) return true;
	}
	return false;
}	
 
// compare les types des pièces
function compareType(C,L,type){
	if (C<0 || C>7 || L<0 || L>7) return false;
	return (stock[C][L].type == type);
}
 
// fin de partie
function finPartie(){
	render();
	alert("Plus de combinaisons possibles, cliquez pour rejouer.");
	init();
}
 
// Dessine le jeu
function render() {	
	ctx.fillStyle = "rgb(51,51,51)";
	ctx.fillRect(0, 0, W, H);
	for(var i=0; i<stock.length; i++){
		for(var j=0; j<stock[0].length; j++){
			ctx.drawImage(images[7], stock[i][j].x, stock[i][j].y);
			ctx.drawImage(images[stock[i][j].frame-1], stock[i][j].x, stock[i][j].y);
		}
	}
	ctx.drawImage(images[8], marqueur.x, marqueur.y);
	ctx.fillStyle = "white";
	ctx.font = "16px Arial";
	ctx.textAlign = "right";
	ctx.fillText(score + " ", W-40, 20);
}

Et voici le second fichier, celui qui représente les tuiles (“tuile.js”) :

// l'objet Tuile
function Tuile(C,L,T,decalX,decalY,stock){
	this.x = 	C*T+decalX;
	this.y = 	L*T-T*5+decalY;
	this.C = 	C;
	this.L = 	L;
	this.type = 	parseInt(Math.random()*7)+1
	this.frame = 	this.type;
	stock[C][L] = 	this;
 
	this.move = function(M){
		if (this.y < this.L*T+decalY) this.y +=  15, M = true;
		if (this.y > this.L*T+decalY) this.y -=  15, M = true;
		if (this.x < this.C*T+decalX) this.x +=  15, M = true;
		if (this.x > this.C*T+decalX) this.x -=  15, M = true;
		return M;
	}
}

Etude du programme

Passons rapidement sur les choses déjà vues lors des précédents exercices.

// variables
var canvas, ctx, W, H, posX, posY, decalX, decalY, T, i, j, C, L, score, p, choix, rendu, marqueur, images, stock, images;
 
// importer un fichier 
function include(fileName){
	document.write("<script type='text/javascript' src='js/"+fileName+".js'></script>" );
}
include("tuile"); 
 
// quand la page est chargée
window.onload = function() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
	W = 480;
	H = 480;
	posX = canvas.offsetLeft;
	posY = canvas.offsetTop;
	canvas.width = W;
	canvas.height = H;
	decalX = 45;
	decalY = 45;
	T = 45;
	loadImages(9);
}		
 
// chargement des images
function loadImages(nbImg){
	images = [];
	for(i=1; i<nbImg+1; i++){
		var b = new Image();
		b.src = "assets/tuile"+i+".png";
		b.onload = function() {
			images.push(this);
			if(--nbImg==0) init();
		};
	}
}

Les variables globales, la préparation du jeu, le chargement des images.
Deux nouvelles variables sont apparues, “decalX” et “decalY”, elles servent simplement de marge pour placer les pièces sur la zone de jeu.

// initialisation du jeu
function init() {	
	stock = 	[];
	supprime = 	[];
	for(i=0; i<8; i++) stock.push([]);
	marqueur = {};
	marqueur.x = -1000;
	marqueur.y = -1000;
	score = 0;
	while(true){
		for (i=0; i<8*8; i++) new Tuile(i%8,parseInt(i/8),T,decalX,decalY,stock);
		if (chercheCombis().length != 0) continue;
		if (!solutions()) continue;
		break;
	}	
	render();
	canvas.addEventListener("click", clickPiece, false);
	setInterval(main, 15);
}

L'initialisation du jeu, on commence par vider les tableaux.
Contrairement à d'habitude, nous n'allons pas travailler avec une liste simple mais avec un tableau à deux dimensions pour générer notre grille. Il est tout à fait possible de travailler avec une simple liste, mais dans ce cas précis il me semble plus facile d'ajouter une dimension à notre tableau principale, ceci va simplifier les calculs. On commence donc par faire une boucle sur 8 lignes et on rempli notre stock de tableaux vides, un tableau par ligne qui contiendra chaque colonne de la ligne.

On crée ensuire un objet “marqueur”, il ne sert qu'à indiquer la picèce choisir par le joueur.
On initialise le score et on démarre une boucle un peu spéciale.

Je crée une boucle infinie avec l'instruction “while” et le paramètre “true”, l'expression “tant que vrai” semble incongrue, mais en fait elle permet de boucler tant que rien ne permet de sortir de la boucle. Et dans cette boucle je vais créer le sprite de rendu, boucler sur toutes les pièces du plateau de jeu et créer chaque tuile à sa position, puis vérifier si une combinaison existe, si c'est le cas on ne cherche pas à lire la suite de la boucle et on recommence, c'est qu'il existe des pièces déjà positionnées pour former une combinaison, et on ne commence pas le jeu avec une combinaison existante. Si aucune combinaison n'existe, je vais rechercher les solutions possibles, c'est à dire si en déplaçant une pièce le joueur peut former une combinaison, si aucune solution n'est trouvée alors le joueur ne pourra pas jouer et le plateau est invalide, du coup là aussi on ne cherche pas à lire la suite et on recommence la boucle. Une fois qu'on à trouvé une disposition où il n'existe pas de combinaison mais qu'une solution est possible on stoppe la boucle avec l'instruction “break” et notre plateau de jeu est prêt. Nous reviendrons plus tard sur la manière de trouver les combinaisons et les solutions, pour le moment terminons de préparer le jeu.

On lance le rendu et on place l'écouteur d'événement souris sur le canvas.
On termine par la boucle principale, cadencée à 15 millisecondes.

// déplacer les pièces
function main(){
	var M = false;
	for (i=0; i<8*8; i++){
		p = stock[i%8][parseInt(i/8)];
		if (p != null)	M = p.move(M);
	}
	if(!M) changePieces();
	render();
}

Cette fonction à pour seul objectif de déplacer les pièces quand elles en ont besoin, c'est à dire à tout moment à partir du moment où une pièce n'est pas à sa place définitive. On commence par déclarer que les pièces peuvent plus bouger à l'aide du Boolean “M”. Puis on boucle sur toutes les pièces du plateau, si la pièce existe on lui demande si elle peut se déplacer, et elle se déplace le cas échéant. Enfin, après le déplacement des pièces, on vérifie si il existe des combinaisons et on lance le rendu graphique.

Il est temps pour nous de regarder de plus près notre objet “Tuile”.

// l'objet Tuile
function Tuile(C,L,T,decalX,decalY,stock){
	this.x = 	C*T+decalX;
	this.y = 	L*T-T*5+decalY;
	this.C = 	C;
	this.L = 	L;
	this.type = 	parseInt(Math.random()*7)+1
	this.frame = 	this.type;
	stock[C][L] = 	this;
 
	this.move = function(M){
		if (this.y < this.L*T+decalY) this.y +=  15, M = true;
		if (this.y > this.L*T+decalY) this.y -=  15, M = true;
		if (this.x < this.C*T+decalX) this.x +=  15, M = true;
		if (this.x > this.C*T+decalX) this.x -=  15, M = true;
		return M;
	}
}

C'est de la POO, on se sert d'une Classe comme moule pour créer nos tuiles.
Sa position dans le canvas, sa position dans la grille, son type et l'image à afficher, et on la range à sa place dans le stock (passé en paramètre).

La méthode “move” de notre objet vérifie si l'objet doit se déplacer.
Si c'est le cas on le déplace et on renvoie l'information au programme principal.

// changer les pieces
function changePieces(){
	var c = chercheCombis();
	for (i=0; i<c.length; i++){
		for (j=0; j<c[i].length; j++){
			p = c[i][j];
			score += (c[i].length-1)*50;
			stock[p.C][p.L] = null;
			for (L=p.L-1; L>=0; L--){
				if (stock[p.C][L] != null){
					stock[p.C][L].L++;
					stock[p.C][L+1] = stock[p.C][L];
					stock[p.C][L] = null;
				}
			}
		}
	}
	for (C=0; C<8; C++){
		for (L=7; L>=0; L--) {
			if (stock[C][L] == null) {
				p = new Tuile(C,L,T,decalX,decalY,stock);
			}
		}
	}
	if (c.length == 0 && !solutions()) finPartie();
}

Lorsque les pièces ont bougé, quelle que soit la cause de ce mouvement, il faut vérifier si la nouvelle disposition engendre des combinaisons et auquel cas modifier le plateau de jeu en conséquence.

Je commence donc par regarder si une combinaison existe, si c'est le cas je boucle sur les pièces qui forment cette combinaison, je vérifie que le plateau de jeu contient bien les pièces impactées par la combinaison, je met à jour le score (50 point par pièce), puis je retire du plateau de jeu et du stock chaque pièce qui compose la combinaison, notez que je ne retire pas l'emplacement de la pièce du stock, ce dernier est invariable, j'indique simplement que cet emplacement est désormais vide (null).

Une fois la pièce retirée du plateau, je regarde si des pièces existent juste au dessus, si c'est le cas les pièces descendent, on met à jour le stock et les références des pièces.

Lorsque toutes les pièces ont été déplacées, supprimées et nettoyées, on se charge de recréer les nouvelles pièces, pour cela on boucle sur toutes les colonnes du plateau de jeu, on part de la ligne la plus basse de chaque colonne, on regarde si on trouve un emplacement vide pour cette case dans le stock et si c'est le cas on recrée une nouvelle pièce pour l'emplacement vide (qui se place automatiquement au dessus du plateau de jeu et donc descend immédiatement à son nouvel emplacement).

Que les pièces aient bougé ou pas, si il n'existe pas de combinaison ni de solution, le plateau ne propose plus de possibilité de jeu et la partie est terminée (ou le plateau est réinitialisé si vous préférez gérer le jeu ainsi).

Avant d'étudier la méthode pour trouver des combinaisons ou des solutions, continuons à nous pencher sur les actions possibles sur les pièces, et notamment lorsque le joueur essayes de permuter deux pièces pour former une combinaison.

// choisir une pièce
function clickPiece(e){
	p = stock[parseInt((e.clientX-posX-decalX)/T)][parseInt((e.clientY-posY-decalY)/T)];
	marqueur.x = -1000;
	marqueur.y = -1000;
	if (choix == null){
		marqueur.x = p.x;
		marqueur.y = p.y;
		choix = p;
	} else {
		if (choix == p) permute(choix,p);
		else if (choix.L == p.L && Math.abs(choix.C-p.C) == 1) permute(choix,p);
		else if (choix.C == p.C && Math.abs(choix.L-p.L) == 1) permute(choix,p);
		else {
			marqueur.x = p.x;
			marqueur.y = p.y;
			choix = p;
		}
	}
	render();
}

A ce stade le joueur vient de cliquer sur une pièce pour la sélectionner.
On récupère la pièce cliquée, on masque le marqueur puis on vérifie que le “choix” n'est pas “null”.
La variable “choix” indique si le joueur à déjà sélectionné une pièce auquel cas la pièce cliquée est celle avec laquelle permuter la première, sinon c'est la première pièce. Si il s'agit de la première pièce sur laquelle le joueur clique, on place le marqueur sur la pièce et on l'affiche, puis on enregistre la pièce sélectionnée. Si il s'agit de la seconde pièce sur laquelle le joueur clique, si il s'agit de la même pièce on permute la pièce sur elle même (vous verrez pourquoi par la suite), sinon on regarde si les pièces sont à côté l'une de l'autre et sur la même ligne ou la même colonne, auquel cas on permute les deux pièces. Dernière option, le joueur a choisi une première pièce et clique sur une seconde pièce qui ne se trouve pas à côté, le coup n'est pas réglementaire, on choisi donc de conserver la dernière pièce choisie comme première pièce.

// permuter les pièces
function permute(p1,p2){
	choix = null;
	permutePieces(p1,p2);
	if (chercheCombis().length == 0) permutePieces(p1,p2);
}
function permutePieces(p1,p2){
	C = p1.C;
	L = p1.L;
	p1.C = p2.C;
	p1.L = p2.L;
	p2.C = C;
	p2.L = L;
	stock[p1.C][p1.L] = p1;
	stock[p2.C][p2.L] = p2;
}

Voici deux petites fonction qui permettent de permuter les pièces lorsque le joueur a fait son choix et que le coup est régulier. La première fonction vide le choix de la pièce en cours, permute les deux pièces choisies et vérifie si la nouvelle disposition permet de faire une combinaison, si ce n'est pas le cas on permute à nouveau les pièces pour les remettre à leur position d'origine.

La seconde fonction a pour rôle de réellement permuter les pièces, pour cela on échange simplement les références des pièces (colonne et ligne) et leur position dans le stock.

Bien, toutes les interactions des pièces sont faites, reste à présent à s'occuper des combinaisons et des solutions possible, on commence par regarder comment trouver une combinaison.

// cherche les combinaisons
function chercheCombis(){
	var liste = [];
	var combi;
	for (L=0; L<8; L++){
		for (C=0; C<8; C++){
			combi = verifLignes(C,L);
			if (combi.length > 2){
				liste.push(combi);
				C += combi.length-1;
			}
			combi = verifColonnes(C,L);
			if (combi.length > 2){
				liste.push(combi);
				L += combi.length-1;
			}
		}
	}
	return liste;
}

Pour trouver une combinaison valable, on va vérifier toutes les cases du plateau de jeu, pour chaque case on va vérifier si pour la ligne entière il existe plus de deux pièces de même type alignées, si c'est le cas on enregistre la combinaison dans la liste puis on passe tout de suite aux colonnes se trouvant après la combinaison et on continue la boucle. On fait la même chose avec toutes les lignes, puis lorsque toutes les lignes et colonnes ont été vérifiées, on se retrouve avec une liste de combinaisons réunissant au moins trois pièces chacune, on sait donc à présent quelles sont les pièces à retirer du plateau.

Voyons comment chaque ligne et chaque colonne est vérifiée pour trouver des combinaisons.

// vérifie les lignes
function verifLignes(C,L){
	var combi = [stock[C][L]];
	for (i=1; i+C<8; i++)	{
		if (stock[C][L].type == stock[C+i][L].type)	combi.push(stock[C+i][L]);
		else return combi;
	}
	return combi;
}
// vérifie les Colonnes
function verifColonnes(C,L){
	var combi = [stock[C][L]];
	for (i=1; i+L<8; i++) {
		if (stock[C][L].type == stock[C][L+i].type) combi.push(stock[C][L+i]);
		else return combi;
	}
	return combi;
}

Ces deux fonctions sont identiques, l'une vérifie les colonnes et l'autres les lignes, je ne vais donc en détailler qu'une seule. Lorsque je veux vérifier une ligne par exemple, je crée un tableau provisoire dans lequel je stocke la pièce à partir de la quelle je veux commencer les tests, rappelez-vous que la fonction “chercheCombis” va commencer la vérification à partir de la colonne 0. Je vérifie une ligne, donc je dois en fait vérifier chaque colonne de la ligne, je fais donc une boucle sur toutes les colonnes de la ligne, en partant de la pièce actuellement testée. Dans cette boucle, je compare le type de la pièce de référence avec celui de la pièce se trouvant dans la colonne suivante, si les deux concordent j'ajoute la nouvelle pièce à la combinaison, sinon je renvoie la combinaison et je stoppe en même temps la boucle. Dans tous les cas, lorsque la vérification de toute la colonne est terminée, je renvoie la combinaison trouvée.

Voyons à présent comment vérifier si une solution existe.

// cherche une solution
function solutions() {
	for (C=0; C<8; C++){
		for (L=0; L<8; L++){
			if (trouveCombis(C, L, [1,0], [[-2,0],[-1,-1],[-1,1],[2,-1],[2,1],[3,0]])) return true;// horizontal deux + un
			if (trouveCombis(C, L, [2,0], [[1,-1],[1,1]])) return true;// horizontal milieu
			if (trouveCombis(C, L, [0,1], [[0,-2],[-1,-1],[1,-1],[-1,2],[1,2],[0,3]])) return true;// vertical deux + un
			if (trouveCombis(C, L, [0,2], [[-1,1],[1,1]])) return true;// vertical milieu
		}
	}
	return false;// pas de solution
}
 
// trouve les combinaisons	
function trouveCombis(C,L, obligatoire, complement) {
 
	var type = stock[C][L].type;
 
	// y a t'il la pièce obligatoire pour créer une combi
	if (!compareType(C+obligatoire[0], L+obligatoire[1], type)) return false; 
 
	// et au moins une pièce pour faire le complément
	for(i=0;i<complement.length;i++) {
		if (compareType(C+complement[i][0], L+complement[i][1], type)) return true;
	}
	return false;
}	
 
// compare les types des pièces
function compareType(C,L,type){
	if (C<0 || C>7 || L<0 || L>7) return false;
	return (stock[C][L].type == type);
}

Trois petites fonction qui travaillent ensemble.

La première effectue une boucle sur toutes les cases du plateau de jeu et regarde si une combinaison existe avec cette pièce.

La seconde regarde si le type de la pièce se trouvant dans cette case correspond au type de la pièce se trouvant soit dans la colonne ou la ligne suivante, soit dans la colonne ou la ligne se trouvant à deux cases d'elle.

La troisième vérifie que l'index choisi pour le test ne sort pas des limites du plateau de jeu et renvoie la valeur de la comparaison des deux types.

// fin de partie
function finPartie(){
	render();
	alert("Plus de combinaisons possibles, cliquez pour rejouer.");
	init();
}
 
// Dessine le jeu
function render() {	
	ctx.fillStyle = "rgb(51,51,51)";
	ctx.fillRect(0, 0, W, H);
	for(var i=0; i<stock.length; i++){
		for(var j=0; j<stock[0].length; j++){
			ctx.drawImage(images[7], stock[i][j].x, stock[i][j].y);
			ctx.drawImage(images[stock[i][j].frame-1], stock[i][j].x, stock[i][j].y);
		}
	}
	ctx.drawImage(images[8], marqueur.x, marqueur.y);
	ctx.fillStyle = "white";
	ctx.font = "16px Arial";
	ctx.textAlign = "right";
	ctx.fillText(score + " ", W-40, 20);
}

On termine par la fonction de fin de partie, puis on trace toutes les tuiles ainsi que le repère, et on termine par le score.

Conclusion

Contrairement à ce que l'on pourrait penser, “Bejeweled” est très simple à coder, ce jeu permet néanmoins de mettre en pratique quelques algorithmes sympas pour retrouver des combinaisons ou des solutions sans un plateau de jeu. Pour ce coup là j'ai choisi de travailler avec des tableaux à deux dimensions et de ne pas forcément pousser le nettoyage et la compression du code en utilisant des raccourcis, le programme est assez court et cela vous évite des manipulations et des formules inutiles à ce stade, et utiliser une liste ou un tableau 2D n'a que peu d'influence sur le comportement du programme et son optimisation. Bien sur vous n'avez qu'une base, comme toujours, à vous de l'agrémenter avec tout un tas d'effets, de bonus, de multiplicateurs en fonction du nombre de pièces dans une combinaison, d'inverseurs, d'indicateurs de combinaisons si le joueur met trop de temps, de pièces spéciales comme des bombes, etc…

Les sources