Das Schildkröten problem (Objektorientierte Programmierung)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Chrisseeey
User
Beiträge: 7
Registriert: Donnerstag 9. Juni 2022, 15:02

Hallo Liebe Menschen!

Ich hoffe das mir hier vielleicht einer weiter helfen kann und Spaß an Schildkröten hat!
Wir schreiben bald Klausur, deswegen bin ich fleißig am verstehen und lernen von Python. Unsere letzte Übung hat mit Schilkröten und Objektorientierter Programmierung zu tun. Der Text könnte etwas länger werden, schon als Vorwarnung :shock:

Ich habe leider immernoch viele Probleme alles hinter Python zu verstehen, aber ich denke, dass ich in dieser Aufgabe schon einen guten Grundstein legen konnte. Und zwar geht es darum, das wir eine Klasse Schildkröte von Anfang an gegeben hatten. Der Code dazu sieht folgendermaßen aus:

Code: Alles auswählen

class Schildkröte(Turtle):
    def __init__(self, position, richtung, farbe="green", form="turtle"):
        Turtle.__init__(self)
        self.farbe = farbe        #Farbe als Zeichenkette (str)
        self.position = position  #Position in Form eines zweielementigen Tupels mit int-Koordinaten (x, y)
        self.richtung = richtung  #Richtung in Grad (int)
        
        self.speed(200)
        self.color(farbe)
        self.shape(form)
        self.setheading(richtung)
        self.jumpto(self.position[0], self.position[1])
        self.penup()
        self.speed(1.0)
        self.tot = False
        
    def gehe_nach(self, position):   # Position als Tupel (siehe oben)
        self.position = position
        self.goto(position[0], position[1])
        
    def zeigt_über_rand(self):   # Hilfsmethode;
        distanz = 30             # gibt True zurück, wenn die Schildkröte mit einem Vorwärtszug der Länge 30
                                 # über den Rand hinaus laufen würde
        return max((abs(self.position[0] + distanz*math.cos(math.pi/180*self.richtung)) - 380), (abs(self.position[1] + distanz*math.sin(math.pi/180*self.richtung)) - 280)) > 0
    
    def richtung_nach(self, andere_Schildkröte):               #Berechnet die benötigte Richtung der Schildkröte,
        dx = andere_Schildkröte.position[0] - self.position[0] #um sich zu einer anderen Schildkröte zu bewegen
        dy = andere_Schildkröte.position[1] - self.position[1] 
        phi = math.atan(dy/dx)                                 
        if (dx<0 and dy>=0) or (dy<0 and dx>=0):
            phi += math.pi
        if dy < 0:
            phi += math.pi
        return int(phi*180/math.pi)  # Rückgabe der Richtung in Grad (int; abgerundet)
    
    def aktualisiere_Attribute(self):
        neue_position = (self.getx(), self.gety())
        self.position = neue_position
        self.richtung = self.getheading()
        
    def lege_ei(self):
        return self.__class__(self.position, 180)
        
    def sterben(self):
        self.tot = True
        self.hideturtle()
        
    def art(self):
        raise NotImplementedError
Dazu hatten wir jetzt mehrere Aufgaben. Zuerst sollten wir eine Unterklasse Landschildkröte der Klasse Schildkröte mit verschiedenen Eigenschaften implementieren. ich denke dass habe ich soweit alles erfüllen können, bis auf einen Error den ich rausbekomme, der bei den Aufgaben davor aber nicht erschienen ist. So sieht mein code aus:

Code: Alles auswählen

class Landschildkröte(Schildkröte):
    farben = {"Ei": "yellow", "geschlüpft": "blue"}
    
    def __init__(self, position, richtung):
        Schildkröte.__init__(self, position, richtung, self.farben["Ei"])
        self.speed(6.0)
        self.status("Ei")
        
    def art(self):
        return "Landschildkröte"
    
    def schlüpfen(self):
        self.color(self.farben["geschlüft"])
        self.form("turtle2")
        self.status("geschlüpft")
    
    def gehe_vorwärts(self, distanz):
        self.forward(distanz)
        self.aktualisiere_Attribute()
        
    def drehe_nach(self, richtung):
        self.position = position
        self.richtung = self.getheading()
