Ajattele kuten ohjelmoija: Kuinka rakentaa Snake käyttämällä vain JavaScriptiä, HTML: ää ja CSS: ää

Hei siellä

Tervetuloa kyytiin. Tänään aloitamme jännittävän seikkailun, jossa teemme oman käärmepelin . Opit kuinka käsitellä ongelmaa jakamalla se pienempiin yksinkertaisempiin vaiheisiin. Tämän matkan loppuun mennessä olet oppinut uusia asioita, ja olet varma, että voit tutkia enemmän omallasi.

Jos olet uusi ohjelmointiohjelma, suosittelen tarkistamaan freeCodeCamp. Se on loistava paikka oppia ... arvasit sen ... ilmaiseksi. Näin aloitin

Okei, okei tarpeeksi sekaisin - oletko valmis aloittamaan?

Löydät lopullisen koodin täältä ja live-esityksen täältä.

Päästä alkuun

Aloitetaan luomalla tiedosto “snake.html”, joka sisältää kaiken koodimme.

Koska tämä on HTML-tiedosto, tarvitsemme ensin -ilmoitusta. Kirjoita seuraava kohta snake.html:

Hienoa, avaa nyt snake.html haluamallasi selaimella. Sinun pitäisi voida nähdä Tervetuloa käärmeeseen!

snake.html avattiin kromissa

Olemme hyvissä lähtökohdissa

Kankaan luominen

Jotta voimme luoda pelimme, meidän on hyödynnettävä HTML -sovellusta. Tätä käytetään piirtämään grafiikkaa JavaScriptin avulla.

Korvaa tervehdysviesti sanalla snake.html seuraavalla:

 

Tunnus identifioi kankaan ja se tulisi aina määritellä. Käytämme sitä päästäksesi kankaaseen myöhemmin. Leveys ja korkeus ovat kankaan mitat, ja ne tulisi myös määritellä. Tässä tapauksessa 300 x 300 pikseliä.

Snake.html-tiedostosi pitäisi nyt näyttää tältä.

Jos päivität selaimesivusi, jolla aiemmin avasit snake.html, näet nyt tyhjän sivun. Tämä johtuu siitä, että kangas on oletuksena tyhjä eikä siinä ole taustaa. Annetaan korjata se.

Anna kankaalle taustaväri ja reunus

Jotta kangas näkyisi, voimme antaa sille reunan kirjoittamalla JavaScriptiä. Sitä varten meidän on lisättävä -tunnisteet -kohtaan, mihin kaikki JavaScript-koodimme menee.

Jos laitat -tunnisteiden väliin. Päivitä koodi alla esitetyllä tavalla.

Ensin saamme piirtoalustan elementin käyttämällä aiemmin määrittämääsi tunnusta (gameCanvas). Sitten saamme kankaalle “2d” -kontekstin, mikä tarkoittaa, että piirrämme 2D-tilaan.

Lopuksi piirrämme 300 x 300 valkoisen suorakulmion mustalla reunuksella. Tämä kattaa koko kankaan, alkaen vasemmasta yläkulmasta (0, 0).

Jos lataat käärme.html-selaimesi uudelleen, sinun pitäisi nähdä valkoinen ruutu mustalla reunuksella! Hyvää työtä, meillä on kangas, jota voimme käyttää käärmepelimme luomiseen! Seuraavaan haasteeseen!

Edustamme käärmettämme

Jotta käärmepeli toimisi, meidän on tiedettävä käärmeen sijainti kankaalla. Sitä varten voimme edustaa käärmeä koordinaattimallina. Siten luodaan vaakasuora käärme kankaan keskelle (150, 150) kirjoittamalla seuraava:

anna käärme = [
  {x: 150, y: 150},
  {x: 140, y: 150},
  {x: 130, y: 150},
  {x: 120, y: 150},
  {x: 110, y: 150},
];

Huomaa, että kaikkien osien y-koordinaatti on aina 150. Kunkin osan x-koordinaatti on -10px (vasemmalla) edellisestä osasta. Matriisin {x: 150, y: 150} ensimmäinen koordinaattipari edustaa käärmeen oikealla puolella olevaa päätä.

