En este artículo, aprenderás a crear un juego de la serpiente con JavaScript.

El juego de la serpiente es un juego sencillo en el que una serpiente se mueve adentro de una caja y trata de comerse una manzana. Una vez que se come la manzana, la serpiente crece de largo y se mueve más rápido.

El juego termina una vez que la serpiente se choca con sí misma o con cualquiera de las cuatro paredes.

Bueno, comencemos con HTML y CSS (los cimientos de nuestro juego).

HTML


<h1>Nokia 3310 snake</h1>
<div class="muestraPuntaje"></div>
<div class="cuadrilla"></div>
<div class="boton">
    <button class="superior">superior</button>
    <button class="fondo">fondo</button>
    <button class="izquierda">izquierda</button>
    <button class="derecha">derecha</button>
</div>
<div class="popup">
    <button class="juegaDeNuevo">Inténtelo de nuevo</button>     
</div>

Este código de HTML es sencillo y tiene:

  • Un div de clase muestraPuntaje que mostrará nuestro puntaje.
  • Un div de clase cuadrilla en el que se desarrollará el juego (la cuadrilla sería de 10 por 10).
  • Una clase buton que contiene un botón para los usuarios que juegan en el teléfono (lo automatizaremos con el teclado para los usuarios de computadora).
  • Una clase popup que mostrará el botón "Inténtelo de nuevo".

Ahora le añadiremos un poco de estilo con CSS.

CSS

      body {
        background: rgb(212, 211, 211);
      }
     .cuadrilla {
        width: 200px;
        height: 200px;
        border: 1px solid red;
        margin: 0 auto;
        display: flex;
        flex-wrap: wrap;
      }
      .cuadrilla div {
        width: 20px;
        height: 20px;
        /*border:1px black solid;
box-sizing:border-box*/
      }
      .serpiente {
        background: blue;
      }
      .manzana {
        background: yellow;
        border-radius: 20px;
      }
      .popup {
        background: rgb(32, 31, 31);
        width: 100px;
        height: 100px;
        position: fixed;
        top: 100px;
        left: 100px;
        display: flex;
        justify-content: center;
        align-items: center;
      }

En CSS, la cuadrilla, que es el tablero del juego, tiene una dimensión establecida y un display de flex. Esto permite que los contenidos (div) de esta cuadrilla estén alineados de forma horizontal como si fuesen elementos en línea en lugar de la disposición normal de los bloques.

La propiedad flex wrap mueve los divs hacia la próxima línea para que no traspasen la dimensión del elemento padre establecida (cuadrilla).

Crearemos de forma dinámica los contenidos del tablero del juego desde JS pero podemos darle anchura y altura desde CSS (con el .cuadrilla div). En el ejemplo, incluí comentarios para ayudarte a ver los divs; por lo que, a medida que avancemos dejaremos el código sin comentarios.

Las clases serpiente y manzana nos muestran donde está la serpiente y el bonus en el juego, mientras que la clase popup es un div fijo que alberga el botón juegaDeNuevo.

En este punto, obtendrás lo siguiente:

Screenshot--1710-


Ahora estamos listo para empezar con JavaScript.

JavaScript

Lo primero que tenemos que hacer es definir las variables:

let cuadrilla = document.querySelector(".cuadrilla") 
let popup = document.querySelector(".popup"); 
let juegaDeNuevo = document.querySelector(".juegaDeNuevo"); 
let muestraPuntaje = document.querySelector(".muestraPuntaje") 
let izquierda = document.querySelector(".izquierda") 
let fondo = document.querySelector(".fondo") 
let derecha = document.querySelector(".derecha") 
let arriba = document.querySelector(".superior") 
let ancho = 10; 
let indiceActual = 0 
let indiceManzana = 0 
let serpienteActual = [2,1,0] 
let direccion = 1 
let puntaje = 0 
let velocidad = 0.8 
let tiempoInterval = 0 
let interval = 0

La variable ancho es la anchura de la cuadrilla (10). Las otras variables tendrán más sentidos a medida que avancemos. Sin embargo, aunque no lo creas, nuestra serpiente en realidad es un arreglo llamado serpienteActual.

Ahora veamos las funciones:

