bild
Skolan för
datorvetenskap
och kommunication

Labb 1 »
Labb 2 »
Labb 3 »
Labb 5 »
Labb 6 »

KTH / CSC / DD1346

Labb 4, Model-View-Controller (MVC)

Målet med denna labb är att introducera designmönstret Model-View-Controller, träna mer på grafisk interaktion samt visa hur man kan göra enkalre simuleringar. Ursprungsversionen av denna labb skapades av Ann Bengtsson.

Del E

För godkänt till betyg E på denna labb krävs att samtliga uppgifter i denna del skall vara utförda, samt att båda i labbgruppen kan svara på samtliga frågor.

Grundstruktur

I denna labb skall ni göra en simulering av Brownsk rörelse, dvs stokastiska partikelförflyttningar. Syftet är i detta fall inte en realistisk simulering, utan vi gör ett antal olika förenklande antaganden för att det ska bli lättare att implementera. Den som vill kan ersätta modellen med en mer realistisk, men det kommer inte att räknas in i bedömningen av labben.

Ni ska använda er av designmönstret Model-View-Controller, vilket innebär att vi separerar programmets olika funktioner i följande delar:

  1. Model
    Detta del innehåller den fysikaliska/matematiska modellen. Här finns uppgift om antalet partiklar. Det bör finnas ett objekt för varje partikel, samt ett ramobjekt som innehåller hela mängden av partikelobjekt. Det senare skall bl.a. innehålla en metod som utför slumpflyttningen av samtliga partiklar.
  2. View
    Denna del ansvarar för att rita upp partiklarna i aktuella positioner. Den består av ett objekt som är bilden av simuleringen.
  3. Controller
    Denna del samordnar simuleringen. Den håller koll på tidens gång och får Model att uppdatera tillståndet, samt tillhandahåller användarkontroller och ändrar modellens parametrar i enlighet med användarens instruktioner. Dessutom skickar den signaler till View när det är dags att uppdatera bilden.
  4. MyFrame
    Denna del är väldigt enkel, och är en körbar komponent som skapar de övriga delarna.

E1. Model

Rekommenderad läsning: Nested Classes

Vi börjar med att definiera modellen. Modellen består av ett Model-objekt som innehåller en större mängd Particle-objekt. Partiklarna finns i ett tvådimensionellt koordinatsystem. I vår förenklade modell används diskretiserad tid, och varje uppdatering av tillståndet medför att varje partikel förflyttar sig sträckan L i slumpvis utvald riktning, dvs vi uppdaterar positionen enligt:

x(t) = x(t-δ) + L*cos(θ)
y(t) = y(t-δ) + L*sin(θ)

där x(t) resp y(t) är respektive koordinat vid tiden t, δ är tidsteget mellan två uppdateringar och θ är en slumpvis utvald riktning, som är likformigt fördelad mellan 0 och .

Uppgift E1.1 Definiera klassen Model. Den ska innehålla alla partikelobjekten och konstanten L. Den ska ha en konstruktor där man anger hur många partiklar som ska ingå, en metod som uppdaterar positionen för samtliga partiklar, en metod som returnerar positionerna på partiklarna, samt metoder som sätter resp. läser värdet på L.
Uppgift E1.2 Definiera klassen Particle. Den ska innehålla partikelns position med god nogrannhet, samt en metod som updaterar positionen enligt formeln ovan. Den måste förstås använda värdet på L från modellobjektet. Particle kan med fördel vara en inre klass till Model. Particle bör ha en konstruktor som tar två koordinater som argument, och en utan argument som slumpar ut positionen.
Fråga E1.3 Vad är fördelen med att låta den argumentlösa konstruktorn anropa den andra?

E2. View

Rekommenderad läsning: Drawing Geometric Primitives

View skall rita upp alla partiklar i ett fönster. Fönstret är detta fall en utökad JPanel, som precis som knappen i labb 2 ligger i en top-level container.

Uppgift E2.1 Definiera klassen View. Den ska ärva från JPanel. Den ska ha en konstruktor där den tar ett Model-objekt som argument. Detta skall den senare använda för att veta vad den skall rita. Ni måste anropa setPreferredSize med lämpliga argument för att fönstret ska få rätt storlek. Några hundra pixlar på respektive ledd kan vara en bra utgångspunkt.
Uppgift E2.2 Definiera metoden paint(graphics g). Här skall alla partiklar ritas upp i fönstret. Partiklarna kan med fördel ritas som små ellipser.

E3. MyFrame

Rekommenderad läsning: Using top-level containers

