Tutorial - Game of 40 (Python)

1. Introduktion

Detta är en tutorial för att skapa spelet Game Of 40 i kursen Programmering01. Genom att följa tutorial och genomföra uppgiften innebär att du löst uppgiften på lägsta nivå för ett godkänt betyg. Jag kommer under denna tutorial lägga fokus på hur du skall/kan tänka när du bygger en lite större applikation. Denna tutorial har fokus på att vara ett inlärningstillfälle likväl som att det skall vara grunden till ett examinerande moment. Det innebär att jag kommer visa saker som vi kanske inte arbetat så mycket med tidigare, även om du kommer känna igen det mesta, samtidigt som jag kommer diskutera för och nackdelar med att använda olika tekniker. Jag vill att denna tutorial skall vara användbar inte bara för de som vill nå betyget E utan även för de som vill utveckla sig som programmerare och lära sig nya tekniker för att nå längre.

Även de elever som strävar efter högre betyg rekommenderar jag att följa denna tutorial för att lära sig hur du skall tänka för att bygga en större applikation. Med denna tutorial som grund får du en stabil applikation som du sedan kan bygga vidare på.

1.1 Uppgiften

Uppgiften är att skapa en applikation inom valfritt textbaserat programmeringsspråk där en eller flera spelare kan spela ett tärningsspel som heter Game of 40.

1.1.1 Grundkrav

Följande grundkrav finns för att ha löst uppgiften.

  • Applikationen skall vara buggfri.
  • Tärningarnas värde behöver lagras på ett sådant sätt att du kan visa värde på varje tärning och inte bara en slutsumma.
  • Innan du börjar koda applikationen bör du skapa en planering där du skissar på ett gränssnitt, grafiskt eller textbaserat, så att du vet hur spelet skall presenteras och spelas. Använd också aktivitetsdiagram/pseudokod där du anser att det behövs för att lösa och/eller förtydliga svårigheter i spelets logik eller kod.
  • I grundvarianten så skall det vara en spelare som spelar mot datorn. Hur datorn spelar spelet skall kodas och dokumenteras. Enklaste varianten är att datorn alltid slår tre slag och sedan sparar sina tärningar.
  • Spelet skall följa de grundläggande reglerna, väljer du att skapa specialregler så skall detta dokumenteras.

1.1.2 Genomförande

Innan vi börjar hacka kod så gör vi en grovplanering av hur vi skall lägga upp arbetet. Det är oftast mer effektivt att sedan lösa en deluppgift i taget. Koden för grundkraven är inga nyheter, det är saker vi har gjort tidigare, det gäller bara att pussla ihop det på rätt sätt.

Vissa delar kommer göras i olika etapper där du själv väljer hur mycket hjälp du vill ha av mig. Det kommer finnas en liten hjälp där du får tips av mig vad du skall tänka på och det finns en stor hjälp då du får hela koden som behövs för den delen. Du kommer sällan få hela koden utan endast den delen som jag belyser i just den delen. TIPS: Det kan vara smart att kolla på kodraden för att få ett hum om var koden skall placeras, fast du behöver tänka själv naturligtvis.

Denna tutorial är uppdelad i huvud- och delavsnitt och min rekommendation är att innan vi påbörjar varje nytt huvudavsnitt så tar du en säkerhetskopia på den kod vi har skapat så långt. Detta är främst för att så enkelt som möjligt gå tillbaka till en tidigare version ifall du skulle gå vilse och göra stora fel i koden.

2. Planering

När vi nu jobbar med en större uppgift så är det viktigt att vi börjar med att fundera på hur vi kan dela upp uppgiften i mindre delar och försöka lösa del för del. Senare, när vi är mer vana vi att lösa denna typ av uppgifter så kräver det mindre planering men om uppgiften är komplex och lösningen är komplicerad så kommer det krävas mer planering. Vi börjar med att punkta upp de saker som vi redan nu vet att vi skall lösa och försöker också sätta dem i rätt ordning, det gör det enklare senare. Denna planering kommer vi behöva gå tillbaka till med jämna mellanrum och bocka av färdiga delar men också lägga till och justera de punkter som då finns kvar. Se det som ett recept på hur vår lösning skall genomföras.

2.1 Grovplanering

