ÖVNINGSUPPGIFT

Uppgift 4 - Djurparken

Försök själv först. Ledtrådar och facit finns längre ner – helt utan inloggning.


Nyckelord: Klasser och Objekt

Djurparken

Vi ska programmera en djurpark! Vi ska göra detta genom att skapa klasser och objekt för olika djur och på så sätt simulera en djurpark. Följ stegen nedan för att skapa programmet. Som vanligt finns det inte ett rätt svar och du får gärna skapa hjälpmetoder för att lösa uppgiften på ditt sätt.

A: Skapa klassen Animal

Vi börjar med att skapa en gemensam klass Animal för alla djur. Ett djur på denna djurpark definieras av följande attribut:

  • djurets namn
  • id-nummer
  • levande eller död

Namnet och id-numret ska initieras i klassens konstruktor och ska inte gå att ändra på efter att objektet skapats. Skapa getters så att användaren enkelt kan få åtkomst till instansvariablerna.

Skapa en metod som heter die(). Denna metod ska sätta medlemsvariabeln som hanterar om djuret lever eller ej till false. Den ska även skriva ut namn och id på djuret och berätta att djuret nyss har dött.

Skapa även en metod toString() som returnerar en sträng som anger djurets namn, id och huruvida det är levande eller ej.

Ledtråd

Skapa en ny fil med samma namn som klassen. I avsnittet skapa ett objekt från en klass ser vi hur man kan skapa en klass Dog. Gör motsvarande kod för klassen Animal.

//Skapar klassen "Dog". Praxis är att klassnamn har stor bokstav
class Dog {
    //Definierar instansvariablerna (attributen/tillstånden) för klassen hund
    String name;
    int age;
    String breed;
    String color;
    //Definiera konstruktorn
    Dog(String name, int age, String breed, String color){
        this.name = name;
        this.age = age;
        this.breed = breed;
        this.color = color;
    }
    //Definierar metoderna för klassen hund
    void bark(){
        System.out.println(this.name + " skäller");
    }
    void eat(){
        System.out.println(this.name + " äter");
    }
    void sleep(){
        System.out.println("zZzZZzzZZz");
    }
}

Metoden toString() kan du enkelt skapa genom följande kod, där strängen som returneras är det du vill skriva ut:

public String toString() {
    return "";
}

B: Klassen Predator

Skapa en klass som heter Predator. Rovdjur är en subklass av Animal och har alla egenskaper som djur, men har även egenskapen att de kan äta upp andra djur. Skapa därför en metod eat(), som tar in ett objekt av klassen Animal som argument. Metoden ska anropa metoden die() i det djur som ätits. Konstruktorn behöver använda nyckelordet super för att sätta djurets namn i basklassen.

Ledtråd

Ärv egenskaperna från Animal med extends:

Delsteg: konstruktorn som anropar super läggs till i det fullständiga lösningsförslaget.

public class Predator extends Animal {

}

I konstruktorn använder man nyckelordet super för att referera till sin parent-klass. Läs mer om detta i vårt avsnitt om arv.

C: Zebror och Lejon

Skapa två klasser, en som heter Zebra och en som heter Lion. Zebror är, som vi alla vet, ett djur medans Lejonet är ett rovdjur. Lejonet ärver alltså alla egenskaper för rovdjur och Zebran ärver alla egenskaper från Animal. Lejon ryter, och ska därför ha metoden roar(), som skriver ut ett rytande i konsolen. Lejon ryter som bekant efter att de ätit något. Overridea därför eat-metoden som finns i rovdjur, och använd super för att använda den ändå men lägg till ett roar() efter det.

Ledtråd

Overrida:a metoden genom att sätta @Override över metoden. Lär dig mer om detta under avsnittet om interface.

@Override
public void eat(Animal animal) {

}

D: Koordinater

Gör en klass med namn Coordinate som innehåller två variabler, x och y. För koordinaterna använder vi enbart heltal. Gör getters och setters för dessa variabler, samt en metod distance(), som tar in en annan koordinat som argument och returnerar avståndet mellan de två koordinaterna i form av en double. Hint: pythagoras sats.

