Creando un Tetris con JavaScript
Tags: spanish, gamedev, javascript, tutorial
Published: 2024-06-04 15:30:07

---
title: Creando un Tetris con JavaScript
published: true
series: JavaScript Tetris
description: Insertrix: un tetris ligeramente distinto.
tags: #spanish #gamedev #javascript #tutorial
cover_image: https://upload.wikimedia.org/wikipedia/commons/4/46/Tetris_logo.png
# Use a ratio of 100:42 for best results.
# published_at: 2024-06-04 14:35 +0000
---

Todos conocemos el juego de [Tetris](https://es.wikipedia.org/wiki/Tetris). Las piezas van cayendo desde la parte superior, y si no hay espacio para que la siguiente pieza "caiga", entonces el juego termina. Haremos el juego ligeramente distinto: personalmente, nunca he entendido que la velocidad de las piezas aumente, más allá de limitar el tiempo de las partidas en los salones arcade. Además, tiene mucho más mérito hacer líneas en la parte superior del tablero que en la inferior. Y finalmente, y esto no es inédito, sería interesante insertar líneas con huecos en la parte inferior, para complicar la eliminación de líneas.

En esta serie, vamos a desarrollar este juego, llamado *Insertrix*, para jugarlo en el navegador. Es decir, vamos a utilizar [JavaScript](https://es.wikipedia.org/wiki/JavaScript) (y algo de HTML).

Para programar con JavaScript hay múltiples posibilidades, incluyendo [Notepad](https://es.wikipedia.org/wiki/Bloc_de_notas), el bloc de notas de Windows, o algo más sofisticado como [Codepen.io](https://codepen.io/), que permite ver el desarrollo actualizándose continuamente, lo que es bastante práctico. En mi caso, utilizaré mi editor de textos de siempre, [Geany](https://www.geany.org/). Soy un clásico.

Básicamente, lo que haremos será dibujar continuamente el tablero de Tetris, con la pieza que está cayendo en ese momento. Para ello, emplearemos un componente de HTML llamado [Canvas](https://es.wikipedia.org/wiki/Canvas_(HTML)), literalmente, lienzo.

Las piezas son esencialmente unos bloques o puntos que se dibujan para formar la pieza completa. El tablero, al fina y al cabo, lo forman los restos de las piezas que han ido cayendo. Así, el elemento más pequeño que dibujaremos será uno de estos puntos o bloques. Si dividimos el tablero en una cuadrícula, podemos fácilmente decidir si hay o no hay un bloque en una posición representándolo con un 1, o un 0 si en esa posición no hay nada.


![Cuadrícula del tablero de Tetris](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d7oaz1sj461hrtgo378.jpg)

En la imagen de más arriba, tenemos un total de 10 filas por 5 columnas. Si tuviéramos la pieza de la "L" en la parte superior del tablero, se vería como se muestra a continuación.

![Cuadrícula del tablero de Tetris con una pieza "L"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fa04yqpb62f906dl0tp.jpg)

Al fin y al cabo, lo que tenemos es una matriz de ceros y unos:

 |0|1|2|3|4|
-|-|-|-|-|-|
0|0|0|0|0|0
1|0|0|0|0|0
2|0|0|0|0|0
3|0|0|1|0|0
4|0|0|1|0|0
5|0|0|1|1|0
6|0|0|0|0|0
7|0|0|0|0|0
8|0|0|0|0|0
9|0|0|0|0|0

Podemos automatizar el dibujado del tablero, por tanto, si por cada casilla pintamos un cuadrado relleno si hay un 1 en ella.

```javascript
class Piece {
    #_shape = null;
    #_color = "black";
    #_height = 1;
    #_width = 1;
    #_row = 0;
    #_col = 0;
    
    constructor(shape, color)
    {
        this._row = 0;
        this._col = 0;
        this._height = shape.length;
        this._width = shape[0].length;
        
        if ( color != null ) {
            this._color = color;
        }
     }
}
```

Creamos la clase **Pieza** (*Piece*), que contendrá los atributos *_shape* (la matriz de unos o ceros que representa la pieza), *_row* y *_col*, que llevan la posición de la pieza en el tablero y finalmente *_height* y *_width*, que representan la altura y anchura de la misma. Estos atributos se prefijan con '#' para indicar que son atributos privados.

El alto y el ancho de la pieza se calculan a partir de la matriz *_shape*. El número de filas de la matriz será su altura, mientras que la longitud de cada fila (todas las filas deberían tener la misma) es el ancho.

¿Cómo creamos una pieza, entonces? Supongamos que queremos crear la "L", entonces:

```javascript
class PieceL extends Piece {
    constructor()
    {
        super( [ [ 1, 0 ],
                 [ 1, 0 ],
                 [ 1, 0 ],
                 [ 1, 1 ] ],
               "orange" );
    }
}
```

Estamos creando la **PieceL** como una clase que hereda de la clase **Piece** que es la pieza genérica. El primer parámetro, como decíamos, es la matriz con la forma de la pieza, mientras el segundo es el color de la misma. ¿Quieres ver un par de piezas más?

```javascript
class PieceInverseS extends Piece {
    constructor()
    {
        super( [ [ 0, 1 ],
                 [ 1, 1 ],
                 [ 1, 0 ] ],
               "darkred" );  // color
    }
}

class PiecePodium extends Piece {
    constructor()
    {
        super( [ [ 0, 1, 0 ],
                 [ 1, 1, 1 ] ],
               "purple" );  // color
    }
}
```

La clase **Piece** tiene unos cuantos métodos más, que son básicamente *getters* y *setters* para la posición. A continuación, podemos verla al completo.

```javascript
class Piece {
    #_shape = null;
    #_color = "black";
    #_height = 1;
    #_width = 1;
    #_row = 0;
    #_col = 0;
    
    constructor(shape, color)
    {
        this._row = 0;
        this._col = 0;
        this._height = shape.length;
        this._width = shape[0].length;
        
        if ( color != null ) {
            this._color = color;
        }

        // Copy shape
        this._shape = new Array( shape.length );
        for(let i = 0; i < shape.length; ++i) {
            this._shape[ i ] = [ ...shape[ i ] ];
        }
    }

    get shape()
    {
        return this._shape;
    }
    
    get width()
    {
        return this._width;
    }
    
    get height()
    {
        return this._height;
    }
    
    get shape()
    {
        return this._shape;
    }
    
    get row()
    {
        return this._row;
    }
    
    set row(v)
    {
        this._row = v;
    }
    
    get col()
    {
        return this._col;
    }
    
    set col(v)
    {
        this._col = v;
    }
    
    get color()
    {
        return this._color;
    }
    
    reset(board)
    {
        this._row = 0;
        this._col = parseInt( ( board.cols / 2 ) - 1 );
    }
}
```

En esta primera parte, hemos discutido como representar el tablero y las piezas, y hemos visto cómo implementar las piezas. Sí, aún falta poder girarlas, pero vayamos poco a poco.