Level 4PXT: Snake

Skrevet av: Håvard Nygård Jakobsen

Kurs: Microbit
Tema: Elektronikk, Blokkbasert, Spill
Fag: Matematikk, Programmering
Klassetrinn: 5.-7. klasse, 8.-10. klasse, Videregående skole

Introduksjon

En eller annen variant av Snake har eksistert på datamaskiner helt siden slutten av 1970-tallet. Mange voksne kjenner spillet fra Nokias mobiltelefoner, mens mange barn kjenner det fra moderne versjoner som slither.io.

I spillet styres en slange rundt på skjermen, og slangen må unngå å krasje i kanten av skjermen og seg selv. Slangen vokser når den spiser mat som dukker opp tilfeldige steder, og spillet går fortere og fortere etterhvert som slangen vokser.

I denne oppgaven bruker vi engelske navn på klossene og variabler. Dette er bla. for at det skal være lettere å finne igjen innebygde funksjoner når vi bytter mellom klossprogrammering og javascript siden funksjonene har engelske navn i javascript. Det er veldig vanlig å bruke engelske navn på funksjoner og variabeler blant programmerere. Dette gjør det blant annet enklere å poste kode på internettforum og få hjelp fra hele verden.

Denne oppgaven er forholdsvis lang, men vi tar det skritt for skritt og forklarer underveis. La oss sette i gang.

Bilde av et Snake-spill

Steg 1: Tegne slangen

Det første vi trenger er en liten kodesnutt som tegner slangen vår. Skjermen vår består av 5x5 ledlys. Disse kan vi skru av og på som vi vil med litt kode.

For å tegne slangen trenger vi noe som kan passe på hvor vi skal tegne slangen. Til dette skal vi bruke en array, en type variabel som inneholder en liste med verdier.

I listen vår bruker vi to verdier for å tegne en bit av slangen, den første verdien sier hvilken rad (Y) og hvilken kolonne (X) vi skal tegne i. Sammen gir dette oss en (X,Y)-posisjon til leden som vi skal skru på.

Bilde av koordinater på microbit

checkSteg for steg

let snake: number[] = []
snake = [
2,
2,
2,
3
]

Nå trenger vi litt kode for å tegne slangen. For å gjøre det litt enklere å holde oversikt over programmet vårt så gjør vi dette med en funksjon.

Hvorfor begynner vi på 0 og går til antall minus en? Tenk deg at du har en stabel med ark, f.eks. en oppgave som denne. Hvor mange ganger må du bla for å lese den første siden? Hvor mange ganger må du bla for å lese alle arkene? Slik er det med en array også, arrayen begynner på posisjon 0, og du må da bruke antall minus en for å bla igjennom alle. Hvis dette er vanskelig å forstå, så tenk "sånn er det bare".

function drawSnake () {
    for (let index = 0; index <= snake.length - 1; index++) {
        x = snake[index]
        index += 1
        y = snake[index]
        led.plot(x, y)
    }
}
basic.forever(function () {
    drawSnake()
})

flagTest prosjektet

Prøv spillet i simulatoren for å teste koden så langt. Sjekk at det blir tegnet to punkter.

Egentlig er det vi skriver noe som heter Typescript som er en variant av Javascript. I vanlig Javascript trenger vi ikke bruke type på variabler, men det må man noen steder i Typescript. F.eks. hvis en variabel skal inneholde tall må vi spesifisere at det er et tall med let x: number.

Da har vi det vi trenger for å tegne slangen. Men det er litt kjedelig når den står stille. Det må vi gjøre noe med!

Steg 2: Slithering snake

Nå skal vi få slangen til å bevege seg. Siden slangen kan bevege seg opp, ned, venstre og høyre så trenger vi en variabel som sier hvilken retning den er på vei. Vi beveger slangen ved å legge til et nytt punkt i begynnelsen og ta vekk det siste punktet på halen.

checkSteg for steg

function updateSnake(){
}
function updateSnake(){
    x = snake[0]
    y = snake[1]
}
function updateSnake(){
    if (direction == 'up') {
        y = y - 1;
    }
    if (direction == 'down') {
        y = y + 1;
    }
    if (direction == 'left') {
        x = x - 1
    }
    if (direction == 'right') {
        x = x + 1
    }
}
function updateSnake(){
    snake.unshift(y);
    snake.unshift(x);
    snake.pop();
    snake.pop();
}
basic.forever(function () {
    drawSnake();
    updateSnake();
})

flagTest prosjektet