Skapa en metod toString som skriver ut koordinaten enligt följande syntax <x, y>.

Ledtråd

Använd klassen Math för att få åtkomst till metoder som sqrt(), abs() och pow().

E: Rörelser

Lägg till ett objekt av klassen coordinate i Animal-klassen, och lägg samtidigt till så att konstruktorn tar in en startposition för varje djur (detta får även läggas till i de klasser som ärver Animal). Lägg även till en metod move() som gör att djuret tar ett slumpmässigt steg i någon riktning. Det finns 9 stycken positioner som djuret kan gå till (vi utgår från att djuret även kan stå kvar på samma position).

Använd klassen Random för att avgöra vilken position djuret ska förflytta sig till.

Ledtråd

Skapa en switch-sats som tar in ett nummer mellan 0 och 8. Uppdatera coordinate för djuret för alla möjliga fall.

F: Zoo

Skapa en klass med namn Zoo. I klassen ska det finnas en lista över alla djur som finns i djurparken, använd klassen ArrayList för detta. Klassen Zoo ska även ha en instansvariabel idCounter som håller räkningen på antal djur som funnits parken. Skapa en metod increaseAnimalId() som ökar denna variabel med 1.

Skapa en metod init() som lägger till ett antal djur i djurparken, exempelvis två lejon och fem zebror. Använd instansvariabeln idCounter som id till varje djur. Glöm inte kalla på increaseAnimalId() efter instansieringen av varje djur så att två djur inte riskerar att få samma id.

Skapa en metod toString() som skriver ut alla djur i djurparken i konsolen.

Ledtråd

Skapa en ny ArrayList som håller objekt av typen Animal. Läs mer om detta i avsnittet om ArrayLists.

ArrayList <Animal> animals = new ArrayList<Animal>();

När du skapar toString() kan du loopa igenom alla djur och anropa toString() inuti Animals för varje djur.

G:

Skapa en metod think() i klassen Djur, och lägg till en variabel stomachContent, som har initialt värde 10. Think ska räkna ner stomachContent med 1, och om värdet på variabeln blir 0 ska metoden die() anropas. Metoden avslutas med att metoden move() anropas.

Ledtråd

Skapa stomachContent som en instansvariabel och tilldela denna 10 i konstruktorn.

H:

Lägg till en metod run() i klassen Zoo. Lägg sedan till en variabel av typen boolean som också kallas run. Skapa en loop i metoden run() som körs tills run är false. I varje ”varv” i loppen ska alla djurs think()-metoder anropas, varefter systemet ska vänta en sekund. Detta kan åstadkommas med Thread.sleep(). I varje iteration ska djurens status (lever, position, maginnehåll) skrivas ut med hjälp av den tidigare konstruerade toString-metoden.

Ledtråd

För att använda Thread.sleep() behöver man kasta ett InterruptedException. Lägg till ett Exception med nyckelordet throws. Tiden för sleep ska anges i millisekunder.

    public void run() throws InterruptedException {
        while (run) {
            for (int i = 0; i < this.animals.size(); i++) {
                this.animals.get(i).think();
            }
            Thread.sleep(1000);
        }
    }

I:

Skapa en metod think() även i klassen Zoo. Anropa denna metod i varje iteration av metoden run. Metoden ska kontrollera så att zebran och lejonen inte har samma koordinat. Zebran dör om lejonen har samma koordinater som zebran. Lejonets stomachContent ska då öka med 10.

Ett djur som dör ska tas bort ArrayListen i Zoo som håller djuren.

Ledtråd

Skapa hjälpmetoder som kollar igenom alla djur och kontrollerar om de har samma koordinat eller ej. För att avgöra om det är en zebra eller Lejon kan du använda instanceof:

if (this.animals.get(j) instanceof Zebra) {
    // this.animals.get(j) är en zebra!
}

J:

Skapa en instans av klassen Zoo i huvudskriptet. Programmet ska stanna när alla djur har dött och då skriva att programmet är slutfört.

