Till KTH:s startsida Till KTH:s startsida

Labb 2

Brownsk rörelse

1 Målsättning

Under denna laboration ska du skriva ett fristående Java-program i flera delar. Följande saker ska du kunna när du är klar med labben:

  • hur man använder objekt dels som data och dels som moduler med väldefinierat ansvar
  • hur man kan simulera ett fysikaliskt förlopp
  • hur man får beräkningen att pågå parallellt med grafik och interaktion
  • hur man ordnar så att man interaktivt kan ändra på parametrar
  • hur man ritar ett UML-diagram som visar hur objekten hänger ihop

Man måste som vanligt lägga ned mer arbete än de schemalagda passen.

2 Lästips

Vi går igenom det mesta som behövs på föreläsningar och övningar. Om du vill läsa någon bok som komplement så försök leta efter följande begrepp:

  • Klass med konstruktor, klass med flera konstruktorer
  • Grafiska komponenter, både awt- och swing-paketen och särskilt JFrame, JPanel, JButton, JScrollbar.
  • Vektorer (fält, arrays) av objekt
  • Animering/simulering med hjälp av trådar, görs med någon av klasserna Thread eller Timer.
  • Händelser och lyssnare, särskilt hur man lyssnar påknappar och scrollbars.
  • Större exempel där olika klasser har ansvar för olika saker och hur de kan känna till varandra och kommunicera.

Labbanvisningen är inte riktigt kronologiskt ordnad efter vad ni ska göra vid de olika labbtillfällena. Avsnitt 3-5 beskriver uppgiften i stora drag, avsnitt 6 tar upp vad programmets olika delar ska göra medan avsnitt 7 ger ett förslag på arbetsordning och ytterligare tips för varje del. Det är därför lämpligt att ni läser åtminstone t.o.m. 1 avsnitt 6 innan ni börjar skriva någon programkod. Betygshöjande extrauppgifterna kommer i avsnitt 9 och sist, i avsnitt 10, finns några förslag till utvidgningar som inte ger poäng.

3 Uppgiften

Skriv ett fristående Java-program som simulerar ett stort antal små partiklar som rör sig slumpmässigt och oberoende av varandra i två dimensioner. Programmet ska rita upp partiklarna i aktuella lägen vid olika tidpunkter så att en rörlig bild erhålls och visar hur partiklarna sprids med tiden. Användaren ska kunna påverka simuleringen medan den pågår. Man ska kunna ändra någon eller några parametrar och ev. också få ut statusinformation från simuleringen. Det ska finnas minst två olika möjligheter till interaktion med programmet. När partiklarna når kanten av ett rektangulärt område ska de "fastna" på kanten, som om de klibbade fast och andra partiklar ska i sin tur kunna fastna på dessa (se tips längre fram). Programmet ska ha en modulariserad struktur med en vettig uppdelning på klasser med olika ansvarsområden. De simulerade partiklarna ska vara separata objekt. Du vill kanske lägga det tjusiga färdiga programmet på din egen webbsida. Det är ganska lite arbete att göra om det till en applet i så fall.

4 Fysikalisk bakgrund

En botanist vid namn Robert Brown upptäckte 1827 att pollen studerade i mikroskop tycktes röra påsig . Han trodde sig se en bekräftelse på att pollen kunde röra sig av egen kraft. I själva verket observerade han en konsekvens av molekylernas värmerörelse. Einstein beskrev fenomenet mer utförligt 1905 och på1920-t alet utvecklade Norbert Wiener matematiken som kom att kallas för Wienerprocesser som modellerar Browsk rörelse. Det finns enormt mycket att läsa om Brownsk rörelse, både på Internet och naturligtvis i böcker, er lärobok i Termodynamik är ett exempel. Här är några länkar till mer information:

http://abel.math.umu.se/fo_info/brown/
http://www.britannica.com/nobel/micro/88_96.html
http://en.wikipedia.org/wiki/Brownian_motion

Genom att söka på lämpliga nyckelord i någon sökmotor kan man hitta mycket, mycket mer ...

5 Simulering