Bis dahin funktioniert alles, bis ich dann eine neue Instant der Klasse Landschildkröte erstellen soll und in der Variable landgänger speichern soll. Dieser soll dann die methoden schlüpfen(), drehe_nach() und gehe_vorwärts() ausführen, bei dem er mir den Attribute Error raushaut und sagt 'Landschildkröte' object has no attribute 'status'.

Das wäre mein erstes Problem. Ich verstehe nicht warum es davor das Problem beim ausführen nicht ausgeworfen hat.

Das nächste Problem wäre die nächste Kröte. Wir sollen eine neue Klasse Killerkröte der Unterklasse Landschildkröte erstellen. Diese soll auch einige besondere Eigenschaften haben und als letztes eine neue Methode distanz_instinkt(self, andere_Schildkröte) bei der die euklidische Distanz zwischen der eigenen Position und der Position der anderen Schildkröte berechnet werden soll. Ich wüsste wie ich das mathematisch berechne aber leider nicht wie ich das in python umsetze. Denn danach sollen wir zwei weitere Instanzen schildkröte1 vom Typ Landschildkröte und schildkröte2 vom Typ Killerkröte mit bestimmten Positionen erzeugen und dann den Abstand beider Schildkröten mit der Methode dinstanz_instinkt berechnen. Das Ergebnis sollen wir in der Variable abstand speichern. Da bin ich mir leider auch nicht sicher, wie das am Ende aussehen soll.
Die Instanzen hätte ich schon:

Code: Alles auswählen

schildkröte1 = Landschildkröte((0, 0), 0)
schildkröte2 = Killerkröte((30, 40), 0)
aber wie ich dann denn abstand bekomme ist mir leider noch ein Rätsel.

Ich weiß, dass ist erstmal sehr viel Text und ich will auch keine vorgefertigte Lösung, weil in der Klausur hock ich ja auch alleine da, ich bräuchte nur ein wenig Hilfe beim Verständnis, dass ich dass dann auch nächste Woche von alleine kapiere :)

Vielleicht hat ja jemand eine gute Seele und Lust und Zeit mir zu helfen :)

Vielen dank im Voraus!


Grüße

Chrissi
Sirius3
User
Beiträge: 18279
Registriert: Sonntag 21. Oktober 2012, 17:20

Methoden schreibt man wie Variablennamen komplett klein, das wurde auch fast überall durchgehalten, bis auf `aktualisiere_Attribute` und `andere_Schildkröte`.
Kommentare sind nur dann sinnvoll, wenn man sie auch lesen kann. Wenn sie aber irgendwo im rechten Rand verschwinden, dann kann man sie nicht lesen.
Die meisten Kommentare sollten Doc-Strings sein, weil sie die Argumente erklären.
Ganz skuril ist der Kommentar bei "distance = 30" mit "gibt True zurück".
Es gibt math.radians und math.degrees um Winkel umzurechnen.
Die Zahlenwerte in `zeigt_über_rand` sollten Konstanten sein, damit man auch über den Namen herausfinden kann, was sie denn bedeuten sollen.
`richtung_nach` gibt es einen Fehler, wenn `dx` 0 ist, deshalb benutzt man fast immer statt atan, atan2. Der Modula-Operator ist hilfreich beim Einschränken auf den Wertebereich 0 bis 360.
Bei `aktualisiere_attribute` ist die Variable `neue_position` überflüssig, da sie ja nur dazu benutzt wird, den Wert an `self.position` zu übergeben.

Code: Alles auswählen