För att kunna göra en grovplanering så är det viktigt att veta hur spelet skall fungera. Förstår vi inte hur spelet fungerar så kan vi inte koda det. Testa att spela det med en kompis, du behöver en tärning och papper/penna.

Vi börjar med en grovplanering med de saker som vi redan nu vet. Så här tänker jag mig att spelet skall fungera/presenteras för mig.

  • En välkomstinfo till spelaren. Slumpa fram vem som skall börja spela.
  • Kör spelet
    • För spelare gäller att det skall vara möjligt att slå ett slag eller att stanna vilket innebär att tärningarnas värde låses och turen går över till motståndaren.
    • För datorn gäller att den slår tre slag och sedan sparas automatiskt tärningarna innan turen går över till spelaren.
    • Om någon slår en sexa (6) så skall alla osparade tärningar kastas och turen går över till motståndaren.
  • När någon spelare har uppnått minst 40 poäng så vinner denna och spelet avbryts. Skapa en bra resultatinfo och avsluta programmet.

2.2 Skissa applikationen

Här skulle jag normalt göra en enklare skiss över applikationen om den hade varit grafisk. Nu skall hela applikationen vara textbaserad men det är ändå smart att göra någon form av skiss för att ha något att utgå ifrån.

När vi byggde banken så skissade vi bara startmenyn men här kommer vi behöva vara tydliga inne i spelet vilket ställer lite andra krav. Jag vill att vi gör flera skisser här. Slutprodukten kanske inte alls blir som vi har planerat, men då har vi något att utgå ifrån.

2.2.1 Välkomstbilden

Här gäller det att välkomna spelaren och även skriva reglerna för spelet. Vill du inte skriva reglerna i välkomstbilden så behöver det finnas möjlighet att se reglerna på något sätt.

Hjälp [klicka för att visa]

2.2.2 Spelexempel 1 - slagit bra tal

Det viktigaste i detta spel är att alltid vara tydlig så att spelaren kan ta rätt beslut i om hen vill slå ett slag till eller stanna. Rita en skiss för hur det skall se ut när spelaren har slagit två bra slag, t.ex. 4 & 5.

Hjälp [klicka för att visa]

2.2.3 Spelexempel 2 - slagit sexa

Om du slår en sexa så förlorar du alla dina sparade poäng och det blir datorns tur. Rita en skiss för detta, tänk på att du först behöver redovisa att du har slagit en sexa och sedan på något sätt klickar dig vidare innan datorn slår.

Hjälp [klicka för att visa]

2.2.4 Spelexempel 3 - precis blivit players tur, computer är klar

Här skall du skissa på hur det skall finnas information om hur det har gått för datorn.

Hjälp [klicka för att visa]

2.2.5 Resultatvisning

Här skissar du på hur vinstrutan skall se ut. Spelet skall bara köras en gång i grundversionen så här skall spelet avslutas också.

Hjälp [klicka för att visa]

Då känner vi oss klara så här långt. Än så länge har vi inte programmerat någonting men det kommer vi börja med i nästa del.

3. Kodens struktur

Nu har vi kommit så långt att det är dags att börja koda. Detta kan göras på flera olika sätt och ju duktigare du blir att bygga applikationer desto större steg kan du ta och direkt ge dig på lösningar som är avancerade och ligger nära den slutliga versionen. Eftersom detta är en applikation som är lite för stor att få en tydlig översikt på hur den skall kodas så kommer vi behöva bygga den del för del med viss risk att vi behöver bygga om vissa delar senare. Tanken är dock att vi skall använda av oss funktioner när det är lämpligt och då tänker jag använda både funktioner som vi själva bygger men även användbara inbyggda funktioner.

3.1 Lagringsstruktur

När vi byggde banken så fanns det en tydlig struktur av mindre delprogram men denna gången så finns det bara två kommandon, slå en tärning eller spara alla tärningar som vi har slagit. Sedan kommer turen gå fram och tillbaka mellan spelaren och datorn fram tills någon har vunnit spelet. Vi skulle egentligen behöva fyra olika listor, två för spelaren med sparade och aktiva tärningar och sedan två likadana listor för datorn. Problemet med dessa listor är att du får vi skapa två helt olika program som körs beroende på vems tur det är. Vi kan inte lagra vems tur det är i en variabel och sedan med denna variabels hjälp anropa rätt lista. Däremot finns det en liknande struktur som heter Dictionary som vi kort gick igenom i Moment04 (4.3.4 Dictionary).