Det finns åtminstone tre olika sätt att simulera rörelsen:

  • Partiklarna rör sig i ett rutnät och flyttas slumpmässigt en ruta i varje tidssteg, åt vänster höger, upp eller ned.
  • Partiklarna rör sig i ett vanligt xy-plan och förflyttas i x- respektive y-led med vektorn (dx, dy) i varje tidssteg där dx och dy är oberoende och normalfördelade (vad det betyder lär man sig i kursen i Sannolikhetsteori och statistik.).
  • Partiklarna förflyttas i varje tidssteg sträckan L i en slumpmässig riktning.

Vi väljer det sista alternativet. Det betyder att varje partikel ska kunna utföra en slumprörelse genom att slumpa en riktning, dvs en vinkel v mellan 0 och 2π, och därefter ändra sin position i xy-planet med (L cos v, L sin v) där L är ett ganska litet tal, L = 3 kan vara lagom att börja med.. Vi betraktar partiklarna som oberoende och tar inte hänsyn till att de kan krocka. Det är helt OK att flera partiklar fastnar så nära varandra att de helt eller delvis överlappar.

5.1 Heltal eller flyttal

När man simulerar ett fysikaliskt förlopp vill man vanligen räkna så noggrant som möjligt. Man gör inte avrundningar till heltal under sina beräkningar och räknar vidare med de avrundade värdena. Heltalsvärden behövs dock när man ska rita i ett pixelindelat fönster. Låt alltså partiklarnas x- och y-värden vara flyttal (double eller float) vilka avrundas till heltal då de ska ritas upp. Se till att de avrundade värdena bara används för ritning och inte påverkar beräkningarna! Om vi simulerar 200 partiklar och låter alla partiklar starta mitt i fönstret där de visas kan det se ut så här i början och efter en liten stund, se figur 1. I figur 2 visas hur det kan se ut när partiklarna har börjat fastna påk anten och när nästan alla har fastnat. Partiklarna är väldigt små här, diametern är nog bara en pixel. Gör dem större om ni vill!

first second


Figur 1: Partiklarna vid två olika tidpunkter.

Kanten1 Kanen2


Figur 2: Partiklarna fastnar på kanterna av ett område.

6 Uppdelning i olika ansvarsområden

Det program vi ska konstruera nu är tillräckligt komplicerat för att det ska vara motiverat att dela upp det i flera delar. Vi ska här använda en teknik där olika objekt tar hand om olika uppgifter. Den föreslagna uppdelningen på ansvarsområden är också en standardteknik som fungerar i flera sammanhang. Tekniken kan självklart användas för andra simuleringar och animeringar av fysikfenomen men också för en del spelprogram. Ett av objekten har ansvar för att starta det hela i ett eget fönster. Detta objekt skapar de övriga ansvarsobjekten. Två av de senare objekten är synliga grafiska objekt och det är huvudobjektets ansvar att placera ut dem så att de blir synliga i fönstret. En uppdelning på fyra ansvarsobjekt kan se ut så här: 

Model

Detta objekt innehåller den fysikaliska/matematiska modellen. Här finns uppgift om antalet partiklar. Model-objektet "äger" alla partikel-objekten och skall bl.a. innehålla en metod som utför slumpflyttningen av samtliga partiklar. 

View

Detta objekt ansvarar för att rita upp partiklarna i aktuella positioner. Objektet är också bilden av simuleringen.

Simulation

Detta objekt håller koll på tiden. Det ser till att modellen med jämna mellanrum uppmanar alla partiklar att flytta sig samt beordrar View-objektet att rita om sin bild så att vi ser hur partiklarna rör sig. Simulation-objektets uppgift kan istället skötas av ett objekt av swing-klassen Timer. Då slipper man hela Simulationklassen och skapar ett Timer-objekt i någon av de andra klasserna, förslagsvis i View.

Manipulation

Detta objekt ansvarar för interaktionen med användaren så att han/hon kan ge order till programmet medan simuleringen pågår. Det går till så att objektet skapar några grafiska kontrollkomponenter, t.ex. "skjutreglage" och knappar på skärmen och ser till att användaren kan påverka simuleringen eller få information om den genom dessa. 