Kör programmet och se hur lejonen rör sig mot zebrorna!

Ledtråd

Utskriften borde bli något i stil med:

<Simba, 0, true, 14, <0,0>> <Mufasa, 1, true, 24, <-3,3>> <Stripe, 3, true, 4, <-1,0>> <Zeb, 4, true, 4, <-3,-2>> 
<Simba, 0, true, 13, <1,0>> <Mufasa, 1, true, 23, <-3,4>> <Stripe, 3, true, 3, <0,0>> <Zeb, 4, true, 3, <-4,-2>> 
<Simba, 0, true, 12, <0,0>> <Mufasa, 1, true, 22, <-2,4>> <Stripe, 3, true, 2, <1,0>> <Zeb, 4, true, 2, <-3,-1>> 
Stripe med id:t 3 dog.
Simba: ROOOAAAAAR!!!
<Simba, 0, true, 21, <0,-1>> <Mufasa, 1, true, 21, <-3,3>> <Stripe, 3, false, 1, <0,-1>> <Zeb, 4, true, 1, <-4,-1>> 
Zeb med id:t 4 dog.
Svar

Animal:

import java.util.Random;

public class Animal {
    final String name;
    final int id;
    boolean alive;
    Coordinate coordinate;
    int stomachContent;

    // Constructor
    Animal(String name, int id, Coordinate coordinate) {
        this.name = name;
        this.id = id;
        this.alive = true;
        this.coordinate = coordinate;
        this.stomachContent = 10;
    }

    public void die() {
        if (!this.alive) return;
        this.alive = false;
        System.out.println(this.name + " med id:t " + this.id + " dog.");
    }

    public void move() {
        // Random number
        Random random = new Random();
        int rand = random.nextInt(9);

        // Move to one of the possible directions
        switch (rand) {
            case 0:
                this.coordinate.setX(this.coordinate.x - 1);
                this.coordinate.setY(this.coordinate.y - 1);
                break;
            case 1:
                this.coordinate.setY(this.coordinate.y - 1);
                break;
            case 2:
                this.coordinate.setX(this.coordinate.x + 1);
                this.coordinate.setY(this.coordinate.y - 1);
                break;
            case 3:
                this.coordinate.setX(this.coordinate.x - 1);
                break;
            case 4:
                this.coordinate.setX(this.coordinate.x + 1);
                break;
            case 5:
                this.coordinate.setX(this.coordinate.x - 1);
                this.coordinate.setY(this.coordinate.y + 1);
                break;
            case 6:
                this.coordinate.setY(this.coordinate.y + 1);
                break;
            case 7:
                this.coordinate.setX(this.coordinate.x + 1);
                this.coordinate.setY(this.coordinate.y + 1);
                break;
            default:
                // do nothing (stand still)
                break;
        }
    }

    public void think() {
        if (!this.alive) return;
        this.stomachContent--;
        if (this.stomachContent <= 0) {
            this.die();
            return;
        }
        this.move();
    }

    public double distanceTo(Animal animal) {
        return this.coordinate.distance(animal.coordinate);
    }

    public String toString(){
        return "<" + this.name + ", " + this.id + ", " + this.alive + ", " + this.stomachContent + ", " + this.coordinate + ">";
    }

    // Getters
    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public Boolean getAlive() {
        return alive;
    }
}

Predator:

public class Predator extends Animal {
    // Constructor
    Predator(String name, int id, Coordinate coordinate) {
        super(name, id, coordinate);
    }

    void eat(Animal animal){
        animal.die();
    }
}

Zebra:

public class Zebra extends Animal {
    // Constructor
    Zebra (String name, int id, Coordinate coordinate) {
        super(name, id, coordinate);
    }
}

Lion:

public class Lion extends Predator {
    // Constructor
    Lion (String name, int id, Coordinate coordinate) {
        super(name, id, coordinate);
    }

    // Overides method so that the lion also roar
    @Override
    public void eat(Animal animal) {
        if (!this.alive || !animal.alive) return;
        super.eat(animal);
        roar();
        this.stomachContent += 10;
    }