Tämä tulee selvemmäksi, kun piirrämme käärmeen seuraavassa osassa.

Käärmeemme luominen ja piirtäminen

Käärme voidaan näyttää kankaalle kirjoittamalla toiminto piirtääksesi suorakulmion jokaiselle koordinaattiparille.

toiminto drawSnakePart (snakePart) {
  ctx.fillStyle = 'vaaleanvihreä';
  ctx.strokestyle = 'tummanvihreä';
  ctx.fillRect (käärmeosa.x, käärmeosa.y, 10, 10);
  ctx.strokeRect (snakePart.x, snakePartyyy, 10, 10);
}

Seuraavaksi voimme luoda toisen toiminnon, joka tulostaa kankaalle osat.

toiminto drawSnake () {
  snake.forEach (drawSnakePart);
}

Snake.html-tiedostomme pitäisi nyt näyttää tältä:

Jos päivität selaimesivusi nyt, kankaan keskellä näet vihreän käärmeen. Mahtava!

Mahdollistaa käärmeen liikkumisen vaakatasossa

Seuraavaksi haluamme antaa käärmeelle mahdollisuuden liikkua. Mutta miten teemme sen?

No, jotta käärme liikkuu yhden askeleen (10 px) oikealle, voimme lisätä käärmeen jokaisen osan x-koordinaattia 10 px (dx = + 10 px). Jotta käärme siirtyisi vasemmalle, voimme pienentää käärmeen jokaisen osan x-koordinaattia 10 px (dx = -10).

dx on käärmeen vaakatason nopeus.

Luo käärme, joka on siirtynyt 10 pikseliä oikealle, pitäisi näyttää tältä

Luo toiminto nimeltä advanceSnake, jota päivitämme käärmeen.

toiminto advanceSnake () {
  const head = {x: käärme [0] .x + dx, y: käärme [0] .y};
  snake.unshift (pää);
  snake.pop ();
}

Ensin luomme käärmeelle uuden pään. Lisäämme sitten uuden pään käärmeen alkuun käyttämällä vaihtamatta ja poistamme käärmeen viimeisen osan popilla. Tällä tavalla kaikki muut käärmeosat siirtyvät paikalleen yllä esitetyllä tavalla.

Boom , saat tämän hetken.

Mahdollistaa käärmeen liikkumisen pystysuunnassa

Siirtääksemme käärmettämme ylös ja alas, emme voi muuttaa kaikkia y-koordinaatteja 10xx. Se siirtäisi koko käärme ylös ja alas.

Sen sijaan voimme muuttaa pään y-koordinaattia. Vähentämällä sitä kymmenellä pikselillä käärmeen siirtämiseksi alas ja lisäämällä kymmenen pikseliä käärmeen siirtämiseksi ylöspäin. Tämä saa käärmeen liikkumaan oikein.

Onneksi tämä on erittäin helppo tehdä tavasta, jolla kirjoitimme ankstoSnake-toiminnon. Päivitä pää etukäteisnauhan sisällä lisätäksesi pään y-koordinaattia dy: llä.

const head = {x: käärme [0] .x + dx, y: käärme [0] .y + dy};

Testaamaan, miten advanceSnake-toiminto toimii, voimme soittaa siihen väliaikaisesti ennen drawSnake-toimintoa.

// Siirry askel oikealle
advanceSnake ()
// Muuta pystysuuntainen nopeus arvoon 0
dx = 0;
// Muuta vaakanopeus arvoon 10
dy = -10;
// Siirrä yksi askel ylöspäin
advanceSnake ();
// Piirrä käärme kankaalle
drawSnake ();

Näin snake.html-tiedostomme näyttää toistaiseksi.

Päivittämällä selaimen voimme nähdä, että käärmemme on muuttunut. Menestys!

Koodin refaktorointi

Ennen kuin siirrymme eteenpäin, tehdään refaktorointi ja siirretään koodi, joka piirtää kankaan toiminnon sisään. Tämä auttaa meitä seuraavassa osassa.