Innan man börjar skriva klasserna som beskriver de olika objekten bör man bestämma sig för gränssnittet mellan dem. Detta betyder att man bestämmer vilka metoder och instansvariabler som skall vara synliga utåt för varje objekt. Gränssnittet skall alltid hållas så litet som möjligt. Deklarera de metoder och variabler som behövs, inte fler! I den här labben kommer ni att få färdiga förslag till gränssnitt mellan objekten. Försök förstå hur objekten är tänkta att samverka genom att studera gränssnitten innan du sätter igång att programmera. Förslagen är givna som skissartade skelett, de är inte kompilerbara moduler. Kommentarerna i skeletten är inte Javakommentarer. Ni måste själva komplettera alla klasser innan de kan provköras. Vissa behöver kompletteras mycket. Det kan behövas instansvariabler, klassvariabler och instansmetoder. Information om vilka klasser som ska importeras till varje modul saknas också. De flesta grafiska komponenter som används i förslagen är från paketet Swing. 

class BrownAnimation extends JFrame {
  BrownAnimation ()

     Skapar några ansvarsbjekt och visar två av dem i fönstret 
     Sätt layoutmanager, storlek och annat som behövs

  public static void main (String[] arg)
    Skapar objektet av BrownAnimation
}

Figur/kod 3: Förslag till skelett för huvudobjektets klass.

6.1 Paketsynlighet

I de förslag som ges är inte alla metoder som ska anropas utifrån märkta med public. Inte heller är alla instansvariabler private. Det står oftast varken private eller public. Om man man inte skriver någon av dessa modifierare så får variabeln eller metoden paketsynlighet. Det betyder att den är tillgänglig för andra klasser inom samma paket. Om man inte säger något alls om paket i sina klasser (och det behöver ni inte göra) så betraktas den aktuella filkatalogen som ett paket. Paketsynlighet är en nivå av skydd som ligger mellan private och public och är ofta lagom för klasser som tillhör samma programmeringsprojekt. Vi låter här de flesta metoder och variabler ha paketsynlighet. Då kan klasser inom paketet komma åt variablerna för att läsa av eller ändra värdena utan att man behöver använda metoder för det. Metoder som anropas av Java-systemet måste vara public, Det gäller t.ex. main-metoden och paint-metoderna. De är förstås public här också.

6.2 Huvudobjektet

Huvudobjektet ska vara det fönster som visar simuleringen och dess interaktion, därför får det vara en subklass till JFrame. Objekt av JFrame är fristående fönster med ram. Huvudobjektet skapar de övriga ansvarsobjekten och placerar ut dem som är grafiska. Allt detta bör ske direkt när programmet startar och placeras därför i klassens konstruktor. Ett lämpligt skelett för huvudobjektets klass finns i figur 3.

6.3 Gränssnittet för Model-objektet

Förslag till skelett för Model-objektets klass finns i figur 4. Model-objektet ansvarar för den fysikaliska/matematiska modellen. Här finns en vektor med partikelobjekt samt den metod som ser till att alla partiklarna flyttas ett steg. Eftersom vi använder ett separat objekt för att rita partiklarna måste det finnas något sätt för ritobjektet att nå partiklarnas positioner utifrån. Den enklaste lösningen är att låta partikelvektorn vara synlig utåt liksom även alla partiklarnas positioner. Så är det också i figur 4 där particle har paketsynlighet. Det är självklart också tillåtet att göra åtkomstmetoder för allting! Framstegningen av tiden sker på initiativ från Simulation-objektet eller Timerobjektet men det är Model-objektet som "vet" vad som ska ske i varje tidssteg, 6

class Model {
    Simuleringstillståndet, en vektor med partiklar,
    kan läsas av utifrån
    Particle[] particle;

    Model ()
      Skapar partiklarna
    void moveAll()
      Utför slumpflyttning av alla partiklar 
    void setL( double value )
      Ändra partiklarnas flyttsträcka L
    void setTimestep( int ms )
      Ändrar tidssteget mellan omritningarna
}

Figur 4: Förslag till skelett för Model-objektets klass.

