Hej, och välkomna tillbaka till “Den där gången jag byggde Snake 2”, en serie texter om den där gången jag byggde Snake 2. Läs del ett här och del två här.

Så var fan var vi?

Jag hade alltså fått fart på ormen. Den rör sig bara i en riktning och försvinner ut från spelplanen när den når kanten. Ja, om man inte använder Firefox så klart, då får man inte ens en spelplan.

Vi gör framsteg, bara inte så snabbt.

Nästa steg jag tog itu med var att få ormen att istället för att fortsätta åt ett håll för evigt, loopa runt och komma ut på andra sidan. Av någon anledning hade jag en tanke om att det här skulle vara mer komplicerat än det var. När jag väl satte mig ner insåg jag dock att det var så enkelt som ett par if-satser.

        if (direction == "right" && prevDirection != "left") {
                headc = headc + 1
        }
        if (headc > columns) {
                headc = 1
        }else if (headc < 1){ headc = columns }else if (headr > rows){
                headr = 1
        }else if (headr < 1){
                headr = rows
        }

För att färska upp minnet så har jag alltså här sparat x-koordinaten för ormens huvud i variabeln headc, det vill säga vilken kolumn huvudet befinner sig i, och y-koordinaten för huvudet i headr, det vill säga vilken rad det befinner sig i. Eftersom ormen i nuläget bara rör sig åt ett håll så adderar rörelsefunktionen nu bara ett på headc och så rör sig ormen ett steg längre högerut. Även den delen befinner sig nu i en if-sats, men vi kommer till den alldeles strax.

Så, jag hoppas vi alla är med på att if-satser är ett sätt att få koden att göra olika saker beroende på olika faktorer. Och om vi tittar specifikt på if (headc > columns) så kollar koden alltså ifall det nya värdet vi får på headc är högre än antalet kolumner, vilket skulle innebära att ormens huvud var utanför spelplanen. Och om det är fallet, så sätts huvudets position istället till 1, vilket placerar det allra längst ut till vänster.

Det här är dock bara ett av hållen som ormen kan åka ut från spelplanen, och även om det hittills är det enda relevanta, så har jag passat på att pula in koden för de andra riktningarna också. Så ifall headc istället för att vara högre än antalet kolumner, är lägre än 1, det vill säga att ormen har åkt ut från spelplanen till vänster, så sätts variabeln istället till samma som antalet kolumner, vilket alltså placerar den längst bort till höger. Och så motsvarande kod för uppåt och nedåt.

Så tillbaka till den där inledande if-satsen: if (direction == ”right” && prevDirection != ”left”). I den här versionen av koden har jag skapat en ny variabel som heter prevDirection, vilket till ingens förvåning är en variabel som sparar vilken riktning ormen rörde sig… tidigare. Hittills är variabeln tom och det finns ingen chans för den här if-satsen att inte köra, men jag börjar här lägga grunden för kontrollerna.

Ormen kan åka i fyra riktningar: upp, ner, höger, vänster. Men vid varje givet tillfälle kan du som spelare bara styra i tre riktningar (varav en är att fortsätta i samma riktning du redan kör). Du kan inte vända 180-grader och köra ormens huvud rakt igenom kroppen och ut ur sin egen röv, så klart. Så idén här var att kolla så att spelaren inte försökte göra just det. Nu var det inte exakt så här jag faktiskt löste det här problemet i senare versioner av koden, men här började tanken ta form i alla fall.

Nu börjar de tråkiga grunderna faktiskt bli klara här och det är dags för oss att ta nästa steg. Dags att ge spelaren kontroll över ormen.

Jag hade som sagt av någon anledning trott att koden ovan skulle vara svårare än den visade sig vara, men mina förväntningar på styrningen var tvärt om att det skulle vara ganska enkelt. Och, för en gång skull, så hade jag rätt.

const container = document.getElementById("container");
document.addEventListener('keydown', function(event) {
        if(event.keyCode == 37 && direction != "right" ) {
                direction = "left"
        } else if(event.keyCode == 38 && direction != "down") {
                direction = "up"
        } else if(event.keyCode == 39 && direction != "left") {
                direction = "right"
        } else if(event.keyCode == 40 && direction != "up") {
                direction = "down"
        }
});

document.addEventListener är en inbyggd funktion i javascript som precis som det låter lyssnar efter händelser på din sida. Och om man specificerar att den ska lyssna efter keydown så kommer den att göra något varje gång någon trycker på en knapp. Så vad koden ovan gör är att lyssna efter en knapptryckning och kolla vilken knapp som det är som har tryckts ner.

Varje knapp på tangentbordet har en keyCode, vilket helt enkelt är en siffra, kopplad till sig och lite googlande visade att piltangenterna har siffrorna 37-40. Så funktionen lyssnar helt enkelt efter någon av de tangenterna och sparar sen riktningen spelaren har valt i variabeln direction.

Redan här har jag kastat bort variabeln jag pratade om i förra steget, prevDirection. Ormens riktning har jag ju sparad i variabeln direction, så jag behöver bara se till att inte göra något när spelaren trycker på pilen åt exakt motsatt riktning mot den nuvarande. Någon extravariabel behövs inte*.

Innan vi känner oss klara för dagen kommer här den sista biten ny kod i den här versionen, vilket är koden för att faktiskt omsätta dina knapptryckningar till rörelse.

const container = document.getElementById("container");
        if (direction == "right") {
                headc = headc + 1
        } else if (direction == "left") {
                headc = headc - 1
        } else if (direction == "down") {
                headr = headr + 1
        } else if (direction == "up") {
                headr = headr - 1
        }

Inga körnstigheter här, beroende på vilken riktning du har valt så ändras antingen värdet för huvudets x-koordinat eller y-koordinat, uppåt eller neråt. Detta följs sen fortfarande av koden som kollar ifall huvudet hamnat utanför spelplanen och ska dyka upp på andra sidan, precis som innan.

Så allt det här resulterar alltså i en liten orm som du kan styra. Du kan fortfarande inte göra mer än så, det finns ingen mat och du kan inte dö, men nu när det finns interaktivitet kan man eventuellt kalla vad vi har för ett faktiskt spel. Om än ett jävligt tråkigt sådant.

Nästa gång är det dags för mat! Missa inte det.

*Okej, en extravariabel behövs typ inte. Under tiden jag har skrivit den här texten har det nämligen slagit mig att det inte vore en helt dum idé att skapa en ny variabel i stil med prevDirection. Det finns nämligen något av en bugg i koden jag beskrev ovan, vilket är att om du trycker på två knappar direkt efter varandra så är det möjligt att lura systemet och göra en helomvändning. Om du exempelvis rör dig åt höger och trycker på piltangent upp följt av piltangent vänster, innan ormen faktiskt hinner vända och köra uppåt, så kommer koden fortfarande tro att det är åt det hållet vi är på väg och därför helt sonika acceptera vänstersvängen.

Min lösning var att lägga in en cooldown efter ett knapptryck, men eftersom jag ville hålla den cooldownen så låg som möjligt så fanns det fortfarande en liten risk att man råkade göra en helomvändning och stendö. Men eftersom risken var liten och jag är lat så lät jag det vara så och tänkte att det antagligen inte kommer ske särskilt ofta.

Vilket för oss tillbaka till prevDirection. Om man delar upp riktningsvariabeln i två separata variabler, en för riktningen ormen för närvarande rör sig och en för riktningen spelaren vill att ormen ska röra sig, och väntar med att uppdatera den första tills efter ormen faktiskt har svängt, så kan man undvika det här problemet helt. Vilket är varför jag nu har lagt tillbaka prevDirection i koden i min senaste version.