Tetris

Game engine experiences
Open source code

Présentation

Tetris est un jeu vidéo de puzzle conçu en 1984 par Alekseï Pajitnov. Bâti sur des règles simples et exigeant intelligence et adresse, il est l’un des jeux vidéo les plus populaires au monde. Ses versions sont innombrables, y compris en 3D, et cette multiplicité se décline sur tous les types d’ordinateurs.

Il est considéré comme un des grands classiques de l’histoire des jeux vidéo aux côtés de Pong, Space Invaders ou encore Pac-Man.

Des pièces de couleur (parfois il n’y a pas de couleur, et ce sont les motifs sur les pièces qui changent) et de formes différentes descendent du haut de l’écran. Le joueur ne peut pas ralentir ou empêcher cette chute mais peut l’accélérer ou décider à quel angle de rotation (0°, 90°, 180°, 270°) et à quel emplacement latéral l’objet peut atterrir. Lorsqu’une ligne horizontale est complétée sans vide, elle disparaît et les blocs supérieurs tombent. Si le joueur ne parvient pas à faire disparaître les lignes assez vite et que l’écran se remplit jusqu’en haut, il est submergé et la partie est finie.

Le jeu ne se termine donc jamais par la victoire du joueur. Avant de perdre, le joueur doit tenter de compléter un maximum de lignes. Faire une seule ligne ne rapporte que 40 points, alors qu’en faire 2 en rapporte 100, 3 lignes rapportent 300 et 4 lignes (le maximum) en rapportent 1200. Le nombre de points est augmenté à chaque niveau selon l’équation f(p, n)= p(n+1) où p est le nombre de points au niveau 0 et n le niveau.

Les pièces de Tetris, sur lesquelles repose entièrement le jeu, sont des tétrominos. Il en existe sept formes différentes, toutes basées sur un assemblage de quatre carrés – le mot « Tetris » (du préfixe grec tetra-, qui signifie quatre) prend donc tout son sens. Le joueur peut faire tourner plusieurs fois, à gauche et/ou à droite selon la version, de 90° n’importe quel bloc pour le poser de la façon désirée pendant que le bloc descend. Chacune des sept pièces dispose d’une couleur qui lui est propre, et certains joueurs se réfèrent aux pièces seulement par ce détail. Au désarroi de ceux-ci, la couleur des pièces varie généralement d’une version de Tetris à une autre. Cependant, d’après les consignes de la Tetris Company, ceci n’a aucune incidence sur le jeu.

Le champ de jeu, aussi connu sous l’appellation « puits » dans les anciens Tetris et en tant que « matrice » dans les plus récents, est l’espace dans lequel tombent les pièces. Il dispose toujours d’une grille en arrière-plan, visible ou non, dont les cases sont de la même grandeur que les carrés des pièces, et que celles-ci suivent dans leur chute. Il est également entouré par une armature appelée « tétrion », infranchissable, qui pose les limites du champ de jeu.

La vitesse de la chute des pièces est déterminée par le niveau auquel vous êtes. Plus le niveau est élevé, plus les pièces tombent vite. Au niveau 0 on peut faire 5-6 déplacements latéraux avant que la pièce tombe d’un rang, au niveau 9 on ne peut plus faire que 1-2 mouvements latéraux. Le niveau 15 équivaut au niveau de la vitesse à tout le temps appuyer sur la flèche du bas, il n’y a plus de déplacements latéraux possibles directement. Le joueur peut alors dans ce cas tenter de les réaliser par des successions rapides de rotations, une pièce n’est définitivement posée sur l’écran de jeu que dans le cas où le joueur cesse toute action sur celle-ci. En effectuant continuellement des rotations sur une pièce, le joueur l’empêche donc de se poser définitivement, il est alors possible d’opérer des déplacements latéraux et selon les cas il peut être envisageable de faire passer une pièce par dessus une autre déjà posée. Ainsi il est toujours possible de placer et d’orienter de façon entièrement libre une pièce quel que soit le niveau de jeu en cours.

Nous n’allons bien entendu pas reproduire la totalité du comportement du jeu, d’une part car il s’agit d’un exercice et non d’un moteur tout prêt, et d’autre part car il faut bien qu’il vous reste un peu de grain à moudre de votre côté

Le code Javascript de la classe jeu.js

// variables
var canvas,ctx,W,H,posX,posY,images,grille,pieces,lateral,vitesse,timer,piece,nextP,nextColor,stock,preview,cadence,T,jeuX,jeuY,previewX,previewY,sounds;
 
var fond = new Image();
fond.src = "assets/fond.jpg";
 
// importer un fichier 
function include(fileName){
	document.write("<script type='text/javascript' src='js/"+fileName+".js'></script>" );
}
include("piece"); 														// importer la classe Piece
include("bloc"); 														// importer la classe Bloc
include("sounds"); 														// importer la classe Sounds
 