Det måste det finnas en metod för flytt av partiklarna i Modelobjektet som kan anropas med jämna mellanrum. Det är metoden moveAll(). Slutligen behöver Manipulation-objektet ha möjlighet att ändra i modellen, ändringar kan göras genom att synliga variablers värden ändras eller, vilket är lite snyggare, med hjälp av mutatormetoder som ändrar värdena. Vad som skall ändras får ni bestämma själva. I skelettet ovan föreslås att L och tidssteget för ritning ändras. Minst en vettig möjlighet till påverkan av simuleringen skall finnas och minst två olika sätt för användaren att interagera. Fler förslag finns senare i texten. I beskrivningen av gränssnittet bör vi också ta med konstruktormetoden för klassen. Detta är den metod som automatisk anropas när objekt skapas med new-operatorn. Konstruktormetoden ska alltid ha exakt samma namn som klassen och skiljer sig från vanliga metoder genom att man inte ska ange någon resultattyp.

6.4 Klassen Particle

Klassen Particle skall användas för att representera partiklarna. Många objekt av klassen ska skapas och det sker i Model-objektet. Ett skelett till Particle finns i figur 5. En partikel har två "moder", rörlig eller stilla. Detta ska lagras som en egenskap hos partikelobjekten. Klassen ska alltsåh a en instansvariabel som sparar information om ifall partikeln fortfarande rör sig eller har stannat. Det räcker inte att avgöra detta genom att testa partikelns läge. I början av arbetet kan isMoving utelämnas eller ignoreras.

class Particle {
    partilkelns läge
    double x, y;

    Rörlig eller ej, rörlig från början
    boolean isMoving = true;

    Particle ()
       slumpa startpositionen

    Particle (double xs, double ys)
       sätt startpositionen till (xs,ys)

    void randomMove()
       ändrar partikelns position
}

Figur 5: Förslag till skelett för klassen Particle.

6.5 Gränssnittet för View-objektet

View-objektet ansvarar för att rita partiklarna i sina aktuella lägen. För att kunna göra detta måste View-objektet känna till vilket Model-objekt den ska hämta sina värden ifrån. Detta sköter man enklast genom att ha en instansvariabel i View som innehåller en referens till Model-objektet. Ett vanligt sätt att ge en sådan variabel rätt värde är att låta View-klassens konstruktormetod ta Model-objektet som parameter. 

class Simulation extends Thread {
    Simulation( Model m, View v )
       Sparar referenser till objekten m och v
       samt startar simuleringen

    public void run()
       Stegar fram tiden i modellen och begär omritning
       med jämna mellanrum
}

Figur 8: Förslag till skelett för Simulation-objektets klass.

6.7 Gränssnittet för Simulation-objektet

Simulation-objektet ansvarar för att tiden stegas fram. För att åstadkomma en riktigt bra simulering krävs att tiden stegas fram parallellt med att programmet sköter andra saker, t.ex. inmatning från användaren. Java har en speciell mekanism inbyggt för denna typ av parallell exekvering, s.k. trådar (engelska: threads). För att få Simulation-objektet att exekvera självständigt som en tråd måste man göra tre saker: 1. Låt klassen vara en utvidgning av klassen Thread 2. Implementera en metod med namnet run som gör det verkliga jobbet 3. Starta tråden genom att anropa metoden start när objektet skapats (förslagsvis i konstruktormetoden). Simulation-objektet måste känna till vilket Model-objekt och vilket View-objekt som det ska ge sina "order" till. Det är en del av Simulation-objektets tillstånd att veta detta. Simulation-objektets konstruktor får därför referenser av typerna Model och View som parameterar.

6.8 Klassen Timer - ett enklare alternativ till Simulation

I det här programmet har Simulation-klassen en enkel uppgift: Den ska se till att följande upprepas (i oändlighet eller tills man stänger programmet):

  • vänta en stund
  • gör något (flytta alla partiklar samt rita om bilden)