”Koodin uudelleenreaktointi on prosessi, jolla järjestetään olemassa oleva tietokonekoodi uudelleen muuttamatta sen ulkoista käyttäytymistä.” -Wikipedia
toiminto clearCanvas () {
  ctx.fillStyle = "valkoinen";
  ctx.strokeStyle = "musta";
  ctx.fillRect (0, 0, gameCanvas.width, gameCanvas.height);
  ctx.strokeRect (0, 0, gameCanvas.width, gameCanvas.height);
}

Olemme edistyneet hyvin!

Käärmeemme liikkuu automaattisesti

Okei, nyt kun olemme onnistuneesti uusineet koodimme, voimme saada käärmeemme liikkumaan automaattisesti.

Aikaisemmin, jotta voimme testata, että advanceSnake-toiminto toimi, kutsuimme sitä kahdesti. Kerran saada käärme liikkumaan oikealle ja kerran saada käärme liikkumaan ylöspäin.

Joten jos halusimme saada käärme liikkumaan viisi askelta oikealle, kutsutaan advanceSnake () viisi kertaa peräkkäin.

clearCanvas ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
drawSnake ();

Mutta kutsumalla sitä viisi kertaa peräkkäin yllä esitetyllä tavalla, käärme hyppää 50xx eteenpäin.

Sen sijaan haluamme saada käärmeen näyttävän liikkuvan eteenpäin askel askeleelta.

Tätä varten voimme lisätä pienen viiveen jokaisen puhelun väliin setTimeout-sovelluksella. Meidän on myös varmistettava, että soitamme drawSnake -sovellukselle joka kerta, kun soitamme advanceSnake. Jos emme tee sitä, emme voi nähdä välivaiheita, jotka osoittavat käärmeen liikkuvan.

setTimeout (toiminto onTick () {
  clearCanvas ();
  advanceSnake ();
  drawSnake ();
}, 100);
setTimeout (toiminto onTick () {
  clearCanvas ();
  advanceSnake ();
  drawSnake ();
}, 100);
...
drawSnake ();

Huomaa, kuinka me kutsumme myös clearCanvas () -tapaa jokaisen setTimeout-sisällä. Tämä on poistaa kaikki käärmeen aiemmat asennot, jotka jättäisivät jäljen jälkeensä.

Vaikka yllä olevassa koodissa on ongelma. Mikään tässä ei kerro ohjelmalle, että sen on odotettava setTimeout ennen kuin se siirtyy seuraavaan setTimeout. Tämä tarkoittaa, että käärme hyppää edelleen 50xx eteenpäin, mutta pienen viiveen jälkeen.

Korjataksesi tämän, meidän on käärittävä koodimme toimintoihin, kutsuen yhtä toimintoa kerrallaan.

Ensimmäinen askel();
    
toiminto stepOne () {
  setTimeout (toiminto onTick () {
    clearCanvas ();
    advanceSnake ();
    drawSnake ();
   // Soita toiselle toiminnolle
   stepTwo ();
  }, 100)
}
toiminto stepTwo () {
  setTimeout (toiminto onTick () {
    clearCanvas ();
    advanceSnake ();
    drawSnake ();
    // Kutsu kolmas toiminto
    stepThree ();
  }, 100)
}
...

Kuinka saamme käärmeemme liikkumaan? Sen sijaan, että luomme äärettömän määrän toisiinsa soittavia toimintoja, voimme sen sijaan luoda yhden päätoiminnon ja soittaa sitä yhä uudelleen.

toiminto main () {
  setTimeout (toiminto onTick () {
    clearCanvas ();
    advanceSnake ();
    drawSnake ();
    // Soita uudestaan
    main ();
  }, 100)
}

Voilà! Meillä on nyt käärme, joka jatkaa liikkumista oikealle. Vaikka se saavuttaa kankaan lopun, se jatkaa ääretöntä matkaansa tuntemattomaan . Korjaamme sen hyvissä ajoin, kärsivällisyys nuori padawan. .

Käärmeen suunnan muuttaminen

