Spiegazione del tutorial su JavaScript Snake

In questo articolo spiegherò come realizzare un gioco Snake utilizzando HTML, CSS e JavaScript.

Non utilizzeremo librerie aggiuntive; il gioco verrà eseguito in un browser. Creare questo gioco è un esercizio divertente che ti aiuta ad allungare ed esercitare i muscoli capaci di risolvere i problemi.

Profilo del progetto

Snake è un gioco semplice in cui guidi i movimenti di un serpente verso il cibo schivando gli ostacoli. Quando il serpente raggiunge il cibo, lo mangia e si allunga. Man mano che il gioco procede, il serpente diventa sempre più lungo.

Il serpente non dovrebbe sbattere contro i muri o se stesso. Pertanto, man mano che il gioco procede, il serpente si allunga e diventa sempre più difficile da giocare.

L’obiettivo di questo tutorial su JavaScript Snake è costruire il gioco seguente:

Il codice del gioco è disponibile su my GitHub. Una versione live è ospitata su Pagine GitHub.

Prerequisiti

Costruiremo questo progetto utilizzando HTML, CSS e JavaScript. Scriveremo solo HTML e CSS di base. Il nostro obiettivo principale è JavaScript. Pertanto, dovresti già capirlo per seguire questo tutorial su JavaScript Snake. In caso contrario, ti consiglio vivamente di consultare il nostro articolo sui posti migliori per imparare JavaScript.

Avrai anche bisogno di un editor di codice in cui scrivere il tuo codice. Oltre a ciò, avrai bisogno di un browser, che probabilmente hai se stai leggendo questo.

Impostazione del progetto

Per iniziare, impostiamo i file di progetto. In una cartella vuota, crea un file index.html e aggiungi il seguente markup.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://wilku.top/javascript-snake-tutorial-explained/./styles.css" />
    <title>Snake</title>
  </head>
  <body>
    <div id="game-over-screen">
      <h1>Game Over</h1>
    </div>
    <canvas id="canvas" width="420" height="420"> </canvas>
    <script src="./snake.js"></script>
  </body>
</html>

Il markup sopra crea una schermata di base di “Game Over”. Commuteremo la visibilità di questa schermata utilizzando JavaScript. Definisce anche un elemento della tela su cui disegneremo il labirinto, il serpente e il cibo. Il markup collega anche il foglio di stile e il codice JavaScript.

Successivamente, crea un file stili.css per lo stile. Aggiungi i seguenti stili.

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Courier New', Courier, monospace;
}

body {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #00FFFF;
}

#game-over-screen {
    background-color: #FF00FF;
    width: 500px;
    height: 200px;
    border: 5px solid black;
    position: absolute;
    align-items: center;
    justify-content: center;
    display: none;
}

Nel set di regole “*”, prendiamo come target tutti gli elementi e reimpostiamo la spaziatura. Impostiamo anche la famiglia di caratteri per ogni elemento e impostiamo il dimensionamento degli elementi su un metodo di dimensionamento più prevedibile chiamato border-box. Per il corpo, abbiamo impostato la sua altezza sull’intera altezza del viewport e allineato tutti gli elementi al centro. Gli abbiamo anche dato un colore di sfondo blu.

Infine, abbiamo stilizzato la schermata “Game Over” per darle un’altezza e una larghezza rispettivamente di 200 e 500 pixel. Gli abbiamo anche dato un colore di sfondo magenta e un bordo nero. Impostiamo la sua posizione su assoluta in modo che sia fuori dal normale flusso di documenti e allineato al centro dello schermo. Quindi, ne abbiamo centrato il contenuto. Impostiamo la sua visualizzazione su nessuno, quindi è nascosto per impostazione predefinita.

Successivamente, crea un file snake.js, che scriveremo nelle prossime sezioni.

Creazione di variabili globali

Il prossimo passo in questo tutorial su JavaScript Snake è definire alcune variabili globali che utilizzeremo. Nel file snake.js, aggiungi le seguenti definizioni di variabile in alto:

