Démineur

Game engine experiences
Open source code

Présentation

Le démineur est un jeu de réflexion dont le but est de localiser des mines cachées dans un champ virtuel avec pour seule indication le nombre de mines dans les zones adjacentes. Le champ de mine est représenté par une grille, qui peut avoir différentes formes : deux ou trois dimensions, pavage rectangulaire ou non, etc.

Chaque case de la grille peut soit cacher une mine, soit être vide. Le but du jeu est de découvrir toutes les cases libres sans faire exploser les mines, c’est-à-dire sans cliquer sur les cases qui les dissimulent.

Lorsque le joueur clique sur une case libre et que toutes les cases adjacentes le sont également, une case vide est affichée. Si en revanche au moins l’une des cases avoisinantes contient une mine, un chiffre apparaît, indiquant le nombre de cases adjacentes contenant une mine. En comparant les différentes informations récoltées, le joueur peut ainsi progresser dans le déminage du terrain. S’il se trompe et clique sur une mine, il a perdu.

Nous avons là l’essentiel utile pour nous attaquer à l’exercice, je vous encourage cependant à lire la définition complète sur Wikipedia car les algorithmes de placement des bombes et pour trouver les valeurs des cases y sont proposés.

Le code Javascript



// variables
var canvas, ctx, posX, posY, T, C, N,F,i,images,tuiles,voisins;
 
// quand la page est chargée
window.onload = function() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
	canvas.width = 480;
	canvas.height = 480;
	posX = canvas.offsetLeft;
	posY = canvas.offsetTop;
	T = 32;
	C = 480/T;
	loadImages(3);
}
 
// chargement des images
function loadImages(nbImg){
	images = [];
	for(i=1; i<nbImg+1; i++){
		var b = new Image();
		b.src = "assets/tuile"+i+".jpg";
		b.onload = function() {
			images.push(this);
			if(--nbImg==0) init();
		};
	}
}
 
// initialisation du jeu
function init() {
 
	tuiles = [];
	voisins = [];
	N = 20;
	F = C*C-N;
 
	// placer les tuiles
	for (i=0;i<C*C;i++){
		var t = {};
		t.x = parseInt(i%C)*T;
		t.y = parseInt(i/C)*T;
		t.width = t.height = T;
		t.id = i;
		t.frame = 1;
		t.val = 0;
		tuiles.push(t);
		voisins.push([]);
	}
 
	// placer les bombes
	while(N) {
		var n = parseInt(Math.random()*tuiles.length);
		var p = tuiles[n];
		if(p.val!=9) {
			p.val=9; 
			N--;
			trouveValeurs(n,p.x/T,p.y/T,C-1);
		}
	}
 
	render();
	canvas.addEventListener("click", action, false);
}
 
// cliquer sur une case
function action(e){
	i = parseInt((e.clientX-posX)/T)+parseInt((e.clientY-posY)/T)*C;
	if(tuiles[i].frame==1) tuiles[i].frame=2, F--;
	if(tuiles[i].val==9) {
		tuiles[i].frame=3;
		finPartie();
		return;
	}
	if(!tuiles[i].val) decouvre(i); 
	if(F==0) finPartie(3);
	render();
}
 
// trouver les valeurs des cases
function trouveValeurs(i,X,Y,L){
	if(Y<L && tuiles[i+C].val != 9) 		tuiles[i+C].val++;
	if(Y>0 && tuiles[i-C].val != 9) 		tuiles[i-C].val++;
	if(X<L && tuiles[i+1].val != 9) 		tuiles[i+1].val++;
	if(X>0 && tuiles[i-1].val != 9) 		tuiles[i-1].val++;
	if(X<L && Y>0 && tuiles[i-C+1].val != 9) 	tuiles[i-C+1].val++;
	if(X>0 && Y>0 && tuiles[i-C-1].val != 9) 	tuiles[i-C-1].val++;
	if(X<L && Y<L && tuiles[i+C+1].val != 9) 	tuiles[i+C+1].val++;
	if(X>0 && Y<L && tuiles[i+C-1].val != 9) 	tuiles[i+C-1].val++;
}
 
// découvrir les cases vides
function decouvre(n){
	trouveVoisin(n,tuiles[n].x/T,tuiles[n].y/T,C-1);
	for (var h = 0; h<voisins[n].length; h++){
		if (voisins[n][h].frame==1) {
			if(!F--) finPartie();
			voisins[n][h].frame = 2;
			decouvre(voisins[n][h].id);
		}
	}
}
 
// trouver les cases vides voisines
function trouveVoisin(i,X,Y,L){
	if(X>0 && !tuiles[i-1].val) voisins[i].push(tuiles[i-1]);
	if(X<L && !tuiles[i+1].val) voisins[i].push(tuiles[i+1]);
	if(Y>0 && !tuiles[i-C].val) voisins[i].push(tuiles[i-C]);
	if(Y<L && !tuiles[i+C].val) voisins[i].push(tuiles[i+C]);
}
 
// fin de partie
function finPartie(){
	render();
	alert("Fin de partie, cliquez pour rejouer.");
	init();
}
 
// Dessine le jeu
function render() {	
	for(var i=0; i<tuiles.length; i++){
		ctx.drawImage(images[tuiles[i].frame-1], tuiles[i].x, tuiles[i].y);
		if(tuiles[i].val && tuiles[i].frame==2) afficheValeur(tuiles[i]);
	}	
}
 
// Affiche la valeur de la pièce
function afficheValeur(p) {		
	ctx.fillStyle = "white";
	ctx.font = "16px Arial";
	ctx.textAlign = "center";
	ctx.fillText(p.val, p.x+p.width/2, p.y+p.height/2+4);
}

A retenir

Ce qu’il est important de voir avec ce petit exercice c’est l’utilisation de la récursivité qui vous sera utile dans de nombreux jeux, mais également la détection des cases voisines qui vous sera également très utile. Notez cependant qu’il existe des méthodes plus optimisées pour chercher les voisines d’une case dans une grille, mais nous n’en sommes pas encore là et je préfères détailler le processus.

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