cover

Iniciando la construcción de un Tetris con JS y Canvas

author photo

Héctorbliss

@hectorbliss


robot logo saying hello

No te quedes atrás: Actualízate

Suscríbete para recibir información sobre nuevos frameworks, updates, eventos, tips, hacks y más.

Sin frameworks solo con JavaScript y Canvas

Primero, colocar todas las piezas que vamos a necesitar en esta pantalla.

Necesitaremos ocupar la mitad de la pantalla.

En el centro, dividiremos el espacio a la mitad para ver del lado izquierdo la puntuación y la siguiente pieza. Mientras que del lado derecho veremos el tablero en movimiento, así que coloquemos este maquetado:

👀 ¡Ojo!, de paso agregaremos nuestra fuente y unos pocos estilos CSS

HTML

<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet" /> <div class="info"> <h2>Tetris</h2> <p>Tiempo:</p> <p>Puntos:</p> <p>Siguiente pieza:</p> <canvas id="next"></canvas> </div> <canvas id="main"></canvas>

CSS

body { margin: 0; font-family: "Press Start 2P"; display: flex; } .info { max-width: 220px; } #main { border: 2px solid; border-radius: 9px; }

Buen inicio. Vamos a colocar los selectores de los elementos que hemos colocado para poder manipular su contenido:

main.js

const mainCanvas = document.querySelector("#main"); const nextCanvas = document.querySelector("#next"); const info = document.querySelector(".info"); const mainCTX = mainCanvas.getContext("2d"); // de paso probamos que el canvas funcione: mainCTX.fillRect(0, 0, mainCanvas.width, mainCanvas.height); //constants const COLS = 10; const ROWS=20; const BLOCK_SIZE = 30; const COLORS = ["#323232", "red", "blue", "yellow", "orange", "green", "cyan"];

También necesitaremos algunas constantes que iremos agregando con el tiempo, por principio el número de columnas, filas, colores y cuanto mide un bloque.

Nuestra primera clase: Board

Vamos a comenzar con las clases. Para dibujar el tablero, necesitaremos una clase Board que controle todo lo que sucede en el tablero.

class Board

// classes class Board { constructor(ctx, nextCTX) { this.ctx = ctx; this.init(); this.color = COLORS[0]; } init() { this.grid = Array(ROWS).fill(Array(COLS).fill(0)); this.ctx.canvas.width = COLS * BLOCK_SIZE; this.ctx.canvas.height = ROWS * BLOCK_SIZE; this.draw(); } draw() { this.ctx.scale(1, 1); this.grid.forEach((row, y) => { row.forEach((num, x) => { this.ctx.fillStyle = this.color; this.ctx.strokeStyle = "#222"; this.ctx.lineWidth=4; this.ctx.fillRect( x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); this.ctx.strokeRect( x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); }); }); } } // Board

Observa cómo el constructor coloca las propiedades mínimas, mientras que el método init() hace la iniciación, incluyendo el tamaño del canvas y el grid del board con 20 filas por 10 filas.

Si instanciamos la clase Board, podremos ver que su inicialización funciona:

// instances const board = new Board( mainCTX, nextCanvas.getContext('2d')); // no olvides pasar el contexto del preview también // esto se hace así pues el board se podrá dibujar a sí mismo en cualquier canvas proporcionado.

¡Muy bien, ya tenemos donde jugar!, ahora necesitamos preparar las piezas para comenzar el juego.

Coloquemos la pieza siguiente en el otro canvas. Esto lo lograremos recibiendo en la instancia el otro canvas también, de esta forma el Board será el encargado de administrar las piezas, esto es importante, pues la pieza no solo se muestra, después entra al juego y el board la necesitará a la mano.

Escribimos la función auxiliar getShape que devolverá una forma a partir de un arreglo de formas y agregamos un método para conseguir y dibujar la pieza dentro del board:

// classes class Board { constructor(ctx, nextCTX) { this.ctx = ctx; this.nextCTX = nextCTX; this.color = COLORS[0]; this.init(); } init() { this.grid = Array(ROWS).fill(Array(COLS).fill(0)); this.ctx.canvas.width = COLS * BLOCK_SIZE; this.ctx.canvas.height = ROWS * BLOCK_SIZE; this.draw(); } draw() { this.drawBoard(); this.drawNext(); } drawNext() { this.nextCTX.scale(BLOCK_SIZE, BLOCK_SIZE); this.next = getShape(); console.log(this.next); //this.nextCTX.scale(1, 1); this.next.forEach((row, y) => { row.forEach((num, x) => { this.nextCTX.fillStyle = COLORS[num]; if (num > 0) { this.nextCTX.fillRect(x, y, 1, 1); } }); }); } drawBoard() { this.grid.forEach((row, y) => { row.forEach((num, x) => { this.ctx.fillStyle = this.color; this.ctx.strokeStyle = "#222"; this.ctx.lineWidth = 4; this.ctx.fillRect( x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); this.ctx.strokeRect( x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); }); }); } } // Board

Hemos agregado una modificación de la escala del contexto para que sea más fácil dibujar el preview. Así queda la clase Board y así la función auxiliar:

// auxiliar functions function getShape() { const shapes = [ [[1, 1, 1, 1]], [ [2, 2, 0], [0, 2, 2] ], [ [0, 3, 3], [3, 3, 0] ], [ [4, 4], [4, 4] ], [ [0, 0, 5], [5, 5, 5] ], [ [6, 0, 0], [6, 6, 6] ] ]; return shapes[Math.floor(Math.random() * shapes.length)]; }

¿Qué bellas piezas no?

Hemos avanzado muchísimo para la primera parte.

Volveremos con una nueva clase en la siguiente learning. Piece nos será una clase útil para controlar el estado de la pieza que estará jugando, pues tendrá que girar y decender además de congelarce cuando toque el suelo. Nos queda mucho por hacer.

Por ahora tomate un respiro, una bebida fría y muestra tu juego a tu familia con orgullo. 🤹🏻‍♀️

Abrazo. Bliss.

Enlaces relacionados

CodePen con el código final

banner

¿Quieres mantenerte al día sobre los próximos cursos y eventos?

Suscríbete a nuestro newsletter

Jamás te enviaremos spam, nunca compartiremos tus datos y puedes cancelar tu suscripción en cualquier momento 😉

robot logo saying hello
facebook icontwitter iconlinkedin iconinstagram iconyoutube icon

© 2016 - 2023 Fixtergeek