För att vi skall kunna använda struktur denna så behöver jag visa hur den fungerar. Det gör jag enklast med ett kodexempel.

Kodexempel: dictionary

from random import randint             # Importera randint som gör att vi slumpa heltal
import pprint                          # För att skriva ut dictionary snyggare

# dictionary
dict1 = {}                             # Skapa dictionary
dict1['player'] = {}                   # Skapa dictionary för player
dict1['computer'] = {}
dict1['player']['active'] = []         # Skapar lista för player / aktiva tärningar
dict1['player']['saved'] = []
dict1['computer']['active'] = []
dict1['computer']['saved'] = []        # Skapar lista för computer / sparade tärningar

# Skapar samma struktur som dict1 fast på en enda rad
dict2 = {'player': {'active': [], 'saved': []}, 'computer': {'active': [], 'saved': []}}

# Lägg till två till fem tärningar för player
for n in range(randint(2,5)):
    dict2['player']['active'].append(randint(1, 6))

# Utskrift med pprint för tydligare utskrift, jämför gärna med print()
pprint.pprint(dict2)
print("\n")

# Lägg till tre sparade tärningar för computer
for n in range(3):
    dict1['computer']['saved'].append(randint(1, 6))

pprint.pprint(dict1)

Utskrift

{'computer': {'active': [], 'saved': []},
 'player': {'active': [3, 1, 1], 'saved': []}}

{'computer': {'active': [], 'saved': [1, 1, 1]},
 'player': {'active': [], 'saved': []}}

Det kanske kräver en förklaring för att förstå hur detta dictionary är uppbyggt, det ser ju komplicerat ut med flera olika index. Vår dictionary anropas genom att vi anger vilken spelare och vilken typ av lista vi vill använda oss av. Vill vi ta reda på vilka tärningar som computer har sparat så anropar vi dict1['computer']['saved'] och på detta sätt kan vi anropa aktuell spelare med active_player = 'computer' och dict1[active_player]['saved'].

Jag visar också genom att använda mig av en tabell för att redovisa dict1.

dict1

player

computer

active

[]

[]

saved

[]

[1, 1, 1]

Så då har vi en grundstruktur för att lagra våra tärningar. Då har det också blivit dags att börja koda på allvar.

4. Skapa ett nytt projekt

Om du inte redan har gjort det så är det nu dags att skapa ett nytt projekt i PyCharm för detta projekt. Ta för vana att skapa ett nytt projekt när vi bygger större applikationer. Då finns det möjlighet att backup på filerna flera gånger och även att skapa små py-filer som vi vill testa olika saker med.

4.1 Välkomstbild

Vi börjar med den första punkten från vår grovplanering och den välkomstbild som vi har skissat på tidigare. Att skriva ut meddelandet vet vi hur vi gör men vi måste dessutom slumpa fram vem som skall börja spelet. Att hålla koll på vems tur det är att spela är en viktig del av spelet och eftersom vi ha två spelare så kommer de ju vara enkelt att byta från den ene till den andra lite senare. För att slippa lagra namnet på spelaren så är det smidigare att skapa en lista med två spelare och sedan lagra det nummer i listan som spelaren har. Vi behöver då skapa en lista som lagrar spelarnas namn och en variabel som lagrar vilken spelare som är aktiv. På det sättet håller vi hela tiden ordning på vems tur det är.

Hjälp [klicka för att visa]

4.2 Grundstrukturen

Nästa punkt var väldigt stor i vår grova planering. Där står det bara tre punkter under själva spelet och det kan ju bli svårt att bygga spelet med så grova instruktioner. Därför får vi ta det steg för steg och göra en lite finare planering.

Det vi då behöver göra är att skapa alla variabler som styr själva spelet. Vi har kikat på hur alla tärningar skall lagras och det är den dictionaryn ihop med vilken spelare som just nu spelar som är allt som behöver lagras för att vår spel skall fungera. Kan vi sedan slå en tärning och bygga spelet kring detta så kommer det att fungera bra. Vi börjar med lagringsstrukturen.