Seuraava tehtävämme on muuttaa käärmeen suuntaa, kun yhtä nuolinäppäimiä painetaan. Lisää seuraava koodi drawSnakePart-toiminnon jälkeen.

toiminnon muutossuunta (tapahtuma) {
  const LEFT_KEY = 37;
  const RIGHT_KEY = 39;
  const UP_KEY = 38;
  const DOWN_KEY = 40;
  const keyPressed = event.keyCode;
  const goingUp = dy === -10;
  const goingDown = dy === 10;
  jatkuva oikea = dx === 10;
  const goingLeft = dx === -10;
  if (keyPressed === LEFT_KEY &&! goingRight) {
    dx = -10;
    dy = 0;
  }
  if (näppäinpainettu === UP_KEY &&! goingDown) {
    dx = 0;
    dy = -10;
  }
  if (näppäinpainettu === RIGHT_KEY &&! goingLeft) {
    dx = 10;
    dy = 0;
  }
  if (näppäinpainettu === DOWN_KEY &&! goingDown) {
    dx = 0;
    dy = 10;
  }
}

Täällä ei ole mitään hankalaa. Tarkistamme, vastaako painettu näppäin yhtä nuolinäppäimistä. Jos niin tapahtuu, muutamme pystysuoraa ja vaakasuuntaista nopeutta aiemmin kuvatulla tavalla.

Huomaa, että tarkistamme myös, liikkuu käärme uuden suunnan vastakkaiseen suuntaan. Tämän on tarkoitus estää käärmettä kääntymästä taaksepäin, esimerkiksi kun painat oikeaa nuolinäppäintä, kun käärme liikkuu vasemmalle.

Käärme peruuttaa

Yhdistääksesi muutossuunnan peliin, käytämme asiakirjan addEventListener-sovellusta "kuunnella" näppäintä painettaessa. Sitten voimme kutsua changeDirection-näppäintä keydown-tapahtuman kanssa. Lisää seuraava koodi päätoiminnon jälkeen.

document.addEventListener ("keydown", changeDirection)

Sinun pitäisi nyt pystyä muuttamaan käärmeen suunta neljällä nuolinäppäimellä. Suuri työ, olet tulessa!

Seuraavaksi katsotaan, kuinka voimme tuottaa ruokaa ja kasvattaa käärmeemme.

Ruoan tuottaminen käärmeelle

Käärmeruokallamme on luotava satunnainen koordinaattijoukko. Voimme käyttää randomTen-auttajatoimintoa tuottaa kaksi numeroa. Yksi x-koordinaatille ja toinen y-koordinaatille.

Meidän on myös varmistettava, että ruoka ei sijaitse käärmessä. Jos se on, meidän on luotava uusi ruokapaikka.

toiminto randomTen (min, max) {
  paluu Math.round ((Math.random () * (max-min) + min) / 10) * 10;
}
toiminto createFood () {
  foodX = randomTen (0, gameCanvas.leveys - 10);
  foodY = randomTen (0, gameCanvas.height - 10);
  snake.forEach (toiminto onFoodOnSnake (osa) {
    const foodIsOnSnake = part.x == foodX && part.y == foodY
    if (foodIsOnSnake)
      createFood ();
  });
}

Sitten meidän on luotava toiminto ruuan piirtämiseksi kankaalle.

toiminto drawFood () {
 ctx.fillStyle = 'punainen';
 ctx.strokestyle = 'tummennettu';
 ctx.fillRect (foodX, foodY, 10, 10);
 ctx.strokeRect (foodX, foodY, 10, 10);
}

Viimeinkin voimme soittaa createFoodille ennen pääsoittoon soittamista. Älä unohda päivittää myös pääohjelmaa DrawFood-toiminnon käyttämiseksi.

toiminto main () {
  setTimeout (toiminto onTick () {
    clearCanvas ();
    drawFood ()
    advanceSnake ();
    drawSnake ();
    main ();
  }, 100)
}

Käärme kasvaa

