En este artículo vamos a aprender cómo hacer el famoso juego de la serpiente. Para seguir el ejemplo es necesario tener conocimientos avanzados de JavaScript. Utilizaremos prototipos para simular una estructura orientada a objetos y HTML5 nos proveerá del canvas para poder desarrollar la acción del juego. Vamos a dividir el ejemplo en dos partes. Primero plantearemos el modelo del juego, dibujaremos lo básico y agregaremos interactividad. Luego, en la segunda parte, agregaremos lo que falta para completarlo.
El ejemplo terminado lo pueden ver aquí.
Y lo que haremos en esta primera parte aquí.
En ambos casos, movemos la serpiente con las teclas: a
,w
,s
,d
.
El Juego
Para los que no lo conocen, es un juego para un único jugador, el cual debe controlar una serpiente que se alimenta y que cada vez que lo hace se vuelve más grande. La serpiente avanza constantemente y el jugador sólo puede cambiarle la dirección (horizontal o vertical). Si la serpiente se enrieda, o llega a los bordes del rectángulo de juego, el jugador pierde. El alimento aparece dentro del área de juego de forma aleatoria y el jugador debe conducir a la serpiente hasta ahí para sumar puntos. En cuanto la serpiente come, se hace una unidad más grande.
Planteo
La serpiente debe moverse en cuatro direcciones y avanzar una unidad a la vez. Esto nos lleva a pensar en cómo definir el mundo sobre el que vivirá y cómo estará representada. La forma de hacerlo es definir una cuadrícula o matriz de cuadrados a la cual se pueda acceder por filas y columnas. El cuadrado será entonces el elemento más pequeño que tendremos. Por lo tanto, para formar parte de ese universo, la serpiente deberá estar formada por cuadrados como así también cualquier otra cosa que aparezca en él.
Prototipos en Javascript
Antes de empezar a ver el código fuente, les sugerimos que repasen este artículo sobre programación orientada a objetos en Javascript, especialmente el apartado de Extension de objetos nativos mediante el prototipo.
Para conocer más sobre los cambios en el trabajo de clases y objetos a partir de ES6 les recomiendo leer el artículo: Clases en JavaScript con ECMAScript 6.
El código
Vamos a meternos de lleno en el código. Es recomendable ir leyendo el artículo y mirando el código fuente para entenderlo al máximo.
Código fuente de esta primera parte
Carga
El juego se carga con el evento onload de window, obtiene el canvas y su contexto los cuales el programa manejará como variables globales. Se instancia la clase Juego y le pasa el control con juego.correr().
Pseudo Clase Juego
Es la clase que controla el flujo del video juego. Tiene una instancia de Serpiente
y de <code. A su vez, crea un manejador de eventos para controlar la entrada por teclado. Otro de los atributos que tiene es el intervalo que se usará para realizar el bucle de juego. En este caso, al intentar orientar a objetos esta parte del código nos encontramos con un problema de JavaScript: se pierde la referencia al atributo intervalo, que guarda un identificador para saber a qué intervalo nos estamos refiriendo (hay que pensar que podríamos tener más de uno). Para resolver este problema existen frameworks que hacen más fácil la programación. En este caso, como programamos en JavaScript plano, usamos una técnica no muy elegante tal vez pero que da resultado.
El método iniciarIntervalo hace que el método correr se pueda ejecutar automáticamente cada determinado periodo de tiempo. Es justamente correr el que dibujará los elementos en el canvas, hará las actualizaciones de estados, limpiará y redibujará el canvas.
Manejador de Eventos
La habíamos visto en el ejemplo de la nave espacial. Aquí no se usa la sintaxis de prototipos que presentamos al principio, sino la que ya conocíamos. La programación de esta pseudo clase es en realidad un híbrido, no llega a estar totalmente orientada a objetos. Recibe un objeto, pero no lo guarda, sino que simplemente le asigna un evento. Su funcionamiento puede asemejarse más a un método encapsulado en una clase o una función auxiliar. El método tecla captura el evento e invoca al método que corresponde del objeto que recibe, que es la serpiente que ya explicaremos mejor cómo está hecha. Las números que aparecen en el switch se corresponden con las teclas para mover la serpiente (a,s,w,d).
Cuadro
El universo de nuestra serpiente será una cuadrícula y esta estará formada por cuadrados a los que haremos referencia por fila y columna. Ahora, pensemos; nuestra serpiente vive en un mundo de filas y columnas, sin embargo, el canvas está hecho en píxeles. ¿Cómo hacemos para salvar este problema? La idea es crear una capa de abstracción entre nuestros objetos que viven en el universo cuadriculado y el canvas. Así podremos olvidarnos de los píxeles y trabajar sólo con filas y columnas.
La clase Cuadro se ocupará de dibujar cuadrados en el canvas y estará completamente desacoplada del juego. Es decir, que dibujará cuadrados sin saber si son partes de la serpiente, si es alimento o si es algún otro elemento.
Veamos por dentro la clase. Tiene cuatro atributos: coordenadas cuaX y cuaY de nuestra cuadrícula y por otro lado posX y posY que son las coordenadas reales que corresponden al canvas. Estas dos últimas se calculan en base a las dos primeras. Los cálculos que se realizan sirven para ubicar correctamente el vértice superior izquierdo del cuadrado en el canvas. Cuando se crea un nuevo cuadrado, se le pasa por parámetro qué fila y columna de nuestra cuadrícula queremos que sea. Automáticamente se calcularán posX y posY para luego en caso de invocar a dibujar, puedan verse en el canvas.El método dibujar toma los valores de posX y posY y dibuja el cuadrado. Recibe un parámetro para determinar el color de pintado.
Los métodos getX
y getY
sirven para averiguar qué posición tiene el cuadrado. Luego vemos que están incDerecha
, <codeincArriba, incAbajo
y también incIzquierda
. Se ocupan de desplazar el cuadrado un lugar en cada una de esas direcciones.
Esta clase nos facilitará todo el trabajo. Ahora podemos crear una serpiente que esté formada por muchos objetos de Cuadro
y poder hacer referencia a cada uno de ellos por fila y por columna.
Serpiente
Por fin hemos llegado a la serpiente. Como acabamos de decir, deberá estar formada por muchos cuadrados. Por lo tanto, tendrá que tener un array de Cuadro. Noten que en la función (pseudo) constructor, se crea el array y se le agrega el primer cuadro que arbitrariamente se ubica en la posición [12,12] de la cuadrícula. También deberá tener una dirección, que se inicializa en esta misma función.
Hasta esta primera parte del ejemplo, el cuerpo de la serpiente será de un único Cuadro, luego en la segunda parte le agregaremos más. El método dibujar recorre el array con el cuerpo y lo dibuja. En este caso, dibuja un único cuadrado. El método avanzar hace desplazar un lugar ese cuadro en la dirección que corresponde.
El método cambiarDir simplemente actualiza la dirección de la serpiente, es invocada desde el manejador de eventos cuando el usuario presiona una tecla. getposX y getposY obtiene la posición de la cabeza de la serpiente y servirá luego para chequear dónde está y si chocó contra algo.
Mundo
La clase mundo está de forma simbólica en este ejemplo puesto que en realidad no entra en juego con los otros objetos. Pero es importante ver que la entidad Mundo podría definir dentro algunos elementos como por ejemplo obstáculos para la serpiente. Aquí simplemente se definieron los límites de la cuadrícula y un método que se encarga de dibujar líneas para que se vean los cuadrados que forman la cuadrícula.
Hasta aquí tendremos un cuadrado que se desplaza por la cuadrícula y al cual le podemos cambiar la dirección. En la segunda parte terminaremos el ejemplo y completaremos la serpiente.