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
}
}










