Mõelge nagu programmeerija: kuidas luua Snake ainult JavaScripti, HTMLi ja CSS-i kasutades

Tere seal

Teretulemast pardale. Täna alustame põnevat seiklust, kus teeme omaenda madude mängu . Õpid, kuidas probleemi lahendada, jaotades selle väiksemateks lihtsamateks sammudeks. Selle reisi lõpuks olete õppinud uusi asju ja tunnete enesekindlalt võimalusi iseseisvalt rohkem uurida.

Kui olete programmeerimises uus, soovitan tutvuda freeCodeCampiga. See on suurepärane koht, kus õppida ... arvasite seda ... tasuta. Nii ma alustasin

Olgu, piisavalt segane - kas olete alustamiseks valmis?

Lõpliku koodi ja reaalajas demo leiate siit.

Alustamine

Alustame faili “snake.html” loomisega, mis sisaldab kogu meie koodi.

Kuna see on HTML-fail, vajame kõigepealt -deklaratsiooni. Tippige väljale snake.html järgmine teave:

Tore, nüüd minge edasi ja avage oma eelistatud brauseris snake.html. Teil peaks olema võimalus näha Welcome to Snake!

kääris avatud snake.html

Oleme hea alguseni jõudnud

Lõuendi loomine

Oma mängu loomiseks peame kasutama HTML-i . Just seda kasutatakse graafika joonistamiseks JavaScripti abil.

Asendage tervitussõnum saidil snake.html järgmisega:

 

Lõuend identifitseerib ID ja see tuleks alati täpsustada. Kasutame seda lõuendile hiljem juurde pääsemiseks. Laius ja kõrgus on lõuendi mõõtmed ja need tuleks ka täpsustada. Sel juhul 300 x 300 pikslit.

Teie fail snake.html peaks nüüd välja nägema selline.

Kui värskendate oma brauseri lehte, kus avasite snake.html, näete nüüd tühja lehte. Seda seetõttu, et vaikimisi on lõuend tühi ja sellel puudub taust. Võimaldab selle parandada.

Andke lõuendile taustvärv ja ääris

Meie lõuendi nähtavaks tegemiseks saame sellele piiri panna, kirjutades JavaScripti koodi. Selleks peame sisestama sildid järele, kuhu läheb kogu meie JavaScripti kood.

Kui lisate sildi siltide vahele. Uuendage oma koodi nagu allpool.

Kõigepealt saame lõuendi elemendi, kasutades id (gameCanvas), mille me varem täpsustasime. Seejärel saame lõuendi “2d” konteksti, mis tähendab, et me tõmbame 2D ruumi.

Lõpuks joonistame musta äärisega 300 x 300 valge ristküliku. See katab kogu lõuendi, alustades vasakust ülanurgast (0, 0).

Kui laadite oma brauseris snake.html uuesti, peaksite nägema valget musta äärisega kasti! Hea töö, meil on lõuend, mida saame kasutada oma madumängu loomiseks! Jätkake järgmise väljakutsega!

Meie madu esindab

Et meie madumäng toimiks, peame teadma madu asukohta lõuendil. Selleks võime mao esindada koordinaatide ridadena. Nii et lõuendi keskele (150, 150) horisontaalse mao loomiseks võime kirjutada järgmist:

lase madu = [
  {x: 150, a: 150},
  {x: 140, a: 150},
  {x: 130, a: 150},
  {x: 120, a: 150},
  {x: 110, a: 150},
];

Pange tähele, et kõigi osade y-koordinaat on alati 150. Mõlema osa x-koordinaat on eelmise osa x-koordinaat -10 (vasakul). Esimene koordinaatide paar massiivis {x: 150, y: 150} tähistab pead madu paremal.

See saab selgemaks, kui järgmises osas joonistame madu.

Meie madu loomine ja joonistamine

Madu kuvamiseks lõuendil võime kirjutada funktsiooni, mille abil saab iga koordinaatide paari jaoks ristküliku joonistada.

funktsioon drawSnakePart (snakePart) {
  ctx.fillStyle = 'heleroheline';
  ctx.strokestyle = 'tumeroheline';
  ctx.fillRect (snakePart.x, snakePartyy, 10, 10);
  ctx.strokeRect (snakePart.x, snakePartyy, 10, 10);
}