Det hela ska ske till synes samtidigt som användaren kan kommunicera med programmet, manipulera ev. knappar och scrollbars. Det finns en klass i swing-paketet som kan utföra just en sån här uppgift, klassen Timer. Den är lämplig att använda just för animeringar. För mer avancerad tråd-användning, t.ex. om trådar ska vänta på varandra o.dyl. måste man använda Thread. Timer-objektet kopplas till något objekt som ska vara en ActionListener med en metod actionPerformed. Timer-objektet kommer att generera ett ActionEvent med ett visst tidssteg så att metoden actionPerformed anropas med 11 jämna mellanrum. Tidssteget motsvarar "vänta en stund" och innehållet i actionPerformed motsvarar "gör något" enligt ovan. Exempel på användning av Timer kommer att tas upp på föreläsningar samt ev. också på någon övning. Läs gärna också i javadokumentationen! Det finns tre st Timer-klasser i Java-biblioteken men bara en i swing-paketet, den heter javax.swing.Timer. 12 class Manipulation extends JPanel implements AdjustmentListener { Manipulation( Model m ) Skapar skjutreglagen public void adjustmentValueChanged( AdjustmentEvent e ) Ändrar modellens parametrar enligt spakarnas lägen } Figur 9: Förslag till skelett för Manipulation-objektets klass.

6.9 Gränssnittet för Manipulation-objektet

Manipulation-objektet ansvarar för att ta hand om användarens inmatning i form av t.ex. knapptryckningar eller ändring i scrollbars. Inmatningen används sedan på något sätt för att ändra parametervärden i modellobjektet eller hämta information från det. Därför måste Manipulation-objektet hålla reda på vilket Model-objekt som avses ( som ska manipuleras). Detta fixar vi genom att låta konstruktormetoden ta en referens till Model-objektet som parameter och lagra detta värde i en instansvariabel. När man ska placera ut ett antal knappar, skjutreglage el. dyl. är det ofta lämpligt att samla dessa i en (osynlig) ruta påsk ärmen. För detta ändamål finns klassen JPanel. Vår Manipulation-klass får bli en utvidgning av JPanel och Manipulationobjektet kan läggas i huvudobjektet med hjälp av add. Samtidigt använder Manipulation-objektet självt add för att lägga till skjutreglage och knappar som objekt att visa inom sig (se figur 10). När användaren ändrar på en Scrollbar anropas automatiskt en metod med namnet adjustmentValueChanged, förutsatt att man "beställt" detta genom ett anrop av addAdjustmentListener där Manipulation-objektet registreras som lyssnarobjekt till det aktuella skjutreglaget. För knappar heter motsvarande metoder actionPerformed respektive addActionListener. Det går bra att låta klassen Manipulation vara både AdjustmentListener och ActionListener. Ett exempel med dessa visas på föreläsning.

7 Implementation av klasserna

Förslag till arbetsordning

Detta förslag behöver inte alls följas i detalj. Det som krävs är att uppgiften löses och att de krav på modularisering som formuleras i början samt i slutet under rubriken "Redovisning av grunduppgiften" uppfylls. Dessutom, även om arbetsordningen innehåller mycket information så är det mycket som ni måste bestämma och komma på själva.

Steg 1 - Klassen Particle

Börja med att skriva klassen Particle enligt gränssnittet i figur 5. När Particle går att kompilera testa dess funktion så här

class TestOfParticle {

    public static void main (String[] x) {

        // Partikel skapas i pos (50,50) och
        // får sedan göra 10 slumpflyttningar.

        System.out.println("Skapa partikel i pos (50, 50), flytta 10 gånger");
        Particle pia = new Particle (50, 50) ;
        for (int i=0; i<10; i++) {
            System.out.println
                ("x= " + pia.x + "   y= " + pia.y);
            pia.randomMove();
        }

        // Partikel skapas i slumpmässig 
        // position och får sedan göra en 
        // slumpflyttning

        System.out.println
            ("\n\nSkapa partikel i slumpposition, flytta 10 gånger");
        Particle per = new Particle();
        for (int i=0; i<10; i++) {
            System.out.println
                ("x= " + per.x + "   y= " + per.y);
            per.randomMove();
        }
    }
}

Kopiera detta till er egen katalog och kör det! Testprogrammet skapar två partikelobjekt, ett med startposition (50, 50) och ett med slumpat startläge. Metoden för slumpflyttning anropas och partiklarnas nya lägen skrivs ut. Kolla att det ser rimligt ut innan ni går vidare! Det är frivilligt att köra testprogrammet.

Steg 2 - Huvudobjektet - JFrame

