Bejeweled

Game engine experiences
Open source code

Présentation

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.

Le code Javascript de la classe 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);
}

Le code Javascript de la classe tuiles.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;
	}
}

A retenir

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…

Commentaires

avatar
  S’abonner  
S'abonner
Facebook Google Linked Skype Twitter
© 2019 Cmarzin - Tous droits réservés | SIRET: 483 511 101 00030 | Mentions légales