Järgmisena saame luua veel ühe funktsiooni, mis prindib lõuendile osad.

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

Meie fail snake.html peaks nüüd välja nägema järgmine:

Kui värskendate oma brauseri lehte nüüd, näete lõuendi keskel rohelist madu. Vinge!

Võimaldab madul horisontaalselt liikuda

Järgmisena tahame anda madule võimaluse liikuda. Aga kuidas me seda teeme?

Noh, selleks, et madu liiguks ühe sammu võrra (10 px) paremale, saame madu iga osa x-koordinaati suurendada 10 px (dx = + 10 px). Madu vasakule liikumiseks saame madu iga osa x-koordinaati vähendada 10 px (dx = -10).

dx on madu horisontaalne kiirus.

10xx paremale liikunud madu loomine peaks siis välja nägema selline

Looge funktsioon advanceSnake, mida kasutame madu värskendamiseks.

funktsioon advanceSnake () {
  const head = {x: madu [0] .x + dx, y: madu [0] .y};
  madu.unshift (pea);
  madu.pop ();
}

Esmalt loome mao jaoks uue pea. Seejärel lisame uue pea madra algusesse, kasutades unshift ja eemaldame madu viimase elemendi, kasutades poppi. Sel moel nihkuvad kõik muud mao osad oma kohale, nagu ülal näidatud.

Boom , sa saad sellest aru.

Võimaldab madul vertikaalselt liikuda

Madu üles ja alla liigutamiseks ei saa me kõiki y-koordinaate 10 piksli võrra muuta. See nihutaks kogu madu üles ja alla.

Selle asemel saame muuta pea y-koordinaati. Vähendades seda 10xx, et mao allapoole nihutada, ja suurendades seda 10xx, et mao üles tõsta. See paneb madu õigesti liikuma.

Õnneks on funktsiooni advanceSnake kirjutamisviisi tõttu seda väga lihtne teha. Uuendage AdvanSnake'i sees pea, et suurendada ka y y-koordinaati dy abil.

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

Meie ankstoSnake-funktsiooni toimimise testimiseks võime selle enne funktsiooni drawSnake ajutiselt kutsuda.

// Liikuge edasi paremale
advanceSnake ()
// Muutke vertikaalse kiiruse väärtuseks 0
dx = 0;
// Muutke horisontaalne kiirus 10-ni
dy = -10;
// Liigutage üks samm üles
advanceSnake ();
// Joonista lõuendile madu
drawSnake ();

Nii näeb meie fail snake.html siiani välja.

Brauserit värskendades näeme, et meie madu on liikunud. Edu!

Meie koodi uuesti sisestamine

Enne kui liigume edasi, teeme mõned refaktorid ja liigutame koodi, mis joonistab lõuendi funktsiooni sisse. See aitab meid järgmises jaotises.

„Koodireaktor on olemasoleva arvutikoodi ümberkorraldamine, muutmata selle välist käitumist.” -Wikipeedia
funktsioon clearCanvas () {
  ctx.fillStyle = "valge";
  ctx.strokeStyle = "must";
  ctx.fillRect (0, 0, gameCanvas.width, gameCanvas.height);
  ctx.strokeRect (0, 0, gameCanvas.width, gameCanvas.height);
}

Me teeme suuri edusamme!

Meie madu liigub automaatselt

Olgu, nüüd, kui oleme oma koodi edukalt ümber kujundanud, saame oma madu automaatselt liikuma panna.

Varem kutsusime seda kaks korda, et testida, kas meie advanceSnake funktsioon töötab. Üks kord, et panna madu paremale liikuma, ja üks kord, et madu üles tõusta.

Seega, kui me tahaksime panna madu liikuma viis sammu paremale, siis kutsuksime AdvanSnake () viis korda järjest.

selgeCanvas ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
advanceSnake ();
drawSnake ();

Kuid kui kutsute seda viis korda järjest, nagu ülal näidatud, teeb see madu 50xx ettepoole hüppamiseks.

Selle asemel tahame, et madu näib liikuvat samm-sammult edasi.

Selleks saame setTimeout abil iga kõne vahele lisada väikese viivituse. Samuti peame kindlasti helistama drawSnake'ile iga kord, kui helistame ankstoSnake'ile. Kui me seda ei tee, ei saa me näha vaheetappe, mis näitavad madu liikumist.

