Todos conocemos el juego de 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 (y algo de HTML).
Para programar con JavaScript hay múltiples posibilidades, incluyendo Notepad, el bloc de notas de Windows, o algo más sofisticado como 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. 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, 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.
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.
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.
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:
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?
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.
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.
Top comments (2)
Interesante forma de abordarlo, seguiré atento a esta serie! :)
Me alegro de que te interese. ¡Espero colmar tus expectativas!