Creando un Tetris con JavaScript III: el tablero
Tags: spanish, gamedev, javascript, tutorial
Published: 2024-06-18 16:11:04

---
title: Creando un Tetris con JavaScript III: el tablero
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
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-06-18 14:57 +0000
---

En nuestro **Tetris** necesitamos un tablero donde poner las piezas, que el usuario pueda moverlas o rotarlas, etc. 

De la misma forma que utilizamos una matriz para representar las piezas, utilizaremos una matriz para representar la totalidad del tablero. Al principio, todo el tablero estará vacío, pero a medida que vayan bajando piezas, estas se irán añadiendo a la parte inferior del mismo. Es decir, cuando una pieza no pueda bajar más (bien porque ha alcanzado la parte inferior del tablero, o bien porque se ha ido a posar en antiguas piezas o partes de piezas que ahora lo conforman), *portaremos* esa pieza al tablero como una parte más.

Mientras una pieza esté bajando, tendremos que pintarla *encima* del tablero, pero no pasará a formar parte de él (de otra forma no podríamos detectar colisiones, al no saber qué puntos forman parte de la pieza y cuáles del tablero).

![Tablero de Tetris, vacío, con una barra bajando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nakhajbs0ltn8emaqoyo.jpg)

Así, a continuación podemos ver un tablero de juego de juego de 10 filas y 5 columnas, y su representación, además de cómo podemos crearlo con JavaScript.

![Tablero de Tetris, vacío.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ae3k71e9zlukrv1jy2pp.jpg)

```javascript
let board = [ /* Fila 0 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 1 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 2 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 3 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 4 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 5 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 6 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 7 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 8 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 9 */ [ 0, 0, 0, 0, 0 ] ];
```

Al final, lo que tenemos es una matriz creada a partir de un vector de vectores. Igual que con las piezas, el vector principal representa a las filas, mientras que cada uno de los vectores que lo forman son las columnas de esa fila.

El código que tenemos ahí arriba es perfectamente válido para crear una matriz 10x5, pero... ¿y si quisiérsmo crear una matriz de forma que no supiéramos el número de filas y columnas previamente? Pues podemos crear cada vector como un objeto **Array**. Le pasaremos el número de elementos necesario. Además, el método `fill()` lo podemos emplear para inicializar cada elemento (a partir de ahora, lo llamaremos *celda*), a cero.

Al final, si el tablero se llama **board**, accederemos a cada celda con un número entre corchetes que representará la fila, y otro número entre corchetes que representará la columna. El primer acceso con corchetes nos devolverá el vector de esa fila, mientras que el segundo número nos devolverá la celda específica. Solo hay que recordar que en JavaScript todos los **Array**'s empiezan en cero.

Así, para crear nuestro tablero, podríamos crear una función como la siguiente:

```javascript
function createBoard(rows, cols)
{
    let toret = new Array(rows);

    for(let i = 0; i < rows; ++i) {
        toret[ i ] = new Array( cols ).fill( 0 );
    }

    return toret;
}
```

Para acceder a una celda en concreto, podríamos utilizar una función como la siguiente:

```javascript
function cellFrom(board, row, col)
{
    const ROWS = board.length;
    const COLS = board[ 0 ].length;

    if ( row < 0
      || row >= ROWS )
    {
        throw new RangeError( "row: 0 - " + ROWS + ": " + row + "??" );
    }
        
    if ( col < 0
      || col >= COLS )
    {
        throw new RangeError( "col: 0 - " + COLS + ": " + col + "??" );
     }

     return board[ row ][ col ];
}
```

Es cierto que ern definitiva vamos a utilizar una clase, pero probablemente ver estas funciones individuales nos ayude a entender el concepto.

Así, para acceder a la tercera columna de la primera fila, llamaríamos a:

```javascript
console.log( cellFrom( board, 0, 2 ) );
```