4.2.1 Lagringsstrukturen

I punkt 3.1 så beskrev jag hur lagringsstrukturen skall se ut och fungera. Nu lägger vi till detta till vårt spel. Välj själv hur du vill skapa upp vårt dictionary, själv väljer jag deklarationen som görs på en enda rad och det är främst av utrymmesskäl. Tycker du att det är lite knepigt och vill använda det andra sättet så gör det. Det viktigaste är att du förstår tillräckligt väl för att kunna ändra om det skulle behövas.

Se också till att vår dictionary skrivs ut eftersom vi kommer behöva hålla koll på allt som lagras i det under tiden vi utvecklar applikationen.

Hjälp [klicka för att visa]

4.2.2 Slå tärning

Nu har det blivit dags att slå tärningen. Vi behöver skapa en variabel som slumpar fram ett värde mellan 1 och 6 och som vi sedan kan göra någonting med. Det vi vet är att om värdet är 6 så kommer något hända (kasta alla icke sparade tärningar och byt till den andra spelarens tur).

Vi tar det ett steg i taget och börjar med att slå tärningen och lägger denna i aktuell spelares lista för aktiva tärningar.

Hjälp [klicka för att visa]

4.2.3 Kör flera gånger

Nu testar vi att upprepa detta flera gånger. Jag vill få reda på vilket värde den senaste tärningen hade, hur listan ser ut och så kan jag avbryta denna upprepning när tärningen får värdet 6.

Hjälp [klicka för att visa]

4.2.4 Byt spelare vid 6:a

Vi testar att istället för att avbryta programmet när vi slår en sexa så skall vi byta spelare så att nästa tärning lagras i motståndarens aktiva lista. När vi slår en sexa skall ju inte heller den sexan lagras i någons lista.

Hjälp [klicka för att visa]

Nu har vi en grund till hur tärningar lagras och hur vi byter aktiv spelare. Nu kräver det att vi tänker till lite för hur vi skall gå vidare med utvecklingen av själva spelet.

4.3 Spelet

För att kunna prioritera i vilken ordning vi skall implementera de olika delarna så behöver vi lista alla delar som vi tror/vet att spelet kommer behöva för att kunna slutföras. Följande saker behövs;

  • När någon spelare har minst 40 poäng sammanlagt i sina två listor så har denne vunnit och spelet avslutas.
  • En spelare väljer att [S - spara] eller [K - kasta]. Sparar spelaren så flyttas alla tärningar från aktiv lista till sparad lista.
  • Om det är datorns tur så skall max tre slag göras innan tärningarna flyttas till sparad lista.
  • Slår någon spelare en 6:a så skall aktiv lista tömmas och turen gå över till motståndaren.
  • Implementering av de olika utskrifterna som vi gjort tidigare.

Där har vi en lista på saker som vi vet kommer behövas i vår applikation. Det känns som att det är lämpligt att lägga in kontrollen om någon har vunnit spelet. Denna kontroll borde komma efter vi har slagit en tärning men innan vi får frågan om vi vill slå en tärning till.

4.3.1 Avbryt spelet ifall någon har vunnit

Vad är det vi behöver tänka på här? Vi vet ju att det inte kan bli oavgjort eftersom bägge spelarna inte kan få minst 40 poäng samtidigt. Det borde innebära att om någon har minst 40 poäng så kan vi avbryta loopen och ta hand om segraren med snygg utskrift utanför denna loopen.

Vi behöver kolla summan av tärningarna i spelarens bägge listor. Jag skulle alltså vilja skicka in spelarens bägge listor till en funktion som sedan räknar ut summan av dessa båda listor. Retur från denna funktion skall då vara ett tal som jag kan jämföra med 40. Denna funktion kan jag också använda för att skriva ut ställningen under spelets gång. Jag kan alltså återanvända funktionen till flera aktiviteter om jag skriver den på rätt sätt.

Hjälp [klicka för att visa]

4.3.2 Spara tärningar

