} Programmeringsteknik för D KURSKOMPENDIUM } EDA016 http://cs.lth.se/eda016 2015/2016 EDA016 Programmeringsteknik, godkända obligatoriska uppgifter 2015/2016 Skriv ditt namn och din namnteckning nedan: Namn: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Namnteckning: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Godkända laborationsuppgifter Datum Laborationsledarens namnteckning Datum Laborationsledarens namnteckning Lab1-quiz Lab2-eclipse Lab3-anv-square Lab4-impl-square Lab5-gissa-tal Lab6-turtle Lab7-maze Lab8-vektor Lab9-teamlab-turtlerace Lab10-life Lab11-teamlab-imagefilters Godkänd inlämningsuppgift (välj en) ( ) bank ( ) mandelbrot ( ) draw ( ) egendef. För att bli godkänd på uppgifterna måste du lösa deluppgifterna och diskutera dina lösningar med en labhandledare. Denna diskussion är din möjlighet att få feedback på ditt program. Ta vara på den! Se till att labhandledaren noterar dina uppgifter som godkända på detta godkännandeblad. Dessa underskrifter är ditt kvitto på att du är godkänd på laborationerna. Spara dem tills du fått slutbetyg i kursen. LUNDS TEKNISKA HÖGSKOLA Institutionen för datavetenskap EDA016 Programmeringsteknik 2015/2016 Innehåll 1 2 Övningar – anvisningar Övning 1 – Hello World . . . . . . . . . . . . . Övning 2 – Paket, kodfiler och dokumentation Övning 3 – beräkningar, klasser och objekt . . Övning 4 – Aritmetik, Logik . . . . . . . . . . . Övning 5 – Klasser, slumptal . . . . . . . . . . Övning 6 – vektorer och registrering . . . . . . Övning 7 – registrering . . . . . . . . . . . . . . Övning 8 – matriser, String och StringBuilder . Övning 9 – ArrayList . . . . . . . . . . . . . . . Övning 10 – arv . . . . . . . . . . . . . . . . . . Övning 11 – sortering, objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 8 11 15 17 18 20 22 24 26 28 30 Laborationer – anvisningar Laboration 1 – Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Laboration 2 – Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . Laboration 3 – använda färdigskrivna klasser, kvadrat . . . . . . . . . Laboration 4 – implementera klasser (Square), samt felsökning . . . . Laboration 5 – implementera klasser, gissa tal . . . . . . . . . . . . . . Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle . Laboration 7 – Maze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Laboration 8 – vektorer, simulering av patiens . . . . . . . . . . . . . . Grupplaboration 9 – TurtleRace, ArrayList och ärvning . . . . . . . . . Laboration 10 – matris, spelet life . . . . . . . . . . . . . . . . . . . . . . Grupplaboration 11 – Matriser, bilbehandling ImageFilter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 34 38 43 49 55 57 63 66 70 74 79 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Inlämningsuppgift – anvisningar 89 Inlämningsuppgift alternativ 1 – bankapplikation . . . . . . . . . . . . . . . . . . . . . . 90 Inlämningsuppgift alternativ 2 – Mandelbrot . . . . . . . . . . . . . . . . . . . . . . . . . 99 Inlämningsuppgift alternativ 3 – Draw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 4 Eclipse – en handledning 109 5 7 1 Övningar – anvisningar 1. Övningarna är förberedelser inför laborationerna och ger viktig träning inför den skriftliga tentamen. 2. Du får hjälp med övningarna av handledare på resurstiderna. 3. Vilken övning som är lämplig att göra vilken vecka framgår av kursprogrammet. Försök ligga i fas, så att du är redo inför laborationerna. 4. Du löser övningsuppgifterna tillsammans med en kamrat eller självständigt. Prova först att lösa övningen självständigt och diskutera gärna sedan din lösning med en kamrat. 5. Lösningsförslag finns på kurshemsidan. Titta på lösningarna efter att du själv löst uppgiften och jämför med din egen lösning. I allmänhet kan programmeringsproblem lösas på många olika sätt; vissa är kanske bättre än andra, medan vissa kan vara bra ur olika aspekter beroende på hur programmet används. 6. Förutom uppgifterna i detta häfte finns övningsuppgifter i läroboken och extra programmeringsuppgifter på kurshemsidan. Övning 1 – Hello World 8 Övning 1 – Hello World Mål: Under denna övning lär du dig att köra igång ett program och att resonera kring variabler och tilldelning. Det viktiga med denna övning är att du bekantar dig med programkod, men du behöver inte förstå alla detaljer. Senare i kursen kommer vi att på djupet reda ut till exempel public, class, static, void, main, String, [], new, Scanner, och andra begrepp som ännu inte fått någon djupare förklaring. Förberedelser Läs i läroboken: avsnitt 1, 3.2 (samt 7.8 för mer om Scanner) Välj editor. Undersök vilka kodeditorer som finns och välj en som du vill lära dig. 1 Nedan finns en lista på populära editorer. Om du inte vet vilken du ska välja, testa först gedit. • gedit – öppen, fri och gratis; lätt att lära men inte så avancerad; finns för Windows, Mac och Linux. Editorn gedit finns på LTH:s Linux-datorer och startas med kommandot gedit Ladda ner till din egen dator här: https://wiki.gnome.org/Apps/Gedit • Sublime Text 3 – gratis att prova på, men programmet föreslår då och då att du köper en licens; finns för Windows, Mac, Linux. http://www.sublimetext.com/3 • Notepad++ – öppen, fri och gratis; finns endast för Windows. https://notepad-plus-plus.org/ • Textwrangler – gratis; lätt att lära men inte så avancerad; finns endast för Mac. http://www.barebones.com/products/textwrangler/ • Atom – öppen, fri och gratis; finns för Windows, Mac, Linux. Utvecklas av GitHub; lovande men ännu inte lika mogen som övriga. https://atom.io/ • Vim – öppen, fri och gratis; hög inlärningströskel; finns för Windows, Mac och är förinstallerad på LTH:s Linux-datorer i E-huset. http://www.vim.org/ • Emacs – öppen, fri och gratis; hög inlärningströskel; finns för Windows, Mac och är förinstallerad på LTH:s Linux-datorer i E-huset. http://www.gnu.org/software/emacs/ Editera, kompilera, exekvera a) Öppna din editor och skriv in nedan program och spara det i en fil med namnet HelloWorld.java i en ny katalog: public class HelloWorld { public static void main(String[] args) { System.out.println("Hej!"); } } 1 Gör till exempel en nätsökning på ”best free code editor” eller se här: http://en.wikipedia.org/wiki/Comparison_ of_text_editors. Övning 1 – Hello World 9 b) Öppna ett kommandofönster (Terminal i Linux/Mac eller Powershell i Windows) och navigera vid behov med kommandot cd till katalogen där din fil befinner sig. c) Kompilera ditt program med kommandot javac HelloWorld.java och kontrollera med kommandot ls att en fil med namnet HelloWorld.class skapats. d) Kör ditt program med kommandot java HelloWorld och kontrollera att utskriften blir som förväntat. e) Ändra textsträngen som skrivs ut till något annat. Spara, kompilera om och kör igen. f) Skapa fel i koden så att kompilatorn skriver ut nedanstående felmeddelanden. Kompilera om efter varje infört fel. Rätta felet igen innan du inför nästa fel. (i) HelloWorld.java:3: error: ’;’ expected (ii) HelloWorld.java:3: error: unclosed string literal (iii) HelloWorld.java:3: error: ’)’ expected (iv) HelloWorld.java:3: error: cannot find symbol symbol: method skrivRad(String) location: variable out of type PrintStream Uppgifter på papper 1. Skriv kod som beräknar summan av talen 3, 8 och 12 och skriver ut resultatet. 2. Betrakta följande programavsnitt: int nbrA = 2; int nbrB = nbrA + 3; int nbrC = 3 * (nbrA + nbrB) - 1; nbrA = nbrC / 5; nbrC = 0; En bild av minnessituationen (vilka variabler som finns och variablernas värden) ska ritas efter varje kodrad. Efter första tilldelningssatsen ser det ut så här: nbrA 2 Rita de fyra övriga bilderna för varje rad. 3. Betrakta följande program, där fyra heltal läses från tangentbordet och några (ointressanta) beräkningar görs: public class Example1 { public static void main(String[] args) { Scanner scan = new Scanner(System.in); int a = scan.nextInt(); int b = scan.nextInt(); int c = scan.nextInt(); int d = scan.nextInt(); int e = (c + d) - (a + b); c = c + 2; a = (2 * e + c) / 4; System.out.println(a + " " + b + " " + c + " " + d + " " + e); } } Övning 1 – Hello World 10 a) En av parenteserna i programmet är onödig. Vilken? Varför? b) Vilka värden skrivs ut när talen 1, 2, 4 och 8 läses in från tangentbordet? Använd en tabell där du noterar de successiva variabelvärdena. c) Vilka värden skrivs ut när talen 1, 2, 8 och 4 läses in från tangentbordet? 4. Två heltalsvariabler a och b har deklarerats och fått värden. Skriv satser som byter värde på a och b (”swappar” värdena). Exempel: int a = 10; int b = 25; // ... dina satser System.out.println(a + " " + b); // ger utskriften 25 10 Extrauppgift vid datorn Skriv följande kod i en fil med namnet HelloArgs.java: public class HelloArgs { public static void main(String[] args) { System.out.println("Argument noll: " + args[0]); if (args[0].equals("42")) { System.out.println("Du har upptäckt hemligheten!"); } else { System.out.println("Tack och hej!"); } } } a) Spara, kompilera och kör med kommandot java HelloArgs gurka b) Vad skriver programmet ut? c) Vad händer om du byter ut gurka mot 42? d) Vad händer om du utelämnar argumentet? e) Vad händer om du ger mer än ett argument? f) Ändra i programmet så att det hemliga meddelandet skrivs ut när det andra argumentet innehåller 42. g) Kompilera om och kör programmet upprepade gånger så här: java HelloArgs gurka 42 java HelloArgs tomat 43 och kontrollera att programmet fungerar som förväntat. Övning 2 – Paket, kodfiler och dokumentation 11 Övning 2 – Paket, kodfiler och dokumentation Mål: Du lär dig skriva ett program med ett simpelt grafiskt fönster. Du lär dig hur kodfiler och klassfiler kan organiseras i paket och sammanföras i jar-filer. Då använder import för att göra klasser i paket tillgängliga. Denna övning visar hur man manuellt skapar en katalogstruktur, kompilerar, bygger och kör en applikation från olika kod-filer; detta sköts automatiskt av en integrerad utvecklingsmiljö, som vi kommer att använda under kommande laborationer. Förberedelser Läs i läroboken: avsnitt Appendix B.1 och B.2 Uppgifter vid dator 1. Skapa ett paket. När man har stora program kan det bli många .java- och .class-filer. Det blir då lätt rörigt att ha alla filer i samma bibliotek. Man brukar därför skapa en katalogstruktur för kodfiler. a) Skapa en katalog ovn2 och skapa i den en underkatalog med namnet src som i sin tur innehåller katalogen greeting med dessa kommandon i terminalen (Linux/Mac) eller powershell (Windows): mkdir ovn2 mkdir ovn2/src mkdir ovn2/src/greeting cd ovn2 b) Skapa med en editor nedanstående program. package greeting; public class Hello { public static void main(String[] args) { String name = javax.swing.JOptionPane.showInputDialog("What name?"); javax.swing.JOptionPane.showMessageDialog(null, "Hello " + name); } } Spara programmet i filen ovn2/src/greeting/Hello.java. c) Nyckelordet package anger i vilket bibliotek som kompilatorn ska lägga .classfilerna. Man brukar placera .class-filer i ett bibliotek med namnet bin. Skapa en katalog bin i katalogen ovn2. d) Prova kommandot javac -help och studera utskriften. Vad gör optionen -d och vilken typ av argument ska man ge efter -d? e) Kompilera ditt program med javac -d bin src/greeting/Hello.java f) Var har kompilatorn lagt Hello.class-filen? g) Kör ditt program med java -classpath bin greeting.Hello där punkten används för att ”gå in i” paketet greeting och ange att main-metoden ligger i klassen Hello. Vad händer? Vad gör metoderna showInputDialog och showMessageDialog? h) Kör kommandot java -help och studera utskriften. Vad används optionen -classpath till? Vad har optionen för kortare motsvarighet? i) Ändra i ditt program så att ledtexten i frågan och utskrifttexten i meddelandet blir något annat. Kompilera om och kör på nytt. j) Lägg till fler frågor och svar i ditt program. 12 Övning 2 – Paket, kodfiler och dokumentation 2. Använda färdiga paket. När man installerar Java medföljer en stor mängd färdiga standardklasser. De är så många att man har paket inuti paket i flera steg. Ett exempel på detta ser du i programmet ovan: Paketet javax innehåller en mängd olika paket varav ett heter swing och innehåller klasser för att skapa grafiska användargränssnitt. Programmet ovan använder klassen JOptionPane och kompilatorn hittar den tack vare att paketen den ligger i anges i tur och ordning med punkt emellan: javax.swing.JOptionPane a) Det blir mycket att skriva om en klass ligger djupt nere i en paketstruktur. Därför finns nyckelordet import. Lägg till denna rad i ditt program efter paket-deklarationen: import javax.swing.JOptionPane; b) Nu kan du direkt använda klassen JOptionPane utan att ange paketvägen javax.swing. före klassnamnet. Ändra i ditt program och kontrollera att så är fallet. 3. Använda jar-fil. Du kommer lära dig mer om att programmera grafiska användargränssnitt i senare kurser. För att göra det enkelt i denna kurs finns bl.a. en färdig klass SimpleWindow som är utvecklad vid cs.lth.se. Denna och andra klasser finns paketerade i en så kallad jar-fil. Jar-filer används för att samla många klassfiler i en enda fil. a) Ladda ner filen http://fileadmin.cs.lth.se/cs/Education/EDA016/cs_pt.jar och lägg den i bilioteket ovn2. I Linux kan du använda detta kommando för att ladda ner filen i aktuell katalog: wget http://fileadmin.cs.lth.se/cs/Education/EDA016/cs_pt.jar b) Skapa med en editor nedanstående program. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package greeting; import se.lth.cs.pt.window.SimpleWindow; public class WindowApp { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(200, 200, "App"); w.setLineColor(java.awt.Color.PINK); w.moveTo(100,100); w.writeText("Hello Pink Panter!"); while (true) { w.waitForMouseClick(); w.lineTo(w.getMouseX(), w.getMouseY()); w.writeText("Hello Pink Panter!"); } } } Spara programmet i filen ovn2/src/greeting/WindowApp.java. c) När du kompilerar programmet behöver du se till att classpath innehåller jar-filen där SimpleWindow ligger: javac -cp cs_pt.jar -d bin src/greeting/WindowApp.java d) När du kör programmet behöver du ha både katalogen bin och jar-filen med SimpleWindow på classpath. I Linux/Mac-terminal, kör programmet med (notera kolon): java -cp "cs_pt.jar:bin" greeting.WindowApp I Windows powershell, byt ut kolon mot semikolon: java -cp "cs_pt.jar;bin" greeting.WindowApp 4. Använda dokumentation. För att det ska bli lättare att använda färdiga klasser finns ofta dokumentation som kan öppnas i en webbläsare. Övning 2 – Paket, kodfiler och dokumentation 13 a) Läs dokumentationen om SimpleWindow på kursens hemsida. Ta reda på vad metoderna som anropas på objektet w i programmet ovan gör. Ändra linjebredden så att ett tjockare rosa streck ritas. Kompilera om och kör. b) Gör en nätsökning på java.awt.Color.PINK och ta reda på med hjälp av dokumentationen vilka fler fördefinierade färger som finns. Ändra i programmet så att hälsningstexten får en annan färg. Kompilera om och kör. Extrauppgifter 5. Skapa jar-fil. Med hjälp av kommandot jar kan man skapa och packa ihop filer i en egen jar-fil, som även komprimeras så att den ska ta mindre plats (i likhet med s.k. zip-filer). a) Undersök vad jar har för optioner med kommandot jar -help b) Skapa en jar-fil med kommandot jar cvf mygreet.jar -C bin greeting c) Lista innehållet i din jar-fil med kommandot jar tf mygreet.jar och titta även i innehållet genom att dubbelklicka på jar-filen (i Windows och Mac behöver du döpa om filen så att den slutar på .zip för att dubbelklick ska öppna filen som en katalog med underkataloger). d) Kör de olika main-metoderna i din jar-file med resp. java-kommando: java -cp mygreet.jar greeting.Hello java -cp mygreet.jar greeting.WindowApp Det senare fungera inte. Vad får du för felmeddelande? Varför? Hur kan du göra så att WindowApp kan hitta SimpleWindow? 6. Skapa dokumentation. Med hjälp av kommandot javadoc kan man skapa webbsidor med dokumentation av klasser. a) Skapa med en editor nedanstående program. Kommentarer som börjar med /** är dokumentationskommentarer (se läroboken appendix B.2). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package greeting; import se.lth.cs.pt.window.SimpleWindow; import java.awt.Color; /** * A collection of greeting methods. */ public class Greet { /** * Asks for a name and then greets politely. */ public static void swingGreeting(){ String name = javax.swing.JOptionPane.showInputDialog("What name?"); javax.swing.JOptionPane.showMessageDialog(null, "Hello " + name); } /** * Greeting of pink creature. Allows user to spam click but not too far down. * Each click is awarded one point. * * @param color the color of the greeting * @return the number of points earned */ public static int panterGreeting(Color color){ SimpleWindow w = new SimpleWindow(200, 200, "App"); w.setLineColor(color); w.moveTo(100,100); w.writeText("Hello Pink Panter!"); int points = 0; while (w.getMouseY() < 180) { w.waitForMouseClick(); points = points + 1; //earn a point for each click Övning 2 – Paket, kodfiler och dokumentation 14 35 36 37 38 39 40 w.lineTo(w.getMouseX(), w.getMouseY()); w.writeText("Your hello-points: " + points); } return points; } } b) Spara programmet i filen ovn2/src/greeting/Greet.java c) Kompilera programmet med javac -cp cs_pt.jar -d bin src/greeting/Greet.java d) Undersök vad javadoc-kommadot har för optioner med javadoc -help e) Skapa katalog doc med mkdir doc i katalogen ovn2 f) Kör kommandot javadoc -d doc src/greeting/* g) Undersök vad som ligger i doc-katalogen och öppna filen index.html genom att dubbelklicka på den. h) Skapa nedan huvudprogram och spara det i filen GreetApp.java 1 2 3 4 5 6 7 8 9 10 11 package greeting; /** A polite Greetings App. */ public class GreetApp { /** Starts the App. */ public static void main(String[] args) { Greet.swingGreeting(); int pts = Greet.panterGreeting(java.awt.Color.PINK); System.out.println("You earned " + pts + " points!"); } } i) Kompilera med javac -cp "cs_pt.jar:bin" -d bin src/greeting/GreetApp.java och kör med java -cp "cs_pt.jar:bin" greeting.GreetApp (Om du kör detta i Windows powershell ska alla kolon bytas ut mot semikolon) j) Gör så att din jar-fil även innehåller klasserrna Greet och GreetApp, samt dokumentationen med dessa kommando: javadoc -d doc src/greeting/* jar cvf mygreet.jar -C bin greeting doc k) Undersök innehållet i jar-filen med jar tf mygreet.jar l) Kör igång dina nya underprogram från jar-filen med java -cp "cs_pt.jar:mygreet.jar" greeting.GreetApp (Om du kör detta i Windows powershell ska kolon bytas ut mot semikolon) m) Undersök hur dokumentationen har utökats med klassen GreetApp genom att dubbelklicka på index.html i katalogen doc Övning 3 – beräkningar, klasser och objekt 15 Övning 3 – beräkningar, klasser och objekt Läs i läroboken: avsnitt 2, 6.3, 7.1-7.3 samt 5.1-5.3 för mer om variabler Inläsning och beräkningar 1. Skriv ett program (en klass med en main-metod) som först läser en starttid (två tal, timmar och minuter, till exempel 12 41) och därefter en sluttid (två andra tal, till exempel 16 28) och därefter beräknar och skriver ut hur många minuter det är mellan tiderna. Du kan förutsätta att sluttiden är större än starttiden. Ledning: det behövs fyra int-variabler för de fyra inlästa talen. Ge dessa variabler vettiga namn. 2. Ändra i programmet från uppgift 1 så att tidsavståndet skrivs ut i timmar och minuter i stället för i minuter. Detta kan man göra på två sätt: antingen genom att använda en if-sats eller genom att använda heltalsdivision och operatorn %, som ger resten vid heltalsdivision (avsnitt 6.3 i läroboken). Läsa specifikationer och använda färdiga klasser 3. Klasserna SimpleWindow och Square från läroboken antas vara tillgängliga. Vad ritas på skärmen när nedanstående program exekveras? Visa med en skalenlig figur. public class SquareExample { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(400, 400, "Squares"); Square sq1 = new Square(100, 100, 200); Square sq2 = new Square(300, 300, 50); sq1.draw(w); sq2.draw(w); } } 4. Nedanstående program innehåller tre fel som upptäcks av kompilatorn. Korrigera felen. public class ErrorTest { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600); sq = new Square(100, 200, 50); while sq.getX() < 300 { sq.draw(w); sq.move(10, 10); } } } Övning 3 – beräkningar, klasser och objekt 16 Klasser och objekt 5. Bilden visar minnessituationen (vilka variabler som finns, variablernas värden, vilka objekt som finns, attributens värden) när följande sats har exekverats: Square sq1 = new Square(20, 30, 40); x y 20 side 40 sq1 30 a) Rita en ny bild som visar hur minnessituationen förändras om man dessutom exekverar följande sats: Square sq2 = sq1; b) Ändra bilden i deluppgift a så att den visar minnessituationen om man slutligen exekverar satsen: sq1 = null; 6. Vi brukar för enkelhets skull rita en referens som en pil som pekar på det objekt referensvariabeln refererar till. Egentligen är referensen ett tal, nämligen adressen till den plats i minnet där objektet är lagrat. Ett exempel (ännu ej fullständigt) på hur referensvariablernas värden och objekten lagras i datorns minne visas här: ... 5200 5204 ... 47540 sq1 sq2 ... 47540 20 x 47544 30 y 47548 40 side ... Gör färdigt figuren så att den visar hur det ser ut efter att följande två satser exekverats: Square sq1 = new Square(20, 30, 40); Square sq2 = sq1; Övning 4 – Aritmetik, Logik 17 Övning 4 – Aritmetik, Logik Läs i läroboken: avsnitt 6 (ej 6.5-6.7), 7 (ej 7.8-7.9 samt 7.12) Beräkning av maximum och minimum 1. a) Skriv ett program som från tangentbordet läser ett antal heltal (använd hasNextInt() för att undersöka om det finns fler tal att läsa). I programmet ska du beräkna och skriva ut det största av talen. b) Hur förändras lösningen i deluppgift a om man istället ska söka efter minsta talet? Aritmetiska uttryck 2. Skriv följande som tilldelningssatser i Java: a) y = sin(20◦ ) b) z = ae x + be−cos( x) √ c) hyp = a2 + b2 3. 4. Talet x är av typ double. Hur konverterar man värdet av x till närmaste int-tal? a) Hur kan man med operatorn % avgöra om heltalet n är jämnt? b) Hur kan man med operatorn % avgöra om heltalet n slutar med en nolla? c) Talet number är tresiffrigt. Skriv tilldelningar så att digit1, digit2, digit3 tilldelas värdet av respektive siffra. Använd operatorerna % och /. Summering 5. Skriv ett program som läser 30 temperaturvärden och beräknar och skriver ut medeltemperaturen. 6. Indata består av 101 reella tal. Det första talet kallas limit. Skriv satser som läser talen och beräknar två summor: dels summan av de tal som är mindre än limit, dels summan av de tal som är större än limit. Tal som är lika med limit ska inte summeras. 7. Ett arbete ger lön enligt följande: första dagen är lönen en krona. De följande dagarna får man dubbelt så mycket betalt som man fick närmast föregående dag. Skriv ett program som avgör hur många dagar man måste arbeta innan man har tjänat ihop en miljon kronor. Logiska uttryck 8. Förenkla följande logiska uttryck (a är en int-variabel, ready är en boolean-variabel): a) a > 2 && a > 5 b) a > 2 || a > 5 c) !(a > 2) d) !(a > 2 && a < 9) e) !(a < 0 || a > 10) f) ready == true g) ready == false Övning 5 – Klasser, slumptal 18 Övning 5 – Klasser, slumptal Läs i läroboken: avsnitt 3, 6.10-6.11 1. Implementera färdigt klassen Car: public class Car { private String nbr; private Person owner; // registreringnummer // ägare /** Skapar en bil med registreringsnummer licenseNbr som ägs av personen owner. */ public Car(String licenseNbr, Person owner) { // Lägg till egen kod här. } /** Tar reda på bilens registreringsnummer. */ public String getLicenseNbr() { // Lägg till egen kod här. } /** Tar reda på bilens ägare. */ public Person getOwner() { // Lägg till egen kod här. } /** Sätter bilens ägare till newOwner. */ public void changeOwner(Person newOwner) { // Lägg till egen kod här. } } 2. Rita en figur som visar vilka objekt som finns samt värdet av alla variabler och attribut efter det att följande satser har exekverats. Person p = new Person("Bo Ek"); Car c1 = new Car("ABC123", p); p = new Person("Eva Alm"); Car c2 = new Car("XYZ789", p); Klassen Car finns i föregående uppgift och klassen Person ser ut så här. public class Person { private String name; /** Skapar en person med namnet name. */ public Person(String name) { this.name = name; } /** Tar reda på personens namn. */ public String getName() { return name; } } Övning 5 – Klasser, slumptal 3. 19 I ett system för bokning av platser på tåg förekommer en klass RailwayCoach som beskriver en vagn i tåget. Klassen har följande specifikation: /** Skapar en vagn med nbrSeats platser. */ RailwayCoach(int nbrSeats); /** Tar reda på antalet platser i vagnen. */ int getNbrSeats(); /** Tar reda på antalet lediga (obokade) platser. */ int getNbrFreeSeats(); /** Bokar n platser i vagnen. Det får förutsättas att n <= antalet lediga platser. */ void makeReservation(int n); Implementera klassen fullständigt, dvs deklarera attribut och implementera konstruktorn och operationerna. Observera att man bara är intresserad av antalet lediga platser, inte numren på de platser som är lediga. 4. Klassen Person beskriver personer (klassen har fler operationer, men dem är vi inte intresserade av här): /** Skapar en person med åldern age. */ Person(int age); /** Undersöker om denna person är äldre än personen p. Returnerar då true, annars false. */ boolean isOlderThan(Person p); Implementera klassen. Slumptal 5. Skriv ett program som drar två olika slumptal mellan 1 och 100 och skriver ut dessa med det minsta talet först. Extrauppgift 6. Ett heltal a0 är givet. Man beräknar en talföljd med följande formler: ( a k +1 = ak /2, om ak är jämnt 3ak + 1, annars För alla a0 större än 0 blir ak förr eller senare = 1. (Detta har ingen lyckats bevisa — det kallas Collatz problem — men de flesta tror att det är så.) Exempel: a0 a0 a0 a0 a0 =3 =4 =5 =6 =7 ger 10, 5, 16, 8, 4, 2, 1 (8 tal i talföljden) ger 2, 1 (3 tal) ger 16, 8, 4, 2, 1 (6 tal) ger 3, 10, 5, 16, 8, 4, 2, 1 (9 tal) ger 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1 (17 tal) Skriv ett program som läser in ett a0 och skriver ut den talföljd som bildas med formlerna, till och med den avslutande 1-an. Skriv också ut antalet tal i talföljden. Övning 6 – vektorer och registrering 20 Övning 6 – vektorer och registrering Läs i läroboken: avsnitt 8.1-8.5, 8.8, 8.10 1. Implementera följande metoder. Undersök inte fler tal än nödvändigt. a) /** Hittar minsta talet i vektorn v. */ public static int min(int[] v); b) /** Undersöker om talen i vektorn v är sorterade i växande ordning. */ public static boolean isSorted(int[] v); c) /** Undersöker om vektorerna v1 och v2 är lika, dvs. om de är lika långa och alla v1[i] == v2[i]. */ public static boolean equal(int[] v1, int[] v2); 2. En vektor a innehåller 100 heltal. Vilken utskrift fås av nedanstående satser om int i = 0; while (i < a.length && a[i] != 0) { i++; } System.out.println(i); a) det finns en nolla i vektorn, i vektorelementet a[33]? b) det inte finns någon nolla i vektorn? c) det finns 50 nollor i vektorn, i vektorelementen med udda index dvs. i a[1], a[3], . . . , a[99]? Övning 6 – vektorer och registrering 3. 21 En bostadslägenhet beskrivs av följande klass (det finns fler operationer, men de används inte i denna uppgift): Apartment /** Tar reda på lägenhetens nummer */ int getId(); /** Tar reda på antalet rum i lägenheten */ int getNbrOfRooms(); En kommun har ett register över samtliga lägenheter i kommunen. Registret beskrivs av följande klass: Register /** Skapar ett tomt register med plats för maxSize lägenheter */ Register(int maxSize); /** Lägger in lägenheten a i registret. Det förutsätts att det finns plats och att lägenheten inte redan finns i registret */ void add(Apartment a); /** Tar bort lägenheten med nummer id ur registret. Om lägenheten inte finns ska ingenting inträffa */ void remove(int id); /** Räknar antalet lägenheter med 1, 2, 3, ... rum, returnerar antalen i en vektor (antalet 1-rummare i [0], osv) */ int[] countApartments(); Implementera klassen Register. Vektorn som returneras i countApartments ska vara lika lång som antalet rum i den största lägenheten som finns i registret. Övning 7 – registrering 22 Övning 7 – registrering Läs i läroboken: avsnitt 8, speciellt 8.10 1. Följande klass beskriver en tärning: Die /** Skapar en tärning. */ Die(); /** Kastar tärningen, returnerar antalet prickar (1-6). */ int roll(); I ett statistikexperiment vill man kasta två tärningar och undersöka hur många gånger som summan av prickarna på tärningarna blir 2, 3, 4, . . . , 11, 12. Experimentet beskrivs av följande klass: Experiment /** Skapar ett experiment där tärningarna d1 och d2 kastas. */ Experiment(Die d1, Die d2); /** Kastar tärningarna n gånger, räknar antalet gånger summan blev 2, 3, 4, ..., 11, 12. Returnerar antalen i en vektor. */ int[] makeExperiment(int n); Implementera klassen. 2. Ola Claessons järnhandel är bygdens största leverantör av fasadsiffror. Störst är försäljningen till nybyggda områden där samtliga hus ska ha fasadsiffror. Oftast får de beställningar på formen ”fasadsiffror till samtliga hus på Nygatan 101 till 125, endast udda nummer”. Skriv följande metod som räknar ut hur många siffror av varje sort som behövs. /** Returnerar en vektor med antal olika siffror av varje sort som behövs för nummer mellan start och stop där intervallet mellan numren är interval. */ int[] nbrDigits(int start, int stop, int interval) { Inparametrar till metoden är tre positiva heltal: första nummer, sista nummer och intervallet. Exempel: Följande vektor returneras vid anropet nbrDigits(101, 125, 2): [0][1][2][3][4][5][6][7][8][9] 5 21 3 3 0 3 0 2 0 2 Övning 7 – registrering 3. 23 I ”handelsresandeproblemet” gäller det för en handelsresande att besöka ett antal städer. Varje stad ska besökas exakt en gång. Handelsresanden vill att den totala resvägen ska bli så kort som möjligt. Att lösa detta problem exakt är tidsödande, eftersom man måste beräkna alla möjliga resvägar för att finna den kortaste vägen. Vi nöjer oss därför i denna uppgift med att hitta en approximation till den bästa lösningen genom att i varje steg besöka den stad som ligger närmast den senast besökta staden. Städerna och kartan med alla städer beskrivs av följande klasser: Town /** Skapar en stad med namnet name. */ Town(String name); /** Tar reda på stadens namn. */ String getName(); /** Tar reda på avståndet till staden t. */ double getDistanceTo(Town t); /** Undersöker om denna stad är besökt. */ boolean isVisited(); /** Markera att denna stad är besökt/inte besökt (b = true/false). */ void setVisited(boolean b); // Övriga metoder i klassen public class Map { private Town[] towns; // städerna /** Skapar en karta med städer. Uppgifter om städerna läses in från en fil med namnet mapFile. */ public Map(String mapFile) { // Här läses antalet städer från filen mapFile och vektorn // towns skapas. Därefter läses uppgifter om städerna, // Town-objekt skapas och läggs in i vektorn towns. // Konstruktorn är färdigskriven. } /** Skriver ut namnet på de städer som besöks, i tur och ordning, när man påbörjar en resa i staden med namnet startTown. Det förutsätts att det finns en stad med det namnet. */ public void printTour(String startTown) { // Markera alla städer som obesökta. // Skriv ut startstadens namn, markera den som besökt. // Leta upp närmaste stad som inte är besökt, skriv ut // dess namn, markera den som besökt.Osv. } /* Tar reda på, och returnera, den obesökta stad som ligger närmast staden t. Om inga obesökta städer finns ska null returneras. */ private Town getNearest(Town t); } Implementera metoderna printTour och getNearest i klassen Map. Övning 8 – matriser, String och StringBuilder 24 Övning 8 – matriser, String och StringBuilder Läs i läroboken: avsnitt 8.6,8.7, 6.6, 6.7 och 11 Matriser 1. Klassen Matrix beskriver en kvadratisk matris: public class Matrix { private double[][] a; // talen i matrisen /** Skapar en matris med n rader och n kolonner. */ public Matrix(int n) { a = new double[n][n]; } /** Tar reda på elementet med index i, k. */ public double get(int i, int k) {...} /** Adderar matrisen m till denna matris (matriserna förutsätts vara lika stora). */ public void add(Matrix m) {...} /** Beräknar spåret av matrisen, dvs summan av diagonalelementen från övre vänstra hörnet till nedre högra hörnet. */ public double trace() {...} } Implementera de tre operationerna get, add och trace. Vid addition av matriser adderar man element för element. String och StringBuilder 2. Antag att metoderna equals och compareTo i klassen String inte är tillgängliga. Skriv följande metod: /** Undersöker om strängarna s1 och s2 är lika långa och alla tecknen i s1 är lika med motsvarande tecken i s2. */ public static boolean equals(String s1, String s2); 3. Skriv följande metod: /** Bildar en sträng som innehåller tecknen i s i omvänd ordning. */ public String reverse(String s); Till exempel ska reverse(”asdfg”) ge strängen ”gfdsa”. Använd ett StringBuilderobjekt. Extrauppgift 4. Slumptal kan användas för att kryptera texter. Man behöver en slumptalsgenerator som kan initieras med ett slumptalsfrö så att den kan upprepa följden av slumptal. (Man måste kunna få exakt samma slumptalsföljd när man senare vill dechiffrera texten.) Klassen java.util.Random är en sådan klass — man använder den konstruktor som har ett slumptalsfrö som parameter. Övning 8 – matriser, String och StringBuilder 25 Kryptering av en text går till på följande sätt: • Välj en krypteringsnyckel, ett long-tal key. • Skapa slumptalsgeneratorn med key som slumptalsfrö. • För varje tecken i texten: dra ett slumptal, addera det till tecknet. Vi förutsätter här att teckenkoderna för tecknen ligger i intervallet [0, 256) och att slumptalen ligger i samma intervall. För att också de krypterade tecknen ska hålla sig inom intervallet ska additionen göras ”cykliskt”, dvs efter tecknet med koden 255 anses tecknet med koden 0 komma. Exempel (teckenkoderna har skrivits i decimal form): Tecken: Teckenkod: Slumptal: Krypterad kod: Krypterat tecken: A 65 4 69 E t 116 207 67 C t 116 6 122 z a 97 1 98 b c 99 12 111 o k 107 255 106 j ! 33 8 41 ) Dekryptering av texten görs genom att man initierar slumptalsgeneratorn med samma krypteringsnyckel och subtraherar slumptalen från teckenkoderna. Skriv en metod som krypterar en text plainText utgående från krypteringsnyckeln key. Den krypterade texten ska returneras som resultat. Metoden ska ha följande rubrik: public static String encrypt(String plainText, long key); Övning 9 – ArrayList 26 Övning 9 – ArrayList Läs i läroboken: avsnitt 12.1-12.7 1. Ett ArrayList-objekt cards är deklarerad och skapad enligt följande: ArrayList<Card> cards = new ArrayList<Card>(); // Här läggs ett antal kort in i cards Klassen Card har följande specifikation: Card /** Konstanter för färgern: */ static final int SPADES = ...; static final int HEARTS = SPADES + 1; static final int DIAMONDS = SPADES + 2; static final int CLUBS = SPADES + 3; /** Skapar ett spelkort med färgen suit (SPADES, HEARTS, DIAMONDS, CLUBS) och valören rank (1-13). */ Card(int suit, int rank); /** Tar reda på färgen. */ int getSuit(); /** Tar reda på valören. */ int getRank(); a) Skriv satser för att summera kortens valörer. b) Skriv satser som tar reda på om spader dam finns bland korten. Resultatet av sökningen ska vara att en variabel pos innehåller spader dams position i vektorn (-1 om spader dam ej finns i vektorn). c) Ett nytt kort med valören r och färgen s ska sättas in i listan. Korten i listan ska vara ordnade i stigande valör. Skriv satser som ser till att detta nya kort skapas och hamnar på sin rätta plats i listan med hänsyn till dess valör. 2. I följande klasser beskrivs en telefonkatalog i vilket man kan lagra namn och telefonnummer för ett antal personer: Person /** Skapar en person med namnet name och telefonnumret phoneNbr. */ Person(String name, String phoneNbr); /** Tar reda på personens namn. */ String getName(); /** Tar reda på personens telefonnummer. */ String getPhoneNbr(); Övning 9 – ArrayList PhoneDirectory /** Skapar en tom telefonkatalog. */ PhoneDirectory(); 27 /** Lägger in en person med namnet name och telefonnumret nbr (om personen inte redan finns i katalogen). Om personen lagts in ska true returneras, annars false. */ boolean insert(String name, String nbr); /** Tar bort personen med namnet name ur registret. */ void delete(String name); /** Tar reda på telefonnumret till personen med namnet name. Om personen inte finns i katalogen returneras null. */ String findNbr(String name); /** Returnerar en lista med namnen på de personer vars namn börjar med strängen s. */ ArrayList<String> startsWith(String s); /** Returnerar en sträng som representerar telefonkatalogen. Strängen innehåller personens namn och telefonnummer med radslutstecken mellan de olika personernas uppgifter. Om telefonkatalogen är tom ska en tom sträng returneras. */ String toString(); Implementera klassen PhoneDirectory. Ledning: Det finns fler metoder i klassen String än vad som behandlas i läroboken. (Se dokumentationen av Javas standardklasser på nätet.) I den här uppgiften behöver du använda metoden: boolean startsWith(String prefix); som returnerar true om strängen startar med tecknen i prefix. Övning 10 – arv 28 Övning 10 – arv Läs i läroboken: avsnitt 9.1-9.8 1. Personer, studenter och lärare har ordnats i följande klasshierarki: Person String name Person(String name) Student String program Student(String name, String program) Teacher String department Teacher(String name, String department) a) Implementera klasserna Person och Student. b) Följande tilldelningssatser är självklart korrekta: Person p = new Person("Nils Nilsson"); Student s = new Student("Bo Ek", "D"); Teacher t = new Teacher("Eva Alm", "CS"); Åskådliggör i en bild hur objekten ser ut. c) Satserna i föregående uppgift har utförts. Vilka av följande satser är korrekta, åtminstone så långt kompilatorn kan avgöra det? p s s s = = = = s; p; t; (Student) p; d) Satsen s = (Student) p; kan ge ett fel under exekvering. När inträffar felet? Vad kallas felet? e) Alla klasser har en operation toString() som ska ge en ”läsbar representation” av objektet (läroboken avsnitt 11.6). I klasserna Person och Student har metoden implementerats enligt följande: public class Person { public String toString() { return name; } } public class Student extends Person { public String toString() { return super.toString() + ", " + program; } } Vad betyder super.toString()? Vilken utskrift får man av nedanstående satser? Person p = new Person("Nils Nilsson"); Student s = new Student("Bo Ek", "D"); System.out.println(p.toString()); System.out.println(s.toString()); p = s; System.out.println(p.toString()); Övning 10 – arv 2. 29 En algoritm som man ibland har nytta av är ”partitionering”, vilket innebär att man delar upp element i två grupper: de element som uppfyller ett villkor och de element som inte uppfyller villkoret. I fortsättningen förutsätter vi att elementen är heltal som är lagrade i vektorer och att partitioneringen innebär att elementen flyttas till början eller slutet av vektorn. Exempel: partitionering av vektorn {1, 2, 3, 4, 5, 6} med villkoret ”jämnt tal” ska medföra att vektorn blir {2, 4, 6, 1, 5, 3} (ordningen mellan de tre första talen är godtycklig, liksom mellan de tre sista). När man implementerar algoritmen i Java vill man kunna ”plugga in” olika sorters partitioneringsvillkor i algoritmen. Ett sätt att göra det är att utnyttja följande klass:2 public abstract class Condition { /** Returnerar true om x uppfyller villkoret, false annars */ public abstract boolean evaluate(int x); } För varje specifikt villkor skriver man sedan en subklass till Condition. Till exempel ser en klass för villkoret ”jämnt tal” ut så här: public class EvenCondition extends Condition { public boolean evaluate(int x) { return x % 2 == 0; } } Nu kan man skicka med ett EvenCondition-objekt till algoritmen för att partitionera enligt villkoret jämnt tal och objekt av andra subklasser för att partitionera enligt andra villkor. Implementera partitioneringsalgoritmen i en metod med följande rubrik: /** Flyttar om talen i vektorn v så att de tal som uppfyller villkoret cond hamnar före de tal som inte uppfyller villkoret */ public static void partition(int[] v, Condition cond); Observera att man bara behöver gå igenom vektorn en gång. 2 Detta kan lösas ännu elegantare med ett gränssnitt, ett interface. Mer om detta i fördjupningskursen. Övning 11 – sortering, objekt 30 Övning 11 – sortering, objekt Läs i läroboken: avsnitt 8.9, 3.7 Sortering 1. (Detta är uppgift 8.7 i boken, omformulerad så att vektorn som ska sorteras är parameter.) ”Insättningssortering” är en bra sorteringsmetod om antalet tal som ska sorteras inte är för stort. I metoden går man igenom talen i tur och ordning och sorterar in varje tal på sin rätta plats bland de redan sorterade talen. Exempel, där talföljden består av 7 tal: 13 4 7 5 27 12 2 När man har klarat av de tre första talen och ska sortera in det fjärde talet ser det ut så här: 4 7 13 5 27 12 2 När nu 5-an ska sorteras in ska man dels finna talets rätta plats (till höger om 4-an), dels flytta de tal som ligger till höger om denna plats (talet 7 och talet 13) ett steg åt höger. När detta är klart kan man lägga in 5-an på den lediga platsen. Implementera en metod som sorterar en vektor med insättningssortering. Vektorn och antalet tal i vektorn som ska sorteras ska vara parametrar. Tänk på att metoden också ska klara fallen när det tal som ska sorteras in är mindre än eller större än alla de redan sorterade talen. Objekt 2. Betrakta följande klass: public class A { private int value; public A(int value) { this.value = value; } public void set(int newValue) { value = newValue; } public void print() { System.out.println("value = " + value); } } Vilken utskrift fås när nedanstående main-metod exekveras? Rita aktiveringsposter, förklara resultatet. public class Test { public static void use(A pa) { pa.print(); Övning 11 – sortering, objekt pa.set(10); pa.print(); } public static void main(String[] args) { A a = new A(5); a.print(); use(a); a.print(); } } 3. Vilken utskrift fås av satserna i uppgift 2 om metoden use har följande definition: public void use(A pa) { pa.print(); pa = new A(10); pa.print(); } 31 33 2 Laborationer – anvisningar 1. Obligatorium. Laborationerna är obligatoriska och tillämpar den teori som behandlas under kursen, samt ger viktig träning på att skriva program. Observera att samtliga laborationer och inlämningsuppgift måste vara godkända innan du får tentera! 2. Laborationstyper. Det finns två typer av laborationer: individuella och grupplabbar. a) De individuella laborationerna ska lösas med självständigt enskilt arbete. Det är tillåtet att diskutera laborationerna och dess lösningar med kurskamraterna, men var och en måste skriva sin egen lösning. b) Grupplabbarna löses i samarbetsgrupper. i. Diskutera i din samarbetsgrupp hur ni vill dela upp ansvaret och arbetet för olika delar av koden. Det är lämpligt att varje klass har en huvudansvarig. Flera kan hjälpas åt med samma klass, t.ex. genom att implementera olika metoder. ii. När ni redovisar er lösning ska ni också kunna redogöra för hur ni delat upp ansvar och arbete mellan er. Var och en redovisar sina delar. iii. Grupplaborationer görs i huvudsak som hemuppgift. Salstiden används primärt för redovisning. 3. Förberedelser. Till varje laboration finns förberedelser som du ska göra före laborationen. Ta hjälp av en kamrat, en handledare, eller kursansvarig om det dyker upp några frågor när du förbereder laborationen. Utnyttja resurstiderna! Innan varje laboration skall du ha: a) studerat läroboken enligt läsanvisningarna, b) läst igenom hela laborationen noggrant och gärna påbörjat uppgifterna, c) löst förberedelseuppgifterna. I dessa uppgifter ska du i förekommande fall skriva delar av de program som ingår i laborationen. Det krävs inte att allt du skrivit är helt korrekt, men du måste ha gjort ett rimligt försök. Ta hjälp om du får problem med uppgifterna, men låt inte någon annan lösa uppgiften åt dig. Vi förutsätter att du har förberett dig enligt ovan innan du kommer till laborationen — det är nödvändigt för att labhandledaren ska kunna hjälpa dig på bästa sätt. Om du har förberett dig väl bör du hinna med alla uppgifterna under laborationen. Om du inte gör det så får du göra de resterande uppgifterna på egen hand och redovisa dem vid påföljande laborationstillfälle eller resurstillfälle (och förbereda dig mera till nästa laboration). 4. Sjukanmälan. Om du är sjuk vid något laborationstillfälle så måste du anmäla detta till kursansvarig (bjorn.regnell@cs.lth.se) före laborationen. Om du uteblir utan att ha anmält sjukdom så får du inte göra uppgiften förrän kursen går nästa gång, dvs nästa år, och då får du inte något slutbetyg i kursen i år. Om du varit sjuk bör du göra uppgiften på egen hand och redovisa den vid ett senare tillfälle. Har du varit sjuk och behöver hjälp för att lösa laborationen så gå till något av de resurstillfällen som finns tillgängliga för det program du läser. Det kommer också att anordnas en ”uppsamlingslaboration” i slutet av varje termin. Laboration 1 – Quiz 34 Laboration 1 – Quiz Mål: Under denna laboration lär du dig skriva och ändra i färdiga program som innehåller variabler, tilldelning, alternativ och repetitioner. Du skriver programkoden i en valfri texteditor och kompilerar i ett terminalfönster. Du ändrar i program som läser data från tangentbordet och från filer. Förberedelseuppgifter • Läs i läroboken: avsnitt 1, 3.2 (samt 7.8 för mer om Scanner) • Gör övning 1. • Skapa en textfil med namnet quiz.txt som innehåller frågor på detta format: 4 Vilket är det vanligaste programspråket enligt Tiobe Index Juni 2015? C C++ Java Scala Python 3 Vem skapade den första kompilatorn? Ada Lovelace Linus Torvalds Torgil Ekman Grace Hopper Adele Goldberg 4 Vem skrev det första datorprogrammet? Ada Lovelace Linus Torvalds Grace Hopper Barbara Liskov Adele Goldberg 1 Vem uppfann Java? Barbara Liskov Martin Odersky Adele Goldberg Bjarne Stroustrup James Gosling 5 Krav på textfilens dataformat: – Första raden ska innehålla ett heltal som anger hur många frågor det finns. – Därefter ska det finnas angivet antal frågor, var och en med detta innehåll i denna ordning: ∗ En textrad med en fråga. ∗ Fem textrader med svarsalternativ. ∗ En rad med ett heltal som anger vilket alternativ som är korrekt. Hitta gärna på andra frågor. Frågorna ovan finns här: http://fileadmin.cs.lth.se/cs/Education/EDA016/exercises/quiz.txt Laboration 1 – Quiz 35 Uppgifter 1. Skriv nedanstående program i valfri texteditor och spara filen i lämplig katalog under namnet GuessNumber.java. Försök medan du skriver in koden att lista ut vad som kommer att hända när programmet exekveras. Du behöver inte skriva in kommentarerna efter // och du behöver inte förstå alla detaljer. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.Scanner; import java.util.Random; // Gör Scanner-klassen tillgänglig // Gör Random-klassen tillgänglig public class GuessNumber { public static void main(String[] args){ Scanner scan = new Scanner(System.in); // Skapa ett Scanner-objekt Random rnd = new Random(); // Skapa ett Random-objekt int secret = rnd.nextInt(100) + 1; // Dra slumptal 1 till 100 boolean found = false; while (!found) { System.out.print("Gissa ett tal mellan 1 och 100: "); int guess = scan.nextInt(); // Läs heltal från tangentbordet if (guess > secret) { System.out.println("För stort :("); } else if (guess < secret) { System.out.println("För litet :("); } else { found = true; } } System.out.println("Du gissade rätt :)"); } } 2. Kompilera ditt program i ett terminalfönster med kommandot javac GuessNumber.java och kontrollera med ls att en .class-fil skapats. Om det blir kompileringsfel för att du stavat fel eller glömt något tecken, rätta felet och kompilera om. När kompileringen lyckats, kör ditt program med kommandot java GuessNumber och försök gissa talet med så få gissningar som möjligt. 3. Lägg till en variabel som håller reda på hur många gånger användaren gissar och skriv ut totala antalet gissningar efter att användaren gissat rätt. 4. Inför fel i programmet och studera kompilatorns felmeddelande. Vad blir felmeddelandet (a) om du glömmer deklarera en variabel innan den används, (b) om du adderar 1.0 istället för 1 på rad 8. Tänk ut och prova några fler syntaxfel. Försök förklara begreppen som kompilatorn använder i felutskrifterna. 5. Inför ett logiskt fel i programmet genom att ändra ett av vilkoren i den nästlade if-satsen, så att programmet kompilerar, men gör fel när det kör. Vad händer under exekveringen av programmet? 6. Vad händer om du tar bort ! i while-satsen och även ger variabeln found startvärdet true? Byt namn på den booleska variabeln found till något som passar den nya logiken bättre (till exempel notFound 3 ), och tilldela den false istället när användaren gissat rätt. 3 Namnet continue hade också passat bra, men det råkar vara ett reserverat ord i Java och får inte användas som variabelnamn. 36 Laboration 1 – Quiz 7. Skriv nedan program i valfri texteditor och spara filen i lämplig katalog under namnet Quiz.java och försök medan du skriver in koden att lista ut vad som kommer att hända när programmet exekveras. Du behöver inte förstå alla detaljer – dessa kommer att förklaras under kursens gång. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; public class Quiz { public static void main(String[] args) throws FileNotFoundException { Scanner fileScanner = new Scanner(new File("quiz.txt")); Scanner inScanner = new Scanner(System.in); int nbrOfQuestions = fileScanner.nextInt(); int nbrOfAlternatives = 5; for (int i = 1; i <= nbrOfQuestions; i++) { fileScanner.nextLine(); // hoppa över radbrytning String question = fileScanner.nextLine(); System.out.println(question); for (int j = 1; j <= nbrOfAlternatives; j++) { String alternative = fileScanner.nextLine(); System.out.println(j + ": " + alternative); } System.out.print("Välj 1-" + nbrOfAlternatives + ": "); int correct = fileScanner.nextInt(); int answer = inScanner.nextInt(); if (correct == answer) { System.out.println("RÄTT!"); } else { System.out.println("FEEEEEL!"); } } fileScanner.close(); } } 8. Kompilera programmet med kommandot javac Quiz.java. Kör det med kommandot java Quiz. Se till att filen quiz.txt från labbförberedelserna finns i samma katalog som programmet. 9. Vad händer om du svarar med ett heltal större än 5? 10. Vad händer om du svarar med bokstäver, t.ex. ETT? 11. Vad händer om du svarar med ett decimaltal, t.ex. 1.0 eller 1,0? 12. Inför en variabel som räknar antalet rätt svar och lägg in en utskrift på slutet som visar totala antalet rätt svar. Extrauppgifter 13. Lägg till en utskrift av andelen rätt svar i procent så här långt, efter varje gång användaren svarat. 14. Byt ut == mot = i if-satsen och studera kompilatorns felmeddelande. Försök förklara varför kompilatorn får problem med typerna. Laboration 1 – Quiz Checklista Exempel på vad du ska kunna efter laborationen: • Skriva och editera programkod i main i en texteditor. • Kompilera och köra program i ett terminalfönster. • Förstå skillnaden mellan kompileringsfel och exekveringsfel. • Tolka några felmeddelanden från kompilatorn. • Åtgärda enkla syntaktiska och logiska fel. • Tolka och ändra i enkel kod som innehåller alternativ och repetitioner. • Deklarera variabler och använda heltalsvariabler som räknare. 37 Laboration 2 – Eclipse 38 Laboration 2 – Eclipse Mål: Du lär dig att editera, kompilera och exekvera enkla Java-program med hjälp av programutvecklingsverktyget Eclipse. Du ska också prova att använda debuggern i Eclipse . Förberedelseuppgifter • Läs igenom kapitel 1 i läroboken. • Läs igenom anvisningarna om Eclipse (finns i kurskompendiet). Uppgifter 1. Logga in på datorn. 2. Under laborationerna kommer du att använda en hel del färdigskrivna eller nästan färdigskrivna program. Nu ska du skapa ett Eclipse-arbetsområde (workspace) som innehåller alla dessa filer. 1. Öppna ett terminalfönster. 2. Skriv följande kommandon: wget http://cs.lth.se/eda016/ws unzip ws Det första kommandot laddar ner en komprimerad zip-fil med ditt workspace. Det andra kommandot packar upp filens innehåll. 3. Nu har du fått en katalog ptd-workspace i din hemkatalog. Katalogen innehåller underkatalogerna cs_pt, Lab1, Lab2, Lab3, . . . Kontrollera innehållet i ptd-workspace med följande kommando: ls ptd-workspace I ptd-workspace finns också en katalog .metadata. När du senare startar Eclipse skapas i din hemkatalog en katalog .eclipse. Dessa kataloger syns inte när du gör ls, eftersom deras namn inleds med punkt. Rör inte dessa kataloger — de används av Eclipse för att spara viktig information, och om de inte finns eller har fel innehåll så går det inte att starta Eclipse. 4. Den nedladdade filen behövs inte mera. Tag bort den med följande kommando: rm ws 3. Nu ska du starta Eclipse. Ge följande kommando: eclipse & Efter en stund får du en dialogruta där Eclipse frågar efter vilket arbetsområde som du vill använda. Ändra förslaget workspace till ptd-workspace. &-tecknet betyder att Eclipse ska startas ”i bakgrunden”, alltså som ett fristående program. Det medför att man inte låser terminalfönstret utan kan arbeta med andra saker i det samtidigt som Eclipse kör. Alternativt kan du starta Eclipse genom att välja Eclipse (den senaste versionen) från Gnome-menyn Applications > Programming. I Eclipse-fönstret finns i projektvyn (”Package Explorer”, längst till vänster) projekten cs_pt, Lab2, Lab3, etc. – det är de projektkataloger som finns i ptd-workspace. Filerna med namn som börjar på lab innehåller färdigskrivna program som utnyttjas under laborationerna. cs_pt innehåller en biblioteksfil (.jar-fil) med klasser som utnyttjas. I samma Laboration 2 – Eclipse 39 projekt finns källkoden (.java-filerna) till dessa klasser – de behövs inte för laborationerna, men en del brukar vara intresserade av att titta på dem. 4. Du ska nu ladda in filen Calculator.java i en editor så att du kan ändra den. Man måste klicka en hel del för att öppna en fil: a) Öppna projektet Lab2 genom att klicka på plustecknet (eller pilen) bredvid projektet. b) Öppna katalogen src genom att klicka på plustecknet. c) Öppna paketet (default package) genom att klicka på plustecknet. d) Öppna filen Calculator.java genom att dubbelklicka på filnamnet. Filen öppnas i en editorflik. 5. Kör programmet: markera Calculator.java i projektvyn, högerklicka och välj Run As > Java Application. I konsolfönstret skrivs texten Skriv två tal. Klicka i konsolfönstret, skriv två tal och tryck på return. Observera: när man skriver reella tal ska man vid inläsning använda decimalkomma. När man skriver reella tal i programkod använder man decimalpunkt. Man kan köra det senaste programmet en gång till genom att klicka på Run-ikonen i verktygsraden. 6. Ändra main-metoden i klassen Calculator så att fyra rader skrivs ut: talens summa, skillnad, produkt och kvot. Exempel på utskrift när talen 24 och 10 har lästs in: Summan av talen är 34.0 Skillnaden mellan talen är 14.0 Produkten av talen är 240.0 Kvoten mellan talen är 2.4 Du ska alltså efter utskriften av summan lägga in rader där talens skillnad, produkt och kvot beräknas och skrivs ut. Subtraktion anger man med tecknet -, multiplikation med *, division med /. Under tiden du skriver så kommer du att märka att Eclipse hela tiden kontrollerar så att allt du skrivit är korrekt. Fel markeras med kryss till vänster om raden. Om du håller musmarkören över ett kryss så visas en förklaring av felet. Om man sparar en fil som innehåller fel så visas felmeddelandena också i Problem-fliken längst ner. 7. Spara filen. Filen kompileras automatiskt när den sparas. När .java-filen kompileras skapas en ny fil med samma namn som klassen men med tillägget .class. Denna fil innehåller programmet översatt till byte-kod och används när programmet exekveras. Man ser inte .class-filerna inuti Eclipse, men de lagras i arbetsområdet precis som .java-filerna. .java-filerna finns i en katalog src under respektive projektkatalog, .classfilerna finns i en katalog bin. 8. Kör programmet och kontrollera att utskriften är korrekt. Rätta programmet om den inte är det. 9. Du ska nu använda Eclipse debugger för att följa exekveringen av programmet. Normalt använder man debuggern för att hitta fel i program, men här är avsikten bara att du ska se hur programmet exekverar rad för rad och att du ska bekanta dig med debuggerkommandona. Laboration 2 – Eclipse 40 • Sätt en brytpunkt på den första raden i main-metoden. (Läs avsnittet ”Hitta fel i program” i Eclipse-handledningen om du inte vet hur man gör.) Kör programmet under debuggern (Debug As > Java Application). • Svara ja på frågan om du vill byta till Debug-perspektivet. • Klicka på Step Over-ikonen några gånger. Notera att den rad i programmet som ska exekveras markeras i editorfönstret och att variabler som deklareras dyker upp i variabelvyn till höger. Variablernas aktuella värden visas i ett av eclipse-fönsterna, se till att du ser detta och kan identifiera hur variablerna får sina värden under programmets exekvering. • Sätt en brytpunkt på raden där du skriver kvoten mellan talen. • Klicka på Resume-ikonen för att köra programmet fram till brytpunkten. • Klicka på Resume igen för att köra programmet till slut. Byt tillbaka till Java-perspektivet genom att klicka på knappen Java längst till höger i verktygsfältet. 10. Följande program använder den färdiga klassen SimpleWindow: import se.lth.cs.pt.window.SimpleWindow; public class SimpleWindowExample { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(500, 500, "Drawing Window"); w.moveTo(100, 100); w.lineTo(150, 100); } } Specifikationen av klassen SimpleWindow finns i appendix C i läroboken, eller online via länk från kurshemsidan. Förklaring av de viktigaste delarna av programmet: • På den första raden importeras den färdiga klassen SimpleWindow. • Raderna som börjar med public och de två sista raderna med } är till för att uppfylla Javas krav på hur ett program ska se ut. • På nästa rad deklareras en referensvariabel med namnet w och typen SimpleWindow. Därefter skapas ett SimpleWindow-objekt med storleken 500 × 500 pixlar och med titeln ”Drawing Window”. Referensvariabeln w tilldelas det nya fönsterobjektet. • Sedan flyttas fönstrets ”penna” till punkten 100, 100. • Slutligen ritas en linje. Programmet finns inte i arbetsområdet, utan du ska skriva det från början. Skapa en fil SimpleWindowExample.java: a) Markera projektet Lab2 i projektvyn, b) Klicka på New Java Class-ikonen i verktygsraden. c) Skriv namnet på klassen (SimpleWindowExample). d) Klicka på Finish. Dubbelklicka på SimpleWindowExample.java för att öppna filen i editorn (om detta inte redan gjorts automatiskt). Som du ser har Eclipse redan fyllt i klassnamnet och de parenteser som alltid ska finnas. Komplettera klassen med main-metoden som visas ovan. Spara filen, rätta eventuella fel och spara igen. Provkör programmet. Laboration 2 – Eclipse 41 11. Ändra programmet genom att välja bland metoderna i klassen SimpleWindow. Till exempel kan du rita en kvadrat, ändra färg och linjebredd på pennan, rita fler linjer, skriva text, etc. 12. Öppna filen LineDrawing.java. I filen finns följande kod, komplettera programmet så att det fungerar. Raderna med kommentarer ska ersättas med riktig programkod. public class LineDrawing { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(500, 500, "LineDrawing"); w.moveTo(0, 0); while (true) { // vänta tills användaren klickar på en musknapp // rita en linje till den punkt där användaren klickade } } } Ledtråd: SimpleWindow innehåller också operationer för att ta hand om musklick. Dessa operationer har följande beskrivning: /** Väntar tills användaren har klickat på en musknapp */ void waitForMouseClick(); /** Tar reda på x-koordinaten för musens position vid senaste musklick */ int getMouseX(); /** Tar reda på y-koordinaten för musens position vid senaste musklick */ int getMouseY(); Fundera ut vad som händer i programmet. while (true) betyder att repetitionen ska fortsätta ”i oändlighet”. Man avbryter programmet genom att välja Quit i File-menyn i SimpleWindow-fönstret. Spara filen, rätta eventuella fel och spara då igen. Provkör programmet. Checklista Exempel på vad du ska kunna efter laborationen: • Starta Eclipse, öppna önskat arbetsområde (workspace) och öppna önskat projekt. • Skapa .java-filer och skriva in enkla Java-program. (Med enkla program menas här ett program på några rader som till exempel Calculator.) • Kunna använda operationen System.out.println och förstå vad som händer när den används. • Förstå vad det innebär att läsa in tal från tangentbordet. • Spara .java-filer (kompilering sker då automatiskt). • Exekvera program. • Använda debuggern: – Sätta och ta bort brytpunkt. Laboration 2 – Eclipse 42 – Starta debuggern. – Köra fram till en brytpunkt med Resume. – Exekvera stegvis med Step over (och senare i kursen med Step Into). – Byta mellan debug-perspektivet och Java-perspektivet. Laboration 3 – använda färdigskrivna klasser, kvadrat 43 Laboration 3 – använda färdigskrivna klasser, kvadrat Mål: Du ska lära dig att läsa specifikationer av klasser och att utnyttja färdigskrivna klasser för att lösa enkla uppgifter. Du ska också lära dig lite mera om Eclipse. Förberedelser • • • • • Studera avsnitt 2 och 3.1-3.2 i läroboken. Läs avsnittet Bakgrund. Lös uppgift 2.2 och 2.5 i läroboken. Läs 7.3, 7.5 och 7.6 för att repetera loopar och if-satser. Tänk igenom följande frågor och se till att du kan svara på dem: 1. 2. 3. 4. 5. Vad är en main-metod? Hur ser en klass med en main-metoden ut i stora drag? Vad är referensvariabler och vad har man dem till? Hur skapar man objekt? Hur utför man en operation på ett objekt? Vad använder man parametrar till? Bakgrund En klass Square som beskriver kvadrater har nedanstående specifikation. /** Skapar en kvadrat med övre, vänstra hörnet i x,y och med sidlängden side. */ Square(int x, int y, int side); /** Ritar kvadraten i fönstret w. */ void draw(SimpleWindow w); /** Raderar bilden av kvadraten i fönstret w. */ void erase(SimpleWindow w); /** Flyttar kvadraten avståndet dx i x-led, dy i y-led. */ void move(int dx, int dy); /** Tar reda på x-koordinaten för kvadratens läge. */ int getX(); /** Tar reda på y-koordinaten för kvadratens läge. */ int getY(); /** Tar reda på kvadratens area. */ int getArea(); Användning av klassen Square I nedanstående program skapas först ett ritfönster. Därefter skapas en kvadrat som placeras mitt i fönstret och ritas upp. import se.lth.cs.pt.window.SimpleWindow; import se.lth.cs.pt.square.Square; public class DrawSquare { Laboration 3 – använda färdigskrivna klasser, kvadrat 44 public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600, "DrawSquare"); Square sq = new Square(250, 250, 100); sq.draw(w); } } • På rad 1 och rad 2 importeras de klasser som behövs, i detta fall: klassen SimpleWindow och klassen Square. De klasserna finns inte i Javas standardbibliotek utan har skrivits speciellt för kurserna i programmeringsteknik. Klasserna finns i ”paket” vars namn börjar med se.lth.cs; det betyder att de är utvecklade vid institutionen för datavetenskap vid LTH, som använder domännamnet cs.lth.se. • På rad 5 skapas ett SimpleWindow-objekt. Refererensvariabeln w refererar till detta objekt. • Därefter skapas ett Square-objekt som referensvariabeln sq refererar till. • Slutligen ritas kvadraten sq. Notera parametrarna som man använder när man skapar objekten. I new-uttrycket som skapar kvadratobjektet står det till exempel (250, 250, 100). Det betyder att kvadraten ska ha läget 250, 250 och sidlängden 100. Läget och sidlängden kan man ändra senare i programmet, med sq.move(dx,dy) och sq.setSide(newSide). Lägg också märke till att referensvariablerna w och sq har olika typer. En referensvariabels typ avgör vilka slags objekt variabeln får referera till. w får referera till SimpleWindow-objekt och sq får referera till Square-objekt. Användning av klassen SimpleWindow Bläddra fram källkoden till klassen Square. Du hittar klassen i Eclipse-projektet cs_pt under katalogen src och i paketet se.lth.cs.pt.square. Titta på källkoden och försök förstå vad som händer. Några av operationerna i SimpleWindow (operationerna för att flytta pennan och för att rita linjer) utnyttjas i operationen draw i klassen Square. SimpleWindow innehåller också operationer, som inte används i Square, t.ex. för att ta hand om musklick. Dessa operationer har följande beskrivning: /** Väntar tills användaren har klickat på en musknapp. */ void waitForMouseClick(); /** Tar reda på x-koordinaten för musens position vid senaste musklick. */ int getMouseX(); /** Tar reda på y-koordinaten för musens position vid senaste musklick. */ int getMouseY(); När exekveringen av ett program kommer fram till waitForMouseClick så ”stannar” programmet och fortsätter inte förrän man klickat med musen någonstans i programmets fönster. Ett program där man skapar ett fönster och skriver ut koordinaterna för varje punkt som användaren klickar på har följande utseende: import se.lth.cs.pt.window.SimpleWindow; public class PrintClicks { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600, "PrintClicks"); Laboration 3 – använda färdigskrivna klasser, kvadrat 45 while (true) { w.waitForMouseClick(); w.moveTo(w.getMouseX(), w.getMouseY()); w.writeText("x = " + w.getMouseX() + ", " + "y = " + w.getMouseY()); } } } while (true) betyder att repetitionen ska fortsätta ”i oändlighet”. Man avbryter programmet genom att välja Quit i File-menyn i SimpleWindow-fönstret. Också i nedanstående program ska användaren klicka på olika ställen i fönstret. Nu är det inte koordinaterna för punkten som användaren klickar på som skrivs ut, utan i stället avståndet mellan punkten och den förra punkten som användaren klickade på. Vi sparar hela tiden koordinaterna för den förra punkten (variablerna oldX och oldY). Gå igenom ett exempel ”för hand” och övertyga dig om att du förstår hur programmet fungerar. import se.lth.cs.pt.window.SimpleWindow; public class PrintClickDists { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600, "PrintClickDists"); int oldX = 0; // x-koordinaten för "förra punkten" int oldY = 0; // y-koordinaten while (true) { w.waitForMouseClick(); int x = w.getMouseX(); int y = w.getMouseY(); w.moveTo(x, y); int xDist = x - oldX; int yDist = y - oldY; w.writeText("Avstånd: " + Math.sqrt(xDist * xDist + yDist * yDist)); oldX = x; oldY = y; } } } Datorarbete 1. Logga in på datorn, starta Eclipse, öppna projektet lab3. (Logga in, starta Eclipse och öppna ett projekt ska du alltid göra, så det skriver vi inte ut i fortsättningen.) 2. Öppna en webbläsare och gå till kursens hemsida, cs.lth.se/eda016 . Under Dokumentation finns en länk till dokumentationen av de färdigskrivna klasser som används under laborationerna, alltså klasserna i paketet se.lth.cs.pt. Leta upp och titta igenom specifikationen av klassen Square. Det är samma specifikation som finns under Bakgrund, fast i något annorlunda form. 3. Klassen DrawSquare finns i filen DrawSquare.java. Öppna filen, kör programmet. 4. Kopiera filen DrawSquare.java till en ny fil med namnet DrawThreeSquares.java. Enklast är att göra så här: 1. Markera filen i projektvyn, högerklicka, välj Copy. 2. Högerklicka på (default package), välj Paste, skriv det nya namnet på filen. Laboration 3 – använda färdigskrivna klasser, kvadrat 46 Notera att Eclipse ändrar klassnamnet i den nya filen till DrawThreeSquares. Ändra sedan klassen så att kvadraten ritas tre gånger. Mellan uppritningarna ska kvadraten flyttas. Du ska fortfarande bara skapa ett kvadratobjekt i programmet. Resultatet ska bli en figur med ungefär följande utseende: Testa programmet, rätta eventuella fel. 5. Någonstans i ditt program skapar du ett kvadratobjekt med en sats som har ungefär följande utseende: Square sq = new Square(300,300,200). Tag bort denna sats från programmet (eller kommentera bort den). Eclipse kommer att markera flera fel i programmet, eftersom sq inte är deklarerad och du senare i programmet utnyttjar den variabeln. Läs och tolka felmeddelandena. Lägg sedan in satsen Square sq = null i början av programmet. Eclipse kommer inte att hitta några fel, eftersom programmet nu följer de formella reglerna för Javaprogram. Exekvera sedan programmet och se vad som inträffar. Studera felmeddelandet så att du kan tolka det. Meddelanden om exekveringsfel skrivs i konsolfönstret. Det skrivs också ut en ”stack trace” för felet: var felet inträffade, vilken metod som anropade metoden där det blev fel, vilken metod som anropade den metoden, osv. Om man klickar på ett radnummer öppnas rätt fil i editorn med den raden markerad (under förutsättning att programtexten, ”källkoden”, för den filen är tillgänglig). Lägg sedan tillbaka den ursprungliga satsen för att skapa kvadratobjektet. Ändra parametern w i det första anropet av draw till null. Kör programmet, studera det felmeddelande som du får. När man får exekveringsfel kan det vara svårt att hitta orsaken till felet. Här är en debugger till stor hjälp, i och med att man kan sätta brytpunkter och köra programmet stegvis. 6. Kör programmen PrintClicks och PrintClickDists. 7. Skriv ett program där ett kvadratobjekt skapas och ritas upp i ett ritfönster. När användaren klickar med musen i fönstret ska bilden av kvadraten raderas, kvadraten flyttas till markörens position och ritas upp på nytt. Titta gärna på den totala specifikationen av SimpleWindow och Square som finns länkat från kurshemsidan. Vilka metoder som verkar användbara för detta kan du hitta i dokumentationen? Välj ett lämpligt namn på klassen. För att skapa en fil för klassen kan du antingen skapa en tom klass (klicka på New Java Class-ikonen, skriv namnet på klassen, klicka på Finish) eller kopiera någon av de filer som du redan har. Testa programmet. 8. Modifiera programmet i uppgift 7 så att varje flyttning går till så att kvadraten flyttas stegvis till den nya positionen. Efter varje steg ska kvadraten ritas upp (utan att den Laboration 3 – använda färdigskrivna klasser, kvadrat 47 gamla bilden raderas). Exempel på kvadratbilder som ritas upp när flyttningen görs i 10 steg: klickat här ursprunglig position Kvadratens slutliga position behöver inte bli exakt den position som man klickat på. Om man till exempel ska flytta kvadraten 94 pixlar i 10 steg är det acceptabelt att ta 10 steg med längden 9. Skriv in och testkör programmet. 9. Observera att programmet från uppgift 8 ritar många bilder av samma kvadrat och att alla bilderna kommer att synas i fönstret när programmet är slut. Om man raderar den ”gamla” bilden innan man ritar en ny bild så kommer man att få en ”rörlig”, ”animerad”, bild. För att man ska se vad som händer måste man då göra en paus mellan uppritning och radering — det gör man med SimpleWindow-metoden delay, som har en parameter som anger hur många millisekunder man ska vänta. Exempel: while (sq.getSide() > 0) { sq.draw(w); SimpleWindow.delay(10); sq.erase(w); sq.setSide(sq.getSide() - 10); } Kodsnutten ovan animerar en ny mindre kvadrat tills dess att kvadratens sida blivit mindre än eller lika med noll. Kopiera koden från uppgift 8 till en ny fil AnimatedSquare.java. Lägg in fördröjning och radering så att kvadratbilden blir ”animerad”. Anmärkning: i ett ”riktigt” program som visar rörliga bilder åstadkommer man inte animeringen på det här sättet. Denna lösning har bristen att man inte kan göra något annat under tiden som animeringen pågår, till exempel kan programmet inte reagera på att man klickar med musen. 10. I denna uppgift ska du lära dig fler kommandon i Eclipse. Det finns ett otal kommandon, mer eller mindre avancerade, och många av dem använder man sällan. Två kommandon som man ofta använder: • Source-menyn > Format. Korrigerar programlayouten i hela filen. Ser till exempel till att varje sats skrivs på en rad, att det är blanka runt om operatorer, och så vidare. Man bör formatera sina program regelbundet, så att de blir läsbara. Observera att programmet ska vara korrekt formaterat när du visar det för labhandledaren för att få det godkänt. Notera också kortkommandot som står intill menyalternativet. Prova att använda kortkommandot också, och lär dig gärna det, då det är snabbare och mer bekvämt än att klicka. Laboration 3 – använda färdigskrivna klasser, kvadrat 48 • Refactor-menyn > Rename. Ändrar namn på den variabel eller metod som markerats (lokalt om det är en lokal variabel, i hela klassen om det är ett attribut, även i andra klasser om det är en publik metod). 11. Kontrollera med hjälp av checklistan nedan att du behärskar de olika momenten i laborationen. Diskutera med övningsledaren om någonting är oklart. Checklista Exempel på vad du ska kunna efter laborationen: • Tyda specifikationer av klasser. • Skriva program som använder färdiga klasser: – Deklarera referensvariabler och skapa objekt. – Utföra operationer på objekt. Laboration 4 – implementera klasser (Square), samt felsökning 49 Laboration 4 – implementera klasser (Square), samt felsökning Mål: Du ska träna på att implementera klasser och leta fel. Förberedelseuppgifter • Studera kap 3 i läroboken noggrant. • Läs igenom texten i avsnittet Bakgrund. Bakgrund Klassen Point beskriver en punkt. Den har följande specifikation: /** Skapar en punkt med koordinaterna x,y. */ Point(int x, int y); /** Tar reda på x-koordinaten. */ int getX(); /** Tar reda på y-koordinaten. */ int getY(); /** Flyttar punkten avståndet dx i x-led, dy i y-led. */ void move(int dx, int dy); /** Returnerar avståndet mellan denna punkt och punkten p. */ double distanceTo(Point p); /** Returnerar en teckensträng som representerar punkten. Strängen innehåller punktens koordinater. Ex: 150 200 */ String toString(); Specifikationen är till för den som ska använda klassen när den är färdig. Här ser man hur man skapar Point-objekt och vilka metoder man kan anropa på ett sådant. String är en typ (egentligen en klass) som används för att beskriva en teckensträng, dvs. en följd av tecken. Se avsnitt 6.7 i läroboken. Implementeringen av klassen Point ser ut så här: public class Point { private int x; // punktens x-koordinat private int y; // punktens y-koordinat /** Skapar en punkt med koordinaterna x,y. */ public Point(int x, int y) { this.x = x; this.y = y; } /** Tar reda på x-koordinaten. */ public int getX() { return x; } /** Tar reda på y-koordinaten. */ public int getY() { return y; Laboration 4 – implementera klasser (Square), samt felsökning 50 } /** Flyttar public void x = x + y = y + } punkten avståndet dx i x-led, dy i y-led. */ move(int dx, int dy) { dx; dy; /** Returnerar avståndet mellan denna punkt och punkten p. */ public double distanceTo(Point p) { return Math.hypot(x - p.x, y - p.y); } /** Returnerar en teckensträng som representerar punkten. Strängen innehåller punktens koordinater. Ex: 150 200 */ public String toString() { return x + " " + y; } } Observera skillnaden mellan specifikationen (beskrivningen) av klassen och implementeringen (programkoden). Implementeringen av klassen Point ska finnas i en egen fil med namnet Point.java. Den innehåller den fullständiga programkoden för klassen. Här deklareras attribut och här finns de satser som utförs när en metod anropas. Studera klassen ordentligt och försäkra dig att du förstår alla detaljer. Tänk igenom följande frågor och se till att du kan svara på dem: 1. 2. 3. 4. 5. Vilka attribut finns i klassen Point? Vad händer inuti konstruktorn? Vad menas med public och private? Varför står det void framför vissa metodnamn? Vad innebär return? Inuti metoden distanceTo måste man komma åt den andra punktens x- och y-koordinat för att kunna beräkna avståndet till den. Trots att attributen är privata kan man skriva p.x och p.y för att nå dessa koordinater. Det beror på att ett attribut som är deklarerat private är privat för klassen och inte bara för ett speciellt objekt. Men man kan även nå den andra punktens koordinater på ett annat sätt. Hur? Felsökning Fel i datorprogram kan yttra sig på två sätt: som kompileringsfel eller som exekveringsfel. Att finna och åtgärda sådana fel är en viktig del av programmeringsarbetet. Kompileringsfel Vid kompileringsfel markerar Eclipse den eller de felaktiga raderna med kryss till vänster i editorfönstret. Om man håller musmarkören över ett sådant kryss så visas en förklaring av felet. Man kan också se en lista med samtliga fel, genom att i menyn välja Window → Show View → Problems: då listas felen längst ner i Eclipse-fönstret, där konsollutskrifter annars visas. Kompileringsfel innebär att programmet inte kan kompileras och därför inte heller exekveras. Kompileringsfelen måste alltså åtgärdas. Förutom kompileringsfel kan kompilatorn också visa varningar: dessa betyder att programmet visserligen kan kompileras, men ändå innehåller något som verkar konstigt. Exempelvis Laboration 4 – implementera klasser (Square), samt felsökning 51 varnar kompilatorn om man deklarerar en variabel utan att använda den. Det brukar löna sig att uppmärksamma varningar – de tyder ofta på att man gjort misstag. Exekveringsfel Exekveringsfel är fel som uppstår vid exekvering, det vill säga när man kör programmet. Om programmet under körning bryter mot Javas regler – exempelvis dividerar ett heltal med noll – så avbryts programmet och ett felmeddelande skrivs ut. Då skrivs också en anropskedja ut, där man kan se dels i vilken metod felet uppstod, dels varifrån den metoden i sin tur anropades. I anropskedjan visas radnummer, och när man klickar på ett sådant radnummer öppnas rätt fil i Eclipse (under förutsättning att programtexten, källkoden, för filen är tillgänglig). Till exekveringsfelen räknas även logiska fel. Ett program kan ge felaktigt resultat även om det följer Javas regler. Om man som programmerare exempelvis skrivit + istället för -, eller använder fel villkor i en if-sats, kommer programmet ju inte att fungera som förväntat. Felsökning Som framgått kan kompilatorn inte avgöra om programmet gör det man själv vill, och även om man tänkt igenom sitt program visar sig vissa fel först då programmet körs. För att felsöka i programmet behöver man alltså provköra det. Felsökning är ett detektivarbete. Genom att pröva olika kombinationer av indata och studera programmets resultat kan man förstå mer om hur felet yttrar sig. Utifrån denna förståelse får man idéer om var i programmet felet kan finnas. När man sedan vill undersöka en viss del av programmet är debuggern till stor hjälp – särskilt möjligheten att sätta brytpunkter, studera variablernas värden och köra programmet stegvis. I denna uppgift ska du få bekanta dig med sådant felsökningsarbete. Datorarbete Denna laboration har två delar: (1) Du ska utgå från en färdig klass och ändra implementationen, samt (2) du ska träna på felsökning. Filen Point.java används i del 1, medan filerna BuggySquareDrawer.java och BuggySquare.java används i del 2. Del 1: Ändra implementation av klassen Square 1. Först ska vi ordna så att du får en egen Square.java, som du kan ändra i. • Kopiera filen Square.java från projektet cs_pt till detta projekt (Lab4). Du finner filen i projektet cs_pt under src/se.lth.cs.pt.square. • Kopiera även filen DrawThreeSquares.java från Lab3 till Lab4. Filen innehåller en main-metod som använder klassen Square. • Din ”nya”, kopierade, DrawThreeSquares.java ska nu använda den version av Square.java som du just kopierat till ditt projekt (Lab4). Detta gör du genom att i filen DrawThreeSquares.java ta bort raden som importerar klassen Square. Nu kommer Java söka efter klassen Square i samma katalog som du lagt DrawThreeSquares.java, och eftersom Square nu finns där behöver den alltså inte importeras. • Kontrollera att inga fel återstår i ditt projekt innan du går vidare till nästa steg. 2. Ett objekts tillstånd kan representeras på olika sätt och ändras utan att det påverkar specifikationen eller de andra klasser i programmet som utnyttjar klassen. Inuti klassen Square finns det två heltalsattribut x och y som håller reda på kvadratens position. Man skulle lika gärna kunna använda en punkt för detta. Byt ut attributen x och y mot ett attribut av Laboration 4 – implementera klasser (Square), samt felsökning 52 typen Point. Gör de ändringar som krävs i resten av klassen. Kör programmet DrawThreeSquares för att kontrollera att klassen fortfarande fungerar. OBS: Koden i DrawThreeSquares ska inte behöva ändras för att få programmet i att fungera. Klassen Square ska alltså bara ändras internt, anropen till den ska inte förändras alls. 3. Lägg till följande metoder i klassen Square: /** Returnerar true om denna kvadrat innesluter punkten (x,y). */ boolean contains(int x, int y); /** Returnerar true om denna kvadrat innesluter punkten p. */ boolean contains(Point p); /** Returnerar true om denna kvadrat innesluter alla punkter i kvadraten sq. */ boolean contains(Square sq); Ledning: Börja med den första contains-metoden, därefter den andra. För den tredje metoden kan det underlätta att rita ett par exempel, och utifrån dessa formulera en lösning på problemet. Sedan uttrycker du din lösning i Java i contains(Square sq). 4. Skriv en klass med en main-metod där du skapar och ritar några Square-objekt och på något sätt använder metoden contains(Square sq) för att testa om metoden är korrekt eller ej. Tänk på att dina tester ska vara tillräckligt omfattande för att övertyga din labbhandledare om att metoden fungerar. Kör programmet och kontrollera att allt fungerar som det ska. Använd gärna debuggern för att hitta eventuella fel. Del 2: Felsökning 5. I filen BuggySquareDrawer.java finns ett litet program som är tänkt att rita upp kvadrater i ett SimpleWindow. Programmet använder BuggySquare.java. Användaren får mata in värden för kvadratens sida och koordinater. När kvadraten ritats i fönstret får användaren möjlighet att mata in uppgifter om en ny kvadrat. Programmet ska dessutom kontrollera om den begärda kvadraten får plats inuti fönstret: om kvadraten inte får plats ska den inte ritas upp. I main-metoden i BuggySquareDrawer.java har en rad kommenterats bort. Ta bort kommentartecknen så att main-metoden ser ut så här: public static void main(String[] args) { runApplication(); } Din uppgift är nu att hitta samtliga fel i BuggySquareDrawer.java och BuggySquare.java och ändra så att allt fungerar som förväntat. Tips: Först måste du åtgärda ett kompileringsfel för att alls kunna köra programmet. Därefter måste du köra och testa programmet noggrant för att hitta alla fel. Det är svårt att ange det exakta antalet fel eftersom det beror på hur man räknar, men totalt krävs ändringar på fyra olika rader i programmet och ett tillägg, i form av en rad som saknas helt, för att få det att fungera. Laboration 4 – implementera klasser (Square), samt felsökning 53 En del av utmaningen är att testa tillräckligt mycket för att inse vad som är fel. Detta är ofta svårare än att hitta det egentliga felet. Du kan inleda din undersökning med att exempelvis testa fallet sida = 10, x = 100, y = 50. Redovisning För att bli godkänd på uppgiften ska du kunna visa upp ett program som fungerar enligt ovan. Därtill ska du vara beredd att berätta om ditt felsökningsarbete. Fyll därför i information om varje fel i tabellen nedan (kategorisera dem utifrån beskrivningen av olika slags fel ovan). Skriv ner dina resultat (exempelvis i den följande tabellen) så att du minns dem till redovisningen. Fel (vilken klass, vilken rad?) Beskriv: Vad var problemet? Typ av fel Hur upptäcktes felet? Kontrollera med hjälp av checklistan nedan att du behärskar de olika momenten i laborationen. Diskutera med övningsledaren om någonting är oklart. Checklista Exempel på vad du ska kunna efter laborationen: • Förstå skillnaden mellan specifikation och implementation. • Implementera en klass: Laboration 4 – implementera klasser (Square), samt felsökning 54 – attribut – konstruktor – void-metoder och funktioner • Tyda felutskrifter vid kompileringsfel och rätta felen. • Tyda programmets resultat vid exekveringsfel och rätta felen. • Förstå hur logiska uttryck kan användas för att jämföra två objekt. • Kunna anropa metoder från andra metoder, och kunna använda metoder som returnerar boolska resultat (typen boolean). Laboration 5 – implementera klasser, gissa tal 55 Laboration 5 – implementera klasser, gissa tal Mål: Du ska fortsätta på att träna att implementera klasser. Du ska också få mer träning i att använda if-, for- och while-satser. Förberedelseuppgifter • Repetera kap 2 och 3 i läroboken. • Läs kapitel 4 i läroboken. • Läs igenom texten i avsnittet Bakgrund. Bakgrund Under den här laborationen ska du skriva ett program för spelet ”Gissa talet”. Spelet går till så att datorn ”tänker” på ett tal inom ett visst intervall. Användaren gissar på ett tal och får reda på om det är för litet, för stort eller korrekt. Spelet fortsätter tills användaren gissat rätt tal. Klassen NumberToGuess beskriver objekt som kan välja ett slumptal i ett visst intervall och hålla reda på detta. /** Skapar ett objekt med ett slumpmässigt valt heltal i intervallet [min, max]. */ NumberToGuess(int min, int max); /** Tar reda på minsta möjliga värde talet kan ha. */ int getMin(); /** Tar reda på största möjliga värde talet kan ha. */ int getMax(); /** Tar reda på om talet är lika med guess. */ boolean isEqual(int guess); /** Tar reda på om talet är större än guess. */ boolean isBiggerThan(int guess); Förutom denna klass ska du skriva en klass med en main-metod som utför själva spelet. Datorarbete 1. Öppna projektet Lab5. Där finns en fil NumberToGuess.java med ett ”skelett” (en klass utan attribut och med tomma metoder) till klassen NumberToGuess. Implementera klassen NumberToGuess: Tänk ut vilka attribut som behövs och skriv in dem i klassen. Skriv gärna en kommentar till varje attribut som förklarar vad attributet betyder. Skriv också konstruktorn och se till att alla attribut får rätt startvärden. Implementera de övriga operationerna. Ett av attributen ska tilldelas ett slumpmässigt valt värde. Användning av slumptal beskrivs i läroboken, avsnitt 6.10. För tillfället behöver du bara veta att man måste skapa ett Random-objekt och att man får ett nytt slumpmässigt heltal i intervallet [0, n) med funktionen nextInt(n). Skrivsättet [0, n) betyder att 0 ingår i intervallet, n ingår inte i intervallet. rand.nextInt(100) ger alltså ett slumpmässigt heltal mellan 0 och 99. 2. I projektet Lab5 finns det ett program TestNumberToGuess som är tänkt att användas för att testa att metoderna i klassen NumberToGuess ger rätt resultat. Studera gärna pro- Laboration 5 – implementera klasser, gissa tal 56 grammet om du vill. Kör programmet. Utskrifterna från programmet ger besked om allt fungerar som det ska eller ej. Rätta eventuella fel och provkör igen. 3. Skriv programmet som utför en spelomgång där användaren får gissa på ett tal upprepade gånger tills rätt tal gissats. Programmet ska börja med att fråga användaren vilket min- och max-värde som ska gälla, varpå dessa läses in med Scannern. Därefter ska användarens gissningar läsas in. Efter varje gissning ska användaren få besked om ifall det gissade talet var för litet, för stort eller korrekt. Sist i programmet ska antal gissningar som krävdes skrivas ut. Testa programmet. 4. (Frivillig uppgift) Det finns olika mer eller mindre smarta sätt att gissa talet. I den här uppgiften ska du skriva ett program som ska ta reda på hur många gissningar som krävs om man använder en optimal strategi. I programmet ska du använda klassen OptimalGuesser (se nedan). Genom att anropa metoden nbrGuesses ett stort antal gånger och ta reda på det maximala antal gissningar som krävdes kan man uppskatta antal gissningar som krävs för ett visst intervall. /** Beskriver en spelare som gissar tal på ett optimalt sätt. */ OptimalGuesser(); /** Gissar tal på ett optimalt sätt tills rätt tal gissats. nbr är det objekt som håller reda på det tal som ska gissas. Returnerar antal gissningar som krävdes. */ int nbrGuesses(NumberToGuess nbr); Implementera klassen OptimalGuesser. Börja med att fundera ut vilken strategi man ska använda för att behöva så få gissningar som möjligt. Diskutera med din labhandledare ifall du är osäker på vilken strategi du ska använda inuti metoden nbrGuesses. 5. (Frivillig uppgift.) Skriv ett program som anropar metoden nbrGuesses ett stort antal gånger och tar reda på största antalet gissningar som krävdes. Ledning: För att beräkna maximala antalet gissningar behöver du en lokal variabel som håller reda på hittills största värde. Kontrollera efter varje anrop av nbrGuesses om denna variabel behöver uppdateras. Att beräkna maximum (eller minimum) är ett vanligt problem (behandlas i läroboken, avsnitt 7.13). Här är en generell algoritmen för att beräkna maximum: max = "litet värde"; för alla värden value = "nästa värde"; if (value > max) { max = value; } Checklista I den här laborationen har du övat på att • skriva ett program med flera egna klasser, • generera slumptal inom ett givet intervall, och • använda if- och while-villkor för att styra exekveringen av program. Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle 57 Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle Mål: Du ska fortsätta att träna på att implementera och använda klasser. Du ska också få mer träning i att använda Eclipse debugger. Du ska även skapa en klass som ärver en annan klass. Förberedelseuppgifter • Repetera kap 3 i läroboken. • Läs kapitel 9.1 – 9.4. • Tänk igenom följande frågor och se till att du kan svara på dem: 1. Vad är en specifikation respektive en implementering av en klass? 2. Vad är ett attribut? Var deklareras attributen? När reserveras det plats för dem i datorns minne och hur länge finns de kvar? 3. När exekveras satserna i konstruktorn? Vad brukar utföras i konstruktorn? 4. Vilka likheter/skillnader finns det mellan attribut, lokala variabler och formella parametrar. 5. Vad menas med public och private? Bakgrund I grafiksystemet Turtle graphics kan man rita linjer. Linjerna ritas av en (tänkt) sköldpadda som går omkring i ett ritfönster. Sköldpaddan har en penna som antingen kan vara lyft (då ritas ingen linje då sköldpaddan går) eller sänkt (då ritas en linje). Sköldpaddan kan bara gå rakt framåt, i den riktning som huvudet pekar. När sköldpaddan står stilla kan den vrida sig så att dess huvud pekar åt olika håll. En klass Turtle som beskriver en sköldpadda av detta slag har följande specifikation: /** Skapar en sköldpadda som ritar i ritfönstret w. Från början befinner sig sköldpaddan i punkten x,y med pennan lyft och huvudet pekande rakt uppåt i fönstret (i negativ y-riktning). */ Turtle(SimpleWindow w, int x, int y); /** Sänker pennan. */ void penDown(); /** Lyfter pennan. */ void penUp(); /** Går rakt framåt n pixlar i den riktning som huvudet pekar. */ void forward(int n); /** Vrider beta grader åt vänster runt pennan. */ void left(int beta); /** Går till punkten newX,newY utan att rita. Pennans läge (sänkt eller lyft) och huvudets riktning påverkas inte. */ void jumpTo(int newX, int newY); /** Återställer huvudriktningen till den ursprungliga. */ void turnNorth(); /** Tar reda på x-koordinaten för sköldpaddans aktuella position. */ int getX(); 58 Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle /** Tar reda på y-koordinaten för sköldpaddans aktuella position. */ int getY(); /** Tar reda på sköldpaddans riktning, i grader från positiv x-led. */ int getDirection(); Observera att vi här bestämmer vilket fönster som sköldpaddan ska rita i när vi skapar ett sköldpaddsobjekt. Det kan väl sägas motsvara verkligheten: en sköldpadda ”befinner” sig ju alltid någonstans. I följande program ritar en sköldpadda en kvadrat med sidorna parallella med axlarna: import se.lth.cs.pt.window.SimpleWindow; public class TurtleDrawSquare { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600, "TurtleDrawSquare"); Turtle t = new Turtle(w, 300, 300); t.penDown(); for (int i = 0; i < 4; i++) { t.forward(100); t.left(90); } } } I nedanstående variant av programmet har vi gjort två ändringar: 1) längden på sköldpaddans steg väljs slumpmässigt mellan 0 och 99 pixlar, 2) efter varje steg görs en paus på 100 millisekunder. Den första ändringen medför att figuren som ritas inte blir en kvadrat, den andra ändringen medför att man ser hur varje linje ritas. import se.lth.cs.pt.window.SimpleWindow; import java.util.Random; public class TurtleDrawRandomFigure { public static void main(String[] args) { Random rand = new Random(); SimpleWindow w = new SimpleWindow(600, 600, "TurtleDrawRandomFigure"); Turtle t = new Turtle(w, 300, 300); turtle.penDown(); for (int i = 0; i < 4; i++) { t.forward(rand.nextInt(100)); SimpleWindow.delay(100); t.left(90); } } } Användning av slumptal beskrivs i läroboken, avsnitt 6.10. För tillfället behöver du bara veta att man måste skapa ett Random-objekt och att man får ett nytt slumpmässigt heltal i intervallet [0, n) med funktionen nextInt(n). Skrivsättet [0, n) betyder att 0 ingår i intervallet, n ingår inte i intervallet. rand.nextInt(100) ger alltså ett slumpmässigt heltal mellan 0 och 99. När klassen Turtle ska implementeras måste man bestämma vilka attribut som klassen ska ha. En sköldpadda måste hålla reda på: • fönstret som den ska rita i: ett attribut SimpleWindow w, • var i fönstret den befinner sig: en x-koordinat och en y-koordinat. För att minska inverkan av avrundningsfel i beräkningarna ska x och y vara av typ double, Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle 59 • i vilken riktning huvudet pekar. Riktningen kommer alltid att ändras i hela grader. Man kan själv välja om attributet som anger riktningen ska vara i grader eller i radianer, • om pennan är lyft eller sänkt. Ett sådant attribut bör ha typen boolean. booleanvariabler kan bara anta två värden: true eller false. Man testar om en booleanvariabel isPenDown har värdet true med if (isPenDown) . . . . När sköldpaddan ska gå rakt fram i den aktuella riktningen ska en linje ritas om pennan är sänkt. Den aktuella positionen ska också uppdateras. Antag att sköldpaddan i ett visst ögonblick är vriden vinkeln α i förhållande till positiv x-led. När operationen forward(n) utförs ska pennan flyttas från punkten ( x, y) till en ny position, som vi kallar ( x1, y1): x (x1, y1) n α (x, y) y Av figuren framgår att ( x1, y1) ska beräknas enligt: x1 = x + n cos α y1 = y − n sin α I Java utnyttjas standardfunktionerna Math.cos(alpha) och Math.sin(alpha) för att beräkna cosinus och sinus. Vinkeln alpha ska ges i radianer. x och y är av typ double. När koordinaterna utnyttjas som parametrar till SimpleWindowmetoderna moveTo och lineTo och när de ska returneras som funktionsresultat i getX och getY måste de avrundas till heltalsvärden. Det kan till exempel se ut så här: w.moveTo((int) Math.round(x), (int) Math.round(y)); ColorTurtle I en av uppgifterna i laborationen ska du använda en klass ColorTurtle som beskriver en sköldpadda som ritar med en färgad penna. För övrigt fungerar klassen precis likadant som en ”vanlig” Turtle. Man skapar objekt av klassen enligt följande exempel: ColorTurtle t1 = new ColorTurtle(w, 100, 100, java.awt.Color.RED); ColorTurtle t2 = new ColorTurtle(w, 100, 100, java.awt.Color.BLUE); Det är inte särskilt svårt att skriva klassen: den enda skillnaden mot Turtle är att klassen behöver ett attribut där färgen sparas och att man i metoden forward sätter linjefärgen till denna färg med SimpleWindow-operationen setLineColor. Man kan kopiera klassen Turtle till ColorTurtle och införa dessa tillägg. Detta är dock inte alls bra — om man senare kommer på att man måste ändra något i Turtle måste man komma ihåg att också ändra samma sak i ColorTurtle. Betydligt bättre är att använda arv: man låter ColorTurtle ”ärva” alla egenskaper från Turtle och skriver bara de tillägg som behövs. Man kallar ColorTurtle för en ”subklass” till ”superklassen” Turtle. Här visas ett exempel på hur man kan implementera ColorTurtle: Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle 60 import se.lth.cs.window.SimpleWindow; import java.awt.Color; public class ColorTurtle extends Turtle { private Color color; public ColorTurtle(SimpleWindow w, int x, int y, Color color) { super(w, x, y); this.color = color; } public void forward(int n) { Color savedColor = w.getLineColor(); w.setLineColor(color); super.forward(n); w.setLineColor(savedColor); } } Kommentarer: • extends, ”utökar”, anger att klassen ColorTurtle ärver alla egenskaper från klassen Turtle. • Alla objektets attribut måste få startvärden. De attribut som finns i ”Turtle-delen” av objektet initierar man genom att anropa Turtle-konstruktorn med super(w, x, y). • I forward sparar man den aktuella linjefärgen, ändrar till sköldpaddans egen färg, anropar forward i superklassen Turtle och sätter tillbaka linjefärgen. • För att det ska fungera måste man göra en ändring i Turtle: skyddet på SimpleWindowattributet w ändras från private till protected. Det betyder att subklasserna får använda attributet. Datorarbete 1. I filen Turtle.java i projektet Lab6 finns ett ”skelett” (en klass utan attribut och med tomma metoder) till klassen Turtle. Implementera klassen Turtle: Skriv in attributen i klassen. Skriv gärna en kommentar till varje attribut som förklarar vad attributet betyder. Skriv också konstruktorn och se till att alla attribut får rätt startvärden. Implementera de övriga operationerna. 2. Klasserna TurtleDrawSquare och TurtleDrawRandomFigure finns i filerna TurtleDrawSquare.java och TurtleDrawRandomFigure.java. Kör programmen och kontrollera att din Turtleimplementation är korrekt. Använd gärna debuggern för att hitta eventuella svårfunna fel. Här kan det vara bra att utnyttja kommandot Step Into. Det fungerar som Step Over med skillnaden att man följer exekveringen in i metoder som anropas. 3. Du ska nu träna mer på att använda debuggern i Eclipse. I fortsättningen förutsätter vi att du utnyttjar debuggern för att hitta fel i dina program. Välj ett av programmen från uppgift 2. Sätt en brytpunkt på en rad där en metod i Turtle anropas, t. ex. t.penDown() eller något liknande. Kör programmet i debuggern. Använd Step Into och följ hur man från main-metoden ”gör utflykter” in i metoderna i klassen Turtle. Lägg märke till vilka storheter (variabler, attribut, parametrar) som är tillgängliga när man ”är i” main-metoden resp. inuti någon metod i klassen Turtle. Tips! Om du klickar på trekanten framför this i variabelvyn visas Turtle-objektets attribut. Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle 4. 61 I projektet finns också programmet TurtleTest, som testar din Turtle-klass i olika avseenden. Ett antal misstag kan upptäckas på detta sätt. Resultatet ska se ut så här: Kör programmet TurtleTest. Om de ritade figurerna är felaktiga, eller något av testerna besvaras med ”NEJ”, gå tillbaka till din Turtle-klass och åtgärda felet. 5. Skriv ett program där en sköldpadda tar 1000 steg i ett fönster. Sköldpaddan ska börja sin vandring mitt i fönstret. I varje steg ska steglängden väljas slumpmässigt i intervallet [1, 10]. Efter varje steg ska sköldpaddan vridas ett slumpmässigt antal grader i intervallet [−180, 180]. 6. Skriv ett program där två sköldpaddor vandrar över ritfönstret. Steglängden och vridningsvinkeln ska väljas slumpmässigt i samma intervall som i föregående uppgift. Vandringen ska avslutas när avståndet mellan de båda sköldpaddorna är mindre än 50 pixlar. Sköldpaddorna ska turas om att ta steg på följande sätt: while ("avståndet mellan sköldpaddorna" >= 50) { "låt den ena sköldpaddan ta ett slumpmässigt steg och göra en slumpmässig vridning" "låt den andra sköldpaddan ta ett slumpmässigt steg och göra en slumpmässig vridning" SimpleWindow.delay(10); } Låt den ena sköldpaddan börja sin vandring i punkten (250,250) och den andra i (350,350). 7. Betrakta klassen Turtle. Ge exempel på ett attribut, en formell parameter och en lokal variabel. Ange också var i klassen/programmet respektive storhet är tillgänglig och får användas. Exempel på Attribut Namn Får användas var Formell parameter Lokal variabel 8. Implementera klassen ColorTurtle genom att ärva Turtle, så att basklassen utökas med ett attribut color. Ändra sedan slumpvandringsprogrammet från uppgift 6 så att en röd och en blå sköldpadda vandrar i fönstret. Laboration 6 – implementera och ärva klasser, Turtle och ColorTurtle 62 Checklista I den här laborationen har du övat på • skillnaden mellan attribut, parametrar och lokala variabler, • att använda konstruktorer för att sätta attributens startvärden, • att använda klassen Math för beräkningar, och • att skapa och använda en egen klass. • att skapa och använda en klass som ärver en annan klass och utökar den med extra attribut och metoder. Laboration 7 – Maze 63 Laboration 7 – Maze Mål: Du ska lösa ett problem där huvuduppgiften är att konstruera en algoritm för att hitta vägen genom en labyrint. Det ska du göra genom att skriva program som använder din Turtleklass från Lab6. Förberedelseuppgifter 1. Repetera kap 3 i läroboken. 2. Läs bakgrundsavsnittet nedan och studera dokumentationen för Maze på kurshemsidan http://cs.lth.se/eda016/doc Bakgrund En labyrint består av ett rum med en ingång och en utgång. Alla väggar är parallella med xeller y-axeln. Ingången finns alltid i den vägg som är längst ner i figuren. Exempel: Ett sätt att hitta från ingången till utgången är att gå genom labyrinten och hela tiden hålla den vänstra handen i väggen. När man vandrar genom labyrinten är fyra riktningar möjliga. En riktning definieras som vinkeln mellan x-axeln och vandringsriktningen och mäts i grader: 90 180 0 270 Labyrinten beskrivs av en färdigskriven klass Maze. Labyrintens utseende läses in från en fil när labyrintobjektet skapas. Klassen har följande specifikation: /** Skapar en labyrint med nummer nbr. */ Maze(int nbr); /** Ritar labyrinten i fönstret w. */ void draw(SimpleWindow w); /** Tar reda på x-koordinaten för ingången. */ int getXEntry(); /** Tar reda på y-koordinaten för ingången. */ int getYEntry(); Laboration 7 – Maze 64 /** Undersöker om punkten x,y är vid utgången. */ boolean atExit(int x, int y); /** Undersöker om man, när man befinner sig i punkten x,y och är på väg i riktningen direction, har en vägg direkt till vänster om sig. */ boolean wallAtLeft(int direction, int x, int y); /** Som wallAtLeft, men undersöker om man har en vägg direkt framför sig. */ boolean wallInFront(int direction, int x, int y); /** Väntar ms millisekunder. */ static void delay(int ms); I operationerna wallAtLeft och wallInFront betraktas alla riktningar R ± n · 360◦ , n = 1, 2, . . . som ekvivalenta med R. Väggarna har bredden 6 pixlar. De ritas dock med bredden 4 pixlar för att det ska bli litet luft mellan sköldpaddan och väggen. Exempel där man vandrar runt ett hörn: 9 8 7 6 5 4 3 2 1 I punkterna 1–4 är riktningen 90. wallAtLeft ger i dessa punkter värdet true, wallInFront värdet false. I punkt 5 ger wallAtLeft värdet false, varför man svänger åt vänster (riktningen 90 ändras till 180). Man fortsätter sedan genom punkterna 6–9. I dessa punkter ger wallAtLeft värdet true, wallInFront värdet false. I uppgiften ska du skriva en klass MazeWalker som låter en sköldpadda vandra i en labyrint. Låt klassen ha följande uppbyggnad: public class MazeWalker { private Turtle turtle; public MazeWalker(Turtle turtle) { // fyll i kod } /** Låter sköldpaddan vandra genom labyrinten maze, från ingången till utgången. */ public void walk(Maze maze) { // fyll i kod } } Uppgifter 1. Till denna uppgift finns inte något färdigt Eclipseprojekt. Du måste alltså skapa ett sådant: 1. Klicka på New-ikonen i verktygsraden. Välj Java Project. 2. Skriv namnet på projektet (t.ex. MazeTurtle). 3. Klicka på Finish. Laboration 7 – Maze 2. 65 Programmet som du skriver kommer att använda klasserna SimpleWindow och Maze. Dessa klasser finns i biblioteksfilen cs_pt.jar, som finns i projektet cs_pt. Programmet kommer också att utnyttja klassen Turtle från laboration 6. Du måste därför göra cs_pt.jar och projektet Lab6 tillgängliga för ditt nya projekt. 1. Högerklicka på projektet MazeTurtle, välj Build Path → Configure Build Path. 2. Klicka på fliken Libraries. 3. Klicka på Add JARS. . . , öppna projektet cs_pt, markera filen cs_pt.jar och klicka OK. 4. Klicka nu på fliken Projects (istället för Libraries). 5. Klicka på Add. . . , markera Lab6 och klicka OK. 6. Klicka på OK för att lämna dialogen. 3. Skapa klassen MazeWalker. I metoden walk ska sköldpaddan börja sin vandring i punkten med koordinaterna getXEntry(), getYEntry() och gå uppåt i labyrinten. Alla steg ska ha längden 1. Lägg in en fördröjning efter varje steg så att man ser hur sköldpaddan vandrar. 4. Skriv ett huvudprogram som kontrollerar att sköldpaddan kan vandra i labyrinterna. Numret på labyrinten ska läsas in från tangentbordet. Testa programmet. Det finns fem olika labyrinter, numrerade 1–5. Givetvis ska alla testas och fungera. Extrauppgifter 5. Utforska källkoden för Maze som finns i projektet cs_pt. Titta också på de textfiler som lagrar utseendet för respektive labyrint. Kan du med hjälp av källkoden förstå hur dessa ska tolkas? 6. Använd ColorTurtle och rita sköldpaddan väg med valfri färg. 7. Summera antalet steg sköldpaddan tar och antalet riktningsändringar som sköldpaddan gör och skriv ut dessa summor när sköldpaddan kommit i mål. Laboration 8 – vektorer, simulering av patiens 66 Laboration 8 – vektorer, simulering av patiens Mål: Du ska skriva ett enkelt simuleringsprogram och lära dig att använda vektorer. Förberedelseuppgifter • Studera avsnitt 8.1–8.3 i läroboken. • Läs avsnittet Bakgrund. • Betrakta följande satser: Card[] theCards = new Card[52]; theCards[0] = new Card(Card.SPADES, 12); theCards[1] = new Card(Card.HEARTS, 13); // 1 // 2 Se till att du kan förklara vad som händer i steg 1 respektive 2 ovan. Rita en figur (på papper) som visar vilka variabler och objekt som finns när man exekverat satserna till och med punkt 1 resp. 2 ovan. Bakgrund Patiensen 1–2–3 läggs på följande sätt: Man tar en kortlek, blandar den och lägger sedan ut korten ett efter ett. Samtidigt som man lägger korten räknar man 1–2–3–1–2–. . . , det vill säga när man lägger det första kortet säger man 1, när man lägger det andra kortet säger man 2, osv. Patiensen går ut om man lyckas lägga ut alla kort i leken utan att någon gång få upp ett ess när man säger 1, någon 2-a när man säger 2 eller någon 3-a när man säger 3. Man kan med hjälp av sannolikhetslära bestämma exakt hur stor sannolikheten är att patiensen ska gå ut.4 Men det är betydligt enklare att uppskatta sannolikheten genom att lägga patiensen många gånger och räkna antalet gånger som den går ut. Allra snabbast går det om datorn lägger patiensen. Då har man användning av följande klasser, som beskriver kort och kortlekar: /** konstanter för färgerna */ static final int SPADES = ...; static final int HEARTS = SPADES + 1; static final int DIAMONDS = SPADES + 2; static final int CLUBS = SPADES + 3; /** Skapar ett spelkort med färgen suit (SPADES, HEARTS, DIAMONDS, CLUBS) och valören rank (1-13). */ Card(int suit, int rank); /** Tar reda på färgen. */ int getSuit(); /** Tar reda på valören. */ int getRank(); /** Returnerar en läsbar representation av kortet, till exempel "spader ess". */ String toString(); 4 Se ”Fråga Lund om matematik”, www.maths.lth.se/query/answers, sök efter ”patiens” på sidan. Laboration 8 – vektorer, simulering av patiens /** Skapar en kortlek. */ CardDeck(); 67 /** Återställer kortleken till fullt antal och blandar den. */ void shuffle(); /** Undersöker om det finns fler kort i kortleken. */ boolean moreCards(); /** Drar det översta kortet i leken. */ Card getCard(); I följande program skriver man ut färg och valör för alla kort som man drar ur en blandad kortlek: public class CardExample { public static void main(String[] args) { CardDeck deck = new CardDeck(); deck.shuffle(); while (deck.moreCards()) { Card c = deck.getCard(); System.out.println(c); } } } Med hjälp av System.out.println(c) skriver man ut ett kort på skärmen. Inuti println anropas metoden toString och den sträng som metoden returnerar kommer att skrivas ut. Det är alltså i Card-klassens toString-metod man bestämmer hur kortet ska presenteras på skärmen. Datorarbete 1. (valfritt) Om du vill kan du få lite mer hjälp av Eclipse när du programmerar. Ta fram fönstret med Eclipse-inställningarna: Window → Preferences (Studentdatorerna, Windows, Linux) Eclipse → Inställningar... (Mac) Välj Java → Editor → Content Assist → Advanced. Klicka i Java proposals och Template proposals. Klicka Apply, därefter OK. Hädanefter kommer Eclipse att ge förslag beroende på sammanhanget. Följande bild visar hur det kan se ut när programmeraren skrivit ”deck”, tryckt punkt, och nu bläddrar bland förslagen: 68 Laboration 8 – vektorer, simulering av patiens 2. Öppna projektet Lab8. I projektet finns bland annat den färdiga klassen Card. Öppna filen Card.java och se hur klassen är implementerad. 3. Ett program med programraderna från den tredje förberedelseuppgiften finns i filen ArrayExample.java. Sätt en brytpunkt i början av programmet och provkör programmet i debuggern. Framför theCards i variabelvyn visas ett plustecken eller en triangel – klicka där så ser du innehållet i vektorn. 4. Implementera klassen CardDeck. I klassen ska man hålla reda på de 52 korten i en kortleken. Det gör man enklast genom att lagra korten i en vektor: import java.util.Random; public class CardDeck { private Card[] cards; private int current; // index för "nästa" kort private static Random rand = new Random(); public CardDeck() { cards = new Card[52]; // skapa vektorn ... // skapa korten, lägg in dem i vektorn current = 0; } ... } Att skapa korten i ordning är inte så enkelt – man känner ju inte värdet på konstanterna som anger färgerna. Men man ser att konstanterna ligger i ordning, så man kan skriva satser som den följande: for (int suit = Card.SPADES; suit <= Card.CLUBS; suit++) ... När man ska blanda kortleken bör man använda följande algoritm: för i = 51, 50, ..., 1 { nbr = slumptal i intervallet [0,i+1) byt plats på korten med index i och index nbr } 5. Ett testprogram (i filen TestCardDeck.java) för CardDeck finns i projektet. Testprogrammet skapar en kortlek och skriver ut alla korten. Därefter blandas kortleken och alla kort skrivs ut på nytt. Kör programmet och kontrollera att det som skrivs ut ser ut att stämma. 6. Skriv ett huvudprogram som utnyttjar klasserna Card och CardDeck för att lägga patiensen ett stort antal gånger och därefter skriva ut sannolikheten för att patiensen ska gå ut. Kör programmet. Den korrekta sannolikheten är ungefär 0.008165. Checklista I den här laborationen har du övat på att Laboration 8 – vektorer, simulering av patiens • använda vektorer för att lagra och hantera objekt, • använda konstanter för att representera egenskaper, och • använda attribut för att lagra information om objektets tillstånd. 69 Grupplaboration 9 – TurtleRace, ArrayList och ärvning 70 Grupplaboration 9 – TurtleRace, ArrayList och ärvning Mål: Du tränar på att använda listor för att hålla reda på objekt och lär dig mer om arv. Förberedelseuppgifter • • • • Studera kapitel 12 i läroboken (framför allt 12.1–12.6) noga! Läs även 9.1–9.8 i läroboken. Läs igenom texten i avsnittet Bakgrund. Läs avsnitt 3.10, om static, i läroboken. Titta i klassen RaceWindow och fundera över de statiska attribut och metoder som finns där. Fundera på varför det är rimligt att dessa är statiska. Om något är oklart, fråga din handledare vid laborationens början. • Diskutera i din samarbetsgrupp hur ni vill dela upp ansvaret och arbetet för olika delar av koden. Det är lämpligt att varje klass har en huvudansvarig. Flera kan hjälpas åt med samma klass, t.ex. genom att implementera olika metoder. • När ni redovisar er lösning ska ni också kunna redogöra för hur ni delat upp ansvar och arbete mellan er. Var och en redovisar sina delar. • Grupplaborationer görs i huvudsak som hemuppgift. Salstiden används primärt för redovisning. Bakgrund I denna laboration utvecklas ett program där olika typer av sköldpaddor tävlar mot varandra i en kapplöpning. Del A – RaceTurtle Vi använder klassen SimpleWindow på ett annorlunda sätt än tidigare: vi har här specialiserat den genom subklassen RaceWindow. Ett RaceWindow fungerar som ett vanligt SimpleWindow (eftersom det ärver från SimpleWindow) men med tillägget att en kapplöpningsbana ritas upp då fönstret skapas. Uppgiften går ut på att skriva ett program som simulerar en (slumpmässig) kapplöpning mellan sköldpaddor (Turtle-objekt). En sköldpadda tar sig fram genom simulerade tärningskast (dvs. slumpmässiga steg framåt mellan 1 och 6), med ett ”kast” i varje runda. Samtliga sköldpaddor ska lagras i en ArrayList. En klass RaceTurtle ska representera en sköldpadda som kan simulera en kapplöpning. RaceTurtle ska ärva från klassen Turtle och därmed kan en RaceTurtle göra allt som en vanlig Turtle kan. Därtill ska en RaceTurtle innehålla en slumptalsgenerator och ett startnummer. RaceTurtle ska ha en metod raceStep() och en metod toString(). Metoden raceStep() ska beskriva hur sköldpaddan tar ett löpsteg. RaceTurtle ska alltså ärva från Turtle och ha följande specifikation: /** * Skapar en sköldpadda som ska springa i fönstret w och som har start* nummer nbr. Sköldpaddan startar med pennan nere och nosen vänd åt höger. */ RaceTurtle(RaceWindow w, int nbr); /** * Låter sköldpaddan gå framåt ett steg. Stegets längd ges av ett * slumptal (heltal) mellan 1 och 6. Grupplaboration 9 – TurtleRace, ArrayList och ärvning 71 */ void raceStep(); /** * Returnerar en läsbar representation av denna RaceTurtle, * på formen "Nummer x" där x är sköldpaddans startnummer. */ String toString(); Del B – Subklasser till RaceTurtle Uppgiften innefattar också att skapa ett antal klasser som beskriver olika slags tävlande sköldpaddor, samt att modifiera huvudprogrammet för att genomföra en sådan kapplöpning. Klassen RaceTurtle från Del A ska representera det som samliga tävlingssköldpaddor har gemensamt. Nu ska emellertid metoden raceStep överskuggas (override) av subklassernas mer specifika definition av metoden. Vi behöver flera subklasser, en för varje slags sköldpadda. Bilden nedan visar arvshierarkin. Programmet ska innehålla lika många tävlande sköldpaddor (subklasser till RaceTurtle) som ni är personer i samarbetsgruppen. Nedan finns exempel på olika slags sköldpaddor: • MoleTurtle, som beskriver en ”mullvadssköldpadda”, d.v.s. en tävlande sköldpadda som då och då går under jorden (genom att lyfta pennan slumpmässigt). • AbsentMindedTurtle, som är en tankspridd sköldpadda. Graden av tankspriddhet anges i procent när sköldpaddan skapas, och sannolikheten att en tankspridd sköldpadda skall gå framåt bestäms av tankspriddhetsgraden. Exempel: en tankspriddhetsgrad på 34 procent ska medföra att sköldpaddan i 34 procent av fallen glömmer att ta sitt steg. • DizzyTurtle, som beskriver en yr tävlingssköldpadda, som vinglar när den skall ta sig framåt. När dessa sköldpaddor skapas ska graden av yrsel (från 1 till 5) anges, och deras förmåga att hålla kursen skall bestämmas av yrselgraden.Det behövs inte någon avancerad simulering av yrseln, men en sköldpadda med högre yrselgrad bör vingla mer än en mindre yr sköldpadda. Turtle @ @ RaceTurtle @ @ AbsentMindedTurtle DizzyTurtle MoleTurtle AnotherTurtle Grupplaboration 9 – TurtleRace, ArrayList och ärvning 72 Datorarbete Del A 1. Inkludera din Turtle från laboration 6 genom att: 1. Högerklicka på projektet Lab9, välj Build Path → Configure Build Path. 2. Klicka på fliken Projects. 3. Klicka på Add. . . , markera Lab6 och klicka OK. 4. Klicka på OK för att lämna dialogen. 2. Implementera klassen RaceTurtle enligt beskrivning och specifikation ovan. När konstruktorn implementeras för RaceTurtle måste dess position beräknas. Det finns två hjälpmetoder i RaceWindow, som beroende på startnummer returnerar en lämplig x- respektive y-position. På så sätt får alla sköldpaddor en egen bana. 3. I klassen RaceTurtleTest finns en main-metod som skapar en sköldpadda och låter denna genomföra ett eget lopp utan motståndare. Klassen kan användas som ett första test av din RaceTurtle. Avkommentera och studera koden – det är meningen att du ska förstå vad den gör. Kör sedan programmet för att se om sköldpaddan går i mål som förväntat. 4. Gör en ny klass TurtleRace för att genomföra ett lopp enligt följande (det är lämpligt att inspireras av testprogrammet): • Skapa åtta sköldpaddor och lagra dessa i en ArrayList. Låt dem ha sin kapplöpning i ett och samma RaceWindow. • Efter loppet ska första, andra och tredje plats skrivas ut (enligt exemplet nedan). Det är ett krav att utskriften använder sig av toString-metoden. På plats 1: Nummer 5 På plats 2: Nummer 6 På plats 3: Nummer 1 • Tips: När en sköldpadda går i mål kan man ta ut den ur den vanliga listan, och sätta in den i en separat lista. När den vanliga listan är tom har alla sköldpaddorna gått i mål, och då görs utskriften ovan. • Observera att for-each-loopen inte tillåter att vi ändrar en listas innehåll i loopen. • Små orättvisor i resultatberäkningen är acceptabla. Kanske upptäcker lösningen gynnar sköldpaddor med lägre startnummer över de med högre startnummer. Förklara för din handledare vari orättvisan består, och diskutera gärna hur man kan åtgärda den. • Det är därmed inte heller nödvändigt att hantera delade placeringar. 5. Provkör ditt sköldpaddslopp. Använd en fördröjning, t. ex. RaceWindow.delay(10), så man hinner se hur loppet fortskrider. Kontrollera att rätt placeringar skrivs ut. Del B 6. Implementera tävlingssköldpaddorna som beskrivs i Bakgrund Del B. Skapa en ny klass för varje typ av sköldpadda och låt varje klass ärva från RaceTurtle. Varje subklass sådan ska ha sin egen definition av metoderna raceStep() och toString() – dessa ska alltså implementeras på nytt. Grupplaboration 9 – TurtleRace, ArrayList och ärvning 73 Observera att raceStep() och toString() inte är abstrakta i RaceTurtle. De har alltså redan en implementation. Det gör att man måste komma ihåg att överskugga dessa i varje subklass, men de går samtidigt att utnyttja för att slippa duplicera kod. Exempelvis kan kan man från subklassernas raceStep-metoder anropa super.raceStep() för att genomföra själva steget, förutom den kod som är specifik för varje subklass (lyfta/sänka pennan, jämföra tankspriddhet, välja riktning). 7. Skapa en ny klass MultiTurtleRace som ärver klassen TurtleRace, men genomför ett lopp enligt följande: • Skapa åtta sköldpaddor av slumpmässig typ. • Notera att då alla ärver från en gemensam klass behöver listan inte ändras på något vis. Se till att du förstår varför en lista av RaceTurtle även kan innehålla objekt av klasserna MoleTurtle, AbsentMindedTurtle och DizzyTurtle. Fråga gärna handledaren. • Då AbsentMindedTurtle ska skapas ska tankspriddhetsgraden slumpas fram mellan 0 och 100. • Då DizzyTurtle ska skapas ska yrselgraden slumpas fram mellan 1 och 5. • Innan loppet börjar ska hela startuppställningen skrivas ut enligt exemplet nedan, ett krav är att detta sker genom respektive sköldpaddas toString-metod. Nummer Nummer Nummer Nummer Nummer Nummer Nummer Nummer 1 2 3 4 5 6 7 8 - MoleTurtle AbsentMindedTurtle (45% Frånvarande) AbsentMindedTurtle (43% Frånvarande) DizzyTurtle (Yrsel: 3) MoleTurtle MoleTurtle AbsentMindedTurtle (71% Frånvarande) DizzyTurtle (Yrsel: 5) • Efter loppet ska första, andra och tredje plats skrivas ut (enligt exemplet nedan). På plats 1: Nummer 5 - MoleTurtle På plats 2: Nummer 6 - MoleTurtle På plats 3: Nummer 4 - DizzyTurtle (Yrsel: 3) • Det kan hända att du behöver ändra i klassen TurtleRace, så att det lättare ska gå att återanvända dess kod i subklassen MultiTurtleRace. • Ändra nu så att varje sköldpaddstyp ritas med en egen färg genom att använda ColorTurtle på lämpligt sätt. Fundera först på olika möjliga lösningar och tänk igenom var ändringar behöver göras. Rita ett klassdiagram som visar ärvningsstrukturen. Checklista I den här laborationen har du övat på att • använda listor för att lagra objekt, • ärva egenskaper från en superklass, • implementera och använda toString-metoden, och • använda statiska hjälpmetoder. Laboration 10 – matris, spelet life 74 Laboration 10 – matris, spelet life Mål: Du ska skriva program som använder matriser. Du ska också få mer träning på att använda färdiga klasser och att implementera egna klasser. Förberedelseuppgifter • Studera avsnitt 8.4 i läroboken. Bakgrund ”Game of Life”, konstruerat av matematikern John Conway, är ett simuleringsspel som uppvisar likheter med de förändringar som äger rum i samhällen med levande individer. Som spelplan används ett rutnät. En ruta kan antingen vara tom eller innehålla en individ. Från en given spelplan, en generation, ska samhället ändra sin struktur enligt vissa regler för födelse och död. Nedanstående regler gäller när en ny generation bildas. Alla förändringar vid en generationsväxling sker samtidigt över hela rutnätet. 1. Fortlevnad. En individ lever vidare till nästa generation om den har 2 eller 3 grannar. 2. Dödsfall. En individ med 4 eller fler grannar dör av trängsel. En individ med 0 eller 1 granne dör av isolering. 3. Födelse. I en tom ruta gränsande till exakt 3 individer föds en individ. Varje ruta i rutnätet har 8 grannrutor. Även de diagonalt intilliggande rutorna betraktas alltså som grannrutor. Exempel på fyra på varandra följande generationer i Life-spelet (individerna markeras med fyllda rutor): Generation 1 Generation 2 Generation 3 Generation 4 Du ska skriva ett program för Life-spelet. Användargränssnittet, det fönster där spelplanen ritas upp, beskrivs av en färdigskriven klass med namnet LifeView. Användaren kan när som helst ändra spelplanens utseende genom att klicka på en ruta i spelplanen. Om man klickar på en tom ruta skapas en individ i rutan, om man klickar på en fylld ruta töms rutan. Detta utnyttjas normalt för att skapa generation 1. Förutom spelplanen ritas en Next-knapp, som användaren klickar på när nästa generation ska skapas och ritas upp, samt en Quit-knapp som användaren klickar på när spelet ska avslutas. Följande klasser ska finnas i lösningen: LifeBoard, som beskriver spelplanen och generationsräknaren. Innehåller operationer för att ta reda på och ändra spelplanens utseende, t ex vilka rutor som ska vara tomma och vilka som ska innehålla individer. LifeView, som beskriver hur spelplanen presenteras på skärmen. Innehåller operationer för att rita upp spelplanen och för att vänta på att användaren klickar i rutorna eller på Next- eller Quit-knappen. Klassen är färdigskriven (specifikationen finns nedan). Laboration 10 – matris, spelet life 75 Life, som beskriver Life-spelet. Innehåller operationer för att skapa en ny generation enligt reglerna och för att skapa en individ i en ruta eller tömma en ruta. LifeController, en klass med en main-metod, som genomför Life-spelet från generation 1 till ”Quit”. Specifikation för LifeBoard och LifeView: /** Skapar en spelplan med rows rader och cols kolonner. Spelplanen är från början tom, dvs. alla rutorna är tomma och generationsnumret är 1. */ LifeBoard(int rows, int cols); /** Undersöker om det finns en individ i rutan med index row,col. Om index row,col hamnar utanför spelplanen returneras false. */ boolean get(int row, int col); /** Lagrar värdet val i rutan med index row, col. */ void put(int row, int col, boolean val); /** Tar reda på antalet rader. */ int getRows(); /** Tar reda på antalet kolonner. */ int getCols(); /** Tar reda på aktuellt generationsnummer. */ int getGeneration(); /** Ökar generationsnumret med ett. */ void increaseGeneration(); /** Skapar vy till spelplanen board. */ LifeView(LifeBoard board); /** Ritar upp de fixa delarna av spelplanen (rutnätet, generationsräknaren och knapparna. */ void drawBoard(); /** Ritar om de delar av fönstret som ändrats sedan föregående uppritning. */ void update(); /** Väntar tills användaren klickar med musen. Ger: 1: klick i ruta på spelplanen. Index för rutan kan hämtas med getRow och getCol. 2: klick i Next-rutan. 3: klick i Quit-rutan. */ int getCommand(); /** Tar reda på radnummer för den klickade rutan efter kommando nr 1. */ int getRow(); /** Tar reda på kolonnummer för den klickade rutan efter kommando nr 1. */ int getCol(); Rutorna i rutnätet numreras 0..rows–1, 0..cols–1. I klassen LifeBoard finns det dock en tänkt ”ram” kring rutnätet. I ramrutorna finns inga individer. Genom att ramen finns behöver du inte specialbehandla rutorna i kanten av rutnätet. Du kan alltså göra get(row,col) med row/col = –1 eller rows/cols. I de fallen ger metoden get alltid resultatet false. Laboration 10 – matris, spelet life 76 Klassen Life har följande specifikation: /** Skapar ett Life-spel med spelplanen board. */ Life(LifeBoard board); /** Skapar en ny generation. */ void newGeneration(); /** Ändrar innehållet i rutan med index row, col från individ till tom eller tvärtom. */ void flip(int row, int col); Datorarbete Det är lämpligt att lösa ett stort problem stegvis. Först skriver man lite kod, sedan testar man att det fungerar innan man går vidare. Därför är laborationsuppgiften uppdelad i mindre deluppgifter. 1. Öppna projektet Lab10. I filen LifeBoard.java finns ett skelett (en klass utan attribut och med tomma metoder) till klassen LifeBoard. Implementera klassen LifeBoard. 2. Ett testprogram (TestLifeBoard.java) för klassen LifeBoard finns i projektet. Testprogrammet testar inte klassen fullständigt, men skapar ett LifeBoard-objekt, låter ett LifeViewobjekt rita upp brädet samt anropar de olika metoderna i LifeBoard. Kör programmet och kontrollera att det fungerar som det ska. Avsluta exekveringen genom att klicka i Quit-rutan på spelplanen. 3. Det är dags att börja arbeta med den riktiga main-metoden. Inspiration kan hämtas från testprogrammet (TestLifeBoard.java). Skapa en fil med namnet LifeController.java som ska innehålla main-metoden. Du ska till att börja med se till att Quit-knappen fungerar (Vänta med rutnätet och Next-knappen). Ledning: Med metoden getCommand i LifeView kan man vänta tills användaren klickar på en ruta eller en knapp. Då returneras ett tal med vars hjälp man kan avgöra vad användaren gjorde. Låt alltså användaren klicka i fönstret tills användaren klickar i Quitrutan. Avsluta då programmet med hjälp av System.exit(0). Kör programmet och kontrollera att du kan avsluta programmet genom att trycka på Quit-knappen. 4. Skapa en fil Life.java som ska innehålla klassen Life. Implementera konstruktorn och metoden flip i klassen Life. Implementera metoden newGeneration, men bara delvis – lägg in ökning av generationsnumret. Spara klassen Life och rätta eventuella kompileringsfel. 5. Fortsätt med klassen med main-metoden. Implementera satser i main-metoden så att rätt saker utförs när man klickar med musen; om man klickar i en ruta ska en individ skapas eller tas bort (med metoden flip). Om man klickar på Next-knappen ska generationsnumret ändras. Om man trycker på Quit-knappen ska programmet avslutas. Kör programmet och kontrollera att det fungerar som det ska. Laboration 10 – matris, spelet life 6. 77 Nu återstår att implementera färdigt metoden newGeneration i klassen Life. Lägg gärna till en intern (privat) metod som beräknar antal grannar till en ruta. Metoden newGeneration blir kortare och mer lättläst då. Tänk på att i metoden newGeneration ska alla förändringar vid en generationsväxling utföras samtidigt över hela spelplanen. Enklast åstadkommer du detta genom att deklarera en hjälpmatris där du lagrar den nya spelplanen. I slutet av metoden kopierar du hjälpmatrisen till den riktiga spelplanen. Alternativt kan du använda ett hjälpbräde (ett annat LifeBoard-objekt) där du lagrar den nya spelplanen. Även i detta fall måste du avsluta med att kopiera hjälpbrädet till den riktiga spelplanen. Testa programmet på åtminstone följande fyra exempel: Exempel 1 (två konfigurationer erhålls omväxlande) Exempel 2 (i generation 7 är alla döda) Exempel 3 (antalet individer växer, i generation 15 erhålls en stabil konfiguration). Observera att spelplanen behöver vara större än vad som visas här – minst 13x13 rutor. Prova också följande. Varför kallar Life-entusiasterna denna figur för ”glider”? Mer att läsa om ”Conway’s Game of Life” och fler exempel att prova finns på nätet. Använd t. ex. sökorden "Conway game of life". Laboration 10 – matris, spelet life 78 Checklista I den här laborationen har du övat på att • använda matriser, • spara och ändra tillståndet hos en spelplan, • använda logiska värden och skriva logiska uttryck, och • strukturerat dela upp funktionalitet mellan klasser. Grupplaboration 11 – Matriser, bilbehandling ImageFilter 79 Grupplaboration 11 – Matriser, bilbehandling ImageFilter Mål: Du lär dig att använda matriser och att implementera mera komplicerade operationer. Du får också en introduktion till bildbehandling. Förberedelseuppgifter • Läs avsnitt 8.6 (matriser), 8.10 (registrering), 9.3 och 9.6 (arv) i läroboken. • Diskutera i din samarbetsgrupp hur ni vill dela upp ansvaret och arbetet för olika delar av koden. Det är lämpligt att varje klass har en huvudansvarig. Flera kan hjälpas åt med samma klass, t.ex. genom att implementera olika metoder. • När ni redovisar er lösning ska ni också kunna redogöra för hur ni delat upp ansvar och arbete mellan er. Var och en redovisar sina delar. • Grupplaborationer görs i huvudsak som hemuppgift. Salstiden används primärt för redovisning. Bakgrund En digital bild består av ett rutnät (en matris) av pixlar. Varje pixel har en färg, och om man har många pixlar flyter de samman för ögat så att de tillsammans skapar en bild. Färger kan anges enligt olika system, men i datorer är det vanligt att man använder RGBsystemet. I RGB-systemet sätts en färg samman av tre grundfärger: röd, grön och blå. Mättnaden av varje grundfärg anges av ett heltal som vi i fortsättningen förutsätter ligger i intervallet [0, 255]. 0 anger ”ingen färg” och 255 anger ”maximal färg”. Man kan därmed representera 256 × 256 × 256 = 16 777 216 olika färgnyanser. Man kan också representera gråskalor; det gör man med färger som har samma värde på alla tre grundfärgerna: (0, 0, 0) är helt svart, (255, 255, 255) är helt vitt. I Java representeras färger av klassen java.awt.Color. Den klassen beskrivs i appendix C i läroboken och utförligare i Javadokumentationen på nätet. När man har en digital bild i datorn kan man förändra och förbättra bilden på olika sätt. Det finns många program för detta ändamål, till exempel Photoshop (kommersiellt) och GIMP (gratis, GNU-projekt). Du ska i denna uppgift skriva ett program som kan göra en del enkla transformationer (kallas i fortsättningen ”filtreringar”) av bilder, till exempel förbättra kontrasten i en bild och markera konturer i en bild. Användargränssnittet till programmet är färdigskrivet; din uppgift är att skriva metoder som utför filtreringarna. Figur 1 visar hur användargränssnittet till programmet ser ut. Man väljer filtreringsmetod i menyn längst till vänster och utför filtreringen genom att klicka på Apply-knappen. En del av filtreringsmetoderna behöver en parameter; värdet på denna skriver man i textrutan. I fönstret visas originalbilden till vänster och den filtrerade bilden till höger (här har Sobelfiltrering använts, som markerar konturer i bilden). Användargränssnittet beskrivs av klassen ImageGUI. main-metoden finns i klassen ImageProcessor. Den klassen har följande utseende: import se.lth.cs.ptdc.images.ImageFilter; import se.lth.cs.ptdc.images.ImageGUI; public class ImageProcessor { public static void main(String[] args) { ImageFilter[] filters = new ImageFilter[1]; filters[0] = new IdentityFilter("Identity Filter"); new ImageGUI(filters); Grupplaboration 11 – Matriser, bilbehandling ImageFilter 80 Figur 1: Användargränssnittet i bildbehandlingsprogrammet. } } De objekt som man lägger in i vektorn filters tas omhand av användargränssnittet. Du kommer senare att skapa flera filterobjekt av olika slag och skicka till ImageGUI. Namnen på filtren (strängparametern till konstruktorn) visas i menyn där man väljer filtreringsmetod. När man trycker på Apply-knappen letar ImageGUI upp filtreringsobjektet med rätt namn och anropar en metod med namnet apply i detta objekt. Om man trycker på Replace-knappen ersätts originalbilden av den filtrerade bilden (det använder man om man vill filtrera en bild med flera olika filter efter varandra). Det finns en färdigskriven filterklass, IdentityFilter. Filtreringsmetoden i den klassen gör ingenting annat än att skapa en identisk kopia av ursprungsbilden. Klassen finns med bara för att visa hur en filterklass är uppbyggd: import java.awt.Color; import se.lth.cs.ptdc.images.ImageFilter; /** IdentityFilter beskriver en identitetstransformation */ public class IdentityFilter extends ImageFilter { /** Skapar ett filterobjekt med namnet name */ public IdentityFilter(String name) { super(name); } /** Filtrerar bilden i matrisen inPixels och returnerar resultatet i en ny matris. Utnyttjar eventuellt värdet av paramValue */ public Color[][] apply(Color[][] inPixels, double paramValue) { int height = inPixels.length; int width = inPixels[0].length; Color[][] outPixels = new Color[height][width]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Color pixel = inPixels[i][j]; outPixels[i][j] = new Color(pixel.getRed(), pixel.getGreen(), pixel.getBlue()); } } return outPixels; } Grupplaboration 11 – Matriser, bilbehandling ImageFilter 81 } Metoden apply har två parametrar: inPixels som är originalbilden (matrisen med Color-objekt) och paramValue som är det värde som man skrivit i Parameter-rutan. (Om man inte har skrivit något i Parameter-rutan, eller skrivit något som inte kan tolkas som ett talvärde, så har paramValue värdet 0.) paramValue-värdet används inte i klassen IdentityFilter. I metoden skapas en matris outPixels med samma storlek som inPixels. Därefter skapas ett nytt Color-objekt i varje matriselement. Eftersom det är en identitetstransformation så är varje pixel i outPixels lika med motsvarande pixel i inPixels (de har samma RGB-värden). Så kommer det naturligtvis inte att vara i de filtreringsmetoder som du ska skriva: där kommer RGB-värdena i outPixels att skilja sig från motsvarande värden i inPixels. Till slut returneras den nya matrisen. ImageGUI omvandlar sedan matrisen till en bild och visar den i användargränssnittet. I superklassen ImageFilter beskrivs det som är gemensamt för alla filter: att de har ett namn och en metod apply som behandlar en bild. Dessutom finns två hjälpmetoder i klassen. Att metoden apply är ”abstrakt” innebär att den måste implementeras i alla subklasser. Klassen har följande utseende: import java.awt.Color; /** ImageFilter är superklass till alla filterklasser */ public abstract class ImageFilter { private String name; /** Skapar ett filterobjekt med namnet name */ protected ImageFilter(String name) { this.name = name; } /** Tar reda på filtrets namn */ public String getName() { return name; } /** Filtrerar bilden i matrisen inPixels och returnerar resultatet i en ny matris. Utnyttjar eventuellt värdet av paramValue. Abstrakt metod, implementeras i subklasserna */ public abstract Color[][] apply(Color[][] inPixels, double paramValue); /** Beräknar intensiteten hos alla pixlarna i pixels, returnerar resultatet i en ny matris */ protected short[][] computeIntensity(Color[][] pixels) { // ... färdigskriven metod, se uppgift 5 nedan } /** Faltar punkten p[i][j] med faltningskärnan kernel. Summan av elementen i kernel är weight */ protected short convolve(short[][] p, int i, int j, short[][] kernel, int weight) { // ... färdigskriven metod, se uppgift 8, laboration ?? } } Grupplaboration 11 – Matriser, bilbehandling ImageFilter 82 Uppgifter Nedan beskrivs ett antal bildfilterklasser med olika svårighetsgrad. Om ni är n personer i samarbetsgruppen, ska ni implementera minst n + 1 bildfilterklasser. Om ni inte vill implementera nedanstående filter kan ni välja andra som ni tycker är intressanta och lagom utmanande; prata i så fall med en handledare om svårighetsgraden. Som frivillig extrauppgift får ni gärna implementera ännu fler filter. Ni kan t.ex. hämta inspiration här: http://www.jhlabs.com/ip/filters/ 1. På kurshemsidan finns en länk till API-specifikationerna för alla standardklasserna i Java. Leta upp specifikationen av klassen Color i paketet java.awt och titta igenom den. 2. I projektet teamlab-imagefilters finns filerna ImageProcessor.java och IdentityFilter.java. Det finns också en katalog images som innehåller ett antal JPEG-bilder som du kan testa programmet med (du kan också använda egna JPEG-bilder om du vill). Testkör programmet. I File-menyn finns kommandon för att öppna bildfiler och för att spara filtrerade bilder på fil. Använd nya filnamn om du sparar filtrerade bilder. 3. Blåfilter. Skriv en klass BlueFilter som skapar en ny bild där varje pixel bara innehåller den blå komponenten. Kopiera klassen IdentityFilter och gör de ändringar som behövs. Ändra också huvudprogrammet så att det nya filtret kommer med i menyn. 4. Invertering av bild. Skriv en klass InvertFilter som inverterar en bild dvs skapar en ”negativ” kopia av bilden. Fundera över vad som kan menas med en inverterad eller negativ kopia: de nya RGBvärdena är inte ett dividerat med de gamla värdena (då skulle de nya värdena bli flyttal) och inte de gamla värdena med ombytt tecken (då skulle de nya värdena bli negativa). 5. Konvertering av färgbild till gråskalebild. Skriv en klass GrayScaleFilter som konverterar en färgbild till en gråskalebild. (Om man utgår från en gråskalebild ska resultatet bli identiskt med ursprungsbilden.) Här gäller det alltså att omvandla varje punkt i RGB-rymden (som har storleken 256 × 256 × 256) till en punkt i gråskalerymden (som har storleken 256). Man behöver ett mått på hur ”ljus” eller ”intensiv” varje färg är. Om vi kallar RGB-komponenterna r, g respektive b får vi ett mått på intensiteten genom att räkna ut avståndet från punkten (r, g, b) till origo. Man kan använda den vanliga avståndsformeln: intensity = Math.sqrt((r * r + g * g + b * b) / 3) Divisionen med 3 kommer sig av att man vill att resultatet ska hamna i intervallet [0, 255]. Det blir dock tidsödande att göra tre multiplikationer och en kvadratrotsberäkning för varje pixel, så man kan i stället använda följande mått: intensity = (r + g + b) / 3 Intensiteter måste beräknas i flera av uppgifterna och man bör beskriva hur man gör det bara en gång. Den färdigskrivna metoden short[][] computeIntensity(Color[][]) som utför beräkningarna har därför placerats i klassen ImageFilter. Den blir därigenom tillgänglig i alla subklasser. Skyddsspecifikationen protected betyder att metoden är tillgänglig i subklasserna men inte utanför klassen. Grupplaboration 11 – Matriser, bilbehandling ImageFilter 83 Att göra en gråskalefiltrering tar inte irriterande lång tid, men vi ska ändå titta på en uppenbar optimering som sparar både tid och minnesutrymme. Gör denna optimering om du vill och undersök om filtreringen blir märkbart snabbare. En gråskalebild innehåller högst 256 olika färger: (0, 0, 0), (1, 1, 1), . . . , (255, 255, 255). Om vi antar att en viss bild har 300 × 500 pixlar och att man skapar ett nytt Color-objekt för varje pixel så blir det totalt 150 000 objekt, varav många är identiska. Man kan klara sig med 256 olika objekt om man börjar med att skapa en vektor med de möjliga färgerna: Color[] grayLevels = new Color[256]; // skapa färgen (0,0,0) och lägg in den i grayLevels[0], // (1,1,1) i grayLevels[1], ..., (255,255,255) i grayLevels[255] När man sedan har bestämt sig för att en pixel ska ha en viss intensitet 0–255 behöver man inte skapa ett nytt objekt med new Color(intensity, intensity, intensity) — ett sådant objekt finns ju färdigt i vektorelementet grayLevels[intensity]. 6. Kryptering av bild med XOR. Skriv en klass XORCryptFilter som krypterar bilden med hjälp av Javas xor-operator ˆ. Denna operator gör binär xor mellan bitarna i ett heltal. Exempelvis ger 8 ˆ 127 värdet 119. Om man gör xor igen med 127, alltså 119 ˆ 127, får man tillbaka värdet 8. Detta ska vi utnyttja för kryptering genom att göra xor på varje RGB-värde med en sekvens av pseudoslumptal. Gör krypteringen så här: 1. Skapa ett Random-objekt, t.ex. kallad rand, med det parametervärde som användaren väljer som slumptalsfrö. 2. För varje pixel: Låt nya RGB-värden bli xor mellan gamla värdet och nästa pseudoslumptal rand.nextInt(256). Detta gör att bilden ser ut som brus (krypterad). Men om man applicerar filtret igen med samma parametervärde får man tillbaka bilden intakt (dekrypterad). 7. Förbättring av kontrasten i bild. Vi inskränker oss här till att förbättra kontrasten i gråskalebilder. Om man applicerar kontrastfiltrering på en färgbild så kommer bilden att konverteras till en gråskalebild. (Man kan naturligtvis förbättra kontrasten i en färgbild och få en färgbild som resultat. Då behandlar man de tre färgkanalerna var för sig.) Många bilder lider av alltför låg kontrast. Det beror på att bilden inte utnyttjar hela det tillgängliga området 0–255 för intensiteten. I diagrammet i figur 2 visas antalet pixlar med olika intensiteter för en bild. Ett sådant diagram kallas ett intensitetshistogram. Här ser vi att det inte finns några pixlar med intensitet mindre än 45 och bara få pixlar med intensitet större än 225 (ungefär). Man får en bild med bättre kontrast om man ”töjer ut” intervallet enligt följande formel (lineär interpolation): newIntensity = 255 * (intensity - 45) / (225 - 45) Som synes kommer en punkt med intensiteten 45 att få den nya intensiteten 0 och en punkt med intensiteten 225 att få den nya intensiteten 255. Mellanliggande punkter sprids ut jämnt över intervallet [0, 255]. För punkter med en intensitet mindre än 45 sätter man den nya intensiteten till 0, för punkter med en intensitet större än 225 sätter man den nya intensiteten till 255. Vi kallar intervallet där de flesta pixlarna finns för [lowCut, highCut]. De punkter som har intensitet mindre än lowCut sätter man till 0, de som har intensitet större än highCut sätter man till 255. För de övriga punkterna interpolerar man med formeln ovan (45 ersätts med lowCut, 225 med highCut). Grupplaboration 11 – Matriser, bilbehandling ImageFilter 84 Det återstår nu att hitta lämpliga värden på lowCut och highCut. Detta är inte något som kan göras helt automatiskt, eftersom värdena beror på intensitetsfördelningen hos bildpunkterna. Man börjar med att beräkna bildens intensitetshistogram, dvs hur många punkter i bilden som har intensiteten 0, hur många som har intensiteten 1, . . . , till och med 255. Detta är ett typiskt registreringsproblem som ska lösas enligt metoden i avsnitt 8.10 i läroboken. I de flesta bildbehandlingsprogram kan man sedan titta på histogrammet och interaktivt bestämma värdena på lowCut och highCut. Så ska vi dock inte göra här. I stället bestämmer vi oss för ett procenttal cutOff (som vi matar in i Parameter-rutan i användargränssnittet) och beräknar lowCut så att cutOff procent av punkterna i bilden har en intensitet som är mindre än lowCut och highCut så att cutOff procent av punkterna har en intensitet som är större än highCut. Exempel: antag att en bild innehåller 100 000 pixlar och att cutOff är 1.5. Beräkna bildens intensitetshistogram i en vektor int[] histogram = new int[256]. Beräkna lowCut så att histogram[0] + histogram[1] + ... + histogram[lowCut] = 0.015 * 100000 (så nära det går att komma, det blir troligen inte exakt likhet). Beräkna highCut på liknande sätt. Sammanfattning av algoritmen: 1. Beräkna intensiteterna hos alla punkterna i bilden, lagra dem i en short-matris. Använd den färdigskrivna metoden computeIntensity. 2. Beräkna bildens intensitetshistogram. 3. Parametervärdet paramValue är det värde som ska användas som cutOff. 4. Beräkna lowCut och highCut enligt ovan. 5. Beräkna nya intensiteter enligt interpolationsformeln och lagra de nya pixlarna i outPixels. Skriv en klass ContrastFilter som implementerar algoritmen. I katalogen images kan bilden moon.jpg vara lämpliga att testa, eftersom den har låg kontrast. Anmärkning: om cutOff sätts = 0 så får man samma resultat av denna filtrering som man får av GrayScaleFilter. Detta kan man se genom att studera interpolationsformeln. Utjämning av konturer, Gaussfiltrering. Gaussfiltrering är ett exempel på så kallad faltningsfiltrering. (Du kommer att läsa mycket om faltning i kommande kurser i matematik 1200 1000 800 Antal pixlar 8. 600 400 200 0 1 16 31 46 61 76 91 106 121 136 151 166 181 196 211 226 241 256 Intensitet Figur 2: Intensitetshistogram. Grupplaboration 11 – Matriser, bilbehandling ImageFilter 85 och signalbehandling.) Filtreringen bygger på att man modifierar varje bildpunkt genom att titta på punkten och omgivande punkter. För detta utnyttjar man en så kallad faltningskärna K som är en liten kvadratisk heltalsmatris. Man placerar K över varje element i intensitetsmatrisen och multiplicerar varje element i K med motsvarande element i intensitetsmatrisen. Man summerar produkterna och dividerar summan med summan av elementen i K för att få det nya värdet på intensiteten i punkten. Divisionen med summan gör man för att de nya intensiteterna ska hamna i rätt intervall. Exempel: intensity = 5 4 9 8 .. . 4 3 8 6 .. . 2 4 7 6 .. . 8 9 7 5 .. . ... ... ... ... .. . 0 K= 1 0 1 4 1 0 1 0 Här är summan av elementen i K 1 + 1 + 4 + 1 + 1 = 8. För att räkna ut det nya värdet på intensiteten i punkten med index [1][1] (det nuvarande värdet är 3) beräknar man: newintensity = 0·5+1·4+0·2+1·4+4·3+1·4+0·9+1·8+0·7 32 = =4 8 8 Man fortsätter med att flytta K ett steg åt höger och beräknar på motsvarande sätt ett nytt värde för elementet med index [1][2] (där det nuvarande värdet är 4 och det nya värdet blir 5). Därefter gör man på samma sätt för alla element utom för ”ramen” dvs elementen i matrisens ytterkanter. Skriv en klass GaussFilter som implementerar denna algoritm. Varje färg ska behandlas separat. Gör på följande sätt: 1. Bilda tre short-matriser och lagra pixlarnas red-, green- och blue-komponenter i matriserna. 2. Utför faltningen av de tre komponenterna för varje element och lagra ett nytt Colorobjekt i outPixels för varje punkt. 3. Elementen i ramen behandlas ju inte, men i outPixels måste också dessa element få värden. Enklast är att flytta över dessa element oförändrade från inPixels till outPixels. Man kan också sätta dem till Color.WHITE, men då kommer den filtrerade bilden att se något mindre ut. I ImageFilter finns en färdigskriven metod med följande rubrik: protected short convolve(short[][] p, int i, int j, short[][] kernel, int weight); Metoden faltar punkten p[i][j] med faltningskärnan kernel och ska anropas med red-, green- och blue-matrisen. weight är summan av elementen i kernel. Faltningskärnan kan vara ett attribut i klassen och skapas enklast på följande sätt: private static short[][] GAUSS_KERNEL = { {0, 1, 0}, {1, 4, 1}, {0, 1, 0} }; Grupplaboration 11 – Matriser, bilbehandling ImageFilter 86 svart tröskelvärde vitt Figur 3: En funktion (heldragen linje) och dess derivata (streckad linje). Det kan vara intressant att prova med andra värden än 4 i mitten av faltningsmatrisen. Med värdet 0 får man en större utjämning eftersom man då inte alls tar hänsyn till den aktuella pixelns värde. Mata in detta värde i Parameter-rutan. Anmärkning: det kan ibland vara svårt att se någon skillnad mellan den filtrerade bilden och originalbilden. Om man vill ha en riktigt suddig bild så måste man använda en större matris som faltningskärna. 9. Markering av konturer, Sobelfiltrering. Med Gaussfiltrering beräknar man medelvärden och jämnar därför ut en bild. Med Sobelfiltrering, som också är ett exempel på faltningsfiltrering, får man motsatt effekt dvs man förstärker konturer i en bild. I princip deriverar man bilden i x- och y-led och sammanstället resultatet. Först en förklaring om varför derivering förstärker konturer. I figur 3 visas en funktion f (heldragen linje) och funktionens derivata f 0 (streckad linje). Vi ser att där funktionen gör ett ”hopp” så får derivatan ett stort värde. Om funktionen representerar intensiteten hos pixlarna längs en linje i x-led eller y-led så motsvarar ”hoppen” en kontur i bilden. Om man sedan bestämmer sig för att pixlar där derivatans värde överstiger ett visst tröskelvärde ska vara svarta och andra pixlar vita så får man en bild med bara konturer. Nu är ju intensiteten hos pixlarna inte en kontinuerlig funktion som man kan derivera enligt vanliga matematiska regler. Men man kan approximera derivatan, till exempel med följande formel: f 0 (x) ≈ f ( x + h) − f ( x − h) 2h (Om man här låter h gå mot noll så får man definitionen av derivatan.) Uttryckt i Java och matrisen intensity så får man: derivative = (intensity[i][j+1] - intensity[i][j-1]) / 2 Allt detta kan man uttrycka med hjälp av faltning. 1. Beräkna intensitetsmatrisen med metoden computeIntensity. 2. Falta varje punkt i intensitetsmatrisen med två kärnor: X_SOBEL = { {-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1} }; Y_SOBEL = { {-1, -2, -1}, { 0, 0, 0}, { 1, 2, 1} }; Grupplaboration 11 – Matriser, bilbehandling ImageFilter 87 Använd metoden convolve med vikten 1 (här behöver man inte normera resultatet). Koefficienterna i matrisen X_SOBEL uttrycker derivering i x-led (ger vertikala konturer), i Y_SOBEL faltning i y-led (ger horisontella konturer). För att förklara varför koefficienterna ibland är 1 och ibland 2 måste man studera den bakomliggande teorin noggrant, men det gör vi inte här. 3. Om resultaten av faltningen i en punkt betecknas med sx och sy så får man en indikator på närvaron av en kontur med Math.abs(sx) + Math.abs(sy). Absolutbelopp behöver man eftersom man har negativa koefficienter i faltningsmatriserna. 4. Sätt pixeln till svart om indikatorn är större än tröskelvärdet, till vit annars. Mata in tröskelvärdet i Parameter-rutan. Skriv en klass SobelFilter som implementerar denna algoritm. 89 3 Inlämningsuppgift – anvisningar Du ska välja en inlämningsuppgift som du ska lösa och redovisa helt själv. Målet med inlämningsuppgiften är att du visa att du kan ska skapa ett större program. Observera att samtliga laborationer och inlämningsuppgift måste vara godkända innan du får tentera! Följande regler gäller: 1. Inlämningsuppgiften ska lösas individuellt och den ska redovisas på därför avsedd laborationstid. 2. Välj en av de föreslagna inlämningsuppgifterna eller hitta på en egen. Din inlämningsuppgift ska demonstrera att du kan följande: a) Skriva ett större program själv från grunden; minst ca 500 rader, minst 5 klasser, gärna mer. b) Skapa egna klasser som samverkar effektivt för att lösa ett icke-trivialt problem. c) Använda färdiga klasser tillsammans med dina egna klasser för att lösa ett icketrivialt problem. d) Skapa, läsa, uppdatera och visa innehållet en datastruktur, till exempel ArrayList. e) Avlusa och förbättra ditt program stegvis. 3. Senast två veckor innan redovisningen ska du komma överens med en handledare om vad din inlämningsuppgift ska innehålla. 4. Var förberedd inför presentationen. Du ska vara beredd att svara på frågor kring ditt program och hur du har tänkt. Du ska även beskriva framväxten av ditt program och hur du stegvis avlusat och förbättrat implementationen. 5. Det är tillåtet att diskutera uppgifterna och tolkningen av dessa med utomstående på ett allmänt plan men inte att få hjälp med de konkreta lösningarna. 6. Det är inte tillåtet att kopiera annans lösningar helt eller delvis. Det är inte heller tillåtet att kopiera från litteratur eller internet. 7. Väsentlig hjälp, av annat än lärare på kursen, för att genomföra en uppgift skall redovisas tydligt. Detsamma gäller om man använt någon annan form av hjälpmedel som läraren inte kan förutsättas känna till. 8. Kontakta ansvarig lärare om du är osäker på någon av punkterna ovan. Inlämningsuppgift alternativ 1 – bankapplikation 90 Inlämningsuppgift alternativ 1 – bankapplikation Uppgiften är att skriva ett program som håller reda på bankkonton och kunder i en bank. Programmet är helt textbaserat: en meny skrivs ut till konsollen och menyvalen görs via tangentbordet. Du ska skriva programmet helt själv, och det ska följa de objektorienterade principer du lärt dig i kursen. Krav Kraven för programmet beskrivs nedan. Sist i häftet återfinns också ett antal exempelkörningar, som illustrerar hur ditt program ska fungera. För att ditt program ska bli godkänt ska det uppfylla följande krav: • Programmet ska ha följande funktioner: 1. Hitta konton för en viss kontoinnehavare 2. Sök kontoinnehavare på (del av) namn 3. Sätta in pengar 4. Ta ut pengar 5. Överföring mellan konton 6. Skapa nytt konto 7. Ta bort konto 8. Skriv ut bankens alla konton 9. Avsluta • Programdesignen ska följa de specifikationer som finns under avsnittet Design nedan. • Du ska använda en lista (t.ex. ArrayList) för att hålla reda på alla bankkonton. • Inga utskrifter eller inläsningar (System.out, Scanner och liknande) får lov att finnas i klasserna Customer, BankAccount eller Bank. Allt som gäller användargränssnitt skall skötas från klassen BankApplication. I BankApplication får du lov att använda valfritt antal hjälpmetoder eller andra hjälpklasser för att sköta användargränssnittet. (Det är vanligt att man begränsar användargränssnittet till vissa klasser på detta sätt. På så sätt kan klasserna Customer, BankAccount och Bank återanvändas i ett annat program med ett annat användargränssnitt.) • Hela projektet får bara innehålla ett Scanner-objekt för att läsa från tangentbordet (d.v.s man får bara göra new Scanner(System.in) en gång). • Klasserna Customer, BankAccount och Bank ska exakt följa specifikationerna som anges under avsnittet Design nedan. Det är tillåtet att lägga till privata hjälpmetoder. (Det är inte ovanligt att en klass, för att fungera ihop med andra klasser, måste se ut på ett visst sätt – följa en viss specifikation – trots att man implementerar funktionaliteten själv.) • Alla metoder och attribut ska ha lämpliga åtkomsträttigheter (private/public). • De indata som ges i exemplet nedan ska ge väsentligen samma utskrift som exemplen i bilagan. Du får gärna byta ordval, men ditt program ska i allt väsentligt fungera likadant. • Listor ska skrivas ut genom att iterera över listans objekt och anropa respektive toString metod. Det är inte ok att använda listans toString-metod direkt då ni då inte kan få önskvärt format på utskriften. Inlämningsuppgift alternativ 1 – bankapplikation 91 • Du behöver inte hantera Exceptions men övrig rimlig felhantering är önskvärd. Exempelvis: Programmet bör inte krascha om användaren matar in oväntade värden, exempelvis kontonummer eller menyval som inte finns, men det är inte nödvändigt att hantera indata av fel typ (exempelvis att användaren matar in bokstäver då programmet förväntar sig ett heltal). Tips och övriga anvisningar • I vissa lösningar kan Scannern tyckas bete sig konstigt vid inläsning av omväxlande tal och strängar. Detta kan exempelvis visa sig i att inlästa strängar blir tomma. Det finns olika sätt att lösa detta. Konkreta tips utdelas på en föreläsning i samband med inlämningsuppgiften. Du kan naturligtvis också fråga någon av handledarna. • Tänk på val av attribut i klasserna. En variabel som bara används i en metod behöver inte vara åtkomligt i hela objektet och ska således ej vara attribut. Fråga en handledare om du är osäker. • Programmet ska testas noggrant. Tänk på att den som rättar kan mycket väl testa på helt andra fall än de som förekommer i exemplen. Frivillig utökning av uppgiften Gör först klart inlämningsuppgiftens obligatoriska delar. Därefter kan du, om du vill, utöka ditt program enligt följande. • Då man söker kunder utifrån en del av deras namn (menyval 2) kan samma kund förekomma flera gånger i resultatet. (Att det är samma kund ser man på att inte bara namn och personnummer är detsamma, utan även kundnumret.) Detta är naturligtvis inte helt önskvärt. Utöka gärna ditt program så att varje kund bara förekommer högst en gång i utskriften från menyval 2. • En annan utökning, som även kan underlätta testningen av programmet, är att implementera läsning från och skrivning till fil och på så vis ge möjlighet att spara kund- och konto-informationen mellan körningarna. Design Följande klass ska användas för att lagra grundläggande information om en kontoinnehavare: Customer /** * Skapar en kund (kontoinnehavare) med namnet ’name’ och id-nummer ’idNr’. * Kunden tilldelas också ett unikt kundnummer. */ Customer(String name, long idNr); /** Tar reda på kundens namn. */ String getName(); /** Tar reda på kundens personnummer. */ long getIdNr(); /** Tar reda på kundens kundnummer. */ int getCustomerNr(); /** Returnerar en strängbeskrivning av kunden. */ Inlämningsuppgift alternativ 1 – bankapplikation 92 String toString(); Följande klass ska användas för att lagra information om ett bankkonto: BankAccount /** * Skapar ett nytt bankkonto åt en innehavare med namn ’holderName’ och * id ’holderId’. Kontot tilldelas ett unikt kontonummer och innehåller * inledningsvis 0 kr. */ BankAccount(String holderName, long holderId); /** * Skapar ett nytt bankkonto med innehavare ’holder’. Kontot tilldelas * ett unikt kontonummer och innehåller inledningsvis 0 kr. */ BankAccount(Customer holder); /** Tar reda på kontots innehavare. */ Customer getHolder(); /** Tar reda på det kontonummer som identifierar detta konto. */ int getAccountNumber(); /** Tar reda på hur mycket pengar som finns på kontot. */ double getAmount(); /** Sätter in beloppet ’amount’ på kontot. */ void deposit(double amount); /** * Tar ut beloppet ’amount’ från kontot. Om kontot saknar täckning * blir saldot negativt. */ void withdraw(double amount); /** Returnerar en strängrepresentation av bankkontot. */ String toString(); Följande klass ska användas som register för att administrera bankens samtliga konton: Bank /** Skapar en ny bank utan konton. */ Bank(); /** * Öppna ett nytt konto i banken. Om det redan finns en kontoinnehavare * med de givna uppgifterna ska inte en ny Customer skapas, utan istället * den befintliga användas. Det nya kontonumret returneras. */ int addAccount(String holderName, long idNr); /** * Returnerar den kontoinnehavaren som har det givna id-numret, * eller null om ingen sådan finns. */ Customer findHolder(long idNr); /** Inlämningsuppgift alternativ 1 – bankapplikation 93 * Tar bort konto med nummer ’number’ från banken. Returnerar true om * kontot fanns (och kunde tas bort), annars false. */ boolean removeAccount(int number); /** * Returnerar en lista innehållande samtliga bankkonton i banken. * Listan är sorterad på kontoinnehavarnas namn. */ ArrayList<BankAccount> getAllAccounts(); /** * Söker upp och returnerar bankkontot med kontonummer ’accountNumber’. * Returnerar null om inget sådant konto finns. */ BankAccount findByNumber(int accountNumber); /** * Söker upp alla bankkonton som innehas av kunden med id-nummer ’idNr’. * Kontona returneras i en lista. Kunderna antas ha unika id-nummer. */ ArrayList<BankAccount> findAccountsForHolder(long idNr); /** * Söker upp kunder utifrån en sökning på namn eller del av namn. Alla * personer vars namn innehåller strängen ’namePart’ inkluderas i * resultatet, som returneras som en lista. Samma person kan förekomma * flera gånger i resultatet. Sökningen är "case insensitive", det vill * säga gör ingen skillnad på stora och små bokstäver. */ ArrayList<Customer> findByPartofName(String namePart); Implementation Implementationen görs lämpligen stegvis, genom att du implementerar några metoder i taget och därefter provkör resultatet. Därefter implementerar och provkör du fler metoder. Förslagsvis följer du dessa steg i ditt arbete: 1. Skapa projektet i Eclipse. 2. Implementera klassen Customer. 3. Implementera klassen BankAccount. 4. Skapa klassen BankApplication, som ska innehålla programmets main-metod. Det är lämpligt att införa ytterligare metoder i BankApplication: exempelvis en metod för att driva hela programmet (t. ex. runApplication) som i sin tur anropar andra metoder beroende på vad som ska göras. Ha gärna en separat metod för att skriva ut menyn. Implementera tillräckligt för att kunna köra igång programmet, skriva ut menyn och låta användaren välja i den. 5. Implementera menyval 6 och 8, som motsvarar metoderna addAccount och getAllAccounts i klassen Bank. Återigen kan du införa metoder i BankApplication, exempelvis för att läsa in data och anropa Bank med dessa. Observera din sortering i getAllAccounts inte ska använda någon av Javas standardklasser för sortering, som t. ex. Collections.sort eller Arrays.sort. Inlämningsuppgift alternativ 1 – bankapplikation 94 Observera också återigen att det inte är tillåtet att använda Scanner och System.out i klassen Bank. 6. Testa menyval 6 och 8 noga. Tips: under testningen kan det bli enformigt att mata in uppgifter om konton om och om igen. Du kan lägga in satser i ditt program som anropar addAccount ett antal gånger, så att ditt program startar med ett antal färdiga testkonton. Låt dessa satser skapa kontona i icke-alfabetisk ordning (med avseende på kontoinnehavarnas namn), så att du ser att sorteringen i getAllAccounts fungerar. 7. Implementera menyvalen 1 och 2. De motsvaras av metoderna findAccountsForHolder och findByPartOfName i klassen Bank. 8. Lägg till några konton (om du inte införde testkonton enligt punkt 6 ovan) och testa menyvalen 1 och 2. 9. Implementera resten av menyvalen en åt gången. Testa efter varje. 10. Testa ditt program som helhet. Om du lade in satser för att automatiskt skapa testkonton (under punkt 6 ovan, ta bort dem. Kör först igång programmet utan att skapa några konton, och kontrollera att menyns alternativ fungerar även utan konton. Skapa därefter ett antal konton och kontrollera att programmets delar fungerar som förväntat. Glöm inte testa med orimliga indata och kontrollera att programmet hanterar dessa på ett rimligt sätt. Gå tillbaka och åtgärda de fel du upptäcker. Redovisning När du gått igenom alla moment ovan, och testat noga, är det dags att redovisa. Förbered dig inför redovisningen. Om du hunnit glömma vad du gjort så repetera innan redovisningen – du förväntas kunna förklara ditt program! Exempel på körning av programmet Här visas en möjlig exempelkörning av programmet. Data som matas in av användaren visas i fetstil. Ditt program behöver inte efterlikna dessa utskrifter i exakta detaljer, men den övergripande strukturen ska vara densamma. De data som matas in av användaren har markerats i fetstil. Ett antal konton skapas, och pengar sätts in på ett par av dem. Ett stort belopp överförs mellan två konton. Slutligen tas de flesta kontona bort, och endast ett finns kvar då programmet avslutas. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 6 namn: Charles Ingvar Jönsson id: 3705209999 konto skapat: 1001 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare Inlämningsuppgift alternativ 1 – bankapplikation 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 6 namn: Ragnar Vanheden id: 4111195555 konto skapat: 1002 - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 6 namn: Jakob Morgan Rockefeller Wall-Enberg Jr id: 2306207777 konto skapat: 1003 - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 6 namn: Jakob Morgan Rockefeller Wall-Enberg Jr id: 2306207777 konto skapat: 1004 - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 2 namn: ROCK Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 2 namn: ar Charles Ingvar Jönsson, id 3705209999, kundnr 101 Ragnar Vanheden, id 4111195555, kundnr 102 - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 95 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - kundnr 103 kundnr 103 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 96 Inlämningsuppgift alternativ 1 – bankapplikation 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 3 konto: 1003 belopp: 1000000.0 konto 1003 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 1000000.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 3 konto: 1001 belopp: 50.0 konto 1001 (Charles Ingvar Jönsson, id 3705209999, kundnr 101): 50.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 4 från konto: 1001 belopp: 900.0 uttaget misslyckades, endast 50.0 på kontot! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 5 från konto: 1003 till konto: 1001 belopp: 500000.0 konto 1003 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 500000.0 konto 1001 (Charles Ingvar Jönsson, id 3705209999, kundnr 101): 500050.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 4 från konto: 1001 belopp: 900.0 konto 1001 (Charles Ingvar Jönsson, id 3705209999, kundnr 101): 499150.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in Inlämningsuppgift alternativ 1 – bankapplikation 97 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 1 id: 2306207777 konto 1003 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 500000.0 konto 1004 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 8 konto 1001 (Charles Ingvar Jönsson, id 3705209999, kundnr 101): 499150.0 konto 1003 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 500000.0 konto 1004 (Jakob Morgan Rockefeller Wall-Enberg Jr, id 2306207777, kundnr 103): 0.0 konto 1002 (Ragnar Vanheden, id 4111195555, kundnr 102): 0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 7 konto: 1001 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 7 konto: 1003 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 7 konto: 1004 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 7 98 Inlämningsuppgift alternativ 1 – bankapplikation konto: 87654 felaktigt kontonummer! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 8 konto 1002 (Ragnar Vanheden, id 4111195555, kundnr 102): 0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1. Hitta konto utifrån innehavare 2. Sök kontoinnehavare utifrån (del av) namn 3. Sätt in 4. Ta ut 5. Överföring 6. Skapa konto 7. Ta bort konto 8. Skriv ut konton 9. Avsluta val: 9 Inlämningsuppgift alternativ 2 – Mandelbrot 99 Inlämningsuppgift alternativ 2 – Mandelbrot I denna uppgift lära du dig att implementera algoritmer med numeriska beräkningar. Bakgrund Den fransk-amerikanske matematikern Benoît Mandelbrot (född i Polen) införde år 1975 ett nytt matematiskt begrepp, fraktal. Fraktaler har anknytning till den relativt unga matematiska vetenskapen kaosteori. En fraktal är en geometrisk figur med ovanliga egenskaper, till exempel är den sådan att mönster i figuren upprepas i det oändliga när man förstorar olika delar av den. Ett välkänt exempel på en fraktal är Mandelbrotmängden M, som är en delmängd av de komplexa talen C. Den innehåller ett oändligt antal punkter men har en begränsad utsträckning i det komplexa talplanet. Man kan undersöka om ett komplext tal c tillhör M på följande sätt: • Definiera en talföljd (Mandelbrotföljden): ( zk = 0, z2k−1 + c, k=0 k = 1, 2, . . . • z0 är 0, så z1 blir c. Nästa z-värde blir z2 = c2 + c, nästa igen blir z3 = c4 + 2c3 + c2 + c, och så vidare. • Följden z0 , z1 , z2 , . . . kan bete sig på tre sätt: 1. Den kan konvergera mot en punkt i det komplexa talplanet. Till exempel ger c = i/2 en följd som konvergerar mot −0.136009 + 0.393075 i. 2. Den kan ”konvergera” mot två eller flera punkter som upprepas periodiskt. Till exempel ger c = i en följd som omväxlande antar värdena −1 + i och −i. 3. Den kan divergera mot oändligheten. Detta gäller till exempel för c = 2. Definition: Ett komplext tal c tillhör Mandelbrotmängden M om följden av komplexa tal inte divergerar, det vill säga om något av fallen 1 eller 2 ovan gäller. Om man i ett datorprogram ska undersöka om en punkt tillhör Mandelbrotmängden så kan man inte använda definitionen direkt — för att kunna avgöra om en följd konvergerar eller divergerar skulle ju man behöva iterera oändligt länge. I ett program nöjer man sig därför med att göra ett begränsat antal iterationer och därefter använda följande sats: Om det för något k gäller att |zk | > 2 så divergerar Mandelbrotföljden. Uppgift Du ska skriva ett program som beräknar Mandelbrotmängden och åskådliggör punkterna i mängden i ett komplext talplan. Programmets användargränssnitt är färdigskrivet. Det är en god idé att följa den arbetsordning som beskrivs nedan. Börja med att skapa ett projekt inl2 för filerna i uppgiften. 1 Komplexa tal I uppgiften behövs en klass som beskriver komplexa variabler. Du behöver bara implementera de operationer på komplexa tal som kommer att behövas i detta program. Skriv alltså en klass Complex med följande specifikation: Inlämningsuppgift alternativ 2 – Mandelbrot 100 /** Skapar en komplex variabel med realdelen re och imaginärdelen im */ Complex(double re, double im); /** Tar reda på realdelen */ double getRe(); /** Tar reda på imaginärdelen */ double getIm(); /** Tar reda på talets absolutbelopp i kvadrat */ double getAbs2(); /** Adderar det komplexa talet c till detta tal */ void add(Complex c); /** Multiplicerar detta tal med det komplexa talet c */ void mul(Complex c); Skriv ett testprogram som kontrollerar att din Complex-klass fungerar. Metoderna getRe och getIm kommer du bara att använda i testprogrammet och när du ritar en cirkel (moment 3). Anledningen till att klassen bara har en operation för att beräkna absolutbeloppet i kvadrat, inte för absolutbeloppet, är att man vill slippa en tidsödande kvadratrotsberäkning. Eftersom programmet vid varje exekvering kommer att göra flera miljoner beräkningar med Complex-objekt är det viktigt att räkneoperationerna implementeras effektivt. Detta innebär att operationerna ska implementeras i rektangulära koordinater, som inte kräver några tidsödande beräkningar av trigonometriska funktioner. Var försiktig när du implementerar operationen mul så att du under beräkningarna inte råkar förstöra ett värde som du senare behöver. Tänk särskilt på fallet z.mul(z). 2 Orientering om användargränssnittet När du senare har genererat Mandelbrotmängden ska du visa en bild av mängden i ett fönster. Användargränssnittet är färdigskrivet (klassen se.lth.cs.ptdc.fractal.MandelbrotGUI). En länk till specifikationen av klassen finns på kursens hemsida. Studera specifikationen noggrant medan du läser vidare. Börja med att skriva en klass Mandelbrot med en main-metod som skapar ett objekt av MandelbrotGUI (använd konstruktorn utan parametrar). Kompilera och testa! I fönstret visas ett komplext talplan och fyra rutor som visar intervallen på planets koordinataxlar. Man kan förstora (”zooma in”) en del av planet genom att med musen dra ut en rektangel. Testa att detta fungerar, dvs att min- och maxvärdena på koordinataxlarna ändras när du zoomar. Överst i fönstret finns några knappar och menyer. Som du märker fungerar de ännu inte (avsikten är förstås att du ska implementera deras funktion). Exempelvis går det inte att avsluta programmet med Quit-knappen. Det finns också en textruta märkt Extra där du kan mata in vilka data du vill och hämta dem till programmet. Se avsnitt 7. Din main-metod ska kommunicera med användargränssnittet genom att fråga efter kommandon som användaren ger genom att trycka på knapparna. I MandelbrotGUI finns en metod getCommand som returnerar ett heltal som är numret på ett kommando. Vidare finns ett antal publika konstanter som är numren på kommandona (RENDER, RESET, osv). Lägg i main-metoden in en repetitionssats där du frågar efter ett kommando och därefter gör olika saker beroende vad som returneras. Använd en switch-sats — förbered med case-grenar för varje kommando men gör tills vidare bara en utskrift som identifierar kommandot. Testa! Ta bort utskrifterna efterhand som du implementerar de olika kommandona. Inlämningsuppgift alternativ 2 – Mandelbrot 0 101 maxIm width – 1 0 minRe maxRe height – 1 minIm Figur 4: Ritytans koordinatsystem och det komplexa koordinatsystemet. Implementera slutligen kommandona RESET och QUIT. RESET ska medföra att axlarnas minoch maxvärden återställs till de ursprungliga och att ritytan töms. QUIT ska medföra att programmet avslutas med System.exit(0). Kontrollera att du nu kan avsluta programmet genom att trycka på QUIT-knappen. Testa också RESET-funktionen. 3 Rita en cirkel (det svåraste problemet) I denna deluppgift ska du börja skriva en klass Generator som ska beräkna punkterna i Mandelbrotmängden och visa dem i fönstret. Det gäller att för varje punkt c i det komplexa talplanet (den del som syns i fönstret) avgöra om punkten tillhör Mandelbrotmängden eller inte. Om c tillhör mängden färgas motsvarande pixel svart (java.awt.Color.BLACK), annars färgas pixeln vit (java.awt.Color.WHITE). Till att börja med så nöjer vi oss dock med att rita enklare bilder än fraktaler, för att vi ska få ordning på de olika koordinatsystem som är inblandade i problemet. Man ska också kunna rita bilder med olika upplösning; det väntar vi också med. I uppgiften har vi att göra med två koordinatsystem. Det första systemet är det komplexa talplanet. I ritfönstret visas en del av detta: från början visas intervallet [−2.4, 0.8] av den reella axeln och intervallet [−1.4, 1.4] av den imaginära axeln. Användaren kan ändra intervallen genom att zooma med musen. Det finns operationer i MandelbrotGUI för att ta reda på intervallgränserna: getMinimumReal, getMaximumReal, getMinimumImag och getMaximumImag. Det andra koordinatsystemet är ritytans system (detta kallas i fortsättningen för ritsystemet). Detta system har som vanligt en bredd och en höjd räknat i pixlar. Det finns operationer för att ta reda på dessa värden (getWidth och getHeight). Ritsystemet har origo i övre vänstra hörnet av ritytan och y-axeln är riktad nedåt. Varje pixel i ritsystemet motsvaras alltså av ett komplext tal. Till exempel motsvaras punkten med index 0, 0 i ritsystemet av ett komplext tal med realdelen getMinimumReal() och imaginärdelen getMaximumImag(). I figur 4 visas ritytan. Den har bredden width pixlar och höjden height pixlar. minRe och maxRe är den reella axelns gränser, minIm och maxIm är den imaginära axelns gränser. Lägg märke till att y-axeln i de båda koordinatsystemen pekar i olika riktningar. Inlämningsuppgift alternativ 2 – Mandelbrot 102 Klassen Generator ska ha följande metoder: /** Ritar en bild i fönstret i användargränssnittet gui */ public void render(MandelbrotGUI gui); /** Skapar en matris där varje element är ett komplext tal som har rätt koordinater (se beskrivning nedan) */ private Complex[][] mesh(double minRe, double maxRe, double minIm, double maxIm, int width, int height); Anvisningar för mesh. Matrisen som returneras ska ha storleken height rader och width kolonner och ska användas för att till varje bildelement associera rätt komplext tal. Tänk dig alltså att matrisen läggs ovanpå ritytan. Fyll i rätt komplext tal i varje element i matrisen. Elementet med index [0][0] ska ha realdelen minRe och imaginärdelen maxIm, elementet med index [height - 1][width - 1] ska ha realdelen maxRe och imaginärdelen minIm. Kontrollera dina formler så att de stämmer. Anvisningar för render. I denna metod ska du göra följande: 1. Anropa disableInput i MandelbrotGUI. Anropet medför att knapparna i användargränssnittet inte reagerar på tryck och att beräkningarna inte störs av att användaren gör operationer i fönstret. 2. Anropa mesh för att skapa matrisen med komplexa tal. Kalla matrisen complex.5 3. Det behövs ytterligare en matris som ska innehålla bildpunkterna som ritas. Deklarera matrisen med Color[][] picture. Matrisen ska vara lika stor som ritytan (detta kommer att ändras senare, när du ska rita bilder med olika upplösning). 4. Gå igenom matrisen picture rad för rad och fyll i en färg på varje plats. Färgen väljs enligt följande: om complex[i][k] har absolutbeloppet > 1 så sätts picture[i][k] till Color.WHITE, annars väljs en unik färg för varje kvadrant av planet. Använd till exempel Color.RED till den första kvadranten, Color.BLUE till den andra, osv. Resultatet ska bli att en cirkel med fyra färger ritas. 5. När varje element har fått en färg ska bilden ritas med putData i MandelbrotGUI. 6. Anropa till sist enableInput för att återställa funktionen hos användargränssnittets knappar. Komplettera sedan din main-metod genom att implementera kommandot RENDER i switch-satsen med ett anrop till render-metoden. Testa! Som du ser är det är en hel del beräkningar att reda ut innan man kan skriva in och testa någon kod. Du sparar många felsökningstimmar vid datorn om du planerar noggrant med papper och penna. Om bilden inte ritas upp rätt så beror det oftast på att man räknat fel i mesh. Skriv då om och testa igen — det är ingen idé att gå vidare förrän detta fungerar. 4 Zoom-funktionen Du ska nu återgå till att skriva kod i main-metoden i Mandelbrot. Implementera ZOOM så att zoomning medför att bilden genereras om i det nya området. Detta ska dock bara göras om det 5 Man kan klara sig utan matrisen complex om man beräknar realdel och imaginärdel för rätt komplext tal när man behöver talet, dvs när man ska generera en bildpunkt. Beskrivningen i fortsättningen blir dock betydligt enklare om man använder sig av matrisen. Inlämningsuppgift alternativ 2 – Mandelbrot 103 Bildmatrisen picture Komplexmatrisen complex Välj det mittersta komplexa talet för varje bildelement Figur 5: Medium upplösning — varje bildelement innehåller 5 × 5 pixlar. redan finns en bild uppritad i fönstret. Om ritytan är tom, som den är när programmet nyss har startats eller efter en RESET-operation, ska inte render anropas. Testa! 5 Rita med olika upplösning Användaren kan i en meny välja upplösning på den resulterande bilden i fem nivåer, från mycket låg upplösning till mycket hög. Vid mycket hög upplösning ska varje bildelement ha bredden 1 och höjden 1, vilket ger att matriserna complex och picture har samma storlek (det är denna upplösning vi använt i föregående avsnitt). Vid lägre upplösning ska bildelementen vara större, enligt följande tabell: Resolution Pixel width Pixel height Very high High Medium Low Very low 1 3 5 7 9 1 3 5 7 9 Bildmatrisen ska nu vara mindre än komplexmatrisen, på så sätt att man delar antalet rader och kolonner med de värden som finns i tabellen för en viss upplösning. Detta ger en mindre bildmatris och snabbare beräkningar. För varje bildelement ska man nu välja det mittersta komplexa talet — se figur 5, där upplösningen är Medium. Ändra renderingsmetoden så att den tar hänsyn till den upplösning som användaren har valt. Provkör programmet och kontrollera att det fungerar — cirkelns periferi ska bli taggig i varierande grad beroende på upplösningen. Du måste noggrant testa så att programmet fungerar med alla upplösningar. Felaktiga formler leder här ofta till att man råkar indexera utanför den ena eller den andra matrisens gränser, vilket naturligtvis inte är acceptabelt. 6 Rita Mandelbrotmängden Nu ska du (äntligen) rita en bild av Mandelbrotmängden i stället för en cirkel. För vart och ett av c-värdena i bildelementens mittpunkter ska du beräkna en följd av komplexa tal med Mandelbrotformeln och med ledning av den sats som presenterades bestämma om punkten tillhör Inlämningsuppgift alternativ 2 – Mandelbrot 104 Mandelbrotmängden M eller inte. Använd förslagsvis högst 200 iterationer. Om punkten tillhör M sätter du bildmatrisens motsvarande element till Color.BLACK, annars till Color.WHITE. Gör inte fler iterationer än nödvändigt! 7 Avslutande förbättringar Du ska förbättra din Generator-klass så att den uppfyller följande krav: • Rita med färg om användaren har begärt det. I stället för att använda vit färg på alla divergerande punkter, låt dem få en annan färg som svarar mot ”hur snabbt” de divergerar. Av effektivitetsskäl bör man inte skapa nya färgobjekt varje gång man ska sätta en färg på en punkt. Det kan ju inte bli fråga om att använda fler än högst så många färger som man har iterationer, så dessa färger bör man skapa en gång och lägga i en vektor (lämpligen i konstruktorn i Generator). Jämför med vektorn grayLevels i deluppgift 5 i lab 11. • Valfritt: utnyttja Extra-rutan i användargränssnittet för att mata in data till programmet, till exempel antalet iterationer som ska göras i Mandelbrotföljden (när man har zoomat in på fina detaljer i bilden är det bra att använda många iterationer), eller för att tala om vilken färgskala som ska användas, . . . Inlämningsuppgift alternativ 3 – Draw 105 Inlämningsuppgift alternativ 3 – Draw I denna uppgift skapar du en enkel ritapplikation. Bakgrund I ritprogram, till exempel xfig, kan man rita figurer som man sedan kan behandla på olika sätt. Man kan till exempel ändra storlek på figurerna, flytta dem och ta bort dem. Här beskrivs ett extremt enkelt ritprogram. Det finns bara tre slags figurer: kvadrater, liksidiga trianglar och cirklar. Det enda användaren kan göra är att flytta omkring figurerna i ritfönstret genom att först klicka på en figur och sedan klicka på den nya positionen. Ritfönstret har följande utseende: I ritprogrammet beskrivs de gemensamma egenskaperna hos figurerna i en klass Shape. Denna klass används som superklass på klasserna som beskriver specifika figurer (kvadrat, triangel och cirkel). public abstract class Shape { protected int x; protected int y; /** Skapar en figur med läget x,y */ protected Shape(int x, int y) { this.x = x; this.y = y; } /** Ritar upp figuren i fönstret w */ public abstract void draw(SimpleWindow w); /** Raderar bilden av figuren, flyttar figuren till newX,newY och ritar upp den på sin nya plats i fönstret w */ public void moveToAndDraw(SimpleWindow w, int newX, int newY) { java.awt.Color savedColor = w.getLineColor(); w.setLineColor(java.awt.Color.WHITE); draw(w); x = newX; y = newY; w.setLineColor(savedColor); draw(w); } Inlämningsuppgift alternativ 3 – Draw 106 /** Undersöker om punkten xc,yc ligger "nära" figuren */ public boolean near(int xc, int yc) { return Math.abs(x - xc) < 10 && Math.abs(y - yc) < 10; } } I programmet håller man reda på figurerna som man skapar genom att man lägger in dem i en lista (ett objekt av klassen ShapeList). I följande program skapar man fem figurer, lägger in dem i listan och ritar upp dem: import se.lth.cs.window.SimpleWindow; import se.lth.cs.ptdc.shapes.ShapeList; public class ShapeTest { public static void main(String[] args) { SimpleWindow w = new SimpleWindow(600, 600, "ShapeTest"); ShapeList shapes = new ShapeList(); shapes.insert(new Square(100, 300, 100)); shapes.insert(new Triangle(400, 200, 100)); shapes.insert(new Circle(400, 400, 50)); shapes.insert(new Square(450, 450, 50)); shapes.insert(new Square(200, 200, 35)); shapes.draw(w); } } Parametrarna till konstruktorerna är figurens läge (x och y) och storlek (sidlängd eller radie). Klassen ShapeList har följande specifikation: /** Skapar en tom lista */ ShapeList(); /** Lägger in figuren s i listan */ void insert(Shape s); /** Ritar upp figurerna i listan i fönstret w */ void draw(SimpleWindow w); /** Tar reda på en figur som ligger nära punkten xc,yc; ger null om ingen sådan figur finns i listan */ Shape findHit(int xc, int yc): Operationen findHit används för att ta reda på vilken figur som användaren pekat och klickat på. Detta hanteras av klassen CommandDispatcher, som ansvarar för programmets kommunikation med användaren. Klassen har följande uppbyggnad: import se.lth.cs.window.SimpleWindow; import se.lth.cs.ptdc.shapes.Shape; import se.lth.cs.ptdc.shapes.ShapeList; class CommandDispatcher { private SimpleWindow w; private ShapeList shapes; public CommandDispatcher(SimpleWindow w, ShapeList shapes) { this.w = w; this.shapes = shapes; Inlämningsuppgift alternativ 3 – Draw 107 } public void mainLoop() { while (true) { // användaren klickar på en figur // användaren klickar på en ny position // figuren flyttas till den nya positionen } } } Uppgifter Råd: i nedanstående uppgifter är det lämpligt att börja med uppgift 1 till 3 men bara hantera kvadrater, så att man ser att allt fungerar. Komplettera sedan programmet med cirklar och trianglar. 1. Klassen Shape finns färdigskriven (se.lth.cs.ptdc.shapes.Shape). Skriv tre subklasser till Shape: Square, Triangle och Circle. Lägg klasserna i separata filer: (Square.java, Triangle.java, Circle.java). I subklasserna måste du definiera vad som avses med ”läget” hos en figur. Koordinaterna x och y kan för en kvadrat ange övre vänstra hörnet, för en triangel nedre vänstra hörnet, för en cirkel medelpunkten. Man ska kunna bestämma figurens storlek med en parameter till konstruktorn. När man ska rita en cirkel är det enklast att tänka sig cirkeln som en regelbunden månghörning med många hörn. I lösningen till en av övningsuppgifterna i kapitel 9 i läroboken finns en metod som ritar en cirkel. 2. Klassen ShapeTest finns i filen ShapeTest.java. I klassen utnyttjas en färdigskriven version av klassen ShapeList. Tag bort kommentartecknen på insert-satserna och testkör programmet. 3. Skriv in klassen CommandDispatcher. Modifiera också main-metoden i ShapeTest så att ett CommandDispatcher-objekt skapas och operationen mainLoop utförs. Testkör det nya programmet. 4. Implementera klassen ShapeList med hjälp av en ArrayList<Shape> (ett programskelett finns i ShapeList.java). Testa programmet med din version av ShapeList. För att göra detta måste du ta bort import-satsen där ShapeList importeras från filerna ShapeTest.java och CommandDispatcher.java. Javasystemet kommer då att hitta den ShapeList-klass som finns i den aktuella katalogen, det vill säga din egen klass. 5. Ändra programmet så att man inte behöver använda insert-satser i main-metoden för att lägga in figurer i listan. I stället ska uppgifter om figurerna som ska skapas läsas från en fil. I projektkatalogen finns en fil shapedata.txt med följande innehåll (filen specificerar samma figurer som i det tidigare exemplet): S T C S S 100 400 400 450 200 300 200 400 450 200 100 100 50 50 35 Inlämningsuppgift alternativ 3 – Draw 108 6. När man ska välja ut en figur att flytta känns det inte naturligt att man måste klicka på ett bestämt hörn (kvadrat och triangel) eller medelpunkten (cirkel). Det hade varit bättre om man kunde klicka på (eller nära) någon av figurens linjer. Detta kan man åstadkomma genom att skriva olika near-funktioner för de olika figurtyperna. Ändra ditt program så att fungerar på det beskrivna sättet. Det är lättast att skriva near-metoden i klassen Circle, så det är lämpligt att börja med den. Tips för kvadrater och trianglar: skriv en metod som undersöker om en punkt ligger nära en linje och anropa den med kvadratens fyra linjer och triangelns tre linjer. När man ska undersöka som en punkt ligger nära en sned linje är det enklast att räkna ut punktens avstånd till linjens båda ändpunkter och jämföra summan av dessa avstånd med linjens längd: xc,yc Utvidgningar Du ska nu förbättra ditt program med valfria utvidgningar. Diskutera i förväg med en handledare på vilket sätt du vill gå vidare. Välj att utvidga programmet med funktioner som du tycker är intressanta att implementera och som samtidigt gör att du tränar på sådant du behöver extra träning på. Nedanstående uppgifter är förslag på lämpliga utvidgningar: 7. Lägg till fler geometriska former i ditt program, till exempel rektangel och polygon. 8. Gör så att det går att byta konturfärg på vald figur genom att trycka på tangenterna R för rött, G för grönt, B för blått, och S för svart. 9. Gör så att figurerna flyttas med en animering: istället för att direkt rita ut figuren på sin nya plats, rita ut den på flera platser på vägen med en kort fördröjnig emellan så det ser ut som om den glider mjukt till sin nya plats. 10. Gör så att det går att flytta vald figur med piltangenterna. 11. Gör så att programmet sparar figurerna på filen, så att figurerna hade sina nya lägen nästa gång man körde programmet. Eclipse – en handledning 4 109 Eclipse – en handledning Inledning Här beskrivs Eclipse, den programutvecklingsmiljö som utnyttjas i programmeringskurserna. Mera information finns på: http://www.eclipse.org http://help.eclipse.org http://www.eclipse.org/downloads/ Eclipse hemsida. Enorma mängder information. Hjälpsidor för Eclipse, senaste versionen. Dessa hjälpsidor finns också lokalt i Eclipse-systemet, under Help Contents i Help-menyn. Nedladdning av Eclipse för olika datorer. Välj ”Eclipse IDE for Java Developers”. Det finns också många handledningar som är betydligt utförligare än denna, till exempel på www.vogella.com/articles/Eclipse/article.html. Att utveckla Javaprogram Ett Javaprogram består av en eller flera klasser som lagras i filer med tillägget .java. Dessa filer innehåller ”vanlig text” (bokstäver, siffror och andra tecken). Innan man kan exekvera (köra) programmet måste filerna kompileras (översättas) till ett annat format (bytekod) som datorn ”förstår”. Bytekoden lagras i filer med tillägget .class. Översättningen görs av en kompilator som heter javac, och programmet körs av en Javatolk som heter java. Allt detta kan man hantera manuellt. Då editerar man .java-filerna med Emacs eller en annan texteditor, kompilerar dem med javac och kör programmet med java. Det kan se ut så här: % emacs HelloWorld.java & % javac HelloWorld.java % ls HelloWorld.class HelloWorld.java % java HelloWorld Hello, world! % Starta en editor, skriv följande programtext: public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, world!"); } } Kompilera programmet Lista innehållet i katalogen Två filer: kompilerad kod och källkod Kör programmet Det är inget fel med att utveckla program på detta sätt, men det har en del nackdelar: stora program består av många klasser och det kan vara svårt att hålla reda på dem, man måste komma ihåg att kompilera varje fil som man ändrar, när kompilatorn hittar fel i ett program måste man i editorn hitta motsvarande rad i filen för att kunna rätta till felet, om man vill följa exekveringen av ett program måste man ha en separat ”debugger” (felsökningsprogram). Ett alternativt sätt att arbeta är att använda en integrerad utvecklingsmiljö (Integrated Development Environment, IDE). Det är ett program med ett grafiskt användargränssnitt där editor, Eclipse – en handledning 110 kompilator och debugger är integrerade. Man startar IDE-programmet och hanterar sedan alla moment i programutvecklingen inifrån denna miljö. Programutvecklingen består fortfarande av editering, kompilering och programkörning, men det blir enklare att utföra dessa moment — de utförs av IDE-programmet genom att man klickar på knappar eller väljer ur menyer. Det finns många olika IDE-program, mer eller mindre avancerade (Eclipse, IDEA, JBuilder, Netbeans, JCreator, . . . ). I programmeringskurserna utnyttjas Eclipse. Eclipse Översikt Eclipse är ett avancerat IDE-program som erbjuder all funktionalitet som behövs för utveckling av Javaprogram. Det finns också ett stort antal ”plugins” som utökar Eclipse med nya möjligheter (utveckling av webbprogram, databasprogram, diagramritning, andra programspråk än Java, osv). Programmet är gratis. Eftersom Eclipse har så många möjligheter är det inte helt enkelt att lära sig att använda fullt ut. Man måste sätta sig in i en hel del terminologi och lära sig hitta bland alla menyer och kommandon. Men grunderna är inte alltför komplicerade, och de beskrivs här. Några termer måste man känna till: Projekt Arbetsområde Perspektiv (project) Ett antal .java- och .class-filer som hör ihop, till exempel alla filer som behövs för en laboration eller en inlämningsupgift. Motsvaras av en katalog i filsystemet. (workspace) Det område där man sparar de projekt som man skapar. Motsvaras av en katalog i filsystemet (projekten är underkataloger till denna katalog). (perspective) Utseendet hos Eclipsefönstret. Ett perspektiv har ett antal fönster och knappar för kommandon. Det finns massor av möjligheter att ställa in Eclipse så att det fungerar på olika sätt (Preferences i Window-menyn). Varje inställningssida har en knapp Restore Defaults som ställer tillbaka alla inställningar till de ursprungliga. Användning av Eclipse Vi förutsätter i detta avsnitt att Eclipse har installerats (hur man gör skiljer sig något mellan olika datorer; se separata anvisningar). Vi förutsätter också att vi har skapat ett arbetsområde med ett projekt test0 som innehåller en fil HelloWorld.java och en kompilerad fil HelloWorld.class. Hur man skapar projekt och filer visar vi i nästa avsnitt. När man startar Eclipse får man ett fönster som visas i figur 6 (under Linux, det kan se något annorlunda ut på andra datorer): Om det inte står Java i fönstrets titelrad så är fel perspektiv aktiverat. Byt då perspektiv med Open Perspective i Window-menyn. I projektvyn (längst till vänster under Package Explorer) ser man de projekt man har skapat. När man klickar på pilen vid ett projekt så öppnas projektet så att man ser innehållet. .java-filerna finns i katalogen src under (default package), .class-filerna finns i en katalog bin som inte visas i projektvyn. Området i mitten är en texteditor. När man dubbelklickar på en fil i ett projekt så laddas filen in i editorn. Man kan editera flera filer samtidigt i olika flikar. I editorn arbetar man som i alla editorer: man skriver och tar bort text som vanligt, markerar text genom att dra med musen, klipper ut och klistrar in med control-x och control-v, och så vidare. Eclipse-editorn känner till hur Javaprogram ska formateras, så den gör automatiskt indragningar där de ska vara, stoppar in en högerparentes av rätt slag när man skriver en Eclipse – en handledning 111 Figur 6: Eclipse-fönster, Java vänsterparentes, och så vidare. Man kan till och med formatera om en hel fil så att programmet följer kodkonventionerna; det gör man med kommandot Format i Source-menyn. Eclipse-editorn kontrollerar också programmet och kompilerar det under tiden man skriver. Om man skriver något som bryter mot Javas regler så markeras rader som innehåller fel med kryss till vänster om raden. Man får en förklaring av felet genom att hålla musmarkören över krysset. Området under texteditorn har flera flikar. I Console visas utskrifter från program som körs, och där skriver man också indata till programmet. I Problems visas felmeddelanden som finns i filer som man sparat. Man kör ett program genom att högerklicka på filen som innehåller main-metoden i projektvyn och välja Run As > Java Application. (Eller genom att välja Run As > Java Application i Run-menyn eller genom att klicka på Run-ikonen i verktygsraden.) Om man råkat stänga till exempel editorfönstret utan att vilja det eller ställt till något annat med Javaperspektivet, så kan man återställa perspektivet till utgångsläget med kommandot Reset Perspective i Window-menyn. Skapa projekt och klass Nu visar vi steg för steg hur man skapar ett nytt projekt med en klass och hur man kör programmet. Projektet ska heta test1 och innehålla en klass Calculator med följande utseende: /** Calculator beräknar summan av två tal */ public class Calculator { public static void main(String[] args) { double nbr1 = 5.5; double nbr2 = 11.3; double sum = nbr1 + nbr2; System.out.println("Summan av talen är " + sum); Eclipse – en handledning 112 } } Detta är inte ett vettigt program, eftersom det bara kan beräkna summan av talen 5.5 och 11.3. Om man vill beräkna summan av två andra tal måste man ändra i programmet och kompilera om det. Något mera användbart skulle programmet bli om det läste in talen från tangentbordet, men vi gör det så enkelt som möjligt nu. Gör följande: 1. Skapa projektet test1. Välj File > New > Java Project, skriv namnet på projektet, klicka på Finish. 2. Skapa en fil Calculator.java. Markera projektet test1 i projektvyn, klicka på New Java Classikonen i verktygsraden, skriv namnet på klassen (Calculator), klicka på Finish. 3. Öppna projektet, src-katalogen och (default package) genom att klicka på pilarna. Dubbelklicka på Calculator.java för att ladda in filen i editorn. Som du ser så har Eclipse redan fyllt i klassnamnet och de parenteser som alltid ska finnas. Komplettera klassen med mainmetoden som visas ovan. 4. Spara filen. 5. Kör programmet genom att högerklicka på filen och välja Run As > Java Application. Utskriften som görs med System.out.println hamnar i konsolfönstret (om utskriften inte blir Summan av talen är 16.8 så har du skrivit fel i programmet). En del program behöver utnyttja klasser som inte finns i det aktuella projektet. (Egentligen gör alla program det, de använder Javas standardklasser, men dessa är alltid tillgängliga och man behöver inte göra något för att programmen ska komma åt dem.) Man måste för projektet ange var dessa klasser finns. Det gör man genom att högerklicka på projektet och välja Build Path > Configure Build Path. Sedan finns det två fall: • Klasserna som ska utnyttjas finns i ett annat projekt i samma arbetsområde. Klicka på Projects > Add. . . och markera projektet som innehåller klasserna. • Klasserna som ska utnyttjas finns i en biblioteksfil (.jar-fil, Java Archive) i ett annat projekt. Klicka på Libraries > Add JARs. . . och välj rätt .jar-fil. Om .jar-filen finns utanför arbetsområdet klickar man i stället på Add External JARs. . . och letar upp filen. Hitta fel i program Fel i program är av två slag: dels ”språkfel” som kompilatorn hittar (man glömmer ett semikolon, glömmer att deklarera en variabel, stavar fel till ett ord och liknande fel), dels ”logiska fel” som medför att ett program ger felaktigt resultat när det exekveras. Som exempel tar vi programmet som beräknar summan av två tal från föregående avsnitt. Satsen som beräknar summan och sparar den i variabeln sum ser ut så här: double sum = nbr1 + nbr2; Om vi i stället hade skrivit dubbel sum = nbr1 + nummer2; hade vi gjort två språkfel: typen för reella tal heter double, inte dubbel, och variabeln där värdet av det andra talet finns heter nbr2, inte nummer2. Kompilatorn markerar alla fel av denna typ, och de är för det mesta enkla att korrigera (det kan vara svårt att tolka felmeddelandena i början, men det lär man sig efterhand). Logiska fel är svårare att hitta. Antag att vi hade skrivit satsen på följande sätt: Eclipse – en handledning 113 double sum = nbr1 - nbr2; Detta är en helt korrekt sats och programmet kan kompileras och köras utan problem, men resultatet blir inte korrekt (man får utskriften Summan av talen är -5.8). Det här programmet är så litet och felet är så uppenbart att man kan hitta felet genom att bara titta på programmet. Men i allmänhet är det inte så enkelt: ”riktiga” program är mycket större och felet kan vara mycket mera komplicerat. För att hitta logiska fel behöver man skaffa sig information om vad som händer under exekveringen av programmet. Ett sätt att göra det är att lägga in utskrifter av viktiga variabelvärden på lämpliga ställen i programmet (det är ju egentligen det vi gjort i ovanstående program, där vi omedelbart efter summeringen skriver ut summavärdet). Detta sätt är dock svårhanterligt och tidsödande. Bättre är att utnyttja en debugger (”avlusare”). Då får man möjlighet att själv kontrollera exekveringen av programmet: man kan köra programmet rad för rad och man kan under tiden titta på hur variabelvärdena ändras. Man kan också sätta brytpunkter i programmet. När man sedan startar programmet så stoppas exekveringen när den når en brytpunkt. Man sätter en brytpunkt på en rad genom att dubbelklicka till vänster om raden, och man tar bort en brytpunkt på samma sätt. När man har satt en brytpunkt kan man starta debuggern. Det gör man genom att högerklicka på filen och välja Debug As > Java Application. Eclipse byter till debug-perspektivet. Fönstrets nya utseende visas i figur 7. I mitten finns editorfönstret. Den rad som står i tur att exekveras är markerad med en pil. Ovanför editorfönstret finns en vy med information om det körande programmet. I verktygsraden finns ikoner för kommandon. De viktigaste är: Figur 7: Eclipse-fönster, debug Eclipse – en handledning 114 Resume — fortsätt exekveringen till nästa brytpunkt eller till programmets slut. Terminate — avsluta exekveringen av programmet. Step over — exekvera en rad i programmet. Step into — exekvera till nästa rad, gå in i metoder som anropas. Importera och exportera filer När man arbetar med Eclipse har man normalt alla sina filer i projekt i arbetsområdet. Om man har filer någon annanstans kan man importera dem till ett existerande projekt. Det gör man genom att högerklicka på projektet, välja Import. . . > General > File System och leta upp filerna. Genom att i stället välja Export kan man exportera filer. Man kan också importera och exportera hela projekt. Det gör man genom att högerklicka i projektvyn och välja Import > General > Archive File respektive genom att högerklicka på ett projekt och välja Export > General > Archive File. Ett arkiverat projekt sparas som en komprimerad .zip-fil. Att skapa nya projekt (för egna övningar) Du kan skapa egna projekt i Eclipse, där du kan öva på att skriva egna program. Om du lägger dina program i egna projekt hamnar de i en egen katalog i Eclipse, precis som laborationerna. Hur du skapar ett nytt projekt ser du i avsnittet ”Skapa projekt och klass” (som börjar på s. 111). Här beskrivs hur du gör kursens klasser (SimpleWindow, Square, m. fl.) tillgängliga för ditt nya projekt. Om du inte gör detta så kommer Eclipse att ge felmeddelanden då du försöker använda dessa (t. ex. i import-satser). • Högerklicka på ditt nya projekt i vänsterspalten i Eclipse. • En lång meny dyker upp, där du väljer Build Path > Configure Build Path... . • I rutan som dyker upp väljer du fliken Libraries. • Klicka på Add JARs... • I listan över projekt öppnar du cs_pt och väljer filen cs_pt.jar. Klicka OK för att lämna projektlistan, därefter OK för att lämna Build Path-inställningarna. Nu ska ditt nya projekt kunna utnyttja kursens klasser, precis som laborationerna.
© Copyright 2024