class Schildkröte(Turtle):
    DISTANZ = 30
    RAND_X = 380
    RAND_Y = 280

    def __init__(self, position, richtung, farbe="green", form="turtle"):
        """
        Erzeugt eine Schildkröte.

        Argumente:
        - position in Form eines zweielementigen Tupels mit int-Koordinaten (x, y)
        - richtung in Grad (int)
        - farbe als Zeichenkette (str)
        """
        Turtle.__init__(self)
        self.farbe = farbe
        self.position = position
        self.richtung = richtung
        
        self.speed(200)
        self.color(farbe)
        self.shape(form)
        self.setheading(richtung)
        self.jumpto(self.position[0], self.position[1])
        self.penup()
        self.speed(1.0)
        self.tot = False
        
    def gehe_nach(self, position):
        """
        Position als Tupel (siehe oben)
        """
        self.position = position
        self.goto(position[0], position[1])
        
    def zeigt_über_rand(self):
        """ 
        Hilfsmethode
        gibt True zurück, wenn die Schildkröte mit einem Vorwärtszug der Länge 30
        über den Rand hinaus laufen würde
        """
        phi = math.radians(self.richtung)
        return max(
            (abs(self.position[0] + self.DISTANZ * math.cos(phi)) - self.RAND_X),
            (abs(self.position[1] + self.DISTANZ * math.sin(phi)) - self.RAND_Y)) > 0
    
    def richtung_nach(self, andere_schildkröte):
        """
        Berechnet die benötigte Richtung der Schildkröte,
        um sich zu einer anderen Schildkröte zu bewegen
        """
        dx = andere_schildkröte.position[0] - self.position[0]
        dy = andere_schildkröte.position[1] - self.position[1] 
        phi = math.atan2(dy, dx) % (2 * math.pi)
        return math.round(math.degrees(phi))
    
    def aktualisiere_attribute(self):
        self.position = (self.getx(), self.gety())
        self.richtung = self.getheading()
        
    def lege_ei(self):
        return self.__class__(self.position, 180)
        
    def sterben(self):
        self.tot = True
        self.hideturtle()
        
    def art(self):
        raise NotImplementedError
In Landschildkröte ist `farben` eine Konstante, und sollte deshalb auch so geschrieben werden: `FARBEN`.

Wo ist denn die Klasse `Turtle` definiert? Die hat offensichtlich keine Methode `status`. Ich kenne auch keine `Turtle`-Klasse mit den Methoden `jumpto`, `getx`, `gety` oder `getheading`.

Wo hast Du konkret Probleme bei `distanz_instinkt`? Im Prinzip ist das ja so ähnlich wie `richtung_nach`.
Benutzeravatar
__blackjack__
User
Beiträge: 14078
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Chrisseeey: Auch wenn wir die `Turtle`-Klasse nicht kennen vermute ich mal ganz stark das die Attribute `farbe`, `position`, und `richtung` falsch sind, weil diese Informationen bereits von der Klasse `Turtle` vorgehalten werden und es unsinnig ist die in der abgeleiteten Klasse redundant zu speichern.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Chrisseeey
User
Beiträge: 7
Registriert: Donnerstag 9. Juni 2022, 15:02

ersteinmal danke für eure Antworten!

@Sirius3 danke für deine ausführlichen Beschreibungen, da kann ich noch einiges dazu lernen!
Nur den Code von der Schildkröte muss bzw. darf ich garnicht verändern, der ist so von meinem Dozenten vorgegeben, aber schön zu wissen, dass der anscheinend garnicht so gut programmiert ist :D

Turte wurde davor bei uns importiert :

Code: Alles auswählen

from ColabTurtlePlus.Turtle import Turtle, Screen
import random
import math

def setup_window():
    win = Screen()
    win.clearscreen()
    win.setup(width=600, height=600)
Die ganzen Methoden hatten wir auch schon in der Vorlesung vordefniert gehabt, keine Ahnung wo er die her hat.

