6.5 Spelserien Gissa tal

Relevanta dokument
Innehållsförteckning

Programmering i C++ En manual för kursen Datavetenskaplig introduktionskurs 5p

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

JAVAUTVECKLING LEKTION 4

1 Iteration. 1.1 for-satsen

Klassdeklaration. Metoddeklaration. Parameteröverföring

C++ Slumptalsfunktioner + switch-satsen

Dagens föreläsning. Repetition. Repetition - Programmering i C. Repetition - Vad C består av. Repetition Ett första C-program

Programmering i C, 7,5 hp

Villkor och tester. Utförande satser bygger upp metoderna, man brukar finindela detta i: sekvenser. Ett program består i princip av: selektioner (val)

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Checklista. Föreläsning 1-2 Innehåll. Programmering.

Tentamen , Grundläggande programmering i Java

Outline. For. I istället för att följa det normala ordningen, man ändra den. I i Java får man inte göra hopp hur som helst

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy cards. Slingor

TDIU01 - Programmering i C++, grundkurs

System.out.println("Jaså du har "+ antalhusdjur+ " husdjur"); if ( antalhusdjur > 5 ) System.out.println("Oj det var många);

Uppgiften är att beskriva en kvadrat i ett Java program. En första version av programmet skulle kunna se ut så här:

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy. Funktioner / metoder. Punktnotation. Evalueringsordning

"if"-satsen. Inledande programmering med C# (1DV402)

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Strukturdiagram. Styra. Algoritmer. Val

Variabler och konstanter

Föreläsning 8 - del 2: Objektorienterad programmering - avancerat

4.4 Swing ett interaktivt grafiskt gränssnitt

Typkonvertering. Java versus C

Föreläsning 3. Iteration while-satsen

Metodanrop - primitiva typer. Föreläsning 4. Metodanrop - referenstyper. Metodanrop - primitiva typer

Föreläsning 8 SLUMPTAL, SIMULERING + INTRODUKTION TILL VEKTORER

OOP Objekt-orienterad programmering

Mer källkod. Styrstrukturer Val Slingor Operatorer Källkodsexempel med minne. Erik Forslin. Rum 1445, plan 4 på Nada

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Föreläsning 1 & 2 INTRODUKTION

F5 Selektion och iteration. ID1004 Objektorienterad programmering Fredrik Kilander

Programmering A C# VT Ett kompendie över Programmering A (50p) i c# Stefan Fredriksson

Föreläsning 3-4 Innehåll

Visual Basic, en snabbgenomgång

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Felsökning. Datatyper. Referenstyper. Metoder / funktioner

Dagens program. Programmeringsteknik och Matlab. Viktiga datum. Ett första exempel. Programmall, vad behöver vi i ett javaprogram?

TDDC77 Objektorienterad Programmering

Objektorienterad programmering i Java

F4. programmeringsteknik och Matlab

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

JAVA Mer om klasser och objektorientering

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

TUTORIAL: KLASSER & OBJEKT

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

Programstruktur och terminologi. Programmet producerar följande utskrift i terminalfönstret: Ett Javaprogram består av en eller flera klasser

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Om att lära sig programmera. Föreläsning 1-2 Innehåll.

Variabler som hör till enskilda objekt. Deklareras på översta nivån i klassen och i regel som private.

Föreläsning 1 & 2 INTRODUKTION

TDIU01 - Programmering i C++, grundkurs

Kort repetition. Programmeringsteknik för Bio1 och I1. Vad ska vi lära oss idag? Ett exempel

Programexempel: tärningsspel. Programexempel: tärningsspel Kasta tärning tills etta. Klassen Die Specifikation. Slumptalsgenerator Klassen Random

Idag. Javas datatyper, arrayer, referenssemantik. Arv, polymorfi, typregler, typkonvertering. Tänker inte säga nåt om det som är likadant som i C.

Föreläsning 3: Booleans, if, switch

(Man brukar säga att) Java är... Denna föreläsning. Kompilering av Java. Historik: Java. enkelt. baserat på C/C++ Allmänt om Java

Frekvenstabell över tärningskast med C#

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Exempelduggan. Luffarschack. Koda spel

OOP Objekt-orienterad programmering

Logik och kontrollstrukturer

Programmering för språkteknologer I, VT2012. Rum

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

Iteration while-satsen

HI1024 Programmering, grundkurs TEN

Programmering för språkteknologer II, HT2011. Rum

TUTORIAL: SAMLING & KONSOLL

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Grundkurs i programmering, 6 hp (725G61) Dugga 2 tillfälle 2

Bankkonto - övning. Övning 2 Skriv en metod, geträntan, som returnerar räntan.

Data, typ, selektion, iteration

Objektorienterad programmering i Java I. Uppgifter: 2 Beräknad tid: 5-8 timmar (OBS! Endast ett labbtillfälle) Att läsa: kapitel 5 6

Föreläsning 3. Iteration. while-satsen for-satsen do-satsen

String [] argv. Dagens Agenda. Mer om arrayer. Mer om arrayer forts. String [] argv. argv är variabelnamnet. Arrayer och Strängar fortsättning

DIAGNOSTISKT PROV. Tid. Hjälpmedel. Antaganden. Rättning. Övrigt. Diagnostiskt Prov. Klockan Inga

Inledande programmering med C# (1DV402) Tärningarna ska kastas

Agenda. Objektorienterad programmering Föreläsning 13

Objektorienterad programmering i Java

Föreläsning 2. Täcker material från lektion 1, 2, 3 och 4:

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

Skriftlig tentamen för kursen DV017A. Inledande programmering i Java A

Föreläsning REPETITION & EXTENTA

SMD 134 Objektorienterad programmering

Föreläsning 5-6 Innehåll

Grundläggande programmering med C# 7,5 högskolepoäng

TENTAMEN OOP

F4 Klasser och Metoder. ID1004 Objektorienterad programmering Fredrik Kilander

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

Att förstå hur man konstruerar modulära program Att kunna skapa nya funktioner Att förstå hur data skickas mellan funktioner

Dagens föreläsning. Repetition. Repetition - Programmering i C. Repetition - Vad C består av. Repetition Ett första C-program

Malmö högskola 2012/2013 Teknik och samhälle

Föreläsning 5 (6) Metoder. Metoder Deklarera. Metoder. Parametrar Returvärden Överlagring Konstruktorer Statiska metoder tostring() metoden javadoc

Uppgift: Algoritm för att beräkna kontrollsiffran i ett personnummer givet de 9 första siffrorna. Torrsimning av algoritm för personnummer

1 Uppgift 1. a) Skapar ett Company-objekt med hjälp av den överlagrade konstruktorn. Du kan själv välja värden på instansvariablerna.

