Detta är del 4 av 4 i vår genomgång av programmeringsspråket Scratch.

Innehållsförteckning till genomgången hittar du här.

Jack-Benny Persson från Cyberinfo Sverige fortsätter att guida oss i Scratch och visar i detta inlägg hur man kan göra ett mattespel i programmeringsspråket.

Vi har kommit långt på vårt äventyr med Scratch och det är dags att göra ett lite större projekt. Denna gång kommer jag inte att gå igenom exakt steg för steg hur programmet skapas. Enbart de lite svårare sakerna  kommer att gås igenom i detalj samt de saker som är helt nya för oss. Till din hjälp kommer du att ha bilder från programmet precis som vanligt, samt en länk till färdiga programmet i slutet av artikeln. Följ gärna bilderna när du sätter ihop ditt egna mattespel.

Tyngdpunkten kommer istället att läggas på att förklara hur och varför saker fungerar som de gör i programmet. Behöver du hjälp att hitta de olika blocken eller navigera i Scratch så läs gärna igenom den allra första delen, En introduktion till Scratch.

Beskrivning av projektet

Programmet kommer att bli ett mattespel som tränar användaren på de fyra räknesätten; multiplikation, division, addition och subtraktion. Spelet kommer att köras ända till användaren väljer att avsluta spelet på den röda stoppknappen.

Under hela spelet håller programmet reda på hur många rätta respektive felaktiga svar du har, samt hur många procent av alla frågor du svarat rätt på. Denna information visas alltid uppe i vänstra hörnet på skärmen.

Programmet slumpar fram om frågan kommer att vara multiplikation, division, addition eller subtraktion. Likaså slumpas talen i frågorna fram, både det vänstra och det högra talet.

Upplägg av spelet

Rent kodmässigt påminner programmet lite om Ohms lag & Scratch som vi gjorde förra gången. Programmet består utav en huvuddel och sex separata block (eller funktioner som det heter i andra språk). Om du inte kommer ihåg hur man gjorde egna block så titta igenom föregående inlägg om Ohms lag & Scratch.

Huvudprogrammet i spelet

Huvudprogrammet i spelet

Här ovan visas huvudprogrammet i spelet. Som du ser kommer vi även här att använda oss av egendefinierade block. Varje räknesätt har fått ett eget block i programmet för att göra huvudprogrammet mindre.

Blocken i spelet

Blocken i spelet

Genomgång av koden

Innan vi börjar koda behöver vi skapa sju stycken variabler. Dessa är:

  • a
  • b
  • antal_procent_rätt
  • felaktiga_svar
  • rätta_svar
  • svar
  • typ

När vi har skapat dessa variabler ska vi dölja alla variablerna på kanvasen förutom rätta_svar, felaktiga_svar och antal_procent_rätt. För att dölja variabler på kanvasen shift-klickar man på den och väljer hide i menyn. Om det inte skulle fungera med shift-klick så testa med högerklick. Man kan även dölja variablerna genom att “bocka ur” dem under Data. Placera sedan de kvarvarande variablerna snyggt upp i vänster hörn så att du hela tiden kan ha koll på hur det går för dig när du spelar.

Placera variablerna som är kvar uppe i hörnet så att du kan följa hur det går för dig

Placera variablerna som är kvar uppe i hörnet så att du kan följa hur det går för dig

Huvudprogrammet

I början av huvudprogrammet har vi tre styck set [] to 0-block. Dessa har vi för att nolla resultatet från föregående körning av programmet så att spelet alltid börjar räkna poängen/procenten från noll.

Börja med att nolla alla variabler

Börja med att nolla alla variabler

Därefter har vi själva huvuddelen av programmet som ligger inuti i en forever-loop. Allting som ligger inuti forever-blocket kommer att köras om och om igen, ända tills vi väljer att stoppa programmet på den röda stoppknappen ovanför katten. Utan detta blocket hade vi bara fått en enda fråga, sedan hade spelet avslutats.

De tre första blocken inuti forever-loopen är något nytt för oss, nämligen pick random. Här har vi skapat tre variabler, som vi alla sätter till ett slumpmässigt tal. Variablerna a och b är talen som kommer att dyka upp i frågorna (operander på programmerings- och matematikspråk). Dessa talen slumpas mellan 0 och 10. Här kan man ändra svårighetsgrad på spelet genom att öka hur stora talen i frågorna kommer att vara.

Talen i frågorna slumpas fram mellan 0 och 10

Talen i frågorna slumpas fram mellan 0 och 10

Spelet slumpar fram typen av fråga

Spelet slumpar fram typen av fråga