Utilizamos excepciones para detectar los casos en los que pedimos valores de fila o columna fuera de rango. La excepción estándar **RangeError** es la adecuada; tendremos que pasarle un mensaje de error, como a cualquier otra.

![Tablero de Tetris con restos de piezas y una pieza bajando.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ms5vnduujzk5tzd16y52.jpg)

La situación en la imagen de arriba se representaría en el tablero de la siguiente forma:

```javascript
let board = [ /* Fila 0 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 1 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 2 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 3 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 4 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 5 */ [ 0, 0, 0, 0, 0 ],
              /* Fila 6 */ [ 0, 0, 0, 1, 1 ],
              /* Fila 7 */ [ 1, 1, 0, 1, 1 ],
              /* Fila 8 */ [ 0, 1, 1, 0, 1 ],
              /* Fila 9 */ [ 1, 1, 0, 1, 0 ] ];
```

¿Dudas? ¡No dudes en ponérmelas en los comentarios!

De acuerdo, entonces podemos crear una nueva clase **Board** que cree y mantenga el tablero con las celdas que necesitamos.

```javascript
class Board {
    static PIXEL_SIZE = 24;
    #_color = "darkgreen";
    #_rows;
    #_cols;
    #_board;
    
    constructor(rows, cols, color)
    {
        this._rows = rows;
        this._cols = cols;
        
        if ( color != null ) {
            this._color = color;
        }
        
        this._board = new Array( rows );
        for(let i = 0; i < rows; ++i) {
            this._board[ i ] = new Array( cols ).fill( 0 );
        }
    }
}
```

Con la clase anterior, tenemos claro cómo se crean el tablero, de hecho en el constructor de la clase.

Necesitamos también *getters* que nos devuelvan, por ejemplo, el número de filas (*rows*) y columnas (*cols*).

Pero aparte de todo eso, necesitaremos sendos métodos, además, que nos devuelva, y nos permita cambiar, una celda determinada.

```javascript
class Board {
    // más cosas...

    cell(row, col)
    {
        if ( row < 0
          || row >= this.rows)
        {
            throw new RangeError( "row: 0 - " + this.rows + ": " + row + "??" );
        }
        
        if ( col < 0
          || col >= this.cols)
        {
            throw new RangeError( "col: 0 - " + this.cols + ": " + col + "??" );
        }

        return this._board[ row ][ col ];
    }

    setCell(row, col, val)
    {
        if ( val == null ) {
            val = 1;
        }
        
        if ( row < 0
          || row >= this.rows)
        {
            throw new RangeError( "row: 0 - " + this.rows + ": " + row + "??" );
        }
        
        if ( col < 0
          || col >= this.cols)
        {
            throw new RangeError( "col: 0 - " + this.cols + ": " + col + "??" );
        }

        
        this._board[ row ][ col ] = val;
    }
}
```

Además, a medida que avance el juego, tendremos que eliminar filas del tablero (porque se han rellenado de celdas con '1', es decir, están llenas) e insertar filas al principio del tablero (para compensar las filas que hemos eliminado).

```javascript
class Board {
    // Más cosas...

    insertEmptyRows(numRows)
    {
        for(let i = 0; i < numRows; ++i) {
            this._board.unshift( new Array( this.cols ).fill( 0 ) );
        }
    }
    
    removeRows(listRows)
    {
        let newBoard = [];
        
        for(let numRow = 0; numRow < this._board.length; ++numRow) {
            if ( !listRows.includes( numRow ) ) {
                newBoard.push( this._board[ numRow ] );
            }
        }
        
        this._board = newBoard;
    }
}
```

Bueno, tenemos el *core* del juego creado. Nos falta la vista para representar tablero y piezas bajando, y el gestor del juego, que lo arranque, lo pare, haga descender piezas, y preste atención a las teclas pulsadas por el usuario.

En la próxima entrega veremos la clase **Canvas**, que se emparejará con un elemento **Canvas** de HTML, y se encargará de pintar el tablero.