Det som funkar nu är att när vi slår en sexa så byter vi spelare men alla tärningar, förutom sexan, finns kvar i listan över aktiva tärningar för en spelare. Så kan vi inte ha det. När du slår en sexa så skall ju alla tärningar som fortfarande är aktiva försvinna. Detta innebär att logiken vi har för byte av spelare behöver byggas ut och detta gör jag genom att skapa en funktion. OBS: Nu skapar jag bara funktionen, jag lägger inte till någon funktionalitet än så länge.

Hjälp [klicka för att visa]

Förhoppningsvis så fungerar det på samma sätt som tidigare. Nu skall vi förbereda för att flytta tärningar från active-listan till saved-listan. Men det skall ju inte göras varje gång. Om vi har slagit en sexa så skall ju inte tärningarna flyttas till saved-listan. För att få detta att fungera så behöver vi skicka in en parameter till som avgör om vi skall spara tärningar eller bara ta bort dem. Med denna parameter så bygger vi ut funktionen change_player(). Det går att diskutera om vi skulle skapa en ny funktion för just detta ändamål men det kommer inte finna behov av en funktion att spara tärningar i en annan lista utan att byta spelare. Därför tycker jag att det är ok att bygga en enda funktion.

Hjälp [klicka för att visa]

Testa nu att köra applikationen och se till att det byter aktiv spelare när vi slår en sexa och att att den aktiva listan töms.

4.3.3 Vad är ställningen?

Innan vi tar tag i den spellogik som vi skall kunna styra genom val så är det viktigt att få mer info om vems tur det är att spela. Detta blir extra viktigt då vi senare kommer behöva stega fram spelet för både spelaren och datorn.

Vi börjar med att skriva ut vems tur det är.

Hjälp [klicka för att visa]

Nu behöver vi på något sätt beräkna hur många poäng varje spelare har, det är viktigt att hålla koll på vad ställningen är för att veta om jag skall spela vidare eller stanna.

Hjälp [klicka för att visa]

När jag kör programmet och väljer att slå några tärningar så ser utskriften ut så här.

Utskrift

Tärningens värde: 3
{'computer': {'active': [4, 3, 3], 'saved': []},
 'player': {'active': [], 'saved': []}}
Totalt har du 0 poäng och datorn har 10 poäng.
Spelare att slå: computer
Tryck på enter för att slå en tärning till.

Utskriften av game-variabeln är ju inte så snygg just nu men vi behöver ha den för att kunna se hur vi skall flytta tärningar mellan olika listor. Dags att ta tag i de olika spelarnas logik.

4.3.4 Datorns logik

Det har blivit dags att ta tag i datorns logik. Vi har tidigare bestämt att datorn skall slå max tre slag innan de ickesparade tärningarna skall sparas. Vi bygger en ram för bägge spelarnas logik eftersom vi senare skall se till att även ta hand om logiken för player.

Hjälp [klicka för att visa]

När jag testar att köra applikationen så kan jag följa tärningarna och ser att computers tärningar flyttas från active-listan till saved-listan när det finns tre tärningar i active-listan. Testa själv. Hos mig ser det ut så här.

Utskrift

Tärningens värde: 5
{'computer': {'active': [5], 'saved': []},
 'player': {'active': [], 'saved': []}}
Totalt har du 0 poäng och datorn har 5 poäng.
Spelare att slå: computer
Tryck för att visa computers nästa tärning.

Tärningens värde: 2
{'computer': {'active': [5, 2], 'saved': []},
 'player': {'active': [], 'saved': []}}
Totalt har du 0 poäng och datorn har 7 poäng.
Spelare att slå: computer
Tryck för att visa computers nästa tärning.

Tärningens värde: 1
{'computer': {'active': [5, 2, 1], 'saved': []},
 'player': {'active': [], 'saved': []}}
Totalt har du 0 poäng och datorn har 8 poäng.
Spelare att slå: computer
Spelare byts

Tärningens värde: 6
Spelare byts
{'computer': {'active': [], 'saved': [5, 2, 1]},
 'player': {'active': [], 'saved': []}}
Totalt har du 0 poäng och datorn har 8 poäng.
Spelare att slå: computer
Tryck för att visa computers nästa tärning.

4.3.5 Spelarens logik

Det har blivit dags att ta tag i spelarens logik. Vi har tidigare bestämt att spelaren har två val, Slå en ny tärning [K - kasta] eller spara tärningarna och låta turen gå över till motståndaren [S - spara]. Alla andra inmatningar skall ge spelaren en ny möjlighet att välja.

