Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice 11 - TETRIS

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

Bonjour,

Nous voici arrivés à la fin de la première série d'exercices.
Il est temps de vous mettre à l'épreuve pour valider vos acquis. Au travers des 10 précédents exercices nous avons décortiqué toutes les notions essentielles à connaitre pour commencer à concevoir des jeux vidéo. Avant d'aller plus loin et de passer à la deuxième série, je vous propose un ultime travail, cette fois nous n'allons pas détailler ensemble chaque partie du code. Ca va être à vous de prendre tout ça en main, mais rassurez-vous, la totalité du code a été commenté ligne par ligne, profitez-en ça sera rarement le cas…

Tetris n'est pas un jeu simple, mais aucune notion ne vous est inconnue si vous avez fait les exercices précédents.

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 TETRIS c'est quoi ? (merci Wikipedia)

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é ;-)

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>TETRIS</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 et les sons

Je prépare tous mes assets, c'est à dire toutes les images prédécoupéeset les sons qui vont servir dans mon jeu.

Je range le tout dans le dossier “assets”.

Le code Javascript

Le code est répati en quatre fichiers, le programme principal (“jeu.js”) et les classes des objets.

On commence par “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);
	}
}

J'utilise trois sortes d'objets dans le programme, les blocs qui composent les pieces, les pieces avec leur plans et les bruitages.

Voyons les blocs pour commencer avec “bloc.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
		}
	}
}

Pour les sons ça se passe dans “sounds.js” :

// l'objet Piece
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'); 
}

Et on attaque la grosse partie avec “piece.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
	}
}

Etude du programme

L'intégralité du code est commenté, ligne par ligne, vous avez donc toute la traduction :)

Ca va être à vous de jouer, il n'y a aucune notion que nous n'aillons vus dans les exercices précédents.
Si vous travaillez avec un éditeur du type Notepad++, je vous recommande de replier tous les blocs de niveau 1 (ou 2).
Vous obtiendrez ainsi l'affichage simplifié que nous allons parcourir rapidement ensemble pour voir la logique générale du programme.

On commence avec le programme principal :

// 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";
 
function include(fileName){		// importer un fichier
 
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		
function loadImages(nbImg){		// Charger des images
function init() {			// Initialer le jeu
function keyDown(e){			// Gestion du clavier
function update(e) {			// Mise à jour du jeu
function newPiece(){			// Créer une piece
function movePiece(){			// Déplacer la piece
function checkLine(){			// Vérifier si une ligne est remplie
function gameover(){			// Terminer la partie
function render() {			// Dessiner le jeu

Déclaration des variables globales, création du background, import des classes utiles.
On prépare le jeu comme d'habitude, on charge les images et on lance l'initialisation.

Le programme principal s'occupe des grandes lignes, il donne des ordres aux pièces et aux blocs, mais ne s'occupe pas de la manière dont ces derniers son implémentés, seul le résultat compte pour lui. Il va donc gérer les événements clavier, mettre à jour les informations, créer les nouvelles pieces et leur demander de se déplacer, vérifier qu'une ligne de la grille est remplie et agir en conséquence, réagir si le joueur a perdu la partie et gérer l'affichage.

L'autre partie intéressante c'est les pieces avec “piece.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.drawInGrid = function(){			// Ajouter la piece dans la grille		
	this.buildPiece = function(){			// Construire la piece	
	this.buildPreview = function(tab,color){	// Construire la preview		
	this.checkMove = function (dir) {		// Vérifier si la piece peut bouger
	this.rotate = function() {			// Changer le sens de la piece
	this.checkRotation = function() {		// Tester si la nouvelle orientation est possible
}

Les pieces sont au nombre de 7, toutes de forme différente.
Les maps représentent les 7 formes possibles, composées de blocs indépendants.
On ne peut jouer qu'une pièce à la fois, 7 pièces suffisent donc pour tout le jeu.
Lorsqu'une pièce est placée elle disparait tandis que sa map est transposée sur la grille.

Chaque piece est indépendante et comporte un certain nombre de méthodes pour la manipuler.

Vous avez la logique générale et les commentaires, à vous de travailler ;)

Conclusion

C'est terminé pour la première série d'exercices.
Débarassés des bases nous allons à présent pouvoir nous attaquer à des jeux un peu plus complexes.

Les sources