setTimeout (funktsioon onTick () {
  selgeCanvas ();
  advanceSnake ();
  drawSnake ();
}, 100);
setTimeout (funktsioon onTick () {
  selgeCanvas ();
  advanceSnake ();
  drawSnake ();
}, 100);
...
drawSnake ();

Pange tähele, kuidas me igal setTimeout-l nimetame ka clearCanvas (). Selle eesmärk on eemaldada kõik madu eelmised positsioonid, mis jätaks jälje maha.

Kuid ülaltoodud koodiga on probleeme. Siin pole midagi öelda programmile, et enne järgmise setTimeouti liikumist peab setTimeout ootama. See tähendab, et madu hüppab ikkagi 50 pikslit edasi, kuid pärast väikest viivitust.

Selle parandamiseks peame mässima oma koodi funktsioonide sisse, kutsudes ühe funktsiooni korraga.

esimene samm();
    
funktsioon stepOne () {
  setTimeout (funktsioon onTick () {
    selgeCanvas ();
    advanceSnake ();
    drawSnake ();
   // Helistage teisele funktsioonile
   samm kaks ();
  }, 100)
}
funktsioon stepTwo () {
  setTimeout (funktsioon onTick () {
    selgeCanvas ();
    advanceSnake ();
    drawSnake ();
    // Kutsu kolmas funktsioon
    sammKolm ();
  }, 100)
}
...

Kuidas panna oma madu liikuma? Selle asemel, et luua lõpmatu arv üksteisele helistavaid funktsioone, saame selle asemel luua ühe peamise funktsiooni ja helistada sellele ikka ja jälle.

funktsioon main () {
  setTimeout (funktsioon onTick () {
    selgeCanvas ();
    advanceSnake ();
    drawSnake ();
    // helistage uuesti
    peamine ();
  }, 100)
}

Voilà! Nüüd on meil madu, kes liigub edasi paremale. Ehkki kui see lõuendi lõpuni jõuab, jätkab see oma lõpmatut teekonda tundmatusse . Parandame selle õigel ajal, kannatlik noor padawan. .

Madu suuna muutmine

Järgmine ülesanne on muuta madu suunda ühele nooleklahvile vajutamisel. Pärast funktsiooni drawSnakePart lisage järgmine kood.

funktsiooni muutusDirection (sündmus) {
  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;
  const goingRight = dx === 10;
  const goingLeft = dx === -10;
  if (keyPressed === LEFT_KEY &&! goingRight) {
    dx = -10;
    dy = 0;
  }
  if (keyPressed === UP_KEY &&! goingDown) {
    dx = 0;
    dy = -10;
  }
  if (keyPressed === RIGHT_KEY &&! goingLeft) {
    dx = 10;
    dy = 0;
  }
  if (keyPressed === DOWN_KEY &&! goingDown) {
    dx = 0;
    dy = 10;
  }
}

Siin ei toimu midagi keerulist. Kontrollime, kas vajutatud klahv vastab ühele nooleklahvidele. Kui see juhtub, muudame vertikaalset ja horisontaalset kiirust, nagu eespool kirjeldatud.

Pange tähele, et kontrollime ka seda, kas madu liigub uue kavandatud suunaga vastupidises suunas. Selle eesmärk on vältida meie mao tagasipööramist, näiteks kui vajutad madu vasakule paremale nooleklahvi.

Madu tagurpidi

ChangeDirectioni ühendamiseks meie mänguga saame dokumendi addEventListeneri abil klahvi vajutamisel kuulata. Siis võime helistamisürituse korral helistada muutmissuunale. Lisage järgmine kood pärast põhifunktsiooni.

document.addEventListener ("keydown", changeDirection)

Nüüd peaks teil olema võimalik madu suunda nelja nooleklahvi abil muuta. Suur töö, olete tulekahjus!

Järgmisena näeme, kuidas toitu toota ja oma madu kasvatada.

Mao jaoks toidu genereerimine

Oma madu toidu jaoks peame genereerima juhusliku koordinaatide komplekti. Kahe numbri saamiseks saame kasutada abistajafunktsiooni randomTen. Üks x-koordinaadi ja teine ​​y-koordinaadi jaoks.