Skriv en första version av den här klassen där inga andra objekt skapas. Använd t.ex. programmet SmallJFrame från föreläsning som förlaga. När ett fönster syns på skärmen, gå vidare! Både View- och Manipulation-objekten ska senare synas i huvudobjektet. De läggs dit med metoden add(). Klasserna JFrame och Frame har BorderLayout som standard. Det enklaste är nog att byta till FlowLayout.Det går att behålla BorderLayout men då måste ni läsa lite mer om parametrarna till metoden add() Vill man ha större kontroll över fönstrets layout kan man välja en mer avancerad layout-manager eller jobba mer med JPanel - objekt. Det krävs dock inte alls att ni gör någon avancerad layout. Huvudobjektet kan till slut se ut ungefär som visas i figur 10.

Steg 3 - Ett första View-objekt

Gör en första version av klassen View. Skriv den med endast en parameterlös konstruktor, strunta alltså i (Model m) så länge. Ge den en storlek och en bakgrundsfärg. Skapa ett objekt av View och lägg detta i BrownAnimation (jämför med uppgiften med rutor påö vning 5-6). En lämplig LayoutManager bör först sättas i BrownAnimation, t.ex. FlowLayout. Provkör! När ni ser View-objektet som en färgad ruta i BrownAnimation, gå vidare!

Steg 4 - Model

Skriv nu en startversion av klassen Model. Skapa en enda partikel i Model eller hela partikelvektorn direkt. Partiklarna skapas i Models konstruktor. När partiklar skapas, använd den Partikel-konstruktor som slumpar positionerna så de får olika lägen. Kompilera Model. Ändra nu i View så att View känner till vilken Model den ska rita upp! Låt View:s konstruktor ta en Model-referens som parameter och spara denna referens för senare användning. Definiera en metod i View (vad ska den heta ?) som ritar upp

  • Huvudobjektet
  • ScrollBar
  • ScrollBar
  • View
  • Manipulation

Reglage

Figur 10: Huvudobjektet placerar ut View- och Manipulation-objektet genom att anropa add. På samma sätt placerar Manipulation-objektet själv ut de två skjutreglagen. partiklarna eller partikeln om ni bara skapat en. Låt BrownAnimation skapa ett Model-objekt som också skickas till View-objektet som parameter. Om ni började med en enda partikel i Model, inför nu vektorn. Skapa partiklar, låt View rita alla. När ni vid provkörning ser många partiklar i olika lägen i View-rutan, gå vidare!

Steg 5 - Låt partiklarna röra sig

Alternativ 1 - Simulation

Simulation-objektet ansvarar för att anropa metoden moveAll i Model-objektet med jämna mellanrum för att åstadkomma animeringen. Vi använder här Javas trådar (eng. threads) som sköter detta på ett elegant sätt. En tråd är ett objekt som kan exekvera parallellt med att andra saker pågår i programmet. Genom att låta Simulation-objektet vara en tråd kan vi genom en enkel repetition åstadkomma att moveAll anropas gång på gång. Tråden startas genom att man anropar metoden start, förslagsvis från konstruktorn. Du ska inte själv definiera start, den finns färdig för alla trådar. När tråden startas anropar systemet metoden med namnet run som vi ska definiera. run ska innehålla en oändlig slinga som gång på gång anropar moveAll. Efter varje moveAll ska vi också anropa repaint i View-objektet för att säkerställa att bilden ritas om. Genom att lägga in en kort fördröjning i slingan får vi effekten att moveAll anropas "lagom" ofta. En fördröjning får man genom att använda den färdiga metoden sleep som ger en paus på ett antal millisekunder. En liten komplikation är att sleep kan avbrytas på ett sätt som måste tas om hand av den som anropar den (d.v.s. vi!). Det gör man genom att använda en speciell try/catch-sats. För att åstadkomma en fördröjning på50 ms kan man skriva på detta sätt:

try {
   sleep( 50 );
} catch ( InterruptedException e ) {
   e.printStackTrace()
}

Figur 9: 

Alternativ 2 - Timer