Efter a och b har vi variabeln typ som får ett slumpmässigt tal mellan 1 och 4. Denna variabeln är till för att slumpa fram om frågan ska vara multiplikation, division, addition eller subtraktion. Direkt efter att talet slumpats fram kommer en lång rad sammanhängande if else-block. Först kontrolleras om typ är 1, i så fall skall vi köra multiplikationsblocket. Sedan kontrolleras om typ är 2, i så fall skall vi köra divisionsblocket. Är typ 3 körs additionsblocket, och om typ varken är 1, 2 eller 3 (else) så körs istället subtraktionsblocket.

Allra sist i programmet har vi ett långt set [] to-block. Det är detta blocket som räknar ut hur många procent utav alla frågorna vi svarat rätt på. Blocket följer formeln för att få fram procenten: 100 * rätta_svar / (felaktiga_svar + rätta_svar). Resultatet av uträkningen sparas i variabeln antal_procent_rätt.

Uträkning för hur många procent rätt vi har

Uträkning för hur många procent rätt vi har

Just detta block kan vara svårt att klura ut hur allting ska placeras så här följer en detaljritning av delarna.

Sätt ihop procent-blocket

Sätt ihop procent-blocket

Blocken

Även inuti våra egendefinierade block kommer vi att använda egna block. Så fort man märker att man upprepar sig i koden är det dags för en funktion (eller block i Scratch). I första versionen av spelet hade jag inte blocken rätt_svar och fel_svar. Men efter att ha tittat igenom spelet upptäckte jag att say Rätt/Fel och Change rätta_svar/felaktiga_svar by 1 var detsamma i varje frågeblock. Så därför bestämde jag mig för att göra om dessa till egna block. En fördel med att ha dem som egna block är också att om vi vill ändra texten “Rätt!” till “Woho, rätt svar!”, så behöver vi nu bara göra det på ett enda ställe, istället för på fyra olika ställen.

Rätt & fel svar

Blocken för rätt respektive fel svar är korta och enkla att förstå. Varje gång man svarar rätt på en fråga körs blocket rätt_svar som innehåller två block, ett say for 2 secs och ett change rätta_svar by 1 för att öka vårt antal av rätta svar med ett. Detsamma gäller för blocket fel_svar fast här ökas istället variabeln felaktiga_svar med ett.

Blocken för rätt och fel svar

Blocken för rätt och fel svar

Multiplikation, addition och subtraktion

Blocken för multiplikation, addition och subtraktion är alla väldigt lika varandra. Det enda som skiljer är tecknet (* + -). Vi tar multiplikationsblocket som exempel här i genomgången.

Multiplikationsblocket

Multiplikationsblocket

Först och främst så frågar programmet vad a * b blir. Detta görs genom att kombinera ihop variablerna med multiplikationstecknet (här ett “x”) i ett say-block. Om a innehåller 3 och b innehåller 4 kommer katten att fråga vad 3*4 blir. När vi svarar på frågan sparas vårt svar i variabeln svar.

Nu kommer vi till ett if else-block som jämför om det svaret vi angav (i variabeln svar) är lika med just a*b (eller 3*4 om vi tar exemplet från ovan). Om så är fallet så körs blocket rätt_svar som vi gick igenom ovan. Annars, om svaret är fel, körs istället fel_svar.

Divisionsblocket

Divisionsblocket skiljer sig lite från de övrig blocken eftersom division blir lite krångligare att hantera i spelet.

Lösning för att komma runt problemet med noll som nämnare

Lösning för att komma runt problemet med noll som nämnare

Det första problemet är att vi inte kan låta variabeln b bli noll. I en division blir variabeln b i vårt spel nämnaren. Att dela något med noll går inte. Eller rättare sagt, det ger ett odefinierat svar. De flesta programmeringsspråk ger ett felmeddelande (division by zero) när vi delar något med noll. Därför måste vi införa en kontroll ifall b är noll och om så är fallet ska b ökas med ett. Se bilden till höger för lösningen.

Nästa problem som uppstår är att de flesta divisioner ger ett resultat med decimaler. Det är väl i och för sig inget problem, men vart ska man dra gränsen för antalet decimaler och hur kommer datorn att avrunda decimalerna? Kommer datorn att ge rätt ifall jag exempelvis skriver 1,111111112 på 10/9, eller ska jag ange 1,1111111111111112 som det rätta svaret? Oavsett vilket så blir det oerhört svårt att räkna fram svaret i huvudet. För att komma runt alla dessa problem har jag förenklat spelet och använder endast heltalsdivisioner. Det vill säga att exempelvis 10/9 från exemplet ovan ger 1 som korrekt svar. Man struntar alltså i resten eller decimaldelen av svaret. Med andra ord kan man tänka att “hur många gånger går 9 i 10?”. Rätt svar är alltså ett, det går en enda gång. Likaså kommer exempelvis 8/10 ge 0 som rätt svar, eftersom 10 inte går någon gång alls i 8.

Avrunda alla talen nedåt med blocket floor