Käärmemme kasvattaminen on yksinkertaista. Voimme päivittää advanceSnake -toiminnomme tarkistaaksesi, koskettaako käärmeen pää ruokaa. Jos se on, voimme ohittaa käärmeen viimeisen osan poistamisen ja luoda uuden ruokapaikan.

toiminto advanceSnake () {
  const head = {x: käärme [0] .x + dx, y: käärme [0] .y};
  snake.unshift (pää);
  const didEatFood = käärme [0] .x === foodX && snake [0] .y === foodY;
  if (didEatFood) {
    createFood ();
  } muuta {
    snake.pop ();
  }
}

Pisteiden seuraaminen

Jotta pelistä tulisi pelaajan nautinnollisempi, voimme lisätä myös pisteet, jotka kasvavat, kun käärme syö ruokaa.

Luo uusi muuttuva pistemäärä ja aseta se arvoon 0 käärmeilmoituksen jälkeen.

anna pistemäärä = 0;

Lisää seuraavaksi uusi div-yksikkö tunnuksella “pisteet” ennen kangasta. Voimme käyttää tätä näyttääksesi pisteet.

0

Päivitä lopuksi advanceSnake lisätäksesi ja näyttääksesi pisteet, kun käärme syö ruokaa.

toiminto advanceSnake () {
  ...
  if (didEatFood) {
    pisteet + = 10;
    document.getElementById ('score'). innerHTML = score;
    createFood ();
  } muuta {
    ...
  }
}

Pheew, sitä oli melko paljon, mutta olemme matkalla pitkälle

Lopeta peli

Jäljellä on yksi viimeinen kappale, ja se on pelin loppu. Sitä varten voimme luoda funktion didGameEnd, joka palauttaa true, kun peli on päättynyt, tai väärän muuten.

funktio didGameEnd () {
  varten (olkoon i = 4; i 
    if (didCollide) palaa totta
  }
  const hitLeftWall = käärme [0] .x <0;
  const hitRightWall = käärme [0] .x> gameCanvas.leveys - 10;
  const hitToptWall = käärme [0] .y <0;
  const hitBottomWall = käärme [0] .y> gameCanvas.height - 10;
  palauta hitLeftWall ||
         hitRightWall ||
         hitToptWall ||
         hitBottomWall
}

Ensin tarkistetaan, koskettaako käärmeen pää käärmeen toista osaa, ja palaako totta, jos se tapahtuu.

Huomaa, että aloitamme silmukan indeksistä 4. Tähän on kaksi syytä. Ensimmäinen on se, että didCollide arvioi välittömästi totta, jos indeksi on 0, joten peli päättyy. Toinen on se, että kolmen ensimmäisen osan on mahdotonta koskettaa toisiaan.

Seuraavaksi tarkistamme onko käärme osunut mihin tahansa kankaan seinämiin ja palaammeko tosi, jos se tapahtui, muuten palaamme väärät.

Nyt voimme palata varhaisessa vaiheessa päätoimintoomme, jos didEndGame palaa totta, mikä lopettaa pelin.

toiminto main () {
  if (didGameEnd ()) palaa;
  ...
}

Snake.html -tuotteemme pitäisi nyt näyttää tältä:

Sinulla on nyt toimiva käärmepeli, jota voit pelata ja jakaa ystävillesi. Mutta ennen juhlia katsotaanpa lopullinen ongelma. Tämä on viimeinen, lupaan.

Sneaky bugs

Jos pelaat peliä tarpeeksi kertaa, saatat huomata, että joskus peli päättyy odottamatta. Tämä on erittäin hyvä esimerkki siitä, kuinka virheet voivat hiipiä ohjelmiin ja aiheuttaa ongelmia .

Kun virhe ilmenee, paras tapa ratkaista se on ensin luotettava tapa toistaa se. Toisin sanoen keksiä tarkat vaiheet, jotka johtavat odottamattomaan käyttäytymiseen. Sitten sinun on ymmärrettävä, miksi ne aiheuttavat odottamattoman käytöksen, ja keksittävä sitten ratkaisu.

Toista virhe