    // Roar!
    public void roar() {
        System.out.println(this.name + ": ROOOAAAAAR!!!");
    }
}

Coordinate:

public class Coordinate {
    int x;
    int y;

    // Constructor
    Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // Calculates the distance between two coordinates
    public double distance(Coordinate cord) {
        return Math.hypot((double) this.x - cord.x, (double) this.y - cord.y);
    }

    @Override
    public String toString() {
        return "<" + this.x + "," + this.y + ">";
    }

    // Getters
    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;
    }

    // Setters
    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
}

Zoo:

import java.util.ArrayList;

public class Zoo {
    ArrayList <Animal> animals;
    public int idCounter = 0;
    public boolean run;

    // Constructor
    Zoo() {
        animals = new ArrayList<Animal>();
        init();
        run = true;
    }

    // creates some animals
    private void init() {
        animals.add(new Lion("Simba", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Lion("Mufasa", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Zebra("Marty", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Zebra("Stripe", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Zebra("Zeb", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Zebra("Zanzabar", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();

        animals.add(new Zebra("Zecar", idCounter, new Coordinate(0, 0)));
        increaseAnimalId();
    }

    // Increase id counter
    public void increaseAnimalId() {
        this.idCounter++;
    }

    // Loops ticks one per second and runs the logic in think()
    public void run() throws InterruptedException {
        while (run) {
            for (int i = 0; i < this.animals.size(); i++) {
                // Think methods for animals
                this.animals.get(i).think();
            }

            // Think method for zoo.
            think();

            // Print zoo object
            System.out.println(this);

            // Removes all dead animals
            removeDead();

            // Check if all animals are dead
            if(isDone()){
                this.run = false;
            }
            Thread.sleep(1000);
        }
    }

    // Handles the logic
    public void think() {
        // Iterate through all animals
        for (int i = 0; i < this.animals.size(); i++) {
            // For each lion
            if (this.animals.get(i) instanceof Lion && this.animals.get(i).alive) {
                // Check if zebra and Lion have the same coordinate
                for (int j = 0; j < this.animals.size(); j++) {
                    if (this.animals.get(j) instanceof Zebra && this.animals.get(j).alive) {
                        double distance = this.animals.get(i).distanceTo(this.animals.get(j));
                        if (distance == 0) {
                            ((Lion) this.animals.get(i)).eat(this.animals.get(j));
                        }
                    }
                }
            }
        }
    }

    /* removes all dread animals. Observe that we loop backwards
     * so that the indices will work even if we remove one object.
    */
    public void removeDead() {
        for (int i = this.animals.size()-1; i >= 0; i--) {
            if(!this.animals.get(i).alive) {
                this.animals.remove(i);
            }
        }
    }

    // Check if any animal is alive
    public boolean isDone() {
        for (Animal animal : this.animals) {
            if (animal.alive) {
                return false;
            }
        }
        return true;
    }

    public String toString() {
        String str = "";
        for (int i = 0; i < this.animals.size(); i++) {
            str += this.animals.get(i).toString() + " ";
        }
        return str;
    }
}

Huvudskriptet:

public class Uppgift4 {
    public static void main(String[] args) throws InterruptedException {
        Zoo zoo = new Zoo();
        zoo.run();
    }
}

Diskussion:

Försök att gå igenom uppgifterna igen och jämför med dina egna klasser och metoder. Har du gjort på ett annat sätt? Grymt bra!

Det finns inte ett rätt svar och många gånger kan man lösa uppgiften på flera sätt. Det man ska ta med sig från denna uppgift är hur man kan skapa subklasser från en klass (parent) och sedan overridea metoder inuti dessa subklasser så att subklassen får ett annat beteende.

Fler uppgifter

Det här är en förenklad simulering, inte biologiska regler. Djuren rör sig slumpmässigt; lejonen söker inte aktivt efter zebror. Ett dött djur deltar inte i fler händelser. För att följa status kan döda djur visas i ett varv innan de tas bort ur listan.