Avrunda alla talen nedåt med blocket floor

För att lösa detta problemet enligt den lösning vi precis gått igenom måste vi avrunda resultatet nedåt till närmste heltal. Avrundar vi bara på vanligt vis kommer allt som är 0,5 eller mer att avrundas uppåt och allt annat nedåt, alltså fel i vårt fall. För att avrunda resultaten nedåt använder vi blocket floor. Detta är en funktion som finns i många andra programmeringsspråk också, exempelvis PHP, Python och C. Motsatsen till floor är ceil, som är förkortning av ceiling (innertak) och avrundar alltid uppåt.

Hela divisionsblocket med lösningarna på problemen

Hela divisionsblocket med lösningarna på problemen

Testkör spelet

Nu har vi faktiskt gått igenom alla delarna av hela mattespelet. Testkör spelet och se så att allting fungerar som det ska. Varje gång du svarar rätt ökar rätta_svar med 1 och varje gång du svarar fel ökar felaktiga_svar med 1. Likaså uppdateras hela tiden antalet_procent_rätt.

Testkörning av programmet. Allt verkar fungera!

Testkörning av programmet. Allt verkar fungera!

Mattespelet i Python

Vi fortsätter med traditionen att jämföra våra Scratch-program med andra programmeringsspråk. Här visas vårt mattespel i programmeringsspråket Python. Denna gången blir dock Python-programmet lite längre än vanligt och mer komplicerat med några specifika delar för Python. Men trots detta är koden ändå väldigt lik Scratch på det stora hela. Man hade kunnat göra Python-versionen bättre och mer elegant, men för att det ska gå att jämföra Scratch-programmet med Python-programmet har jag valt att göra Python-versionen så här.

#!/usr/bin/env python3

# Importera modulen för slumpmässiga (random) tal och modulen
# för floor-funktionen (math)
import random
import math

# Vi måste göra alla variablerna globala i Python
# för att de ska vara nåbara inifrån funktionerna.
# Detta görs genom att skapa dem utanför alla funktioner.
a = 0
b = 0
typ = 0
felaktiga_svar = 0
ratta_svar = 0
antal_procent_ratt = 0


### Definiera alla funktionerna ###

def ratt_svar():
    global ratta_svar
    print ("Rätt!")
    ratta_svar = ratta_svar + 1

def fel_svar():
    global felaktiga_svar
    print ("Fel")
    felaktiga_svar = felaktiga_svar + 1

def multiplikation():
    svar = int(input(str(a) + " x " + str(b) + ": "))
    if ( svar == a*b ):
        ratt_svar()
    else:
        fel_svar()

def division():
    global b
    if ( b == 0 ):
        b = b + 1
    svar = int(input(str(a) + " / " + str(b) + ": "))
    if ( svar == math.floor(a/b) ):
        ratt_svar()
    else:
        fel_svar()

def addition():
    svar = int(input(str(a) + " + " + str(b) + ": "))
    if ( svar == a+b ):
        ratt_svar()
    else:
        fel_svar()

def subtraktion():
    svar = int(input(str(a) + " - " + str(b) + ": "))
    if ( svar == a-b ):
        ratt_svar()
    else:
        fel_svar()


### Huvuddelen av programmet ###

while True:
    a = random.randint(1, 10)
    b = random.randint(1, 10)
    typ = random.randint(1, 4)
    
    if ( typ == 1 ):
        multiplikation()
    elif ( typ == 2 ):
        division()
    elif ( typ == 3 ):
        addition()
    else:
        subtraktion()

    antal_procent_ratt = (100 * ratta_svar / (felaktiga_svar + ratta_svar))

    # Extra kod för att skriva ut antalet rätta & felaktiga svar
    print ("")
    print ("Antal rätt: " + str(ratta_svar))
    print ("Antal fel: " + str(felaktiga_svar))
    print ("Procent rätt: " + str(antal_procent_ratt))
    print ("")

När vi kör ovanstående program i en terminal kommer det att se ut ungefär så här:

6 x 6: 36
Rätt!

Antal rätt: 1
Antal fel: 0
Procent rätt: 100.0

1 x 8: 8
Rätt!

Antal rätt: 2
Antal fel: 0
Procent rätt: 100.0

8 - 1: 7
Rätt!

Antal rätt: 3
Antal fel: 0
Procent rätt: 100.0

10 - 4: 5
Fel

Antal rätt: 3
Antal fel: 1
Procent rätt: 75.0

2 - 4:

För att avbryta programmet tryck två gånger på CTRL-C, alternativt en gång på CTRL-D.

Länkar

Mattespelet finns allmänt tillgängligt på mitt Scratch-konto. Kopiera gärna (med Remix-knappen längst upp till höger) till ditt egna Scratch-konto och experimentera och modifiera.