馃敽 TETRIS 馃敽
Se ha utilizado vanilla Javascript, Canvas para la animaci贸n y Tailwind para los estilos. El proyecto consiste en el tradicional juego del tetris:
鉁旓笍 Dibujo del tablero y su matriz. |
鉁旓笍 Funcion que dibuje cada ficha. |
鉁旓笍 Descenso autom谩tico de las piezas (animaci贸n). |
鉁旓笍 Colores aleatorios en cada partida. |
鉁旓笍 Movimiento con A-W-S-D y flechas. |
鉁旓笍 Creaci贸n de la clase 'Bolsa' para la gesti贸n de fichas aleatorias. |
鉁旓笍 Gesti贸n de colisiones. |
鉁旓笍 Rotaci贸n de las piezas. |
鉁旓笍 L贸gica para borrar filas con casillas ocupadas. |
鉁旓笍 Score sin vista. |
鉁旓笍 Gesti贸n de la velocidad. |
鉁旓笍 Sonido. |
鉁旓笍 Creaci贸n de la web con Tailwind. |
鉁旓笍 Dise帽o de niveles, botones de sonido, play, selecci贸n en Tailwind. |
鉁旓笍 Score con vista. |
鉁旓笍 Mostrar la siguiente ficha dentro de la bolsa. |
鉁旓笍 Dificultad HARD. |
鉁旓笍 Desplazamiento aleatorio |
鉁旓笍 Dise帽o adaptativo a m贸vil |
鉁旓笍 Ventana del Game Over |
鉁旓笍 localStorage + tabla de puntuaci贸n |
El juego cuenta con 2 tipos de dificultades:
El tetris trae una paleta de colores por defecto, pero gracias a la propiedad FILTER
y al efecto hue-rotate() es posible hacer que cada partida tenga una apariencia diferente a la anterior.
Para la creaci贸n de un tablero 'gen茅rico' se ha utilizado POO (programaci贸n orientada a objetos) y CANVAS. En este caso, existe una clase llamada Tablero:
Esta clase tambi茅n tiene un m茅todo llamado setTablero() que realiza 2 funciones:
Comprobar la creaci贸n del tablero:
1. Inspeccionar y seleccionar 'Fuentes'
2. Crear un punto de ruptura en la linea 43 del main.js
3. Recargar
La bolsa representa un conjunto de PIEZAS
de la cual se va obteniendo una FICHA
hasta quedar completamente vac铆a. Una vez vac铆a, se rellena autom谩ticamente con otro conjunto de PIEZAS
aleatorias. Esto evita la repetici贸n de una misma ficha varias veces. La 煤nica vez que se da el caso de repetici贸n es cuando coincide la 煤ltima ficha de la primera bolsa con la primera ficha de la siguiente bolsa.
PIEZAS es un array predefinido de las 7 piezas que tienen una letra y color. Ejemplo:
const PIEZAS = [
['T', "#E71D36"],
['Z', "#FF44FF"],
['S', "#F46036"],
['J', "yellow"],
['L', "#00FF00"],
['I', "cyan"],
['O', "white"]
]
Se hace una copia de la matriz PIEZAS y a partir del length de 'copia' se va sacando un n煤mero aleatorio.
En la primera vuelta el n煤mero ser谩 de 0 a 6, como splice muta la matriz, en la siguiente vuelta tendr谩 una longitud de 6 y el n煤mero aleatorio ser谩 de 0 a 5.
Como el array 'bolsa' ya esta desordenado gracias al m茅todo anterior, basta con comprobar si quedan piezas en la bolsa y hacer un .splice()
para sacar la ficha.
Despu茅s, he ampliado el m茅todo para saber cual va ser la siguiente pieza por salir. Al eliminar la pieza 'actual' con splice, si volvemos a llamar a this.piezasAleatorias[0]
obtendremos la pr贸xima ficha.
Asimismo, podemos sacar la letra del array y concatenarla para crear una URL para la etiqueta <img>
.
Para borrar filas, cuando una pieza colisiona con otra o el tablero, cambia su valor interno de '1' a '2'. De esta forma, es posible iterar sobre el tablero y comprobar si una fila tiene todos sus valores a '2'.
En este caso es posible usar la funci贸n every()
que nos devuelve true
si todos los valores cumplen el valor especificado y false
si no es as铆. La ventaja de usar este m茅todo es que, en el momento que no coincida un 煤nico valor para de buscar coincidencias, devuelve false
y pasa a la siguiente fila.
Para insertar una nueva fila tambi茅n podemos usar:
const fila = [0,0,0,0,0,0,0,0,0,0]
En un principio la dificultad HARD
invert铆a los controles de movimiento hacia la derecha/izquierda para a帽adirle ese plus de dificultad. Sin embargo, si sigues jugando hay un momento en el que te acostumbras a la 'inversi贸n'.
Para evitar que esta funcionalidad solo sirva como 'sorpresa' he a帽adido un m茅todo adicional que, a partir de la segunda partida, seleccionar谩 de forma aleatoria entre controles invertidos y controles tradicionales. De esta forma, el jugador nunca sabr谩 si la partida va a tener los controles invertidos o no hasta que empiece a jugar.
Para implementar esta funcionalidad he usado sessionStorage
que permite crear variables y guardarlas como clave/valor. Estos datos sobreviven a la recarga de p谩gina. Tambi茅n existe localStorage, que permite una persistencia de datos superior (sobrevive a recargas de p谩gina, a la apertura de la web en diferentes pesta帽as del navegador e incluso al cierre del mismo). En este caso, me interesaba que solo se mantuvieran durante el refresco de p谩gina para asegurar que la primera experiencia con el modo HARD
siempre fuera con los controles invertidos.
M谩s informaci贸n aqu铆.
session
y la inicializamos con el valor 0
.contador
.IF-ELSE
y asignamos el evento seg煤n el n煤mero de recargas web && el n煤mero aleatorio.Esta funci贸n se ejecuta despu茅s de seleccionar el modo hard y jugar una partida.
El juego define 2 variables de localStorage
para guardar el numero de partidas y la puntuaci贸n de la partida actual.
Al unir ambos, tendriamos el numero de la partida y los datos (puntuaci贸n) de la misma. Ejemplo:
Despu茅s, se recorre todo el objeto y se imprime din谩micamente con Javascript en una tabla.
La raz贸n por la que se usa el .subString() es porque las claves o keys no pueden contener espacios en blanco. Es decir, puedes tener partida1, pero no partida 1.
Uno de los grandes desaf铆os de este juego es sin duda sus colisiones. Hay 3 tipos de colisiones que debes de manejar:
Para comprobar, por ejemplo, la colisi贸n con las fichas en el eje Y tienes que ver si la posici贸n actual
de la ficha esta rellena y la posici贸n siguiente
es una casilla ocupada o final del tablero.
La rotaci贸n
de las piezas tambi茅n influye en este apartado. Por ejemplo, para calcular la posici贸n correcta de la pieza "I"
tuve que ver si en su siguiente posici贸n no este fuera del tablero y las posiciones que fuera a ocupar la fila de esa ficha estuvieran libres (valor != 2).
Por un lado, el juego tiene una animaci贸n que desplaza la ficha una casilla en el eje Y cada segundo. Esta animaci贸n se realiza de forma autom谩tica mediante requestAnimation
. Por otro lado, el jugador puede mover la ficha dentro de los l铆mites del tablero con (A-W-S-D o las flechas). Este evento lo gestiona exclusivamente el addEventListener
.
Muchos de los problemas de pintado, superposici贸n de fichas y errores de colisi贸n ocurren por la desincronizaci贸n de estos dos m茅todos.
La soluci贸n
para que tanto los eventos como las animaciones funcionen en armon铆a es usar algoritmos que calculen el tiempo entre las diferentes llamadas de forma precisa. Es decir, no podemos depender de setTimeout().
Utilizaremos la clase Date
y calcularemos el tiempo que pasa entre la ejecuci贸n de funciones.
Adem谩s, esta f贸rmula tambi茅n permite evitar el desplazamiento autom谩tico de la pieza mientras el jugador la mueva o rote.
Tetris Game by Dmitry Poldnev is licensed under CC BY 4.0