Objektorienterad Programmering (TDDC77)

Malmö högskola 2008/2009 CTS

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 11

Föreläsning 2. Variabler, tilldelning och kodblock{} if-satsen Logiska operatorer Andra operatorer Att programmera

Klasshierarkier - repetition

Programmeringsteknik I

JAVAUTVECKLING LEKTION 3

I Skapa Hej.java och skriv programmet. I Kompilera med javac Hej.java. I Rätta fel och repetera tills du lyckas kompilera ditt program

Transkript:

6.5 Spelserien Gissa tal Här introduceras ett litet enkelt spel som i fortsättningen kommer att utvecklas steg för steg över flera kapitel. I varje version av det kommer vi att lära oss ett nytt koncept. Låt oss kalla det för Gissa tal : Användaren ska gissa fram ett hemligt tal inom ett visst intervall. Talet är hårdkodat i de första och slumpat i de senare versionerna av spelet. Som hjälp får användaren reda på inom vilket intervall talet ska ligga samt om det gissade talet var mindre än, större än eller lika med det hemliga talet. För att kunna ge den hjälp användaren behöver, måste programmet vid varje gissning välja bland dessa tre alternativ. Därför är Gissa tal -spelet programmeringstekniskt ett exempel på ett trevägsval. Tänkbara utvecklingssteg är: Till att börja med kan det hemliga talet vara en hårdkodad konstant. Sedan kan man gå över till att använda Javas slumptalsgenerator för att förse hemliga talet med slumptal i ett önskat intervall. Så lär vi oss på köpet hanteringen av slumptal i Java. Önskemålet att kunna upprepa gissningarna tills man gissat rätt och genomföra flera spelomgånger leder till att skriva repetitioner (loopar) i Java. För att kontrollera loopars korrekta avslutning behöver man kunskaper i logik som vi ägnar oss åt i nästa kapitel. Slutligen kommer vi att skriva spelet som en klass. Vi börjar med att lösa problemet med: Nästlad if-else Denna nästlade kontrollstruktur kan tänkas både som alternativ och komplement till switch-satsen för att koda flervägsval. Vi kommer att lära oss båda möjligheter. Men först ska vi använda den för att få fram den första versionen av Gissa tal -spelet: // GuessIfElse.java // Flervägsval med nästlad if-else-sats import javax.swing.joptionpane; class GuessIfElse public static void main(string[] a) int gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett tal mellan 1 och 20")); if (gissat <= 17) if (gissat == 17) System.out.print('\u0007'); // datorljud JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); else JOptionPane.showMessageDialog(null, "För litet!"); else JOptionPane.showMessageDialog(null, "För stort!"); 152