Samuti peame jälgima, et toit ei asuks seal, kus madu praegu on. Kui see on nii, peame looma uue toidu asukoha.

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

Seejärel peame looma funktsiooni, et toitu lõuendile joonistada.

funktsioon drawFood () {
 ctx.fillStyle = 'punane';
 ctx.strokestyle = 'pimendatud';
 ctx.fillRect (foodX, foodY, 10, 10);
 ctx.strokeRect (foodX, foodY, 10, 10);
}

Lõpuks võime enne maini helistamist helistada createFoodile. Ärge unustage ka värskendada peamist, et kasutada funktsiooni drawFood.

funktsioon main () {
  setTimeout (funktsioon onTick () {
    selgeCanvas ();
    drawFood ()
    advanceSnake ();
    drawSnake ();
    peamine ();
  }, 100)
}

Mao kasvatamine

Meie mao kasvatamine on lihtne. Saame värskendada oma avanenud madu funktsiooni, et kontrollida, kas madu pea puudutab toitu. Kui see on nii, võime mao viimase osa eemaldamise vahele jätta ja luua uue toidukoha.

funktsioon advanceSnake () {
  const head = {x: madu [0] .x + dx, y: madu [0] .y};
  madu.unshift (pea);
  const didEatFood = madu [0] .x === foodX && madu [0] .y === toiduline;
  if (didEatFood) {
    luuaFood ();
  } veel {
    madu.pop ();
  }
}

Hinde jälgimine

Mängu mängijale meeldivamaks muutmiseks võime lisada ka skoori, mis suureneb, kui madu sööb toitu.

Looge uus muutuv skoor ja määrake see pärast madu deklaratsiooni väärtuseks 0.

las skoor = 0;

Järgmisena lisage lõuendi ette uus div koos id-numbriga “score”. Saame seda kasutada tulemuse kuvamiseks.

0

Lõpuks värskendage advanceSnake, et suurendada ja kuvada tulemust, kui madu sööb toitu.

funktsioon advanceSnake () {
  ...
  if (didEatFood) {
    skoor + = 10;
    document.getElementById ('skoor'). innerHTML = skoor;
    luuaFood ();
  } veel {
    ...
  }
}

Pheew, seda oli üsna palju, kuid oleme jõudnud kaugele way

Lõpetage mäng

Alles on jäänud üks tükk ja see on mängu lõpp . Selleks saame luua funktsiooni didGameEnd, mis tagastab tõese, kui mäng on lõppenud, või vale muul viisil.

funktsioon didGameEnd () {
  jaoks (las i = 4; i 
    if (didCollide) naaseb tõeseks
  }
  const hitLeftWall = madu [0] .x <0;
  const hitRightWall = madu [0] .x> gameCanvas.width - 10;
  const hitToptWall = madu [0] .y <0;
  const hitBottomWall = madu [0] .y> gameCanvas.height - 10;
  tagasta hitLeftWall ||
         hitRightWall ||
         hitToptWall ||
         hitBottomWall
}

Esmalt kontrollime, kas mao pea puudutab madu mõnda teist osa ja kui see on tõsi, vastab see tõele.

Pange tähele, et alustame oma silmust indeksist 4. Sellel on kaks põhjust. Esimene on see, et didCollide hindab kohe tõeseks, kui indeks on 0, seega mäng lõppeb. Teine on see, et esimesel kolmel osal on võimatu üksteist puudutada.

Järgmisena kontrollime, kas madu tabas mõnda lõuendi seina ja tagastame selle tõesuse korral, vastasel juhul tagastame vale.

Nüüd võime oma põhifunktsiooni alguses tagasi pöörduda, kui didEndGame vastab tõele, lõpetades sellega mängu.

funktsioon main () {
  if (didGameEnd ()) naaseb;
  ...
}

Meie snake.html peaks nüüd välja nägema selline:

Nüüd on teil toimiv madude mäng, mida saate mängida ja oma sõpradega jagada. Kuid enne tähistamist võiksime vaadata ühte viimast probleemi. See on viimane, ma luban.

Tigedad vead

Kui mängite mängu piisavalt mitu korda, võite märgata, et mõnikord lõpeb mäng ootamatult. See on väga hea näide, kuidas vead võivad meie programmidesse hiilida ja probleeme tekitada .

