Tutorialul JavaScript Snake explicat

În acest articol, voi explica cum să creați un joc Snake folosind HTML, CSS și JavaScript.

Nu vom folosi biblioteci suplimentare; jocul va rula într-un browser. Crearea acestui joc este un exercițiu distractiv care vă ajută să vă întindeți și să vă exercitați mușchii care rezolvă problemele.

Schița proiectului

Snake este un joc simplu în care ghidezi mișcările unui șarpe către mâncare în timp ce eviți obstacolele. Când șarpele ajunge la hrană, o mănâncă și crește mai mult. Pe măsură ce jocul progresează, șarpele devine din ce în ce mai lung.

Șarpele nu ar trebui să se lovească de pereți sau de el însuși. Prin urmare, pe măsură ce jocul progresează, șarpele devine mai lung și devine din ce în ce mai greu de jucat.

Scopul acestui tutorial JavaScript Snake este de a construi jocul de mai jos:

Codul jocului este disponibil pe mine GitHub. O versiune live este găzduită Pagini GitHub.

Cerințe preliminare

Vom construi acest proiect folosind HTML, CSS și JavaScript. Vom scrie doar HTML și CSS de bază. Accentul nostru principal este pe JavaScript. Prin urmare, ar trebui să înțelegeți deja că trebuie urmat împreună cu acest tutorial JavaScript Snake. Dacă nu, vă recomand cu căldură să consultați articolul nostru despre cele mai bune locuri pentru a învăța JavaScript.

Veți avea nevoie și de un editor de cod în care să vă scrieți codul. În plus, veți avea nevoie de un browser, pe care probabil îl aveți dacă citiți acest lucru.

Configurarea Proiectului

Pentru început, să setăm fișierele de proiect. Într-un folder gol, creați un fișier index.html și adăugați următorul marcaj.

<!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>

Markupul de mai sus creează un ecran de bază „Game Over”. Vom comuta vizibilitatea acestui ecran folosind JavaScript. De asemenea, definește un element de pânză pe care vom desena labirintul, șarpele și mâncarea. Markupul conectează, de asemenea, foaia de stil și codul JavaScript.

Apoi, creați un fișier styles.css pentru stil. Adăugați următoarele stiluri la acesta.

* {
    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;
}

În setul de reguli „*”, vizam toate elementele și resetăm spațierea. De asemenea, setăm familia de fonturi pentru fiecare element și setăm dimensionarea elementelor la o metodă de dimensionare mai previzibilă numită border-box. Pentru corp, i-am setat înălțimea la înălțimea completă a feței de vizualizare și am aliniat toate elementele la centru. I-am dat și o culoare de fundal albastru.

În cele din urmă, am stilat ecranul „Game Over” pentru a-i oferi o înălțime și o lățime de 200, respectiv 500 de pixeli. I-am dat și o culoare de fundal magenta și un chenar negru. Am stabilit poziția sa la absolut, astfel încât să fie în afara fluxului normal de documente și aliniat la centrul ecranului. Apoi, i-am centrat conținutul. Îi setăm afișarea la niciunul, deci este ascuns în mod implicit.

  Care este mai bun pentru programare?

Apoi, creați un fișier snake.js, pe care îl vom scrie în următoarele câteva secțiuni.

Crearea variabilelor globale

Următorul pas în acest tutorial JavaScript Snake este definirea unor variabile globale pe care le vom folosi. În fișierul snake.js, adăugați următoarele definiții de variabile în partea de sus:

// 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");

Aceste variabile stochează referințe la ecranul „Game Over” și elementele pânzei. Apoi, am creat un context, care va fi folosit pentru a desena pe pânză.

Apoi, adăugați aceste definiții variabile sub primul set.

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

Primul definește dimensiunea grilei în pixeli. Al doilea definește o unitate de lungime în joc. Această unitate de lungime va fi utilizată în mai multe locuri. De exemplu, îl vom folosi pentru a defini cât de groși sunt pereții labirintului, cât de gros este șarpele, înălțimea și lățimea hranei și treptele în care se mișcă șarpele.

Apoi, adăugați următoarele variabile de joc. Aceste variabile sunt folosite pentru a urmări starea jocului.

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

Variabila șarpe ține evidența pozițiilor ocupate în prezent de șarpe. Șarpele cuprinde unități, iar fiecare unitate ocupă o poziție pe pânză. Poziția pe care o ocupă fiecare unitate este stocată în matricea șarpelor. Poziția va avea valorile x și y ca coordonate. Primul element din matrice reprezintă coada, în timp ce ultimul reprezintă capul.

Pe măsură ce șarpele se mișcă, vom împinge elemente până la capătul matricei. Acest lucru va muta capul înainte. De asemenea, vom elimina primul element sau coada din matrice, astfel încât lungimea să rămână aceeași.

Variabila de poziție a alimentelor stochează locația curentă a alimentelor folosind coordonatele x și y. Variabila de direcție stochează direcția în care se mișcă șarpele, în timp ce variabila ciocnită este o variabilă booleană marcată la adevărat atunci când a fost detectată o coliziune.

Declararea Funcțiilor

Întregul joc este împărțit în funcții, făcându-l mai ușor de scris și gestionat. În această secțiune, vom declara acele funcții și scopurile lor. Următoarele secțiuni vor defini funcțiile și vor discuta algoritmii acestora.

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