Programmet ovan läser in ett tal, avgör om det är mindre, lika med eller större än 17 och skriver ut det. Så här kan tre typiska körningar se ut för de tre olika alternativen: Samtidigt kommer programmet GuessIfElse producera datorljudet när man gissat rätt pga escapesekvensen \u0007 skickas till konsolen om det inmatade tal som läses in till variabeln gissat, är 17. Men låt oss titta hur den nästlade strukturen ser ut: if (gissat <= 17) if (gissat == 17) Inre... Yttre else... else... Vi har en inre if-else-sats som är nästlad i if-delen av en yttre if-else-sats. Den yttre if-else-satsen behandlar de två grupperade alternativen <= 17 i if-delen och alternativet > 17 i else-delen. Den inre if-else-satsen tar hand om gruppen, splittrar upp den i sina beståndsdelar < 17 och == 17, behandlar == 17 i if-delen och < 17 i else-delen. På så sätt återförs trevägsvalet till två tvåvägsval som var och en löses med en if-else-sats. Man anar hur komplexiteten växer med större antal alternativ. För att inte råka ut för det s.k. luriga-else-fenomenet så kallas det när något else paras med fel if låter vi alla else hitta rätt if genom att skriva if-else alltid som par och inte hoppa över något else. Regeln är nämligen att else automatiskt paras till närmaste if. Man kan jämföra det med parenteser i ett uttryck: Öppnar man en parentes med if måste man även stänga den med else. 153

switch med tomma case-satser Visst kunde vi lösa trevägsvalet i programmet GissaIfElse med nästlad if-else-sats (sid 152). Men var det optimalt? Kan man inte utnyttja den klarare strukturen hos switch för att behandla trevägsvalet i Gissa tal och liknande flervägsval med större antal alternativ? Jo, det går: När en likhet inträffar utför switch-satsen alla satser som följer efter case ända tills break kommer eller switch-satsen avslutas. Utnyttjar man denna möjlighet genom att skriva tomma case-satser och utelämna break, kan man med switch lösa flervägsval med olikheter dvs jämförelser med <, <=, > och >=. Följande program demonstrerar denna möjlighet: // GuessSwitchEmp.java // Gissa tal-spelet med switch-satsen // Tomma case-satser utan break: flervägsval med olikheter import javax.swing.joptionpane; class GuessSwitchEmp public static void main(string[] a) int gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett " + "tal mellan 1 och 10")); switch (gissat) case 1: // Fem tomma case-satser case 2: // utan break: case 3: // gissat = 1, 2, 3, 4, 5, 6 case 4: case 5: case 6: JOptionPane.showMessageDialog(null, "För litet!"); break; case 7: // gissat = 7 System.out.print('\u0007'); JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); break; case 8: // gissat = 8, 9, 10 case 9: case 10: JOptionPane.showMessageDialog(null, "För stort!"); break; default: JOptionPane.showMessageDialog(null, "Du gissade tal utanför intervallet [1, 10]\n\n"); 154

Programmet ovan ger exakt samma resultat (sid 153) som programmet GuessIfElse. För alla inmatningar 1-6 skrivs ut För litet! Inmatningen 7 ger Grattis, du har gissat rätt! Matar man in 8-10 får man utskriften För stort! Alla andra inmatningar ger ett lämpligt fel meddelande. För att koden inte ska bli alltför stor har vi hållit oss till intervallet 1-10. Man kan alltså gruppera flera case-satser till ett fall genom att rada upp dem efter varandra, utelämna break och i den sista skriva det som skall utföras i just detta fall. I den sista får break förstås inte utelämnas, annars fortsätter programflödet till default-del och utför dessutom alla satser som står där. Kombination av switch och if-else Även om tomma case-satser löser trevägsvalet med olikheter, är det ju inte precis någon elegant lösning att rada upp en massa case utan innehåll, särskilt om man skulle vilja utvidga gissningsintervallets storlek. Alternativt kan problemet lösas med en kombination av switch för if-else. Då det gäller att skilja mellan de tre alternativen lika med, större än och mindre än 17 kan switch-satsen testa likheten i en case-sats. När det är gjort, har man reducerat trevägsvalet till ett tvåvägsval mellan större än och mindre än. Tvåvägsvalet tar sedan hand om fallen större än och mindre än i en vanlig if-else-sats som kan placeras i switch-satsens default-del. case-satsen behandlar alltså ett fall och default-satsen de två andra fallen. Så här kan det se ut: // GuessSwitch.java // Gissa tal-spelet med kombinerad med if-else // Reduktion av trevägsval till tvåvägsval import javax.swing.joptionpane; class GuessSwitch public static void main(string[] a) int gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett tal mellan 1 och 20")); switch (gissat) case 17: System.out.print('\u0007'); // datorljud JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); break; default: if (gissat < 17) JOptionPane.showMessageDialog(null, "För litet!"); else JOptionPane.showMessageDialog(null, "För stort!"); 155

