PXT: Snake
Skrevet av: Håvard Nygård Jakobsen
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.
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å.
Steg 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() })
Test 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.
Steg 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(); })
Test 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.
Steg 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 })
Test 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.
Steg 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; } })
Test 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.
Steg for steg
input.onButtonPressed(Button.A, function () { if (isPlaying) { if (buttonPressed) { return; } buttonPressed = true; } })
Test 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!
Steg 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; } }
Test prosjektet
På tide å prøve spillet på micro:bit
Steg 5: Litt lyd, takk!
Steg for steg
function updateSnake(){ music.playTone(Note.C5, 20) }
function checkGameOver(){ music.beginMelody(music.builtInMelody(Melodies.Wawawawaa), MelodyOptions.Once); }
Test 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:
Steg 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(); } })
Test 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?
Steg 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(); } })
Test 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å.
Steg 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; } } })
Test 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.
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!