Hej, det är Linus. Ni vet, spelutvecklaren. Det har som ni kanske märkt stått ganska still med mitt spelutvecklande sen jag till slut fick ut mitt clicker-spel förra året. Jag har fokuserat min energi på annat istället (läs: stirrat in i väggen och hatat livet). Men så för ett litet tag sen tändes utvecklarlågan igen, och jag kläckte ur mig följande lilla hemsida.

Sidan byggdes inte så mycket för att jag var intresserad av resultatet som för att jag var nyfiken på hur det gick att lösa. Jag hade sedan tidigare pillat på kod som lät en ändra färg på rutor i ett rutnät. Det var ju väldigt simpelt, men svallvågsfunktionen presenterade lite intressanta problem. Jag behövde lista ut hur jag automatiskt skulle hitta ut alla intilliggande rutor, och sedan expandera utåt, och det var en skoj tankenöt.

Lång historia kort, problemet löstes genom att ge varje rad och varje kolumn ett id så att det var enkelt att räkna ut en rutas position, och sen placerades rutorna som skulle bytas färg på i en array och rutorna som redan hade fått färgen bytt (och därmed inte skulle ändras igen) i en annan array. Inte superspexigt kanske, men en skoj lösning på ett skoj problem.

Man skulle väl om man verkligen ville kunna kalla mitt lilla experiment för ett spel, men det är inte det som den här texten kommer att handla om. Istället är det idén jag fick när jag var klar. Jag insåg nämligen att när jag nu knåpat ihop funktioner för att tända och släcka pixlar baserat på arrays, så är jag en bra bit på vägen mot att bygga Snake. Gamla goda Snake. Så jag bestämde mig för att försöka göra det.

Reglerna för mitt lilla projekt var som följer: jag fick såklart googla bäst jag ville, eftersom det är det enda sättet jag vet hur man kodar, men jag fick inte googla specifikt efter hur koden för Snake ser ut. Hur spelet skulle funka behövde jag lista ut på egen hand.

Innan jag satte igång passade jag även på att lära mig från senaste gången jag kodade något som jag sen gjorde Content om och såg till att ha ett system för att spara versioner av koden vid jämna mellanrum så att jag kunde visa utvecklingen i efterhand. Hur gjorde jag då det? Använde jag Git eller något annat existerande verktyg som en rimlig och normal utvecklare?

Nej, jag fulkodade en funktion i bash. Låt mig va.

Så, nu, äntligen till mitt kodande av Snake. Hittills i texten har jag hastat mig fram lite, men jag ska försöka mig på att faktiskt förklara vad fan jag pratar om när jag säger saker. Nu kör vi!

Först och främst behövde jag bygga spelplanen. När jag byggde svallvågs-sidan så skrev jag helt enkelt ut rutnätet i html med divar inuti divar. En div, för den som aldrig kodat en hemsida, är som en ruta som du stoppar saker i. Om du tittar på sidan där du läser det här så är exempelvis hela området där texten ligger en div, som i sin tur ligger i större divar som innehåller mer av sidan. Mitt rutnät bestod av 15 stycken divar av klassen “row” som hette r1-r15, som var och en innehöll 15 divar av klassen ”col” som hette c1–c15. Så varje rad såg i koden ut så här:

<div id="r1" class="row">
    <div id="c1" class="col"></div>
    <div id="c2" class="col"></div>
    <div id="c3" class="col"></div>
    <div id="c4" class="col"></div>
    <div id="c5" class="col"></div>
    <div id="c6" class="col"></div>
    <div id="c7" class="col"></div>
    <div id="c8" class="col"></div>
    <div id="c9" class="col"></div>
    <div id="c10" class="col"></div>
    <div id="c11" class="col"></div>
    <div id="c12" class="col"></div>
    <div id="c13" class="col"></div>
    <div id="c14" class="col"></div>
    <div id="c15" class="col"></div>
</div>

Och det är en lösning. Som fungerar. Vilket väl är det bästa man kan säga om den. För det är inte en särskilt snygg, smidig eller flexibel lösning och jag visste direkt att jag var tvungen att kasta ut den av flera anledningar.