Programmet ovan ger exakt samma resultat (sid 153) som programmet GuessIfElse. Kombinationen av de två kontrollstrukturerna switch och if-else leder till en avsevärt förenkling och klarhet av koden. Även denna kombination är förstås en slags nästling: if-else-satsen är nästlad i switch-satsen. Men nästlingen här innebär mindre komplexitet än hos den nästlade if-else-satsen. Enkelheten och klarheten i strukturen motiverar användningen av default-satsen på ett okonventionellt sätt. Den egentliga fördelen med den här lösningen är att man tillämpat idén att bryta ned ett stort, svårt problem (trevägsval) till ett mindre, enklare problem (tvåvägsval) vars lösning redan är känd en metodik som med fördel kan användas även i andra sammanhang. I matematiken är det vanligt att bevisa nya satser (stora, svåra problem) med hjälp av redan kända satser (mindre, enklare problem). I programmeringen kallar man metoden för modularisering och använder den för att bryta ned stora program till mindre moduler och sätta ihop dem sedan enligt Lego-principen. Här har tvåvägsvalet brytits ned till två enkla tvåvägsval. Idén om modularisering förverkligas på ett mer genomgripande sätt när man skriver klasser och metoder. 156

6.6 Efter-testad repetition: do-satsen Datorn har några egenskaper som är helt överlägsna motsvarande egenskaper hos människan: snabbheten, noggrannheten och förmågan att effektivt lagra och hantera stora datamängder samt förmågan att inte bli trött. Datorn kan upprepa en sak miljardtals gånger utan att tappa i noggrannhet. Denna förmåga utnyttjas i stor skala av alla möjliga datorprogram. Och därför har man en speciell kontrollstruktur i algoritmer som beskriver den: repetitionen *. Att låta datorn göra grovjobbet innebär som regel att datorn utför en repetition. Beroende på hur repetitionen, speciellt hur avslutningsvillkoret formuleras och var det placeras, skiljer man mellan tre olika typer av repetition: Efter-testad repetition För-testad repetition Bestämd repetition Efter-testad repetition Det är en upprepningsslinga även kallad loop där avslutningsvillkoret testas efter slingans instruktioner dvs efter det som egentligen ska upprepas. Så här kan den formuleras i pseudokod och som flödesplan: Pseudokod Flödesplan REPETERA instruktion(er) SÅ LÄNGE villkor uppfyllt Instruktion(er) Loop Villkor sant falskt I Java inleds den efter-testade repetitionen med det reserverade ordet do: do sats(er); while (villkor); Första raden är do-satsens huvud och får inte avslutas med semikolon. Resten är do-satsens kropp som består av ett block (sid 140). Om kroppen består endast av en sats kan klamrarna och utelämnas. Till skillnad från if-satsen kan här kroppens avslutande klammer inte ersätta do-satsens avslutande semikolon, eftersom do-satsen inte är kom- * I några böcker kallas det för iteration. En besläktad term är rekursion som behandlas på sid 266. Vi undviker båda dessa termer, för att undvika missförstånd, eftersom de används i annan kontext. 157

