En mis ratos libres se me dio por recrear el clásico juego de la serpiente en el cual la dinámica es conseguir la mayor cantidad de puntos mediante la comida que encuentras en el escenario evitando colisionar con los bordes o contigo mismo.
El escenario
El juego vive dentro de un objeto canvas por el cual esta siendo redibujado cada 100 milisegundos.
let interval = setInterval(draw, 100);
Movimiento de la serpiente
Para habilitar el movimiento de la serpiente asignaremos un evento keyup y usaremos las teclas (arriba|derecha|izquierda|abajo). Esta lógica será la encargada de dar definir la dirección hacia donde se mueve la serpiente.
Como restricción la serpiente no puede moverse al lado opuesto que se dirige. Por ejemplo, si va al a derecha, no puede ir a la izquierda y si va hacia arriba, no podrá ir hacia abajo.
document.addEventListener('keyup', (e) => {
if (self.game.lockKey) {
return;
}
if (e.keyCode === 38 && self.snake.position !== self.position.down) {
self.snake.position = self.position.up;
self.game.lockKey = true;
}
if (e.keyCode === 40 && self.snake.position !== self.position.up) {
self.snake.position = self.position.down;
self.game.lockKey = true;
}
if (e.keyCode === 39 && self.snake.position !== self.position.left) {
self.snake.position = self.position.right;
self.game.lockKey = true;
}
if (e.keyCode === 37 && self.snake.position !== self.position.right) {
self.snake.position = self.position.left;
self.game.lockKey = true;
}
});
Nota: la serpiente puede tener llegar a un cuerpo compuesto por N unidades o bloques, pero el que sirve de guía o manda hacia donde moverse siempre será la cabeza.
Cuerpo de la serpiente
Esta parte fue la más compleja, en resumen quien define la dirección es siempre la cabeza y las demás partes de su cuerpo harán un recorrido por donde paso la cabeza. Para hacer esto declaré un array donde guardo la coordenada de cada parte de la serpiente.
Veamos un ejemplo, supongamos que tenemos una serpiente que ha comido 3 manzanas; por ende, su cuerpo es de 3 unidades. En cada índice del array se define sus coordenadas y la primera será siempre la cabeza.
[{x: 0, y: 60},{x: 0, y: 40},{x: 0, y: 20}]
Luego si cambiamos de dirección lo que haremos es pasar el último objeto del array al primer índice y reemplazar sus coordenadas por la nueva posición de la cabeza.
[{x: 0, y: 80},{x: 0, y: 60},{x: 0, y: 40}]
¿Se entiende?
Las manzanas
La serpiente para crecer deberá comer las manzanas. La lógica esta compuesta en 2 partes:
Aparece aleatoriamente después que es comida por la serpiente.
self.food.x = Math.floor(Math.random() * (self.canvas.width / self.game.size)) * self.game.size;
self.food.y = Math.floor(Math.random() * (self.canvas.height / self.game.size)) * self.game.size;
Lo que hago es calcular el ancho y alto del canvas, y los bloques que serán ocupados por la serpiente y la comida.
if (self.food.x === self.snake.body[0].x && self.food.y === self.snake.body[0].y) {
// your awesome code goes here
}
Y la colisión entre la cabeza de la serpiente y la manzana, el cual es basicamente jugar con la posición x-y de ambas partes.
Colisiones
En el juego tenemos 2 tipos de colisiones:
- Bordes: no podemos tocar la pared o perdemos. Basicamente es jugar con las coordenadas actuales de la cabeza y de las paredes.
- Cuerpo: es similar al anterior pero hacemos un forEach para recorrer si la cabeza se encuentra en la posición actual de algunas de sus otras partes.
Sonidos e imágenes
He usado respectivamente las clases Audio e Image para manipular estos.
Les adjunto el GitHub del código de fuente.