// Creating references to HTML elements
let gameOverScreen = document.getElementById("game-over-screen");
let canvas = document.getElementById("canvas");

// Creating context which will be used to draw on canvas
let ctx = canvas.getContext("2d");

Queste variabili memorizzano i riferimenti alla schermata “Game Over” e agli elementi della tela. Successivamente, abbiamo creato un contesto, che verrà utilizzato per disegnare sulla tela.

Successivamente, aggiungi queste definizioni di variabile sotto il primo set.

// Maze definitions
let gridSize = 400;
let unitLength = 10;

Il primo definisce la dimensione della griglia in pixel. Il secondo definisce un’unità di lunghezza nel gioco. Questa unità di lunghezza verrà utilizzata in diversi posti. Ad esempio, lo useremo per definire quanto sono spesse le pareti del labirinto, quanto è spesso il serpente, l’altezza e la larghezza del cibo e gli incrementi con cui si muove il serpente.

Successivamente, aggiungi le seguenti variabili di gioco. Queste variabili vengono utilizzate per tenere traccia dello stato del gioco.

// Game play variables
let snake = [];
let foodPosition = { x: 0, y: 0 };
let direction = "right";
let collided = false;

La variabile serpente tiene traccia delle posizioni attualmente occupate dal serpente. Il serpente comprende unità e ciascuna unità occupa una posizione sulla tela. La posizione occupata da ciascuna unità viene memorizzata nell’array del serpente. La posizione avrà valori x e y come coordinate. Il primo elemento dell’array rappresenta la coda, mentre l’ultimo rappresenta la testa.

Mentre il serpente si muove, spingeremo gli elementi alla fine dell’array. Questo sposterà la testa in avanti. Rimuoveremo anche il primo elemento o coda dall’array in modo che la lunghezza rimanga la stessa.

La variabile posizione del cibo memorizza la posizione corrente del cibo utilizzando le coordinate xey. La variabile direzione memorizza la direzione in cui si muove il serpente, mentre la variabile collisione è una variabile booleana contrassegnata come vera quando viene rilevata una collisione.

Dichiarare funzioni

L’intero gioco è suddiviso in funzioni, il che rende più semplice la scrittura e la gestione. In questa sezione dichiareremo tali funzioni e i loro scopi. Le sezioni seguenti definiranno le funzioni e discuteranno i loro algoritmi.

function setUp() {}
function doesSnakeOccupyPosition(x, y) {}
function checkForCollision() {}
function generateFood() {}
function move() {}
function turn(newDirection) {}
function onKeyDown(e) {}
function gameLoop() {}

In breve, la funzione setUp imposta il gioco. La funzione checkForCollision controlla se il serpente si è scontrato con un muro o con se stesso. La funzione doSnakeOccupyPosition prende una posizione, definita dalle coordinate xey, e controlla se qualche parte del corpo del serpente si trova in quella posizione. Ciò sarà utile quando cerchi una posizione libera in cui aggiungere cibo.

La funzione di spostamento muove il serpente in qualunque direzione punti, mentre la funzione di svolta cambia quella direzione. Successivamente, la funzione onKeyDown ascolterà la pressione dei tasti utilizzati per cambiare direzione. La funzione gameLoop sposterà il serpente e controllerà le collisioni.

Definire le funzioni

In questa sezione definiremo le funzioni che abbiamo dichiarato in precedenza. Discuteremo anche come funziona ciascuna funzione. Ci sarà una breve descrizione della funzione prima del codice e commenti per spiegarlo riga per riga dove necessario.

funzione di configurazione