plett utan fortsätter med villkoret. Och villkoret kan bara testas när det som vanligt skrivs inom vanliga parenteser som följer det reserverade ordet while. Först efter villkoret är do-satsen komplett vilket bekräftas med det avslutande semikolonet. Med hjälp av den nya kontrollstrukturen efter-testad repetition ska vi nu skriva en dosats. Som applikation tar vi Gissa tal för att vidareutveckla det. Den stora nackdelen av alla tre versioner hittills var att man inte kunde gissa flera omgånger utan var tvungen att köra om programmet för att gissa vidare. Följande program använder en do-loop för att kunna köra programmet tills man gissat rätt: // GuessDo.java // Gissa tal-spelet i dialog med do-loop import javax.swing.joptionpane; class GuessDo public static void main(string[] a) int gissat; do // do-loopen börjar gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett tal mellan 1 och 20")); switch (gissat) case 17: System.out.print('\u0007'); // datorljud JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); break; default: if (gissat < 17) JOptionPane.showMessageDialog(null, "För litet!"); else JOptionPane.showMessageDialog(null, "För stort!"); while (gissat!= 17); // do-loopen slutar do-satsen är en lämplig variant av repetition när det gäller att åstadkomma en dialog mellan datorn och användaren. I GuessDo inleds dialogen med inläsning av gissat. Sedan tar switch-satsen hand om valet mellan tre alternativ, nämligen om det gissade talet är lika med, mindre än eller större än spelets hemliga tal 17. I slutet testas om gissat är skilt från 17. Om så är fallet, återvänder programflödet till början av do-blocket och allt upprepas tills gissat någon gång blir lika med 17. 158

I do-satsen utförs satserna första gången oavsett om villkoret är sant eller falskt. Sedan testas villkoret: är det sant upprepas satserna. Sedan testas villkoret igen: är det fortfarande sant, fortsätts repetitionen osv. Är villkoret falskt, stoppas repetitionen. Man kan alltså säga: dörrvakten (villkoret) står vid utgången till lokalen (slingan). Konsekvensen blir att, när villkoret är falskt från början, kommer satserna i alla fall att utföras åtminstone en gång. I nästa avsnitt behandlas en annan variant av repetition, den för-testade repetitionen där dörrvakten så att säga står vid ingången till lokalen och inte tillåter att någon sats exekveras när villkoret är falskt från början. Är villkoret sant hela tiden, kommer slingan att snurra i all evighet. Därför kallas den evighetsslinga (sid 163). En körning av GuessDo kan ge en dialog med datorn av den typ som programmet GuessIfElse visade med samma resultat (sid 153). Skillnaden är att vi då fick köra programmet en gång för varje gissning, medan nu kan vi göra flera gissningar vid endast en körning. Programmet avslutas först när vi hittat det hemliga talet. Det är avgörande att skilja mellan repetition och selektion. I selektionens pseudokod har vi nyckelordet OM och i Java det reserverade ordet if, vilket innebär ett val en enda gång, dvs ingen upprepning alls. I repetitionens pseudokod har vi SÅ LÄNGE och i Java det reserverade ordet while, vilket innebär att villkoret testas upprepade gånger. I selektionens flödesplan går allt flöde endast framåt dvs alla pilar nedåt, se sid 135, 143 och 146. I repetitionens flödesplan ovan går pilen efter instruktionerna tillbaka till villkoret för att testa det igen. Orsaken till att programflödet går tillbaka är att det finns en hoppats inbyggd i alla repetitioner som skickar programflödet tillbaka till loopens villkor. Ett annat sätt att se på efter-testad repetition är att i pseudokoden (sid 164) använda nyckelordet TILLS istället för SÅ LÄNGE. Så kan logiken ibland uppfattas enklare: REPETERA instruktion(er) TILLS villkor inte uppfyllt Om man väljer samma villkor som i formuleringen med SÅ LÄNGE, dvs bibehåller villkorets formulering, måste man negera villkoret när man går över till TILLS. Det beror på skillnaden i den logiska innebörden av SÅ LÄNGE och TILLS. I flödesplanen av den eftertestade repetitionen blir det ingen strukturell ändring, bara man sätter sant och falskt på de logiskt korrekta utgångarna av villkoret. I Morgonsyssla-algoritmen (sid 20-24) finns exempel på en efter-testad repetition med just TILLS-formuleringen. Hantering av slumptal En nackdel av programmet GuessDo är att det hemliga talet är hårdkodat som 17. Det skulle innebära en väsentlig förbättring av Gissa tal om programmet kunde generera ett slumptal mellan 1 och 20 som hemligt tal varje gång man körde det. Därför öppnar vi här en liten parentes om slumptal och deras hantering i Java. Generellt kan man med datorn som en deterministisk maskin inte producera äkta slumptal utan endast simulera dvs på något sätt beräkna s.k. pseudoslumptal enligt en 159