MyFrame skall vara ett körbart program som skapar Model och View. Om allt fungerar som det bör ska ni nu kunna se era partiklar på skärmen. Om ni tycker att ni har kodat felfritt och ändå inte ser några partiklar bör ni kontrollera följande:

  1. Har ni anropat setVisible, add och pack där det behövs?
  2. Har ni gett saker och ting lämpliga färger? Partiklarna bör vara en färg som skiljer sig från bakgrunden.
  3. Startar partiklarna på lämpliga koordinater? Visar fönstret samma område som partiklarna finns i?
  4. Om ni bara ser en partikel - kontrollera att partiklarna börjar på lämpliga avstånd från varandra.

Uppgift E3.1 Definiera klassen MyFrame. Den ska ärva från JFrame. Den skall skapa ett Model-objekt och ett View-objekt.

E4. Controller

Rekommenderad läsning: How to Use Sliders och How to Use Swing Timers.

Controller skall vara ett objekt som låter användaren styra vad som händer, och som dessutom driver simuleringen framåt. Användaren får styra med hjälp av JSlider-spakar.
För att driva simuleringen behöver vi ett sätt att automatiskt genomföra regelbundna anrop. Ett sätt att göra detta på är att använda en Timer. Den startas med ett tidsinterval som argument, och när den tiden har förflutit så skickar den ett ActionEvent. När denna del är färdig bör era partiklar röra sig på skärmen. Ser det konstigt ut kan det vara lämpligt att undersöka vilka värden ni satt till L resp. δ
Om ni har kodat vettigt borde det gå bra att simulera åtminstone 10000 partiklar utan att det blir hackigt ens på en ganska modest dator.

Uppgift E4.1 Definiera klassen Controller. Den ska ärva från JPanel. Den ska ha en konstruktor där den tar ett Model-objekt och ett View-objekt som argument. Den ska skapa två stycken JSlider, en som kan ändra värdet på L, och en som kan ändra värdet på δ. Tänk på att låta Controller implementera gränssnittet ChangeListener för att kunna hantera inmatning från JSlider.
Uppgift E4.2 Se till att Controller definierar en metod stateChanged(ChangeEvent e) som ändrar parametrar i enlighet med användarens instruktioner.

För att hålla koll på vilken slider det är som har anropat metoden kan man t.ex använda sig av getSource(). Om vi antar att det finns ett JSlider-objekt som heter LSlider och ett som heter deltaSlider, kan vi skriva:

public void stateChanged(ChangeEvent e){
if(e.getSource()==LSlider){
// skriv lämplig kod här
}
if(e.getSource()==deltaSlider){
// skriv annan lämplig kod här
}
}

Uppgift E4.3 Se till att Controller startar en Timer med ett lämpligt värde på δ. Controller måste alltså dessutom implementera gränssnittet ActionListener, och definiera en metod actionPerformed(ActionEvent e) där den uppdaterar Model och anropar repaint() i View.

E5. Exportera data

Rekommenderad läsning: Character Streams och The StringBuilder Class.
När man har gjort en simulering kan det ofta vara intressant att kunna spara sina resultat för att kunna analysera och bearbeta dem på olika sätt vid ett senare tillfälle. I det här momentet ska ni spara alla era partiklars banor till en CSV-fil som kan läsas och manipuleras med t.ex MatLab.

CSV står för "Comma Separated Values", och betyder helt enkelt att man sparar alla värden som en sträng med siffror i en textfil. Man använder decimalpunkt för att separera heltal och decimaler, och kommatecken för att separera ett sparat variabelvärde från nästa. Väldigt många olika program har stöd för att läsa in filer av den här typen, förutom MatLab så kan t.ex Excel eller OpenOffice Calc importera dem.

Filen som ert program skall exportera ska ha en rad för varje tidssteg. Den första posten i varje rad skall vara tiden som "mätningen" gjordes vid, och de efterföljande posterna skall vara koordinaterna, enligt följande mönster:

t, x1, y1, x2, y2, x3, y3, x4, y4, .... xn, yn,

Här är förstås x1 den första partikelns x-koordinat, y1 den första partikelns y-koordinat, x2 den andra partikelns x-koordinat, och så vidare. t är tidpunkten för mätningen (i sekunder). Om vi antar att vi har 3 partiklar som alla startar i (0,0), att L är 1 och att δ är 100 ms skulle vi kunna få en fil som ser ut så här om vi kör 5 tidssteg:

0, 0, 0, 0, 0, 0, 0,
0.1000, 0.8555, -0.5178, -0.6737, -0.7390, 0.8180, 0.5752,
0.2000, 0.6774, 0.4662, -1.6306, -1.0294, 1.7826, 0.3114,
0.3000, 1.6532, 0.2474, -1.0822, -0.1932, 2.7656, 0.1277,
0.4000, 2.6172, -0.0185, -2.0779, -0.1014, 3.0762, -0.8229,

För att läsa in en sådan fil i MatLab kan man t.ex skriva

X = load('<Filnamn>');