Prøv koden i simulatoren for å teste koden så langt.

basic.forever(function () {
	basic.clearScreen();
	drawSnake();
	updateSnake();
})
basic.forever(function () {
	basic.clearScreen();
	drawSnake();
	updateSnake();
	basic.pause(1000);
})

Steg 3: Styring

Nå er det på tide at vi legger inn litt styring av slangen. Men først lager vi en liten startskjerm.

checkSteg for steg

basic.showIcon(IconNames.Snake)
let isPlaying = false
basic.forever(function () {
    if (isPlaying) {
        basic.clearScreen()
        drawSnake();
        updateSnake();
        basic.pause(1000);
    }
})
input.onButtonPressed(Button.A, function () {
    isPlaying = true
})
input.onButtonPressed(Button.B, function () {
    isPlaying = true

})

flagTest prosjektet i simulatoren

Nå skal slangeikonet vises til du trykker på en knapp, da starter spillet. Men vi trenger litt mer kode for å styre. Når man spiller skal A-knappen styre slangen 90 grader mot venstre fra nåværende retning mens B-knappen styre slangen 90 grader mot høyre.

checkSteg for steg

input.onButtonPressed(Button.A, function () {
    if (isPlaying) {
        if (direction == 'up') {
        	direction = 'left';
        }
        else if (direction == 'left') {
            direction = 'down';
        }
        else if (direction == 'down') {
            direction = 'right';
        }
        else if (direction == 'right') {
            direction = 'up';
        }
    } else {
        isPlaying = true
    }
})
input.onButtonPressed(Button.B, function () {
    if (isPlaying) {
        if (direction == 'up') {
            direction = 'right';
        }
        else if (direction == 'right') {
            direction = 'down';
        }
        else if (direction == 'down') {
            direction = 'left';
        }
        else if (direction == 'left') {
            direction = 'up';
        }
    } else {
        isPlaying = true;
    }
})

flagTest prosjektet

Prøv spillet i simulatoren for å teste koden så langt. Sjekk at styringen virker.

Nå kan du styre slangen, men det er et lite problem. Hvis vi trykker for to ganger på en knapp så går slangen i stikk motsatt retning. Det vil vi ikke siden slangen på denne måten går gjennom seg selv og det blir bare tull.

checkSteg for steg

input.onButtonPressed(Button.A, function () {
    if (isPlaying) {
        if (buttonPressed) {
            return;
        }
        buttonPressed = true;
		}
})

flagTest prosjektet

Prøv spillet i simulatoren for å teste koden så langt. Hvis alt stemmer nå er det bare ett knappetrykk som gjelder for hvert hakk slangen beveger seg.

Steg 4: GAME OVER!

Men vi kan jo styre slangen selv om den kjører utenfor skjermen, det skal selvfølgelig ikke være lov!

checkSteg for steg

function checkGameOver(x: number, y: number) {
    if(x < 0 || x > 4 || y < 0 || y > 4)
    {
        direction = 'up';
        snake = [2, 1, 2, 2]

        basic.showIcon(IconNames.Skull);
        basic.pause(2000);
        basic.showIcon(IconNames.Snake);
        isPlaying = false;
    }
}

flagTest prosjektet

På tide å prøve spillet på micro:bit

Steg 5: Litt lyd, takk!

checkSteg for steg

function updateSnake(){
    music.playTone(Note.C5, 20)
}
function checkGameOver(){
    music.beginMelody(music.builtInMelody(Melodies.Wawawawaa), MelodyOptions.Once);
}

flagTest prosjektet

Koble til hodetelefoner eller høytaler til micro:bit og sjekk at du får lyd. Test i simulatoren hvis du ikke har mulighet til å koble til noe.

Steg 6: Mat

Nå har kan man styre slangen, det blir game over og vi har litt lyd. På tide å legge til litt mat slik at vi får et skikkelig spill. Maten skal vi generere på et tilfeldig sted, men vi må passe på at det ikke er på slangen. Slik gjør vi:

checkSteg for steg

let foodX: number = 0
let foodY: number = 0
function generateFood(){
    foodX = Math.randomRange(0, 4);
    foodY = Math.randomRange(0, 4);
}
function isOnSnake(myX: number, myY: number) {
    for (let index = 0; index <= snake.length - 1; index+=2) {
        if (myX === snake[index] && myY === snake[index+1]) {
            return true;
        }
    }
    return false;
}
function generateFood() {
    foodX = Math.randomRange(0, 4);
    foodY = Math.randomRange(0, 4);
    if (isOnSnake(foodX, foodY)) {
        generateFood();
    }
}
function drawFood() {
    led.plot(foodX, foodY);
}
basic.forever(function () {
    if (isPlaying) {
        basic.clearScreen()
        drawFood();
        drawSnake();
        basic.pause(1000);
        updateSnake();
    }
})