viss algortim. Överallt vi pratar om slumptal menar vi egentligen pseudoslumptal. I Java kan man simulera slumptal på olika sätt, bl.a. med metoden random() som är definierad i klassen Math. Denna metod returnerar slumptal av typ double mellan 0 och 1, närmare bestämt från och med 0 till, men inte med 1, dvs: 0 <= Math.random() < 1 För att skräddarsy random() för vårt ändamål, att få slumptal mellan 1 och 20 utförs en skalning med faktor 20, en typkonvertering till int och en skiftning med 1: 1 + (int) (Math.random() * 20) Följande program testar både Math.random() och transformationen till [1, 20] samt producerar utskriften i rutan till höger: // DoRand.java // Skriver ut 4 slumptal mellan 0 och 1 och 4 mellan 1 och 20 import javax.swing.joptionpane; class DoRand public static void main(string[] a) int i = 1, j = 1; String output = "Math.random() ger slumptal\n" + "mellan 0 och 1:\n\n"; do // do-loop output += Math.random() + "\n"; while (++i < 5); // i ökar först, testas sedan output += "\n1 + (int) (Math.random() * 20)\n" + "ger slumptal mellan 1 och 20:\n\n"; do // do-loop output += 1 + (int) (Math.random() * 20) + "\n"; while (++j < 5); // j ökar först, testas sedan JOptionPane.showMessageDialog(null, output, "Slumptal", 1); Skalningen Math.random() * 20 förstorar slumpvärdena och ger decimaltal från och med 0 till, men inte med 20. Typkonvertering till int ger heltal mellan 0 och 19. Skiftningen är en förskjutning av intervallet [0, 19] med +1 till [1, 20]. Det hela är en transfor- 160