För det första så blev min indexfil lång som ett jävla ösregn och att navigera i den blev en mardröm. För det andra så var det en mardröm utan dess like att ändra storlek på rutnätet, eftersom jag var tvungen att gå igenom och antingen ta bort eller lägga till rader i varje enskild row-div. Och för det tredje så hade jag ingen möjlighet att dynamiskt skala spelplanen efter skärmstorlek. Så uppgift ett blev att ersätta den lösningen med en javascript-funktion som kunde ta antal rutor som variabler och spotta ur sig ett rimligt rutnät.

En vad som tar vad för att göra vaddå? Kanske du undrar nu. Eller så gör du inte det. Vem vet. Men jag tänker förklara lite i alla fall. Javascript känner nog de flesta till, det är ett kodspråk som används av de flesta hemsidor på ett eller annat sätt, och låter dig bygga funktioner som enkelt förklarat gör saker. Så exempelvis kan du knåpa ihop ett rutnät i html och sedan använda en javascript-funktion som aktiveras när du klickar på någon av rutorna och då byter färg på den.

Vad jag ville i det här fallet var en javascript-funktion som kördes när sidan laddades och då skapade alla divar som behövdes för rutnätet automatiskt, baserat på variabler för höjd och bredd. För den som minns högstadiematten så är variabler alltså en standin för ett värde, x = 14, och så vidare. I kod är en variabel en bokstav eller ett ord som innehåller ett annat värde, så i den här funktionen ville jag exempelvis ha variablerna rows och cols, som definierade hur många rader och kolumner.

Är alla med så långt? Bra. Så hur gick jag då tillväga för att uppnå det? Jo, jag googlade hur man uppnår det och stal någon annans kod.

const container = document.getElementById("container");

function makeRows(rows, cols) {
    container.style.setProperty('--grid-rows', rows);
    container.style.setProperty('--grid-cols', cols);
    for (c = 0; c < (rows * cols); c++) {
        let cell = document.createElement("div");
        cell.innerText = (c + 1);
        container.appendChild(cell).className = "grid-item";
    };
};

makeRows(16, 16);

Koden ovan konstruerar en funktion som heter makeRows där du definierar antalet rader och kolumner som sedan skapas inuti i en större div. Precis vad jag ville ha, helt enkelt! Eller, nästan i alla fall. En snabb kik på rutnätet som genererades visar att vi inte riktigt är hemma än. Och med inte riktigt menar jag inte alls.

Så, dags att börja modda koden så att vi kan få ut vad vi faktiskt vill ha, vilket är ett rutnät som kan representera pixlarna på en spelplan, utan några mellanrum. Och lika viktigt, varje ruta behöver få ett id som gör det möjligt att hålla reda på vilka pixlar som ska vara svarta och vilka som inte ska vara det. Så efter lite fixande och trixande så har vi pulat om funktionen så att den ser ut som följer:

function makeRows(rows, cols) {
        container.style.setProperty('--grid-rows', rows);
        container.style.setProperty('--grid-cols', cols);
        for (c = 0; c < (rows * cols); c++) {
                let cell = document.createElement("div");
                container.appendChild(cell).className = "grid-item";
                x = ((c + 1) % 100);
                if (x == 0) {
                        x = 100;
                }
                y = Math.floor(c / 100) + 1
                cell.id = "y" + String(y) + "x" + String(x);
        };
};

Utöver det passade jag också på att utöka från 16×16 rutor till 49×100, av den enkla anledningen att det var en rimlig mängd rutor för att passa in på min skärm. Jag är redan här smärtsamt medveten om att jag vid något tillfälle behöver göra spelet responsivt till skärmstorlek, men väljer att ignorera det en liten stund till. För allting som har att göra med responsivitet suger och jag hatar det.

Om vi ska kika lite närmare på koden, så är funktionen en enkel for-loop, alltså en funktion som körs om och om igen i ett givet antal loopar. I det här fallet så börjar funktionen med variabeln ”c” som är satt till 0, och adderar 1 till den tills den når upp till antalet rader gånger antalet kolumner, det vill säga antalet rutor. Utöver att bara öka värdet på c adderas en ny div för varje loop tills rutnätet är klart, och så ger den varje div ett eget id i formen yRADxKOLUMN.

Jag är lite osäker på hur mycket jag ska gå in på detaljerna här, men jag bestämde mig precis för att dela den här texten i delar, så vi går ordentligt på djupet här, så får ni svara i kommentarerna om ni tycker att jag borde hålla mig till de breda dragen och inte fastna i kodsnack. Så, en titt på funktionen, rad för rad, ish:

function makeRows(rows, cols) {

Vi börjar med att definiera funktionen. Vi ger den namnet ”makeRows” och berättar att den ska ta två argument, rows och cols. Det innebär att när man kör den så kör man makeRows(RADER, COLUMNER), och siffrorna man anger kommer att representeras av variablerna rows och cols.

        container.style.setProperty('--grid-rows', rows);
        container.style.setProperty('--grid-cols', cols);

Därefter tar vi och bestämmer hur många rader och kolumner vår spelplan ska ha, så att när vi börjar stoppa den full av divar så vet den när den ska bryta för ny rad, och så vidare.

        for (c = 0; c < (rows * cols); c++) {

Här påbörjar vi vår loop! Vi anger att c ska vara lika med 0, att loopen ska köras så länge c är mindre än rows gånger cols, och att vi ska addera 1 till c för varje loop som körs.

                let cell = document.createElement("div");

Här skapar vi sen en div, som vi så länge sparar i variabeln ”cell”.

                container.appendChild(cell).className = "grid-item";

Och så stoppar vi vår nya div i den existerande diven ”container”, och ger den klassen ”grid-item”. Hade vi avslutat loopen här så hade vi fått ett rutnät precis som vi ville ha det på ytan, men vi behöver fortfarande döpa rutorna.

                x = ((c + 1) % 100);

Kolla, matte! Eftersom c är den variabel vi har som för närvarande håller reda på vilken ruta i ordningen vi är på, så behöver vi konvertera den till en siffra för rad och en för kolumn. Och vi börjar med kolumn. Först adderar vi ett till c, eftersom c börjar på 0, men rutnätet börjar på ruta 1. Insåg jag medan jag skrev det här att jag antagligen hade kunnat sätta c till att börja på 1 istället och skippa det här steget? Kanske. Men strunt i det. Efter det delar vi i alla fall resultatet med antalet kolumner, och sparar resten som blir över som variabeln x.

Det innebär att om vi är på ruta 187, till exempel, och delar det med 100 (eftersom det är antalet kolumner i det här fallet), så blir vår rest 87, och vi vet att rutan vi precis skapade är i kolumn 87. Och innan någon påpekar det, nä jag borde så klart inte ha skrivit 100 med siffror här, utan använt variabeln för antalet kolumner istället, så att det är skalbart, men jag kom på det senare.

                if (x == 0) {
                        x = 100;
                }

Det här stämmer inte helt och hållet dock, eftersom om vi delar 100 med 100 så blir resten noll, men vi är inte på kolumn 0, utan på kolumn 100! Så här har vi en liten if-sats som kollar ifall resten är noll, och då sätter x till att vara samma som antalet kolumner.

                y = Math.floor(c / 100) + 1

Och när vi har antalet kolumner så är det dags att ta fram antalet rader, vilket vi gör på samma sätt fast tvärt om. Det vill säga, vi delar c med antalet kolumner, och rundar neråt, sen lägger vi till ett. Så om vi är på ruta 187 igen, så blir det delat med 100 1,87, som vi sedan rundar neråt till 1, och adderar 1, så vet vi att vi är på rad 2.

                cell.id = "y" + String(y) + "x" + String(x);

Och sen sätter vi divens id till y plus variabeln y konverterat till en sträng, plus x, plus variabeln x konverterat till en sträng. Så i fallet 187, y2x87.

        };
};

Och så knyter vi ihop loopen och funktionen.

Resultatet blev, föga förvånande, ett rutnät. Förutom om man besöker sidan från Firefox, såklart. I min kod har jag nämligen inte angivit någon storlek på rutorna, vilket Chrome bara köper, och ger rutorna en basstorlek ändå, men Firefox läser bokstavligt och ger rutorna ingen storlek.

Ja, det där var ju ett väldigt långt sätt att komma inte särskilt långt. Som jag sa beslutade jag att göra det här till en serie, så att det inte blir bara en text lång som en bok. Spelet är dock redan klart, så om ni vill tjuvkika och testspela så kan ni göra det på svampriket.se/schnake, där ni också hittar alla olika versioner av spelet. Lämna gärna en kommentar och berätta om det här var det minsta intressant eller bara smärtsamt navelskådande, så ses vi eventuellt i del två!