Som vanligt kan man lösa uppgiften på många sätt -- i detta fall kan vi börja med att fundera på vilka värden vi behöver hålla reda på:
Aktuell position: detta är ett heltal i intervallet
0..antal stenar - 1, vi kan kalla det pos
.
Antal steg: även detta är ett heltal, vi kallar det
steps
.
Om gubben har ramlat i vattnet eller ej: detta är
ett logiskt värde (det är antingen true
eller
false
), och vi kan spara det i en variabel
inWater
(av typen boolean
). Till en början
sätter vi den till false
, men så fort gubben ramlar
sätter vi den till true
.
Sannolikheten att gå framåt: ett reellt tal i
intervallet 0..1, vi kan lagra det i variabeln
pForward
.
Sannolikheten att ramla: ett reellt tal i
intervallet 0..1, vi kan lagra det i variabeln
pFall
.
Dessutom behöver vi ha ett fönster att rita i, en klocka för att programmet inte skall gå för fort, och en slumptalsgenerator (klockan och slumptalsgeneratorn behöver egentligen inte vara globala, se nedan):
Fönster: path
.
Klocka: clock
.
Slumptalsgenerator: rng
.
Vi skulle även kunna hålla reda på antalet stenar med hjälp av en
global heltalsvariabel, men det visar sig att fönstret redan har denna
information (operationen length()
).
Alla dessa värden kan vi deklarera som globala variabler (eftersom de är viktiga för programmets 'tillstånd'):
import se.lth.cs.pt.walk.Path; import se.lth.cs.pt.clock.Clock; import se.lth.cs.pt.io.Keyboard; import java.util.Random; class HomerWalking { public static void main(String[] args) { new HomerWalking().run(); } int pos; // aktuell position int steps; // antal steg hittills boolean inWater; // har ramlat i vattnet? double pForward; // sannolikhet att gå framåt double pFall; // sannolikhet att ramla Path path; // fönstret med stenar Clock clock; // klocka, för att pausa Random rng; // för att avgöra vad som händer // i nästa steg ... }
Vi kan sedan önsketänka när vi skriver vårt huvudprogram: vi skall först läsa in olika värden och skapa ett fönster, därefter skall vi så länge gubben fortfarande håller sig uppe och inte har nått fram till andra sidan ta ett steg åt gången. Avslutningsvis skall vi fira (eller sörja) tillsammans med gubben.
void run() { setup(); while (stillUp() && !reachedGoal()) { step(); } celebrate(); }
För att läsa in värden och initiera variabler kan vi skriva (det är praktiskt att läsa in våra värden innan vi skapar vårt sten-fönster, eftersom det annars lätt kommer i vägen för terminalfönstret):
void setup() { int nbrOfStones = Keyboard.nextInt("Antal stenar: "); pForward = Keyboard.nextDouble("Sannolikhet att hoppa framåt (i %): ") / 100; pFall = Keyboard.nextDouble("Sannolikhet att falla (i %): ") / 100; path = new Path(nbrOfStones); clock = new Clock(); rng = new Random(); steps = 0; pos = 0; inWater = false; drawMan(); }där
drawMan()
ritar gubben på rätt plats:
void drawMan() { if (stillUp()) { path.drawMan(pos); } else { path.drawManInWater(pos); } }
Att avgöra om gubben fortfarande håller sig på benen blir enkelt om
vi har den globala variabeln inWater
:
boolean stillUp() { return !inWater; }
Det går jättebra att testa om inWater == false
, eller
något liknande, dvs
boolean stillUp() { // alternativ variant (lite 'pratigare') if (inWater == false) { return true; } else { return false; } }är helt OK!
Även att avgöra om gubben har nått fram eller ej blir enkelt med de givna globala variablerna:
boolean reachedGoal() { return pos == path.length()-1; }och precis som i fallet med
stillUp()
går det utmärkt att
skriva denna med hjälp av en if
-sats.
Vi hade klarat oss utan funktionerna stillUp()
och
reachedGoal()
, om vi istället hade skrivit loopen i
huvudprogrammet som:
while (!inWater && pos < path.length()-1) { step(); }
Om man vill använda funktioner som stillUp()
och
reachedGoal()
eller hellre testar villkoren direkt, som
här, är mycket en fråga om tycke och smak (jag föredrar dock
funktionerna).
I step()
-proceduren, som skall få gubben att gå ett
steg framåt, behöver vi dra slumptal och vänta -- vi hade mycket väl
kunnat skapa vår slumptalsgenerator och klocka här istället för att ha
dem som globala variabler.
Metoden för att räkna ut vad som skall hända baserat på slumpvärden beskrivs i kompendiet (avsnitt 3.5.2):
void step() { double rval = rng.nextDouble(); if (rval < pForward) { // framåt pos++; steps++; } else if (rval < 1 - pFall) { // bakåt if (pos > 0) { // inte bakåt från första stenen pos--; steps++; } } else { // ramlar i inWater = true; } clock.pause(500); drawMan(); }
Även denna procedur skulle vi kunna dela ner i mindre delar, exempelvis:
void step() { double rval = rng.nextDouble(); if (rval < pForward) { // framåt forward(); } else if (rval < 1 - pFall) { // bakåt backward(); } else { // ramlar i fall(); } clock.pause(500); drawMan(); }och
void forward() { pos++; steps++; } void backward() { if (pos > 0) { // inte bakåt från första stenen pos--; steps++; } } void fall() { inWater = true; }
Variabeln rval
i step()
skall definitivt
inte vara någon global variabel, dess värde är bara intressant just
när vi tar ett steg.
När vi (eventuellt) skall fira landstigningen får vi återigen nytta
av reachedGoal
, vi kunde naturligtvis även här istället
testa värdet på pos
:
void celebrate() { clock.pause(1000); if (reachedGoal()) { System.out.printf("Klarade det på %d hopp!\n", steps); } else { System.out.printf("Hoppade %d gånger, men misslyckades...\n", steps); } System.exit(0); }
Programmet använder System.exit(0)
för att avsluta,
det beror på att det är ett enkelt sätt att få alla fönster att stänga
sig automatiskt.