Pe scurt, funcția setUp setează jocul. Funcția checkForCollision verifică dacă șarpele s-a ciocnit cu un perete sau cu el însuși. Funcția doesSnakeOccupyPosition ia o poziție, definită de coordonatele x și y și verifică dacă vreo parte a corpului șarpelui se află în acea poziție. Acest lucru va fi util atunci când căutați o poziție liberă în care să adăugați mâncare.

Funcția de mutare mută șarpele în orice direcție îndreptată, în timp ce funcția de întoarcere schimbă acea direcție. Apoi, funcția onKeyDown va asculta apăsările de taste care sunt folosite pentru a schimba direcția. Funcția gameLoop va muta șarpele și va verifica dacă există coliziuni.

Definirea Funcțiilor

În această secțiune, vom defini funcțiile pe care le-am declarat mai devreme. Vom discuta, de asemenea, cum funcționează fiecare funcție. Va exista o scurtă descriere a funcției înainte de cod și comentarii pentru a explica rând cu rând acolo unde este necesar.

Funcția de configurare

Funcția de configurare va face 3 lucruri:

  Camera curată a datelor — explicat în 5 minute sau mai puțin
  • Desenați granițele labirintului pe pânză.
  • Configurați șarpele adăugând pozițiile sale la variabila șarpe și desenând-o pe pânză.
  • Generați poziția inițială a alimentelor.
  • Prin urmare, codul pentru acesta va arăta astfel:

      // 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();

    doesSnakeOccupyPosition

    Această funcție ia coordonatele x și y ca poziție. Apoi verifică existența unei astfel de poziții în corpul șarpelui. Folosește metoda JavaScript array find pentru a găsi o poziție cu coordonatele care se potrivesc.

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

    checkForCollision

    Această funcție verifică dacă șarpele s-a ciocnit cu ceva și setează variabila ciocnită la adevărat. Vom începe prin a verifica dacă există coliziuni împotriva pereților din stânga și din dreapta, a pereților de sus și de jos și apoi de șarpele însuși.

    Pentru a verifica coliziunile împotriva pereților din stânga și din dreapta, verificăm dacă coordonata x a capului șarpelui este mai mare decât gridSize sau mai mică decât 0. Pentru a verifica coliziunile împotriva pereților de sus și de jos, vom efectua aceeași verificare, dar cu coordonatele y.

    În continuare, vom verifica dacă există ciocniri împotriva șarpelui însuși; vom verifica dacă vreo altă parte a corpului său ocupă poziția ocupată în prezent de cap. Combinând toate acestea, corpul pentru funcția checkForCllision ar trebui să arate astfel:

     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;
      }
    }

    generateFood

    Funcția generateFood folosește o buclă do-while pentru a căuta o poziție pentru a plasa hrana care nu este ocupată de șarpe. Odată găsită, poziția alimentelor este înregistrată și desenată pe pânză. Codul pentru funcția generateFood ar trebui să arate astfel:

    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);
    }

    mișcare

    Funcția de mutare începe prin a crea o copie a poziției capului șarpelui. Apoi, pe baza direcției curente, crește sau scade valoarea coordonatei x sau y a șarpelui. De exemplu, creșterea coordonatei x este echivalentă cu deplasarea spre dreapta.

      O introducere în programarea asincronă în Rust

    Odată ce a fost făcut, împingem noua poziție headPosition în matricea șarpelui. De asemenea, desenăm noua poziție headPosition pe pânză.

    Apoi, verificăm dacă șarpele a mâncat mâncare în acea mișcare. Facem acest lucru verificând dacă headPosition este egală cu foodPosition. Dacă șarpele a mâncat mâncare, numim funcția generateFood.

    Dacă șarpele nu a mâncat mâncare, ștergem primul element al matricei de șarpe. Acest element reprezintă coada, iar îndepărtarea acestuia va menține lungimea șarpelui aceeași, dând în același timp iluzia de mișcare.

    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);
      }
    }

    întoarce

    Ultima funcție majoră pe care o vom acoperi este funcția de întoarcere. Această funcție va lua o nouă direcție și va schimba variabila de direcție în acea nouă direcție. Cu toate acestea, șarpele se poate întoarce doar într-o direcție perpendiculară pe cea în care se deplasează în prezent.

    Prin urmare, șarpele se poate întoarce la stânga sau la dreapta doar dacă se mișcă în sus sau în jos. În schimb, se poate întoarce în sus sau în jos doar dacă se deplasează la stânga sau la dreapta. Având în vedere aceste constrângeri, funcția de întoarcere arată astfel:

    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

    Funcția onKeyDown este un handler de evenimente care va apela funcția de întoarcere cu direcția corespunzătoare tastei săgeată care a fost apăsată. Prin urmare, funcția arată astfel:

    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

    Funcția gameLoop va fi apelată în mod regulat pentru a menține jocul să funcționeze. Această funcție va apela funcția de mutare și funcția checkForCollision. De asemenea, verifică dacă coliziunea este adevărată. Dacă da, oprește un temporizator de interval pe care îl folosim pentru a rula jocul și afișează ecranul „game over”. Funcția va arăta astfel:

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

    Începând Jocul

    Pentru a începe jocul, adăugați următoarele linii de cod:

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

    În primul rând, numim funcția setUp. Apoi, adăugăm ascultătorul de evenimente „keydown”. În cele din urmă, folosim funcția setInterval pentru a porni cronometrul.

    Concluzie

    În acest moment, fișierul dvs. JavaScript ar trebui să arate ca cel de pe mine GitHub. În cazul în care ceva nu funcționează, verificați de două ori cu repo. Apoi, poate doriți să aflați cum să creați un glisor de imagine în JavaScript.