document.addEventListener("DOMContentLoaded",function(){ 
    document.addEventListener("keyup",control) 
    crearTablero() 
    comiezaJuego() 
    juegaDeNuevo.addEventListener("click", repeticion); 
})

Hay un eventListener en el objeto del documento llamado DomContentLoaded y este evento se ejecutará una vez que el contenido del HTML se cargue en la pantalla.

Una vez que esto sucede, establecemos un eventListener en el documento para esperar clics del teclado (después veremos esto con más detalles). Luego, queremos crear el juego de mesa, empezar el juego y prestarle atención a los clics en el botón de juegaDeNuevo.

Función crearTablero

function crearTablero(){ 
    popup.style.display = "none"; 
    for(let i=0;i<100;i++){
        let div = document.createElement("div") 
        cuadrilla.appendChild(div) 
    }
} 

Como mencioné antes, este es un cuadrilla de 10 por 10 por lo cual vamos a necesitar 100 divs. Entonces, co el fragmento anterior, cerramos el div popup, creamos un nuevo div con un bucle de 100 iteraciones y insertamos a la cuadrilla (juego de meza).

Inmediatamente se añadirán algunos de los estilos creados en un principio (el .cuadrilla div). Puedes remover los /* */del estilo CSS y observarás los divs creados (vuelve agregar los /* */).

Función comenzaJuego

function comenzaJuego(){ 
let cuadrados = document.querySelectorAll(".cuadrilla div") 
manzanaAlAzar(cuadrados) 
// manzana al azar
direccion = 1 
mostrarPuntaje.innerHTML = puntaje 
tiempoInterval = 1000 
serpienteActual = [2,1,0] 
indiceActual = 0 
serpienteActual.forEach(index => cuadrados[index].classList.add("serpiente")) 
interval = setInterval(moverResultado,tiempoInterval) 
} 

La función comenzaJuego primero toma todos los divs (ya que estamos creando los divs en el tiempo de ejecución, no podemos incluirlos en la parte superior del código).

El siguiente paso es seleccionar un lugar dónde ubicar la manzana. Esto se realizará más abajo dentro de la función manzanaAlAzar. La variable direccion se refiere hacia donde se dirige la serpiente -1 para la derecha, -1 para la izquierda y así.

Con tiempoInterval se establece el tiempo que le toma a la serpiente moverse, mientras que serpienteActual define la ubicación exacta de la serpiente en el cuadrilla (ten en cuenta que básicamente la serpiente está formada por un par de divs con un color particular).

Para mostrar a nuestra serpiente en la pantalla, realizaremos bucles sobre el arregloserpienteActual con un forEach. Cada valor obtenido, se utilizará con cuadrados. Recuerda que debes utilizar querySelectorAll para acceder a los cuadrilla divs y, después, podemos tener acceso a ellos como un arreglo que está utilizando números. En este caso, estos son los valores de serpienteActual.

El siguiente paso insertar el método setInterval (moverResultado, tiempoInterval) en la variable interval. Con esto podemos llamar fácilmente a clearInterval en esa variable.

moverResultado se ejecuta cada 1000ms (1s) y básicamente define qué sucede cuando mueves la serpiente.

Función moverResultado

function moverResultado() { 
let cuadrados = document.querySelectorAll(".cuadrilla div") 
if(compruebaPorGolpes(squares)) {
alert("golpeaste algo") 
popup.style.display="flex" 
return clearInterval(interval) 
} else { 
mueveSerpiente(cuadrados) 
}
} 

Como con la función comenzaJuego mencionada previamente,  primero obtenemos todos los cuadrilla divs y luego verificamos si la función compruebaPorGolpes devuelve true (verdadero).

Si lo hace, quiere decir que la serpiente se chocó con algo y aparece el botón "Inténtelo de nuevo" y borra el interval. Si la función devuelve false (falso), la serpiente no chocó con nada y podemos mover la serpiente con la función mueveSerpiente.

Por lo tanto, cada 1 segundo el juego termina si compruebaPorGolpes  es true o la serpiente realiza un movimiento más si compruebaPorGolpes es false. Primero explicaré la función mueveSerpiente.  

Función mueveSerpiente

