Tabla de contenido
Una vez más aquí. Hoy vengo a dejar un pequeño artículo sobre algo que me pidió un usuario la semana pasada. Este usuario debe de estar estudiando JavaScript, por que me pidió un articulo sobre cómo crear el mítico juego de la serpiente con JavaScript.
Este juego creo que es algo típico que se pide a todos los estudiantes de JavaScript, por que en su día a mi también me lo pidieron. Busqué un poco por mis archivos, y me encontré una versión que actualicé un poco, para que se pueda jugar también desde dispositivos móviles.
¿Qué es el juego de la serpiente?
Yo me imagino que en el mundo queda poca gente que no conozca este juego, pero por qué no aclarar que el juego de la serpiente (también conocido como Snake en inglés) es un juego clásico que se originó en la década de 1970. El objetivo del juego es controlar una serpiente que se mueve por una pantalla y hacerla comer manzanas o cualquier cosa que el programador de turno quiera, para hacerla crecer.
La serpiente se mueve continuamente en una dirección y el jugador puede cambiar su dirección para evitar chocar contra las paredes o la propia serpiente. Cada vez que la serpiente come una fruta, su cuerpo se alarga y el jugador debe evitar que la cabeza de la serpiente choque con su propio cuerpo.
Y bueno, teniendo esto claro, podemos pasar a ver el código para crear este juego en nuestro equipo.
El código del juego
Para crear este sencillo juego, solo vamos a necesitar tres archivos, y un cuarto que será una imagen de la comida de la serpiente. El primero de los archivos será el index.html, que nos servirá para mostrar el juego. El segundo será el style.css, donde aplicaremos las reglas CSS necesarias para que todo se vea de forma correcta. Y el tercero lo llamaremos script.js, y será en el que añadiremos la lógica del juego.
El código que vamos a ver, es un juego de serpiente que utiliza HTML, CSS y JavaScript. El código crea una cuadrícula de juego y una serpiente que se mueve por la cuadrícula. El jugador controla la dirección de la serpiente utilizando las teclas de flecha, y el objetivo es comer la comida que aparece aleatoriamente en la cuadrícula, sin chocar con los bordes de la cuadrícula o con el cuerpo de la serpiente.
El archivo index.html
En este archivo vamos a añadir el código básico de una página HTML que tiene como objetivo mostrar una página web para el juego, al que llamaré «La serpientica«.
<!DOCTYPE html> <html lang="es"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>La serpientica</title> <link rel="stylesheet" href="style.css"/> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"/> </head> <body> <div class="wrapper"> <div class="titulo">LA SERPIENTICA</div> <div class="game-details"> <span class="score">Puntuación: 0</span> <span class="high-score">Puntuación más alta: 0</span> </div> <div class="play-board"></div> <div class="controls"> <i data-key="ArrowLeft" class="fa-solid fa-arrow-left-long"></i> <i data-key="ArrowUp" class="fa-solid fa-arrow-up-long"></i> <i data-key="ArrowRight" class="fa-solid fa-arrow-right-long"></i> <i data-key="ArrowDown" class="fa-solid fa-arrow-down-long"></i> </div> </div> <script src="script.js" defer></script> </body> </html>
Este código no tiene mucho que explicar. Lo único sería las etiquetas «<i>«, que son las que vamos a utilizar para añadir los iconos para los controles de movimientos que se mostrarán en la versión móvil. El resto es la típica página html.
Archivo style.css
El código que vamos a añadir a este archivo, son las reglas CSS que se utilizarán para definir los estilos visuales de este juego.
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } body { min-height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #410077; font-family: 'Open Sans', sans-serif; } .wrapper { width: 65vmin; height: 70vmin; display: flex; flex-direction: column; justify-content: center; border-radius: 5px; background-color: #414c5fe8; box-shadow: 0 0 0 4px #f00, inset 2px 2px rgba(136, 0, 0, 0.26), 3px 4px rgba(0, 0, 0, 0.144); overflow: hidden; } .game-details { color: #B8C6DC; font-weight: 500; font-size: 1.2rem; padding: 20px 27px; display: flex; justify-content: space-between; } .play-board { height: 100%; width: 100%; display: grid; background: #212837; grid-template: repeat(30, 1fr) / repeat(30, 1fr); } .play-board .food { background-repeat: no-repeat; background-image: url("./img/Manzana.png"); background-size: 15px; } .play-board .head { background: #25df00; border-radius: 20%; } .controls { display: none; justify-content: space-between; } .controls i { padding: 25px 0; text-align: center; font-size: 1.3rem; color: #B8C6DC; width: calc(100% / 4); cursor: pointer; border-right: 1px solid #171B26; } /*Título*/ .titulo { display: inline-block; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; border: none; font: normal 38px / normal "Warnes", Helvetica, sans-serif; color: rgba(255, 255, 255, 1); text-decoration: normal; text-align: center; -o-text-overflow: clip; text-overflow: clip; white-space: pre; text-shadow: 0 0 10px rgb(255, 0, 0), 0 0 20px rgba(255, 0, 0), 0 0 30px rgba(255, 0, 0), 0 0 40px #727272, 0 0 70px #727272, 0 0 80px #4e4e4e, 0 0 100px #2b2b2b; -webkit-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); -moz-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); -o-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); width: 100%; margin-top: 10px; } .titulo:hover { text-shadow: 0 0 10px rgb(184, 0, 0), 0 0 20px rgb(184, 0, 0), 0 0 30px rgb(236, 0, 0), 0 0 40px #b80707, 0 0 70px #ff0000, 0 0 80px #bd0000, 0 0 100px #b41212; } @media screen and (max-width: 800px) { body{ min-height: 80vh; } .wrapper { width: 85vmin; height: 115vmin; } .game-details { font-size: 1rem; padding: 15px 27px; } .controls { display: flex; } .controls i { padding: 15px 0; font-size: 1rem; } .game-details{ font-size: 0.8em; } } @media screen and (max-width: 530px) { .play-board { height: 68%; } .play-board .food { background-size: 9px; } .wrapper { width: 95vmin; } }
A continuación, vamos a describir brevemente lo que cada sección de código hace:
- La primera sección establece los valores predeterminados de margen, relleno y caja para todos los elementos de la página, lo que asegura que no habrá margen o relleno no deseado que afecte la apariencia de los elementos.
- La sección de «body» establece el estilo para el cuerpo de la página, como el color de fondo, la fuente y la alineación de contenido.
- La sección de «wrapper» define el estilo para un contenedor específico utilizado en la página, incluyendo su tamaño, forma, color de fondo, sombra y otros aspectos visuales.
- Las secciones de «game-details» y «play-board» establecen los estilos para las secciones específicas de la página que se utilizan en el juego, como la información del juego y el tablero de juego.
- La sección «controls» define el estilo para los controles de juego, como las flechas que se usan para controlar la dirección de la serpiente.
- La sección «titulo» establece el estilo para el título de la página.
- Finalmente, hay algunas secciones que utilizan «media queries» para cambiar los estilos en función del tamaño de la pantalla del dispositivo que se está utilizando para ver la página, lo que ayuda a asegurar que la página se vea bien en una amplia variedad de dispositivos y tamaños de pantalla.
Archivo Script.js
El código que vamos a guardar dentro de este archivo, en términos generales, comienza definiendo algunas variables y elementos de la página, como el tablero de juego, la puntuación y la posición de la comida. También establece algunas funciones para actualizar la posición de la comida, cambiar la dirección de la serpiente y finalizar el juego.
Después de definir las funciones, el código establece un intervalo de tiempo para actualizar el estado del juego y llamar a la función «iniciarJuego«. Dentro de esta función, se actualiza la posición de la serpiente y se comprueba si ha colisionado con la pared o con su propio cuerpo. Si la serpiente come la comida, se actualiza la puntuación y se genera una nueva posición para la comida.
const playBoard = document.getElementsByClassName("play-board")[0]; const scoreElement = document.getElementsByClassName("score")[0]; const highScoreElement = document.getElementsByClassName("high-score")[0]; const controls = document.querySelectorAll(".controls i"); let gameOver = false; let foodX, foodY; let snakeX = 5, snakeY = 5; let velocityX = 0, velocityY = 0; let snakeBody = []; let setIntervalId; let score = 0; // Se busca la puntuación más alta del almacenamiento local let highScore = localStorage.getItem("high-score") || 0; highScoreElement.innerText = `Puntuación más alta: ${highScore} puntos`; // Actualizamos la posición de la comida de forma aleatoria const actualizarPosicionComida = () => { foodX = Math.floor(Math.random() * 30) + 1; foodY = Math.floor(Math.random() * 30) + 1; }; // Final del juego const finJuego = () => { clearInterval(setIntervalId); alert("Game Over! Pulsa Aceptar para volver a jugar..."); location.reload(); }; // Cambiar la dirección de la serpiente const cambiarDirection = e => { switch (e.key) { case "ArrowUp": if (velocityY !== 1) { velocityX = 0; velocityY = -1; } break; case "ArrowDown": if (velocityY !== -1) { velocityX = 0; velocityY = 1; } break; case "ArrowLeft": if (velocityX !== 1) { velocityX = -1; velocityY = 0; } break; case "ArrowRight": if (velocityX !== -1) { velocityX = 1; velocityY = 0; } break; default: break; } }; // Llamar a cambiarDirection en cada clic y pasar el valor del conjunto de datos clave como un objeto controls.forEach(button => button.addEventListener("click", () => cambiarDirection({ key: button.dataset.key }))); const iniciarJuego = () => { if (gameOver) return finJuego(); let html = `<div class="food" style="grid-area: ${foodY} / ${foodX}"></div>`; // Comprobamos si la serpiente se come la comida. if (snakeX === foodX && snakeY === foodY) { actualizarPosicionComida(); snakeBody.push([foodY, foodX]); // Empujamos la comida a la matriz del cuerpo de la serpiente, para que la serpiente crezca score++; // Se incrementa la puntuación en 1 // Guardamos las puntuaciones highScore = score >= highScore ? score : highScore; localStorage.setItem("high-score", highScore); scoreElement.innerText = `Puntuación: ${score} puntos`; highScoreElement.innerText = `Puntuación más alta: ${highScore} puntos`; } // Actualización de la posición de la cabeza de la serpiente en función de la velocidad actual snakeX += velocityX; snakeY += velocityY; // Desplazando hacia adelante los valores de los elementos en el cuerpo de la serpiente en uno for (let i = snakeBody.length - 1; i > 0; i--) { snakeBody[i] = snakeBody[i - 1]; } snakeBody[0] = [snakeX, snakeY]; // Configuración del primer elemento del cuerpo de la serpiente en la posición actual de la serpiente // Comprobando si la cabeza de la serpiente está fuera de la pared, si es así se configura gameOver en verdadero para finalizar el juego if (snakeX <= 0 || snakeX > 30 || snakeY <= 0 || snakeY > 30) { return gameOver = true; } for (let i = 0; i < snakeBody.length; i++) { // Se dibuja un div para cada parte del cuerpo de la serpiente. html += `<div class="head" style="grid-area: ${snakeBody[i][1]} / ${snakeBody[i][0]}"></div>`; // Comprobamos si la cabeza de la serpiente golpeó el cuerpo, si es así, establezca gameOver en verdadero if (i !== 0 && snakeBody[0][1] === snakeBody[i][1] && snakeBody[0][0] === snakeBody[i][0]) { gameOver = true; } } playBoard.innerHTML = html; } // Llamar a la actualización de la posición de la comida actualizarPosicionComida(); setIntervalId = setInterval(iniciarJuego, 100); document.addEventListener("keyup", cambiarDirection);
En resumen, este código va a crear un juego de serpiente básico en el que el jugador debe evitar chocar con obstáculos mientras intenta comer la comida para aumentar su puntuación.
Si quieres probar este juego antes de copiarlo a tu disco local, puedes hacerlo desde la sección de ejemplos en este enlace. En caso de que quieras descargarlo, sin tener que copiar todo este código, puedes hacerlo desde el repositorio en GitHub donde lo he alojado. Además, desde ahí podrás descargar también la imagen de la comida de la serpiente.
Y bueno, con esto espero que todo el que necesite un juego de este estilo, pueda crearlo sin mayor problema.
2 Comentarios
A mi me funciona el movimiento y todo, pero no las manzanas, practicamente, la comida…
Hola. Si lo que no te funciona es la comida, eso o bien tienes algún problema con la función Math o random, o es que la función
// Actualizamos la posición de la comida de forma aleatoria
no la has copiado correctamente. Por que como se puede ver en el enlace de ejemplo, tal cual aparece en el post, funciona correctamente. Ten cuidado con los espacios o los ; del final de cada línea. Salu2.const actualizarPosicionComida = () => {
foodX = Math.floor(Math.random() * 30) + 1;
foodY = Math.floor(Math.random() * 30) + 1;
};