flagTest prosjektet

Test i simulatoren for å sjekke at maten blir tegnet.

Nå har vi laget maten, da gjenstår det bare å spise den. Hvordan vet vi at vi er på riktig sted for å spise maten? Jo, hvis x og y i updateSnake() er den samme posisjonen som foodX og foodY. Da skal slangen vokse med et punkt. Hvordan gjør vi det? Vi lar bare være å fjerne det siste punktet på halen i updateSnake(). Smart?

checkSteg for steg

function updateSnake(){
    if (x == foodX && y == foodY) {
        music.beginMelody(music.builtInMelody(Melodies.BaDing), MelodyOptions.Once);
        generateFood();
    } else {
        snake.pop();
        snake.pop();
    }
}
function checkGameOver(x: number, y: number) {
    if (x < 0 || x > 4 || y < 0 || y > 4 || isOnSnake(x,y)) {
        direction = 'up';
        snake = [2, 1, 2, 2]

        music.beginMelody(music.builtInMelody(Melodies.Wawawawaa), MelodyOptions.Once);
        basic.showIcon(IconNames.Skull);
        basic.pause(2000);
        basic.showIcon(IconNames.Snake);
        isPlaying = false;
    }
}
function updateSnake(){
    if (x === foodX && y === foodY) {
        music.beginMelody(music.builtInMelody(Melodies.BaDing), MelodyOptions.Once);
        updateRate *= 0.95;
        generateFood();
    } else {
        snake.pop();
        snake.pop();
    }
    buttonPressed = false
    updateRate -= 5;
}
basic.forever(function () {
    if (isPlaying) {
        basic.clearScreen();
        drawSnake();
        drawFood();
        basic.pause(updateRate);
        updateSnake();
    }
})

flagTest prosjektet

Prøv spillet på micro:bit og sjekk at alt fungerer.

Steg 7: Litt pynt

Nå har vi i grunnen et fungerede spill. Men vi skal pynte bittelitt på det for å gjøre det litt bedre. Det kan være litt vanskelig å se maten noen ganger siden den kan komme hvor som helst og er lik slangen. For å gjøre det lettere å se den skal vi få den til å blinke. Det gjør vi med å bruke input.runningTime(). Denne funksjonen gir oss antall millisekund siden siden microbiten ble skrudd på.

checkSteg for steg

function drawFood() {
    const currentTime = input.runningTime();
    if(currentTime%500 > 250){
        led.plot(foodX, foodY);
    }
}
input.onButtonPressed(Button.A, function () {
    lastUpdateTime = input.runningTime();
}
basic.forever(function () {
	  if (isPlaying) {
	      basic.clearScreen();
	      drawSnake();
	      drawFood();
	      const currentTime = input.runningTime();
	      if(currentTime - lastUpdateTime >= updateRate)
	      {            
	          updateSnake();
	          lastUpdateTime = currentTime;
	      }
	  }
})

flagTest prosjektet på micro:bit

Prøv spillet på micro:bit. Forhåpentligvis fungerer det brillefint. Gratulerer, du har nå gjort ferdig spillet.

Her kommer et par utfordringer!

Utfordring

Legg til score og highscore som vises etter at du dør. Spill en liten melodi og vis New highscore hvis spilleren slo rekorden.

Tips: Du kan bruke lengden til snake-arrayen til å regne ut poeng.

Utfordring

Styr slangen automagisk. Få den til å gå rundt og finne mat selv og prøve å ikke krasje.

Tips: Du har allerede skrevet kode som sjekker om slangen kjører utenfor eller kolliderer med seg selv. Du kan bruke den samme koden til å sjekke posisjonen og endre retning(direction) mot høyre eller venstre hvis den krasjer, finne ny posisjon og sjekke om den også krasjer. For å finne maten kan du snu mot den når du kommer på samme rad eller kolonne som maten.

Lisens: CC BY-SA 4.0

Forbedre denne siden

Funnet en feil? Kunne noe vært bedre?
Hvis ja, vennligst gi oss tilbakemelding ved å lage en sak på Github eller fiks feilen selv om du kan. Vi er takknemlige for enhver tilbakemelding!