function mueveSerpiente(cuadrados){
let cola = serpienteActual.pop()  
cuadrados[cola].classList.remove("serpiente") 
serpienteActual.unshift(serpienteActual[0]+direccion)  
// movimiento termina aquí
comeManzanas(cuadrados,cola)  
cuadrados[serpienteActual[0]].classList.add("serpiente")  
} 

La función mueveSerpiente recibe un argumento llamado cuadrados lo que no es necesario obtener de nuevo el .cuadrilla div en esta función.

Lo primeo que necesitamos hacer es quitar el último elemento del arreglo serpienteActual con un pop (este sería la cola y el primer elemento del arreglo sería la cabeza). Básicamente, la serpiente se mueve un paso hacia adelante y sale de la posición en la que estaba anteriormente. Después de esto, insertamos un nuevo valor al principio del arreglo con unShift.

Supongamos que nuestra serpiente empezó a moverse y se dirige hacia la derecha (direccion = 1). La dirección se añadirá a cabeza serpienteActual y la suma se colocará como el nuevo cabezaDeSerpiente.

Por ejemplo, si la serpiente estaba en posición [2,1,0], eliminamos el último elemento y dejamos la posición [2,1]. Luego, tomamos la cabeza que sería el elemento 2, le añadimos la dirección que es el elemento 1 y hacemos que este valor sea el nuevo valor [3,2,1] que mueve nuestra serpiente un paso hacia a la derecha después de un segundo.

Si queremos mover la serpiente hacia abajo, la dirección se establecerá a el ancho (que es 10) y se insertará en el primer elemento (que es 12 y se pondrá al principio del arreglo) [12,2,1].

Después, verificamos si la serpiente se comió la manzana y si se visualiza la nueva cabeza de la serpiente en el DOM.

Función compruebaPorGolpes

function compruebaPorGolpes(cuadrados) {  
    if (  
    (serpienteActual[0] + ancho >=(ancho * ancho) && direccion === ancho) ||
    (serpienteActual[0] % ancho === ancho -1 && direccion ===1) ||   
    (serpienteActual[0] % ancho === 0 && direccion === -1) ||   
    (serpienteActual[0] - ancho <= 0 && direccion === -ancho) ||
    cuadrados[serpienteActual[0] + direccion].classList.contains("serpiente") 
    ) { 
        return true  
    } else {  
    	return false 
    }
}   

La función compruebaPorGolpes tiene una sentencia if. Según la condición definida, podría devolver true (la serpiente se chocó con algo) o false.

La primera condición es si serpienteActual [0] (la cabeza de la serpiente) + ancho (10) es igual al total del área del ancho (que es ancho * ancho = 100) y la dirección es igual al ancho.

Ahora asumamos que la cabeza de la serpiente está en posición 97 que es la última capa de nuestro cuadrilla. Si añadieses 10 a 97 (= 107), superaría al total de la cuadrilla que es de 100. Si la dirección de la serpiente continúa hacia abajo, quiere decir que la serpiente se chocó con el borde inferior.

Si la serpiente estaba en 97 (97 + 10= 107) pero el jugador pudo cambiar la dirección a 1 (tocando la tecla izquierda), no se chocará con nada.

Si la cabeza de la serpiente está en posición 39 y la dirección todavía es 1 (la serpiente sigue dirigiéndose hacia la pared) y se chocará con el borde derecho.

Cualquier otra condición es prácticamente lo contrario de las dos condiciones anteriores. La última condición permite que si la cabeza de la serpiente se dirige hacia otro elemento que ya contiene la clase serpiente, la serpiente se chocará con sí misma.

Por lo que si una de las condiciones mencionadas previamente se cumple, significa que la serpiente se chocó con algo y se devolverá true (sino false). Y en ese caso, el juego se terminó. Pero si es falso, con mueveSerpiente mueves la serpiente realiza otro movimiento.

Function comeManzanas

function comeManzanas(cuadrados,cola){ 
    if(cuadrados[serpienteActual[0]].classList.contains("manzanas")){ 
        cuadrados[serpienteActual[0]].classList.remove("manzanas") 
        cuadrados[cola].classList.add("serpiente") 
        serpienteActual.push(cola)
        manzanaAlAzar(cuadrados) 
        puntaje++ 
        muestraPuntaje.textContent = puntaje 
        clearInterval(interval) 
        tiempoInterval = tiempoInterval * velocidad 
        interval = setInterval(moverResultado, tiempoInterval) 
    }
} 

