Lösningsförslag till den hoppande gubben

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å:

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):

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.