window.onload = function() {													// Préparer le jeu
    canvas = document.getElementById('canvas');											// récupére le canvas
    ctx = canvas.getContext('2d');												// récupére le context
	W = 480;														// largeur du jeu
	H = 480;														// hauteur du jeu
	posX = canvas.offsetLeft;												// décalage du canvas sur X
	posY = canvas.offsetTop;												// décalage du canvas sur Y
	canvas.width = W;													// largeur du canvas
	canvas.height = H;													// hauteur du canvas
	T = 21;															// largeur des tuiles
	jeuX = 		4*T;													// position de la zone de jeu sur x
	jeuY = 		2*T;													// position de la zone de jeu sur y
	previewX = 	15*T;													// position de la zone de preview sur x
	previewY = 	2*T;													// position de la zone de preview sur y
	canvas.setAttribute('tabindex','1'); 											// sélectionne le canvas dans la page
	canvas.focus(); 													// donne le focus au canvas
	loadImages(5);														// charge 5 images
}		
 
function loadImages(nbImg){													// Charger des images
	images = [];														// vide le tableau contenant les images 
	for(i=1; i<nbImg+1; i++){												// boucle sur le nombre d'images
		var b = new Image();												// crée une nouvelle image
		b.src = "assets/tuile"+i+".jpg";										// associe son bitmap
		b.onload = function() {												// quand l'image est chargée
			images.push(this);											// ajoute la au stock d'images
			if(--nbImg==0) init();											// s'il n'y a plus d'image à charger initialise le jeu
		};
	}
}
 
function init() {														// Initialer le jeu
	stock = 	[];													// liste des blocs de la zone de jeu
	preview = 	[];													// liste des blocs de la zone de preview
	grille = 	[];													// grille affichée à l'écran
	pieces = 	[];													// liste des différents types de pieces
	vitesse = 	18;													// vitesse du jeu
	timer = 	0; 													// décompte le temps passé
	piece = 	null; 													// la piece en cours de jeu
	nextP = 	Math.floor(Math.random()*7); 										// référence de la prochaine piece
	nextColor = Math.floor(Math.random()*5); 										// référence de la prochaine couleur
	cadence = 	setInterval(update, 15); 										// cadence du jeu
	for(var i=0; i<20; i++)	{grille.push([0,0,0,0,0,0,0,0,0,0])}; 								// création dela grille de jeu 
	for(var i=0; i<7; i++)	{pieces.push(new Piece(i,T,grille,stock))};	 						// création des types de pieces 
	document.addEventListener("keydown", keyDown, false); 									// écoute l'appui d'une touche sur la page
	sounds = new Sounds();
}
 
function keyDown(e){														// Gestion du clavier
	if (e.keyCode == 37 && piece.checkMove("g")) lateral=true; 								// déplace la piece à gauche 
	if (e.keyCode == 39 && piece.checkMove("d")) lateral=true; 								// déplace la piece à droite 
	if (e.keyCode == 38) piece.rotate(), sounds.rotate.play();	 							// oriente la piece
	if (e.keyCode == 40) while(piece.checkMove("b")) timer = 0; 								// fait checkLine la piece 
}
 
function update(e) {														// Mise à jour du jeu
	piece == null ? newPiece() : movePiece(); 										// crée une piece si aucune en cours, sinon déplace la
	lateral = false; 													// annule les mouvements latéraux 
	checkLine(); 														// vérifie si une ligne est remplie 
	render(); 														// rendu graphique du jeu
};
 
function newPiece(){														// Créer une piece
	preview = []; 														// vide le tableau de preview
	piece = pieces[nextP]; 													// la piece devient la nouvelle piece
	piece.init(nextColor); 													// initialise la piece avec la bonne couleur
	nextP = Math.floor(Math.random()*7); 											// référence de la nouvelle piece
	nextColor = Math.floor(Math.random()*5); 										// référence de la nouvelle couleur
 	pieces[nextP].buildPreview(preview,nextColor); 										// construit la preview de la nouvelle piece
	if(!piece.buildPiece()) gameover(); 											// construit la piece, fin de partie si un bloc touche le haut
}
 
function movePiece(){														// Déplacer la piece
	if (timer>=vitesse || lateral) { 											// si mouvement latéral ou temps écoulé
		if (piece.checkMove("b")) { 											// si la piece peut descendre elle descend
			lateral ? piece.Y-- : timer = 0; 									// si mouvement latéral la piece remonte sinon fin du temps
		} else { 													// sinon
			sounds.touchDown.play();										// bruitage
			piece.drawInGrid(); 											// ajoute les blocs de la piece à la grille
			piece = null; 												// vide la piece en cours
			for (var i=0;i<stock.length;i++){ 									// parcours tous les blocs
				var b = stock[i]; 										// référence le bloc testé
				if (b.T == "piece") { 										// si le bloc fait partie de la piece
					stock[i--] = new Bloc("tuile",b.couleur,b.x/T,b.y/T); 					// remplace le bloc par une tuile
				}
			}
		}
	} else { 														// sinon
		timer++; 													// incrémente le temps
	}	
}
 