Bei der Methode distanz_instinkt bin ich mir eben nicht sicher, wie ich das schreiben soll. Ich brauche ja die positionen von x und y aber wie ich dann die euklidische Distanz berechnen ist mir eben unverständlich bis jetzt. Und wie ich danna uch die variable abstand definiere, da bin ich mir nicht sicher.
schreibe ich dann einfach abstand = schildkröte1.distanz_instinkt()? und das für beide oder wie?

@_blackjack_ das mit den Attributen war so leider in der Aufgabe vorgegeben, weil die farbe auch eine andere sein sollte, als die der eigentlichen Klasse
Sirius3
User
Beiträge: 18279
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei Vererbung muß man immer besonders darauf achten, dass nicht irgendwelche Funktionen eine andere Bedeutung bekommen. In Turtle ist position() eine Methode, die die Position zurückgibt, bei der `Schildkröte` ist es aber plötzlich ein Attribut. Zudem sollte man doppelte Datenhaltung vermeiden, nicht nur, weil das unnötig kompliziert ist, sondern auch, weil die Gefahr besteht, dass die Informationen inkonsistent werden. In `Schildkröte` muß man wissen, wann man `aktualisiere_attribute` aufrufen muß, damit genau das nicht passiert, eine große Fehlerquelle.
`position` kann man daher einfach löschen, und wo es gebraucht wird, durch `position()` ersetzen, richtung wird durch `heading()` ersetzt, `farbe` durch `color()`.
Natürlich könnte man jetzt Properties definieren, um das jetztige Interface beizubehalten, da das aber eh ein Irrweg war, sollte man das einfach refaktorieren. Wie das geht, zeige ich bei `tot`.

Code: Alles auswählen

class Schildkröte(Turtle):
    DISTANZ = 30
    RAND_X = 380
    RAND_Y = 280

    def __init__(self, position, richtung, farbe="green", form="turtle"):
        """
        Erzeugt eine Schildkröte.

        Argumente:
        - position in Form eines zweielementigen Tupels mit int-Koordinaten (x, y)
        - richtung in Grad (int)
        - farbe als Zeichenkette (str)
        """
        Turtle.__init__(self)
        self.speed(200)
        self.color(farbe)
        self.shape(form)
        self.setheading(richtung)
        self.jumpto(self.position[0], self.position[1])
        self.penup()
        self.speed(1.0)

    @property
    def tot(self):
        return self.is_visible()
        
    def sterben(self):
        self.hideturtle()
        
    def gehe_nach(self, position):
        self.goto(position)
        
    def zeigt_über_rand(self):
        """ 
        Hilfsmethode
        gibt True zurück, wenn die Schildkröte mit einem Vorwärtszug der Länge 30
        über den Rand hinaus laufen würde
        """
        phi = math.radians(self.heading())
        x, y = self.position()
        return max(
            (abs(x + self.DISTANZ * math.cos(phi)) - self.RAND_X),
            (abs(y + self.DISTANZ * math.sin(phi)) - self.RAND_Y)) > 0
    
    def richtung_nach(self, andere_schildkröte):
        """
        Berechnet die benötigte Richtung der Schildkröte,
        um sich zu einer anderen Schildkröte zu bewegen
        """
        x1, y1 = self.position()
        x2, y2 = andere_schildkröte.position()
        phi = math.atan2(y2 - y1, x2 - x1) % (2 * math.pi)
        return math.round(math.degrees(phi))
    
    def lege_ei(self):
        return type(self)(self.position(), 180)
        
    def art(self):
        raise NotImplementedError
Eine Distanz berechnet man zwischen zwei Schildkröten, genauso wie man für richtung_nach auch eine zweite Schildkröte braucht. Wenn Du mathematisch weißt, wie Du den Abstand berechnest, dann ist der Schritt zum Code nicht mehr weit.
Antworten