Skapa ett Timerobjekt t.ex. i klassen View. Låt View själv vara den ActionListener som Timern jobbar mot. Utvidga således View-klassen så att den implements ActionListener och har metoden actionPerformed() där flyttning av alla partiklar och omritning med repaint() görs. När Timer-objektet skapas anges som parametrar till dess konstruktor först vilket tidsintervall det ska vara mellan händelserna och sedan det objekt som utför arbetet. Läs mer om Timer i böcker eller javadokumentationen! På någon föreläsning tar vi (förmodligen) upp ett exempel med Timer.

Steg 6 - Partiklarna fastnar på kanten

Här blir det inte så detaljerade instruktioner. Partiklarna ska fastna på kanten till ett område som ni definierar själva i någon av klasserna, Model och Particle ligger närmast till hands. Rektangulärt område är nog enklast men ni får välja en annan form. När en partikel når kanten så ska partikelobjektets tillstånd ändras (variabeln isMoving) och partikeln ska inte flyttas mer. Det är tillräckligt att lämna partikelns position som den är när man konstaterar att den är på eller över kanten. Ni behöver inte se till att alla partiklar ligger exakt på kanten. Stillastående partiklar ska ritas ut. Det ser trevligt ut om de rörliga och de stilla partiklarna har olika färg men det är inget krav.

Steg 7 - Manipulation

Manipulation-objektet ska skapa minst två skjutreglage eller knappar som vi kan använda för att styra simuleringen eller få information om den. Manipulation-objektet detekterar att man tryckt påen knapp eller ändrat i skjutreglaget och ändrar i eller hämtar info från Model-objektet. För att kunna skapa JScrollBar- eller JButton-objekt måste du veta vilka parametrar dess konstruktorer behöver. Denna information hittar man förstås i dokumentationen på webben Om du inte tidigare läst påSuns sida där Java-klasserna dokumenteras så kan det vara dags nu: Den första URL:en avser den Javaversion som vi har här på CSC nu, den andra avser den allra senaste versionen.

http://java.sun.com/j2se/1.5.0/docs/api/
http://java.sun.com/javase/6/docs/api/

I den långa spalten i nedre vänstra hörnet finns alla klasser i bokstavsordning. Användning av JButton och/eller Button kommer att visas på föreläsningar eller Följer man instruktionerna ovan och skapar två skjut reglage så får man en bild på sin skärm som liknar figur 10 men Manipulation-objektet är mindre, liksom även JScrollBar-objekten. Dessa är så små att de är lite svåra att använda som som reglage. Alla grafiska komponenter har en metod som heter setSize som man gärna tror ändrar på objektets storlek. setSize fungerar så bara i vissa lägen. Tyvärr fungerar den varken på JScrollBar- eller Scrollbar- objekten här och inte heller direkt på Manipulation-objektet.

Labben blir godkänd med ett sådant litet Manipulation-objekt men om ni vill åtgärda det så finns det ett enkelt sätt. Alla Swing-komponenter har metoden setPreferredSize() som just sätter den storlek man vill ha. Om variabeln s refererar till en JScrollBar så kan man sätta den storlek man vill ha med t.ex. s.setPreferredSize(new Dimension(20, 150)); JScrollBar-objektet får nu bredd 20 och höjd 150. När användaren flyttar ett skjutreglage anropas adjustmentValueChanged och efter en knapptryckning anropas actionPerformed. I adjustmentValueChanged ska vi läsa av skjutreglagets värde och översätta detta till ett lämpligt värde för den parameter i modellen vi vill ändra. Observera att värdena från en JScrollBar alltid är heltal. Påm otsvarande sätt skall man i metoden actionPerformed definiera effekten av en knapptryckning. Vad skall manipuleras? Det skall finnas minst tvåo lika möjligheter till interaktion med programmet och minst en möjlighet att påverka simuleringsförloppet. Manipuleringen kan ske t.ex. genom Scrollbars eller knappar. I swing finns ocksåen komponent som heter JSlider som kan vara lämplig att använda. Förslag påp arametrar att variera samt andra manipuleringsmöjligheter finns nedan. Ni får gärna införa andra egenskaper hos modellen och komma med egna förslag påma nipulering.

  • sträckan L som partiklarna rör sig i varje tidssteg
  • tiden mellan förflyttningarna och uppritningarna av partiklarna
  • möjlighet att stoppa simuleringen (partiklarna står still) och återuppta den igen
  • möjlighet att starta om simuleringen från början med samma antal partiklar eller med annat antal än förra gången.
  • möjlighet att få utskrivet i terminalfönstret hur många partiklar som har stannat (eller fortfarande är rörliga)