La funzione di configurazione farà 3 cose:

  • Disegna i bordi del labirinto sulla tela.
  • Imposta il serpente aggiungendo le sue posizioni alla variabile serpente e disegnandolo sulla tela.
  • Genera la posizione iniziale del cibo.
  • Pertanto, il codice sarà simile a questo:

      // Drawing borders on canvas
      // The canvas will be the size of the grid plus thickness of the two side border
      canvasSideLength = gridSize + unitLength * 2;
    
      // We draw a black square that covers the entire canvas
      ctx.fillRect(0, 0, canvasSideLength, canvasSideLength);
    
      // We erase the center of the black to create the game space
      // This leaves a black outline for the that represents the border
      ctx.clearRect(unitLength, unitLength, gridSize, gridSize);
    
      // Next, we will store the initial positions of the snake's head and tail
      // The initial length of the snake will be 60px or 6 units
    
      // The head of the snake will be 30 px or 3 units ahead of the midpoint
      const headPosition = Math.floor(gridSize / 2) + 30;
    
      // The tail of the snake will be 30 px or 3 units behind the midpoint
      const tailPosition = Math.floor(gridSize / 2) - 30;
    
      // Loop from tail to head in unitLength increments
      for (let i = tailPosition; i <= headPosition; i += unitLength) {
    
        // Store the position of the snake's body and drawing on the canvas
        snake.push({ x: i, y: Math.floor(gridSize / 2) });
    
        // Draw a rectangle at that position of unitLength * unitLength
        ctx.fillRect(x, y, unitLength, unitLength);
      }
    
      // Generate food
      generateFood();

    faSnakeOccupyPosition

    Questa funzione accetta le coordinate xey come posizione. Quindi controlla che tale posizione esista nel corpo del serpente. Utilizza il metodo di ricerca dell’array JavaScript per trovare una posizione con coordinate corrispondenti.

    function doesSnakeOccupyPosition(x, y) {
      return !!snake.find((position) => {
        return position.x == x && y == foodPosition.y;
      });
    }

    checkForCollision

    Questa funzione controlla se il serpente è entrato in collisione con qualcosa e imposta la variabile collisione su true. Inizieremo controllando le collisioni contro le pareti sinistra e destra, le pareti superiore e inferiore e poi contro il serpente stesso.

    Per verificare le collisioni contro le pareti sinistra e destra, controlliamo se la coordinata x della testa del serpente è maggiore di gridSize o inferiore a 0. Per verificare le collisioni contro le pareti superiore e inferiore, eseguiremo lo stesso controllo ma con coordinate y.

    Successivamente, controlleremo le collisioni con il serpente stesso; controlleremo se qualche altra parte del suo corpo occupa la posizione attualmente occupata dalla testa. Combinando tutto ciò, il corpo della funzione checkForCllision dovrebbe assomigliare a questo:

     function checkForCollision() {
      const headPosition = snake.slice(-1)[0];
      // Check for collisions against left and right walls
      if (headPosition.x < 0 || headPosition.x >= gridSize - 1) {
        collided = true;
      }
    
      // Check for collisions against top and bottom walls
      if (headPosition.y < 0 || headPosition.y >= gridSize - 1) {
        collided = true;
      }
    
      // Check for collisions against the snake itself
      const body = snake.slice(0, -2);
      if (
        body.find(
          (position) => position.x == headPosition.x && position.y == headPosition.y
        )
      ) {
        collided = true;
      }
    }

    generareCibo

    La funzione generateFood utilizza un ciclo do- while per cercare una posizione in cui posizionare il cibo non occupata dal serpente. Una volta trovata, la posizione del cibo viene registrata e disegnata sulla tela. Il codice per la funzione generateFood dovrebbe assomigliare a questo:

    function generateFood() {
      let x = 0,
        y = 0;
      do {
        x = Math.floor((Math.random() * gridSize) / 10) * 10;
        y = Math.floor((Math.random() * gridSize) / 10) * 10;
      } while (doesSnakeOccupyPosition(x, y));
    
      foodPosition = { x, y };
      ctx.fillRect(x, y, unitLength, unitLength);
    }

    mossa

    La funzione di spostamento inizia creando una copia della posizione della testa del serpente. Quindi, in base alla direzione corrente, aumenta o diminuisce il valore della coordinata x o y del serpente. Ad esempio, aumentare la coordinata x equivale a spostarsi a destra.

    Una volta fatto ciò, inseriamo la nuova headPosition nell’array serpente. Disegniamo anche la nuova headPosition sulla tela.

    Successivamente, controlliamo se il serpente ha mangiato cibo durante quella mossa. Lo facciamo controllando se headPosition è uguale a foodPosition. Se il serpente ha mangiato del cibo, chiamiamo la funzione generateFood.

    Se il serpente non ha mangiato cibo, cancelliamo il primo elemento dell’array serpente. Questo elemento rappresenta la coda e rimuovendolo manterrai la stessa lunghezza del serpente dando l’illusione del movimento.

    function move() {
      // Create a copy of the object representing the position of the head
      const headPosition = Object.assign({}, snake.slice(-1)[0]);
    
      switch (direction) {
        case "left":
          headPosition.x -= unitLength;
          break;
        case "right":
          headPosition.x += unitLength;
          break;
        case "up":
          headPosition.y -= unitLength;
          break;
        case "down":
          headPosition.y += unitLength;
      }
    
      // Add the new headPosition to the array
      snake.push(headPosition);
    
      ctx.fillRect(headPosition.x, headPosition.y, unitLength, unitLength);
    
      // Check if snake is eating
      const isEating =
        foodPosition.x == headPosition.x && foodPosition.y == headPosition.y;
    
      if (isEating) {
        // Generate new food position
        generateFood();
      } else {
        // Remove the tail if the snake is not eating
        tailPosition = snake.shift();
    
        // Remove tail from grid
        ctx.clearRect(tailPosition.x, tailPosition.y, unitLength, unitLength);
      }
    }

    giro

    L’ultima funzione importante che tratteremo è la funzione di svolta. Questa funzione prenderà una nuova direzione e cambierà la variabile di direzione in quella nuova direzione. Tuttavia, il serpente può girare solo in una direzione perpendicolare a quella in cui si sta attualmente muovendo.

    Pertanto, il serpente può girare a sinistra o a destra solo se si muove verso l’alto o verso il basso. Al contrario, può alzarsi o abbassarsi solo se si sposta a sinistra o a destra. Tenendo presente questi vincoli, la funzione di svolta si presenta così:

    function turn(newDirection) {
      switch (newDirection) {
        case "left":
        case "right":
          // Only allow turning left or right if they were originally moving up or down
          if (direction == "up" || direction == "down") {
            direction = newDirection;
          }
          break;
        case "up":
        case "down":
          // Only allow turning up or down if they were originally moving left or right
          if (direction == "left" || direction == "right") {
            direction = newDirection;
          }
          break;
      }
    }

    onKeyDown

    La funzione onKeyDown è un gestore di eventi che chiamerà la funzione turn con la direzione corrispondente al tasto freccia che è stato premuto. La funzione, quindi, si presenta così:

    function onKeyDown(e) {
      switch (e.key) {
        case "ArrowDown":
          turn("down");
          break;
        case "ArrowUp":
          turn("up");
          break;
        case "ArrowLeft":
          turn("left");
          break;
        case "ArrowRight":
          turn("right");
          break;
      }
    }

    gameLoop

    La funzione gameLoop verrà chiamata regolarmente per mantenere il gioco in esecuzione. Questa funzione chiamerà la funzione move e la funzione checkForCollision. Controlla anche se la collisione è vera. In tal caso, interrompe un timer a intervalli che utilizziamo per eseguire il gioco e visualizza la schermata di “game over”. La funzione sarà simile a questa:

    function gameLoop() {
      move();
      checkForCollision();
    
      if (collided) {
        clearInterval(timer);
        gameOverScreen.style.display = "flex";
      }
    }

    Avvio del gioco

    Per avviare il gioco, aggiungi le seguenti righe di codice:

    setUp();
    document.addEventListener("keydown", onKeyDown);
    let timer = setInterval(gameLoop, 200);

    Per prima cosa chiamiamo la funzione setUp. Successivamente, aggiungiamo il listener di eventi “keydown”. Infine, utilizziamo la funzione setInterval per avviare il timer.

    Conclusione

    A questo punto, il tuo file JavaScript dovrebbe assomigliare a quello su my GitHub. Nel caso in cui qualcosa non funzioni, ricontrolla con il repository. Successivamente, potresti voler imparare come creare uno slider di immagini in JavaScript.