Hjälp [klicka för att visa]

Jag använder ingen try-except:-sats eftersom det inte finns någon felaktig inmatning som kan krascha applikationen eftersom alla inmatningar kan vara en sträng men bara de korrekta inmatningarna K/S godkänns. När jag testkör applikationen så ser det ut så här.

Utskrift

Tärningens värde: 4
{'computer': {'active': [], 'saved': [5, 1, 4]},
 'player': {'active': [4], 'saved': []}}
Totalt har du 4 poäng och datorn har 10 poäng.
Spelare att slå: player
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar: K

Tärningens värde: 4
{'computer': {'active': [], 'saved': [5, 1, 4]},
 'player': {'active': [4, 4], 'saved': []}}
Totalt har du 8 poäng och datorn har 10 poäng.
Spelare att slå: player
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar:
Felaktig inmatning, försök igen
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar: R
Felaktig inmatning, försök igen
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar: S
Spelare byts

Tärningens värde: 4
{'computer': {'active': [4], 'saved': [5, 1, 4]},
 'player': {'active': [], 'saved': [4, 4]}}
Totalt har du 8 poäng och datorn har 14 poäng.
Spelare att slå: computer
Tryck för att visa computers nästa tärning.

Viktigt här är att du verkligen ser att även spelarens tärningar kan sparas. Här har jag också testat att göra en felaktig inmatning och ser att applikationen fungerar som den skall trots detta.

Då går spelet att spela men vi behöver ta bort lite onödiga utskrifter och kanske göra om några utskrifter också.

4.3.6 Snygga till utskrifter

Vi börjar med att ta bort utskriften av game-variabeln som vi inte längre vill ha. Men denna utskriften har hjälpt oss mycket när vi har byggt spelet så testutskrifter av denna typ skall inte underskattas under utvecklingen av ett projekt.

Hjälp [klicka för att visa]

Nu är det viktigt att spela spelet för att hitta saker som inte är tydliga nog i spelet. En grej som jag upptäcker som inte är tillräckligt tydlig är att jag inte vet vilka tärningar jag riskerar att förlora om jag slår en sexa. Jag vet bara hur många poäng jag har men inte relationen mellan sparade och aktiva tärningar.

Utskrift

Tärningens värde: 5
Totalt har du 17 poäng och datorn har 17 poäng.
Spelare att slå: player
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar: 

Det måste jag fixa. Jag har redan en funktion som skriver ut ställningen, standings() som jag bygger vidare på.

Hjälp [klicka för att visa]

Testar jag detta kommer utskriften se ut på följande sätt;

Utskrift

Tärningens värde: 3
player har slagit [4, 3] (summa 7 poäng)
Totalt har du 14 poäng och datorn har 10 poäng.
Spelare att slå: player
Välj [K] för att kasta tärningen igen eller [S] för att spara dina tärningar: 

Det blev ju mycket bättre. Nu har du all info du behöver för att kunna ta ett bra beslut.

5. Avrundning

Nu har vi en fungerande applikation. Det vi märker är att det inte är helt enkelt att göra det snyggt och användarvänligt när vi jobbar med en konsollapplikation. Det finns några texter som absolut kan ändras och viss kommunikation med användaren kan absolut förbättras. Jag lämnar dock detta till dig att snygga till om du vill. För ett E är detta fullt tillräckligt.

Innan du helt lämnar uppgiften så gå igenom koden och snygga till den. Ta bort koder som är bortkommenterade, ta bort onödiga mellanslag och se till att koden är snyggt kommenterad. Allt pyssel du lägger på koden kommer vägas in vid betygsbedömning.

5.1 Förbättringar

Det är naturligtvis även möjligt att bygga om applikationen på andra sätt än det rent språkliga. Vi har tidigare flyttat alla funktioner till en egen fil och även skapat en config-fil där vi har deklarerat variabler som som användas i hela applikationen. Detta ger jag dig möjlighet att fixa på egen hand.

6. Hela applikationens kod

Då detta är ett slutprojekt har jag valt att inte redovisa hela den färdiga koden utan tanken är att du skall bygga upp denna under arbetets gång.