så lagras alla data i matrisen X. Vill man sedan plotta alla partiklar vid t.ex 3:e tidssteget så kan man skriva:

plot(X(3,2:2:end),X(3,3:2:end),'*');

indexet (k,m:n:end) betyder att man plockar värden från rad k, börjar i kolumn m, hoppar över n steg i taget och fortsätter tills raden är slut. I detta fall plockar vi alltså först ut alla x-koordinater från rad 3, och sedan alla y-koordinater från samma rad, och plottar asterisker på dessa positioner.

Uppgift E5.1 Låt klassen Controller spara ner alla simuleringsdata till en fil enligt beskrivningen ovan. Om ni har många partiklar kan det gå väldigt långsamt att skapa nya strängar med "+"-operatorn. Då kan det gå fortare att använda sig av StringBuilder och dess append-metod i stället. För att få ett bra slut på loggfilen (och undvika extremt stora filer), kan ni låta loggandet avslutas efter ett visst antal steg. Ni bestämmer antalet själva, men sätt värdet så att ni får bra indata till E5.5
Fråga E5.2 Hur många partiklar kan man ha utan att det går markant långsammare om man använder strängkonkatenering med "+"?
Fråga E5.3 Hur många partiklar kan ni hantera om ni använder StringBuilder?
Fråga E5.4 När/varför skulle man hellre vilja använda "+"-operatorn?
Uppgift E5.5 Skriv ett MatLab-program som laddar in era sparade data, och genom att loopa över radindexet spelar upp simuleringen i en plot. Ett tips kan vara att börja med ett fåtal partiklar så att ni får överskådliga filer som är lättare att felkolla. Man kan behöva anropa drawnow i MatLab för att tvinga den att verkligen rita upp alla partiklarna i varje varv.
Uppgift E5.6 Använd Matlab för att plocka fram följande ur era data:
  1. Största resp. minsta x- och y-värde i varje tidpunkt. Plotta resultatet som en funktion av t.
  2. Medelvärde och stadardavvikelse för x- resp. y-värde som en funktion av tiden. Plotta resultatet.



Del C

För godkänt till betyg C på denna labb krävs - utöver godkänd E-del - att samtliga uppgifter i denna del skall vara utförda, samtliga frågorär besvarade, samt att båda i labbgruppen kan förklara hur koden fungerar, och varför ni löste problemen som ni gjorde.

Uppgift C1 Gör så att partiklar som kommer fram till kanten av fönstret fastnar där och inte flyttas mer. Efter ett tag borde alltså samtliga partiklar ha fastnat i kanten. Partiklar som har fastnat ska byta färg.
Uppgift C2 Gör så att en partikel som kommer tillräckligt nära (t.ex en pixel ifrån) en partikel som redan har fastnat fastnar i denna och också stannar upp och byter färg. Detta bör ge som resultat att "kristallstrukturer" växer fram. Förmodligen går det för långsamt att beräkna avståndet mellan samtliga partiklar, så ni får använda någon smart metod för att slippa detta. Ett sätt kan vara att dela in området i mindre regioner, så att man bara mäter avståndet till partiklar som finns i närheten. Ert program bör fortfarande kunna hantera 10000 partiklar utan problem. Nedanstående bild visar hur tillståndet kan tänkas se ut efter ett tag:

Bild på partikelkondensering

I bilden ovan är gula partiklar i rörelse, medan röda partiklar har "kondenserat" och stannat.

Uppgift C3 Definiera något mer än bara ytterkanten som partiklarna kan "kondensera" mot, tex en cirkel, ett kryss, en punkt i mitten av området, eller motsvarande, tex som nedan:

Bild på partikelkondensering kring cirkel

Uppgift C4 Lägg till en start/stopp knapp för att skriva loggar. Partikelpositioner skall bara sparas när loggen är startad. Knappen ska visa vilket tillstånd den är i.
Fråga C5 I den här labben har vi låtit slider-objekten vara en del av Controller för att få en naturlig lättöverskådad arbetsgång. Om man vill hårdra MVC-mönstret så skulle man kunna placera den lite annorlunda. Hur?

Frivillig Extrauppgift

Denna uppgift behöver ej lösas för vare sig E- eller C-betyg på denna labb, men kan vara en intressant utmaning för den som vill göra något lite svårare. Detta är helt frivilligt.

Uppgift X1 Gör så att en partikel som kommer tillräckligt nära (t.ex en pixel ifrån) en annan partikel, fastnar i denna oavsett om någon av dem sitter fast i kanten. Detta bör ge som resultat att ni får stadigt växande partikelaggregat som växer ihop med varandra och så småningom fastnar i någon vägg. Ni bör fortfarande kunna hantera ungefär 10000 partiklar.
Sidansvarig: Christian Smith <ccs@kth.se>
Uppdaterad 2011-05-20