Meidän tapauksessamme virheen toistamiseksi tehdyt vaiheet ovat seuraavat:

  • Käärme liikkuu vasemmalle
  • Soitin painaa nuolinäppäintä
  • Pelaaja painaa heti oikeaa nuolinäppäintä (ennen kuin 100 ms on kulunut umpeen)
  • Peli päättyy

Virheen ymmärtäminen

Tarkastellaan mitä tapahtuu askel askeleelta.

Käärme liikkuu vasemmalle

  • Vaakatason nopeus, dx on -10
  • päätoimintoa kutsutaan
  • advanceSnake -nimellä kutsutaan käärme -10px vasemmalle.

Soitin painaa nuolinäppäintä

  • changeDirection kutsutaan
  • keyPressed === DOWN_KEY && dy! goingUp arvioi totta
  • dx muuttuu arvoon 0
  • dy muuttuu arvoon +10

Pelaaja painaa heti oikeaa nuolta (ennen kuin 100 ms on kulunut umpeen)

  • changeDirection kutsutaan
  • keyPressed === Oikea_näppäin &&! goingLeft arvioi totta
  • dx muuttuu arvoon +10
  • dy muuttuu arvoon 0

Peli päättyy

  • päätoiminto kutsutaan 100 ms: n kuluttua.
  • advanceSnake -nimiä kutsutaan käärmeen 10 pikseliä oikealle.
  • const didCollide = käärme [i] .x === käärme [0] .x && käärme [i] .y === käärme [0] .y arvioi totta
  • didGameEnd palauttaa true
  • päätoiminto palaa aikaisin
  • Peli päättyy

Korjaa virhe

Tutkittuaan mitä tapahtui, opimme, että peli päättyi, koska käärme kääntyi.

Tämä johtuu siitä, että kun pelaaja painoi nuolta, dx asetettiin arvoon 0. Näin näppäinpainettu === RIGHT_KEY &&!

On tärkeää huomata, että suunnanmuutos tapahtui ennen kuin 100 ms oli kulunut umpeen. Jos 100 ms raukesi, käärme olisi ensin ottanut askeleen alaspäin eikä olisi kääntynyt taaksepäin.

Vian korjaamiseksi meidän on varmistettava, että voimme muuttaa suuntaa vasta sen jälkeen, kun main ja advanceSnake on kutsuttu. Voimme luoda muuttujan muuttava suunta. Tämä asetetaan arvoon true, kun changeDirection kutsutaan, ja false arvoon, kun advanceSnake kutsutaan.

ChangeDirection-toiminnon sisällä voimme palata varhain, jos changeDirection on totta.

toiminnon muutossuunta (tapahtuma) {
  const LEFT_KEY = 37;
  const RIGHT_KEY = 39;
  const UP_KEY = 38;
  const DOWN_KEY = 40;
  if (ChangeDirection) palauttaa;
  ChangeDirection = totta;
  ...
}
toiminto main () {
  setTimeout (toiminto onTick () {
    ChangeDirection = epätosi;
    
    ...
  }, 100)
}

Tässä on lopullinen versio snake.html-tiedostosta

Huomaa, että lisäsin myös joitain tyylejä -tunnisteiden väliin. Tämä tarkoittaa, että kangas ja pisteet näkyvät näytön keskellä.

johtopäätös

Onnittelut!!

Olemme saavuttaneet matkan lopun. Toivon, että nautit oppimisesta kanssani ja olet nyt varma jatkamaan seuraavaan seikkailuusi.

Mutta sen ei tarvitse loppua tähän. Seuraava artikkeli keskittyy auttamaan sinua pääsemään alkuun avoimen lähdekoodin erittäin jännittävästä maailmasta.

Avoin lähdekoodi on hieno tapa oppia paljon uusia asioita ja tuntea uskomattomia ihmisiä. Se on erittäin palkitsevaa, mutta voi olla aluksi pelottavaa .

Voit seurata minua saadaksesi ilmoituksen seuraavan artikkelini julkaisemisesta.

Oli ilo olla tällä matkalla kanssasi.

Seuraavaan kertaan.