function checkLine(){														// Vérifier si une ligne est remplie
	var full,couleur,C,L,b,i, ligne = 19; 											// variables locales
	while (ligne>=0) {													// teste toutes les lignes de l'aire de jeu à partir de la dernière
		full = true; 													// par défaut la ligne est remplie
		for(C=0; C<10; C++){												// vérifie chaque colonne
			if(!grille[ligne][C]) full = false; 									// si une case est vide la ligne n'est pas remplie
		}
		if (full) {													// si la ligne est remplie
			sounds.full.play();											// bruitage
			for (i=stock.length-1;i>=0; i--){									// parcours tous les blocs
				if(stock[i].L == ligne) stock.splice(i,1); 							// retire tous les blocs concernant cette ligne
			}
			for (L=ligne; L>0; L--) {										// remonte les lignes depuis la dernière
				for (C=0; C<10; C++) {										// parcours toutes les colonnes de la ligne 
					grille[L][C] = grille[L-1][C];								// la case prend la référence de celle du dessus
					if (grille[L][C]) {									// si la case n'est pas vide
						for (i=0;i<stock.length;i++){							// parcours tous les blocs
							b = stock[i];								// référence le bloc en cours
							if(b.L == L-1 && b.C == C) {						// si il s'agit du bloc au dessus de la case
								couleur = b.couleur;						// récupére la couleur du bloc
								stock.splice(i,1);						// supprime la le bloc
							}
						}
						stock.push(new Bloc("tuile",couleur,C,L));					// crée une nouvelle tuile pour cette case
					}				
				}
			}
			for (C=0; C<10; C++) grille[0][C] = 0;									// vide la première ligne de l'aire de jeu
			ligne++; 												// indécrement la ligne
		}
		ligne--; 													// décrémente la ligne
	}
}
 
function gameover(){														// Terminer la partie
	clearInterval(cadence); 												// stoppe la boucle principale
	alert("game over"); 													// signale au joueur qu'il a perdu
	init(); 														// relance le jeu
}
 
function render() {														// Dessiner le jeu
	ctx.fillStyle = "rgb(51,51,51)";
	ctx.fillRect(0, 0, W, H);
	ctx.drawImage(fond, 0, 0);
	for(var i=0; i<stock.length; i++){
		ctx.drawImage(images[stock[i].couleur], stock[i].x+jeuX, stock[i].y+jeuY);
	}
	for(var i=0; i<preview.length; i++){
		ctx.drawImage(images[preview[i].couleur], preview[i].x+previewX, preview[i].y+previewY);
	}
}

Le code Javascript de la classe blocs.js

// l'objet Bloc
function Bloc(type,color,C,L,cX=0,cY=0){
	this.couleur = color;						// couleur du bloc
	this.C = C;							// colonne
	this.L = L;							// ligne
	this.x = (C+cX)*T;						// position sur x
	this.y = (L+cY)*T;						// position sur y
	this.T = type;							// type de bloc
 
	this.move = function(X,Y){					// Déplacer le bloc
		if (this.T == "piece") {				// si le bloc fait partie de la piece en cours
			this.x = (this.C+Math.floor(X))*T;		// nouvelle position sur x
			this.y = (this.L+Math.floor(Y))*T;		// nouvelle position sur y
		}
	}
}

Le code Javascript de la classe sounds.js

// l'objet Sounds
function Sounds(){
	this.full = new Audio('assets/SFX_line.ogg'); 
	this.touchDown = new Audio('assets/SFX_PieceTouchDown.ogg'); 
	this.rotate = new Audio('assets/SFX_rotate.ogg'); 
}

Le code Javascript de la classe blocs.js