Partiklar som fastnar

Ta hänsyn till om partiklarna hamnat nära varandra. Låt partiklarna fastna i andra partiklar som är stilla. Förutom att fastna på kanterna ska partiklar fastna i andra stillastående partiklar. Simuleringen ska kunna köras med tiotusentals partiklar utan att det går trögt. När man ska avgöra om en partikel är nära en annan partikel som är still så bör man inte beräkna och testa avstånden till samtliga partiklar som är still, det blir alldeles för mycket onödigt jobb när man har många partiklar. Man bör begränsa sig till att undersöka omgivningen kring den aktuella partikeln. Ett sätt att spara arbete är att införa en "karta" över området där partiklarna rör sig och markera alla stillastående partiklar på kartan. Kartan kan representeras som en matris med ett element för varje pixelposition. När en partikel fastnat i en viss postition så markeras den i kartan. När man ska undersöka om en rörlig partikel kommit nära en stillastående så tittar man på kartan på de positioner som är nära den rörliga. Man undersöker några få kartpositioner i stället för att beräkna avstånd till tusentals partiklar. Genom att lagra mer data (kartan) så sparar vi arbete och programmet blir effektivare. Andra lösningsmetoder är tillåtna förutsatt att de är så effektiva att man utan effektivitetsproblem kan simulera åtminstone 10000 partiklar.

8 Redovisning av grunduppgiften

Programmet ska skrivas med en uppdelning på olika ansvarsobjekt som ligger nära den som beksrivs i labbanvisningen. Måttliga avvikelser accepteras om de motiveras väl. Följande ska ovillkorligen finnas med:

  • Klassen Particle med två konstruktorer, en som slumpar startposition för partikeln och en som har startvärden för x och y som parametrar
  • Klassen Particle ska ha en instansvariabel av typen boolean som anger om partikeln rör sig eller har stannat, variabeln ska användas på ett vettigt sätt.
  • Particle ska också ha en metod för slumpflyttning enligt gränssnittet i figur 5.
  • En egen klass för huvudobjektet som också är en Frame eller JFrame.
  • En egen klass för modellen där du enkelt kan byta mellan 200 partiklar (för att se att den gör rätt) och 10 000 partiklar (för att se att den är rimligt effektiv).
  • Ytterligare minst en klass (dvs totalt 4-6 klasser).
  • Programmet ska kunna köras fristående, ska ha en main-metod.

9 Extrauppgift (betygshöjande)

Extrauppgift1

10 Frivilliga tillägg

Ingen extra poäng ges för dessa.

  • Använd setPreferredSize() för att göra JScrollBar-objekten större om ni inte redan gjort det.
  • Med hjälp av en mer avancerad layoutManager i klassen BrownAnimation och/eller i Manipulation-klassen kan man få bättre kontroll på hur objekten ser ut.
  • Experimentera med olika former för partikelkällor eller olika startpositioner för stillastående partiklar som läggs ut då simuleringen börjar.
  • Experimentera med randområdet där partiklarna kan fastna. Att definiera ett cirkulärt område är ganska lätt men hur gör man för att få en mer komplicerad rand? Randen kan bestämmas i programmet eller definieras interaktivt av användaren.
  • Gör simuleringen 3-dimensionell. Nya grafikhjälpmedel kan behövas.

Stefan Nilsson skapade sidan 10 januari 2017

En användare har tagit bort sin kommentar
Lärare kommenterade 30 januari 2017

Edin, det är nog inte meningen att det ska finnas ett skelett för View-klassen i den här labblydelsen, men det är förstås fritt fram att fråga på labbarna om man behöver mer info eller hjälp.

En användare har tagit bort sin kommentar
Assistent kommenterade 23 februari 2017

Man ska aldrig använda javas inbyggda Vector till någonting. Den har ersatts av ArrayList och ArrayList går bra att använda.