A la función comeManzana se la llama desde la función mueveSerpiente cada vez que la serpiente realiza un movimiento.

Recibe dos argumentos cuadrados, .cuadrilla div y cola, básicamente es el valor que apareció de la serpiente en moverResultado. Luego verifica si la próxima posición del movimiento de la serpiente contiene una manzana.

Si lo hace, simplemente añade la cola que sacamos devuelta al arreglo. Esto sucede porque cada vez que nuestra serpiente se come una manzana, su largo incrementa un valor.

Después solo tenemos que seleccionar una nueva posición para nuestra manzana con manzanaAlAzar (ver más adelante). Y insertamos un valor de uno a nuestra puntuación, se lo mostramos al usuario, y borramos el tiempoInterval (para poder incrementar la velocidad de la serpiente). Por último, establecemos el intervalo de nuevo.

Función manzanaAlAzar

function manzanaAlAzar(cuadrados){ 
do { 
    appleIndex = Math.floor(Math.random() * cuadrados.length) 
} while(cuadrados[appleIndex].classList.contains("serpiente")) 
    cuadrados[appleIndex].classList.add("manzana") 
} 

Lo que hace manzanaAlAzar es seleccionar un lugar donde ubicar a nuestra manzana al utilizar un bucle do while. Primero selecciona una posición al azar con Math.random() en el bucle do y verifica si el lugar seleccionado ya contiene la clase serpiente.

Esto quiere decir que la condición en la sentencia do se ejecutará hasta que encuentre un lugar en el que no esté la serpiente (esto se continua ejecutando hasta que sea true). Una vez que encuentre un lugar, le da a ese lugar la clase manzana.

Establecer controles

Ahora tenemos que establecer nuestros controles. Vamos a empezar con los usuarios del teclado.

function control(e){ 
    if (e.keycode === 39){
        direccion = 1 // derecha 
    } else if (e.keycode === 38){ 
        direccion = -ancho // si presionamos la flecha de arriba, la serpiente irá 10 divs hacia arriba
    }else if (e.keycode === 37){ 
        direccion = -1 // izquierda, la serpiente irá un div a la izquierda
    }else if (e.keycode === 40){
        direccion = +ancho // la serpiente irá 10 divs hacia abajo desde el div actual
    } 
} 

Recuerda que establecimos un eventListener para keyup. Esta función se ejecuta inmediatamente al presionar y soltar una tecla.

Ahora cada botón del teclado tiene un valor llamado keycode (numeros) desde el cual tenemos acceso y nos permite saber qué numero se cliqueó. Básicamente, estaremos atentos a las flechas con sus respectivos valores (keycodes). Gracias a esto, cambiamos la dirección, por ejemplo -1, 10 y así.

Espero que hayan comprendido cómo ahora se podrá mover a la serpiente.

El siguiente conjunto de botones es para dispositivos móviles y básicamente estamos haciendo lo mismo:

arriba.addEventListener("click",() => direccion = -ancho ) 
fondo.addEventListener("click",() => direccion = +ancho ) 
izquierda.addEventListener("click",() => direccion = -1 ) 
derecha.addEventListener("click",() => direccion = 1 )

Por último, necesitamos crear el div repetición que aparecerá cuando la serpiente se choque con algo. Con este botón, podremos borrar el juego.  

Función repetición

 function repetición() { 
 cuadrilla.innerHTML="" 
 crearTablero()   
 comiezaJuego()  
 popup.style.display = "none"; 
 }  

Con este fragmento, borramos la cuadrilla (juego de mesa) y ejecutamos las funciones previas.

¡Felicidades! Llegaste al final. Aquí tenemos el resultado final:

Screenshot--1709-

Espero que hayas podido seguir el código y lo hayas disfrutado.

En este tutorial, aprendimos a crear juego de la serpiente con JavaScript. Algunos de los conceptos importantes que vimos incluyen push, pop, setInterval, clearInterval y eventListener.

Para jugar al juego haz clic aqui:

Gracias por leer el artículo. Sigueme en Twitter:

Traducido del artículo de Fakorede Damilola - How to Build a Snake Game In JavaScript