// l'objet Piece
function Piece(mapNum, T, grid, stock){
 
	this.X = 4;
	this.Y = 0;
	this.T = T;
	this.grille = grid;
	this.stock = stock;
	this.color = Math.floor(Math.random()*5);
 
	var C;
	var L;
	var temp;
	var maps = [
					[[1,1,1,1],[0,0,0,0],[0,0,0,0],[0,0,0,0]],
					[[1,1,1,0],[0,0,1,0],[0,0,0,0],[0,0,0,0]],
					[[0,0,1,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]],
					[[0,1,0,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]],
					[[1,1,0,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]],
					[[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]],
					[[0,1,0,0],[1,1,0,0],[1,0,0,0],[0,0,0,0]]
				]
 
	var map = maps[mapNum];	
 
	this.init = function(color){											// Initialiser la piece
		this.X = 4;												// colonne
		this.Y = 0;												// ligne
		this.color = color;											// couleur
	}
 
	this.drawInGrid = function(){											// Ajouter la piece dans la grille
		for (L=0; L<4; L++) {											// lignes de la piece
			for (C=0; C<4; C++) {										// colonnes de la ligne
				if (map[L][C]) {									// si la case est remplie
					this.grille[this.Y+L][C+this.X] = map[L][C];					// mise a jour de la grille
				}
			}
		}
	}			
 
	this.buildPiece = function(){											// Construire la piece
		for (L=0; L<4; L++) {											// lignes de la piece
			for (C=0; C<4; C++) {										// colonnes de la piece
				if (map[L][C]) {									// si la case est remplie
					this.stock.push(new Bloc("piece",this.color,C,L,this.X,this.Y));		// ajoute le bloc correspondant
					if (this.grille[this.Y+L][this.X+C]) return false;				// si le bloc touche le haut de la zone de jeu, partie perdue
				}
			}
		}
		return true;												// la piece a été construire
	}
 
	this.buildPreview = function(tab,color){									// Construire la preview
		for (L=0; L<4; L++) {											// lignes de la piece
			for (C=0; C<4; C++) {										// colonnes de la piece
				if (map[L][C]) {									// si la case est remplie
					tab.push(new Bloc("preview",color,C,L));					// ajoute le bloc correspondant
				}
			}
		}
	}
 
	this.checkMove = function (dir) {										// Vérifier si la piece peut bouger
		var col = 0;												// la nouvelle colone
		var lig = 0;												// la nouvelle ligne
		if(dir=="b") lig = 1;											// si la piece descend 
		if(dir=="g") col = -1;											// si la piece va a gauche
		if(dir=="d") col = 1;											// si la piece va a droite
		for (var L=0; L<4; L++) {										// lignes de la piece
			for (var C=0; C<4; C++) {									// colonnes de la piece
				if(map[L][C]) {										// si la case n'est pas vide
					if(dir=="b" && this.Y+L+1>=20) 			return false;			// si le bloc sort par le bas, ne bouge pas
					if(dir=="g" && this.X<=0) 			return false;			// si le bloc sort par la gauche, ne bouge pas
					if(dir=="d" && this.X+C+1>9) 			return false;			// si le bloc sort par le droite, ne bouge pas
					if(this.grille[this.Y+L+lig][this.X+C+col]) 	return false;			// si le bloc est occupée dans la grille, ne bouge pas
				}
			}
		}
		this.X += col;												// nouvelle colone
		this.Y += lig;												// nouvelle ligne
		if(dir=="b") {												// si la piece descend
			for (var i=0; i<this.stock.length; i++) {							// parcours tous les blocs
				this.stock[i].move(this.X,this.Y);							// déplace le bloc
			}
		}
		return true;												// la piece peut bouger
	}
 
	this.rotate = function() {											// Changer le sens de la piece
		if (this.checkRotation()){										// si la piece peut bouger
			for (var i=this.stock.length-1; i>=0;i--){							// parcours tous les blocs
				if (this.stock[i].T == "piece") this.stock.splice(i,1);					// retire les blocs de la piece
			}
			this.buildPiece();										// construit la nouvelle piece
		}
	}
 
	this.checkRotation = function() {										// Tester si la nouvelle orientation est possible
		temp = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];							// orientation temporaire de la piece
		for (L=0; L<4; L++) {											// lignes de la piece
			for (C=0; C<4; C++) {										// colonnes de la ligne
				temp[C][3-L] = map[L][C];								// rotation de la piece
			}
		}
		while (!(temp[0][0] || temp[1][0] || temp[2][0] || temp[3][0])) {					// Tant que la première colonne est vide
			for (L=0; L<4; L++) {										// lignes de la piece
				for (C=1; C<4; C++) {									// colonnes de la ligne
					temp[L][C-1] = temp[L][C];							// décale les colonnes vers la gauche
				}
				temp[L][3] = 0;										// vide la dernière colonne
			}
		}
		for (L=0; L<4; L++) {											// lignes de la piece
			for (C=0; C<4; C++) {										// colonnes de la ligne
				if (temp[L][C]){ 									// si la case est remplie
					if (this.grille[L + this.Y][C + this.X] || C + this.X > 9 || L + this.Y > 19) return false; // si une erreur est détectée la piece n'est pas tournée
				}
			}
		}
		map = temp;												// valide la nouvelle orientation de la piece
		return true;												// la piece est tournée
	}
}

A retenir

A ecrire…

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