mation som kan generaliseras: Vill man ha slumptal mellan a och b där a < b, kan man transformera decimaltal mellan 0 och 1 till heltal mellan a och b så här: a + (int) (Math.random() * (b-a+1) Två olika do-satser i DoRand tar hand om slumptalen i olika intervall. All utskrift ackumuleras i variabeln output och dumpas till utskrift i programmets sista sats (sid 88). Gissa tal med slumptal Resultatet från DoRand kan vi nu använda i följande version av Gissa tal för att slumpa fram det hemliga talet varje gång vi kör och kunna spela tills vi gissat rätt: // GuessDoRand.java // Gissa tal-spelet med slumptal som hemligt tal i dialog med do import javax.swing.joptionpane; class GuessDoRand public static void main(string[] a) int gissat; int hemligt = 1 + (int) (Math.random() * 20); do gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett tal mellan 1 och 20 (Avsluta med 0)")); if (gissat == 0) JOptionPane.showMessageDialog(null, "Programmets " + "hemliga tal var " + hemligt, "Avbrott", 1); break; // Bryter do-loopen if (gissat == hemligt) System.out.print('\u0007'); // datorljud JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); break; // Bryter do-loopen if (gissat < hemligt) JOptionPane.showMessageDialog(null, "För LITET, försök igen!"); else JOptionPane.showMessageDialog(null, "För STORT, försök igen!"); while (gissat!= hemligt); 161

Spelet har förbättrats i två avseenden: För det första bestäms programmets hemliga tal inte längre redan i koden utan slumpas fram med metoden Math.random(). För det andra kan man avsluta innan man hunnit gissa rätt: Man matar in 0. Programmets första if-sats bryter då do-loopen med hjälp av break. När vi behandlade switch-satsen sade vi att break är ett reserverat ord som bryter programflödet även i loopar (sid 150). Och det är precis vad den gör här. break bryter do-satsen utan att testa do-satsens avslutningsvillkor (gissat!= hemligt) som i regel dvs när break inte utförs kommer till användning och avslutar dialogen när man gissat rätt. Annars fortsätter dialogen så länge man gissar fel. Frågan som dyker upp när man tittar på koden i programmet GuessDoRand, är: Varför används inte längre switch i kombination med if-else som i den senaste versionen av Gissa tal hade gett bra resultat. Vi hade helst velat göra det. Men övergången till slumptal gör att slumptalet måste lagras i en variabel i det här fallet hemligt och switch-satsen inte tillåter jämförelse med en variabel. I spelets första versioner var programmets hemliga tal hårdkodat som konstanten 17 och switch kunde jamföra väljaren gissat med denna konstant. Men nu lagras det hemliga talet i variabeln hemligt. Den allmänna strukturen: switch (väljaren) case konstant1 :... sätter stopp för användningen av switch i GuessDoRand därför att väljaren kan vara en variabel av typ int eller char i vårt fall är gissat en int-variabel (det är ok) medan konstant1 måste vara ett konstant uttryck, annars kan man inte kompilera. I vårt fall är hemligt som skulle skrivas efter case, inget konstant uttryck utan en intvariabel, vilket inte är ok. Vi stöter här på switch-satsens begränsningar. Därför används i GuessDoRand en enkel if- samt en if-else-sats för att avgöra trevägsvalet gissat lika med, mindre eller större än hemligt. En körning ger: 162

Har man efter ett tag ingen lust att gissa vidare och vill avsluta, kan man mata in 0. Man får då reda på programmets hemliga slumptal vid just den aktuella körningen: do-satsen är en lämplig variant av repetition för dialoger mellan dator och användare. Frågan om vilken variant av repetition man ska välja, kan inte besvaras generellt, eftersom det är det konkreta problemet som avgör valet. Innan vi avslutar do-satsen vill vi nämna en företeelse som man kan råka ut för när man jobbar med loopar: Evighetsslinga I det inledande exemplet DoRand är den första do-satsens avslutningsvillkor ++i < 5. Detta innebär att före villkorets test, i:s värde ökas med 1 och att villkoret i < 5 testas med i:s ökade värde. Tas ++ bort dvs ersätts avslutningsvillkor med i < 5, avslutas loopen och därmed programmet aldrig: Man har råkat ut för en s.k. evighetsslinga. Orsaken är att villkoret är sant från början i har ju initierats till 1 och förblir sant hela tiden, så loopen fortsätter i all evighet. Generellt innehåller en loop alltid möjligheten till en evighetsslinga. För att undvika den, måste villkoret och satserna i kroppen formuleras på ett sådant sätt att villkorets sanningsvärde ändras, så att villkoret blir falskt efter några varv. Detta krav har realiserats i programmet DoRand genom att använda ++i. Dvs, har man med en lämplig initiering av i kommit in i do-satsen, kommer i att öka med 1 i varje varv så att det någon gång blir = 5. Då stoppas loopen. Glömmer man ökningen ++ och initierar i med ett värde mindre än 5, blir do-satsen en evighetsslinga. Omvänt: Är do-villkoret falskt från början, görs ingenting. Initieras i till ett värde >= 5, blir villkoret falskt från början och man kommer aldrig in i kroppen ( aldrigslinga ). Programflödet fortsätter vid första satsen efter do-slingan. Testa gärna dessa möjligheter. Gå också igenom i detalj dvs varv för varv, varför do-satserna i programmet DoRand producerar exakt 4 utskrifter av slumptal (sid 160). Vi kommer i övernästa avsnitt, i programmet ForRand att översätta dessa do-satser till for-satser (sid 168). Där diskuteras också antal varv och båda programmen som producerar samma resultat, jämförs med varandra. 163

8.4 "Gissa tal" som klass Nu kan vi sammanföra våra kunskaper om klasser, objekt och referenser för att skriva en klassvariant till Spelserien Gissa tal som introducerades på sid 152 och vidareutvecklades sedan i ett antal steg. Samtidigt tillämpar vi idéerna om modularisering och återanvändning av kod: Klassen RandNr samt dess metod slumpa() används utan att ändra koden för att initiera programmets hemliga tal med ett slumpvärde mellan 1 och 20. Följande klass anropar slumpa() för att förse varje objekt av den med ett nytt slumptal: // GuessNr.java // Klass som implementerar "Gissa tal"-spelet med två datamedlem- // mar för det gissade och hemliga talet och en metod med spelets // regler som hjälper användaren att gissa rätt import javax.swing.joptionpane; class GuessNr int gissat; int hemligt = RandNr.slumpa(1, 20); // Anrop av statisk // metod i ickevoid spela() // statisk miljö do gissat = Integer.parseInt(JOptionPane.showInputDialog( "Gissa ett tal mellan 1 och 20 (Avsluta med 0)")); if (gissat == 0) JOptionPane.showMessageDialog(null, "Programmets " + "hemliga tal var " + hemligt, "Avbrott", 1); break; if (gissat < hemligt) JOptionPane.showMessageDialog(null, "För LITET, försök igen!"); if (gissat > hemligt) JOptionPane.showMessageDialog(null, "För STORT, försök igen!"); while (gissat!= hemligt); if (gissat == hemligt) System.out.print('\u0007'); // datorljud JOptionPane.showMessageDialog(null, "Grattis, du " + "har gissat rätt!", "\"Gissa tal\" gratulerar:", 1); 214

I klassen GuessNr anropas metoden slumpa() för att initiera datamedlemmen hemligt med ett slumptal i intervallet [1, 20]. Anropet framhävt med vit bakgrund görs med klassnamnet pga metoden är statisk. Dessutom är det inbakad i en tilldelningssats därför att slumpa() är en metod med returvärde. Att initiera en datamedlem direkt i klassen är inte så vanligt. I det här fallet är det motiverat eftersom variationen i slumpen ger olika värden för de olika objekt som kommer att skapas av klassen GuessNr. Dessutom ska varje spelomgång och därmed varje objekt ha endast ett hemligt tal som användaren ska gissa fram sig till. Att initieringen görs i klassen betyder inte att datamedlemmen hemligt är en klassvariabel. Då borde den vara deklarerad som static vilket den inte är. Utan den är en instansvariabel precis som den andra datamedlemmen gissat som initieras först i metoden spela(). Att det överhuvudtaget är möjligt att anropa den statiska metoden slumpa() i klassen GuessNr:s icke-statiska miljö det hade lika bra varit möjligt att anropa den i GuessNr:s icke-statiska metod spela() beror på regeln enligt vilken statiska metoder kan anropas i icke-statiska miljöer, men inte omvänt: Ickestatiska metoder kan inte anropas i statiska miljöer (sid 247). Klassen ovan kan kompileras för sig, men inte exekveras eftersom main() saknas. Programmet i sin helhet består av tre klasser (moduler) i tre filer som lagras i en mapp: Klassen ovan, klassen RandNr och följande klass som testar klassen ovan i den bemärkelse att placera metoden main() i den, skapa där ett GuessNr-objekt och anropa dess metod spela(). Man kan också säga att test klassen (nedan) är en slags drivrutin eller drivklass för klassen GuessNr. Den sätter igång ( driver ) hela programmet. Utan den fungerar ingenting. Men huvudjobbet görs ändå av klassen GuessNr eftersom den innehåller spelets egentliga kod i form av metoden spela(). // GissaTalTest.java // Testar klassen GuessNr genom att skapa ett objekt av den och // anropa objektets metod spela() // Kan både kompileras och exekveras: Utgör med klasserna Gissa- // Tal och RandNr ett program som gör samma sak som "Gissa // tal"-spelets tidigare versioner class GissaTalTest public static void main(string[] a) GuessNr g = new GuessNr(); // Objekt skapas g.spela(); // Metod anropas Allt går ut på att anropa void-metoden spela() som implementerar spelets regler och genomför det i ordande former dvs låter användaren med lite hjälp gissa flera gånger tills den gissat rätt eller på begäran avslöjar spelets hemliga tal. Den innehåller i huvudsak en loop som håller igång, styr, kontrollerar och avslutar en spelomgång. Vi har tagit över koden från Gissa tal -spelets senaste version GuessNEG och skrivit den i klassen 215

GuessNr på förra sidan efter att ha eliminerat allt som har att göra med den logiska operatorn NEGATION för att ha fokus på programmets objektorienterade aspekter. Men metoder i Java och därmed även metoden spela() kan endast anropas utifrån ett objekt eller en klass. När det sker från en klass måste de vara statiska vilket spela() inte är (se klassen GuessNr). Därför behöver det ett objekt för att anropas. Ett sådant skapas i klassen GissaTalTest med new GuessNr() och tilldelas referensvariabeln g. Sedan anropas metoden med denna objektreferens: g.spela(), dvs metoden anropas i det objekt som g refererar till. Man skulle kunna anropa spela() även med ett anonymt objekt i en enda sats: new GuessNr().spela(); och därmed spara undan referensvariabeln vilket inte just förbättrar kodens läslighet. Förutom klasserna ovan är programmets tredje modul klassen RandNr som innehåller metoden slumpa() och som vi har skrivit och använt tidigare i programmet ExternMeth (sid 244). Nu drar vi nytta av att vi har lagrat den externt i en separat fil och kan använda den direkt utan att behöva ändra en enda rad kod. Därför återges koden inte en gång till här utan hänvisas till sid 244. Det enda som behöver göras är att kopiera över filen till samma mapp där även filerna GuessNr.java och GissaTalTest.java är lagrade för att klasserna ska hitta varandra. Egentligen räcker det om class-filen ligger i samma mapp. Även detta steg är i professionella sammanhang onödigt om man syr ihop sina klasser till ett paket. För körresultat av programmet ovan hänvisas till de dialoger vi fick av spelets tidigare versioner eftersom de är bortsett från slumpens variation identiska (sid 162/163). 216