Kui mõni viga ilmneb, on parim viis selle lahendamiseks kõigepealt usaldusväärne viis selle reprodutseerimiseks. See tähendab, et tule välja täpsed sammud, mis ootamatu käitumiseni viivad. Siis peate mõistma, miks need ootamatut käitumist põhjustavad, ja seejärel leidma lahenduse.

Vea reprodutseerimine

Meie puhul on vea reprodutseerimise sammud järgmised:

  • Madu liigub vasakule
  • Mängija vajutab allanoolklahvi
  • Mängija vajutab kohe nooleklahvi kohe (enne, kui 100 ms on möödunud)
  • Mäng lõpeb

Vea mõistmine

Jagageme, mis juhtub samm-sammult.

Madu liigub vasakule

  • Horisontaalne kiirus, dx on -10
  • põhifunktsiooni nimetatakse
  • advanceSnake nimetatakse selleks, mis viib madu -10 pikslit vasakule.

Mängija vajutab allanoolklahvi

  • changeDirection kutsutakse
  • keyPressed === DOWN_KEY && dy! goingUp hindab tõeseks
  • dx muutub väärtuseks 0
  • dy muutub +10

Mängija vajutab kohe paremat noolt (enne kui 100ms on möödunud)

  • changeDirection kutsutakse
  • keyPressed === RIGHT_KEY &&! goingLeft hindab tõeseks
  • dx muutub väärtuseks +10
  • dy muutub väärtuseks 0

Mäng lõpeb

  • põhifunktsioon kutsutakse välja pärast 100ms möödumist.
  • advanceSnake nimetatakse siis, mis liigutab madu 10xx paremale.
  • const didCollide = madu [i] .x === madu [0] .x && madu [i] .y === madu [0] .y hindab tõeseks
  • didGameEnd tagastab true
  • põhifunktsioon naaseb varakult
  • Mäng lõpeb

Vea parandamine

Pärast juhtunu uurimist saame teada, et mäng lõppes seetõttu, et madu pööras tagurpidi.

Seda seetõttu, et kui mängija vajutas allanoolt, seati dx väärtusele 0. Seega klahviPressed === RIGHT_KEY &&! GoingLeft väärtuseks tõene ja dx muudeti väärtuseks 10.

Oluline on märkida, et suuna muutus toimus enne 100 ms möödumist. Kui 100ms oleks möödunud, oleks madu kõigepealt astunud sammu maha ega oleks tagurdanud.

Meie vea parandamiseks peame veenduma, et suunda saame muuta alles pärast seda, kui main ja mainSnake on kutsutud. Saame luua muutuva muutuva suuna. See seatakse väärtusele true, kui kutsutakse changeDirection, ja väärtusele false, kui kutsutakse advanceSnake.

Funktsioonis changeDirection võime naasta varakult, kui changeDirection on tõene.

funktsiooni muutusDirection (sündmus) {
  const LEFT_KEY = 37;
  const RIGHT_KEY = 39;
  const UP_KEY = 38;
  const DOWN_KEY = 40;
  if (ChangeDirection) naasmine;
  ChangingDirection = tõene;
  ...
}
funktsioon main () {
  setTimeout (funktsioon onTick () {
    idingDirection = vale;
    
    ...
  }, 100)
}

Siin on meie snake.html lõplik versioon

Märkus. Lisasin siltide vahele ka mõned stiilid . See tähendab, et lõuend ja skoor ilmuvad ekraani keskele.

Järeldus

Palju õnne !!

Oleme jõudnud oma reisi lõppu. Loodan, et teile meeldis koos minuga õppimine ja tunnete end nüüd kindlalt oma järgmise seikluse jätkamiseks.

Kuid see ei pea siin lõppema. Minu järgmine artikkel keskendub sellele, kuidas aidata teil alustada avatud lähtekoodiga väga põnevat maailma.

Avatud lähtekood on suurepärane viis õppida palju uusi asju ja tundma õppida hämmastavaid inimesi. See on väga rahuldust pakkuv, kuid võib alguses hirmutav olla.

Kui teate, kui mu järgmine artikkel välja tuleb, saate mind jälgida!

Sellel teekonnal oli rõõm koos teiega olla.

Kuni järgmise korrani.