Objektorientierte Programmierung ist eines dieser Konzepte, die in einem Lehrbuch durchaus Sinn machen, sich dann aber schwierig anfühlen, wenn man versucht, sie in einem echten Projekt anzuwenden. Was genau ist self? Wann nutzen Sie die Vererbung? Was ist der Unterschied zwischen einer Klasse und einem Objekt?

Der beste Weg, OOP zum Klicken zu bringen, besteht darin, etwas damit zu bauen. In diesem Projekt werden wir mithilfe von Python-Klassen ein textbasiertes Gartensimulatorspiel komplett von Grund auf erstellen. Sie können über die Befehlszeile nach Samen suchen, sie pflanzen, Ihren Garten pflegen und Ihre Ernte ernten. Auf dem Weg dorthin wird jedes OOP-Konzept, das wir verwenden, einen unmittelbaren, greifbaren Existenzgrund haben.

Was Sie lernen werden

Am Ende dieses Tutorials wissen Sie, wie Sie:

  • Entwerfen Sie eine Klassenhierarchie mithilfe der Vererbung
  • Definieren Sie Attribute und Methoden, um Ihren Objekten Eigenschaften und Verhaltensweisen zu verleihen
  • Überschreiben Sie geerbte Attribute in untergeordneten Klassen
  • Schreiben Sie eine Hilfsfunktion, die über mehrere Klassentypen hinweg funktioniert
  • Erstellen Sie eine interaktive Spielschleife mit Eingabevalidierung und Fehlerbehandlung
  • Benutzen Sie die random Modul, um das Gameplay unvorhersehbarer zu machen

Bevor Sie beginnen

Sie benötigen Python 3.8+ und eine Jupyter Pocket book-Umgebung. Über die in Python integrierte Bibliothek hinaus sind keine weiteren Bibliotheken erforderlich random Modul.

Eine gewisse Vertrautheit mit Python-Schleifen, -Funktionen und -Wörterbüchern wird hilfreich sein. Wenn Sie zuerst auffrischen möchten, können Sie dies tun Python-Grundlagen für die Datenanalyse Der Pfad deckt alles ab, was Sie brauchen. Auf das vollständige Projekt können Sie im zugreifen Dataquest-Appund das Lösungsnotizbuch ist eingeschaltet GitHub.

Der Spielplan

Bevor wir eine einzelne Codezeile schreiben, skizzieren wir, was wir erstellen:

  • Nahrungssuche: Suchen Sie nach zufälligen Samen, die Sie Ihrem Inventar hinzufügen können
  • Pflanzen: Wählen Sie einen Samen aus Ihrem Inventar und pflanzen Sie ihn
  • Pflege: Kümmern Sie sich um Ihre Pflanzen, um sie durch die Wachstumsstadien zu bringen
  • Ernte: Sammeln Sie den Ertrag ausgewachsener Pflanzen

Wir verwenden zwei Klassen, um dies zu modellieren: Plant (und seine Unterklassen), um die Dinge darzustellen, die angebaut werden, und Gardener um den Spieler zu repräsentieren. Eine Hilfsfunktion übernimmt die Auswahl der Gegenstände und eine Hauptspielschleife verbindet alles miteinander.

Beginnen wir mit unserem einzigen Import.

import random

Das ist es. Eine Bibliothek, um die Futtersuche-Mechanik unvorhersehbarer zu machen.

Teil 1: Die Pflanzenklasse

Attribute definieren

In OOP, a Klasse ist eine Blaupause. Es macht von alleine nichts, bis wir damit ein tatsächliches Objekt erstellen. Unser Plant Klasse ist der Bauplan für jede Pflanze im Spiel. Es definiert, welche Eigenschaften eine Pflanze hat und was eine Pflanze leisten kann.

class Plant:
    def __init__(self, title, harvest_yield):
        self.title = title
        self.harvest_yield = harvest_yield
        self.growth_stages = ("seed", "sprout", "mature", "flower", "fruit", "harvest-ready")
        self.current_growth_stage = self.growth_stages(0)
        self.harvestable = False

Der __init__ Methode ist eine spezielle Methode, die bei jeder neuen Methode automatisch ausgeführt wird Plant Objekt wird erstellt. Es initialisiert alle Attribute für diese bestimmte Anlageninstanz.

self Es lohnt sich, darüber nachzudenken, wenn es für Sie neu ist. Denken Sie nach self als Bezug auf die spezifische Pflanze, die wir erschaffen. Wenn wir schließlich eine Tomatenpflanze erschaffen, self ist das eine Tomate? Wenn wir eine Karotte herstellen, self ist das eine Karotte? Jedes Objekt erhält unabhängig von jedem anderen Objekt eine eigene Kopie dieser Attribute.

Unsere Pflanze hat fünf Attribute: einen Namen, einen Ernteertrag (wie viel wir bekommen, wenn wir sie pflücken), eine Liste der Wachstumsstadien, die sie durchläuft, ihr aktuelles Wachstumsstadium (beginnend bei „seed“) und eine boolesche Markierung, die angibt, ob sie derzeit erntefähig ist (beginnend mit False).

Lerneinblick: Attribute sind das „Was“ einer Klasse und Methoden sind das „Wie“. Der Identify einer Pflanze, ihr Wachstumsstadium und ihr Erntestatus sind entscheidend Ist. Anbau und Ernte sind das A und O tut. Wenn Sie diese Unterscheidung klar halten, können Sie Ihre Klassen viel einfacher entwerfen, bevor Sie Code schreiben.

Methoden hinzufügen

Geben wir unserer Pflanze nun einige Verhaltensweisen.

    def develop(self):
        current_index = self.growth_stages.index(self.current_growth_stage)
        if self.current_growth_stage == self.growth_stages(-1):
            print(f"{self.title} is already absolutely grown!")
        elif current_index < len(self.growth_stages) - 1:
            self.current_growth_stage = self.growth_stages(current_index + 1)
            if self.current_growth_stage == "harvest-ready":
                self.harvestable = True

    def harvest(self):
        if self.harvestable:
            self.harvestable = False
            return self.harvest_yield
        else:
            return None

develop() findet die aktuelle Place in der Liste der Wachstumsstadien und bringt die Pflanze einen Schritt vorwärts. Befindet es sich bereits in der letzten Part, gibt es eine Meldung aus und stoppt. Wenn es durch Vorrücken auf „Erntebereit“ gestellt wird, wird es umgedreht harvestable Zu True.

harvest() Prüft, ob die Pflanze geerntet werden kann, und setzt das Flag auf zurück Falseund gibt den Ertrag zurück. Rückkehr None Wenn die Pflanze noch nicht bereit ist, können wir diesen Fall elegant behandeln, wenn der Gärtner diese Methode aufruft.

Teil 2: Pflanzenunterklassen

Unser Common Plant Klasse ist die Grundlage, aber nicht jede Pflanze wächst auf die gleiche Weise. Tomaten blühen und tragen Früchte, bevor sie erntereif sind. Salat und Karotten nicht. Hier kommt die Vererbung ins Spiel.

class Tomato(Plant):
    def __init__(self):
        tremendous().__init__("Tomato", 10)

class Lettuce(Plant):
    def __init__(self):
        tremendous().__init__("Lettuce", 5)
        self.growth_stages = ("seed", "sprout", "mature", "harvest-ready")

class Carrot(Plant):
    def __init__(self):
        tremendous().__init__("Carrot", 8)
        self.growth_stages = ("seed", "sprout", "mature", "harvest-ready")

Tomato(Plant) bedeutet „Tomate erbt von Pflanze.“ Der tremendous().__init__() name übernimmt alles aus der übergeordneten Klasse, sodass wir alle fünf Attribute und beide Methoden erhalten, ohne etwas neu zu schreiben. Wir geben einfach den Namen und die spezifischen Ertragswerte einer Tomate ein.

Lettuce Und Carrot Machen Sie dasselbe, weisen Sie dann aber sofort neu zu self.growth_stages zu einer kürzeren Liste. Das ist Attributüberschreibung: Die untergeordnete Klasse beginnt mit den Wachstumsstadien der übergeordneten Klasse und ersetzt sie dann durch ihre eigenen. Die Pflanze hat keine Erinnerung an das, was vorher da battle.

Stellen Sie sich das wie eine Neuzuweisung von Variablen vor. Wenn x = 1 und dann x = 2Python erinnert sich nicht daran x battle jemals 1. Gleiches Prinzip hier.

Wenn drei Pflanzentypen definiert sind, können Sie problemlos weitere hinzufügen, indem Sie neue Unterklassen erstellen und den Namen, den Ertrag und die Wachstumsstadien nach Bedarf anpassen.

Teil 3: Die select_item() Hilfsfunktion

Bevor wir das bauen Gardener Klasse benötigen wir eine Hilfsfunktion, die eine Liste oder ein Wörterbuch mit Optionen anzeigt und es dem Spieler ermöglicht, eine Choice anhand der Nummer auszuwählen. Dieselbe Funktion wird beim Pflanzen (Auswählen aus dem Inventar) und Ernten (Auswählen aus gepflanzten Pflanzen) verwendet, daher ist es die richtige Entscheidung, sie getrennt und wiederverwendbar zu halten.

def select_item(gadgets):
    if kind(gadgets) == dict:
        item_list = listing(gadgets.keys())
    elif kind(gadgets) == listing:
        item_list = gadgets
    else:
        print("Invalid gadgets kind.")
        return None

    for i in vary(len(item_list)):
        attempt:
            item_name = item_list(i).title
        besides:
            item_name = item_list(i)
        print(f"{i + 1}. {item_name}")

    whereas True:
        user_input = enter("Choose an merchandise: ")
        attempt:
            user_input = int(user_input)
            if 0 < user_input <= len(item_list):
                return item_list(user_input - 1)
            else:
                print("Invalid enter.")
        besides:
            print("Invalid enter.")

Die Funktion akzeptiert entweder ein Wörterbuch oder eine Liste. Wenn ein Wörterbuch (wie das Inventar des Spielers) übergeben wird, werden die Schlüssel in eine Liste umgewandelt. Wenn eine Liste übergeben wird (z. B. die gepflanzten Pflanzen), wird diese direkt verwendet.

Die Anzeigeschleife versucht, darauf zuzugreifen .title auf jeden Artikel zuerst. Wenn das funktioniert, bedeutet das, dass wir uns mit einem befassen Plant Objekt. Wenn es fehlschlägt (weil wir es mit einer einfachen Zeichenfolge wie "tomato"), es verwendet nur das Aspect selbst. Dies ist ein kleines Beispiel dafür, wie ein Attributname über mehrere Klassen hinweg geteilt wird (beide Plant Und Gardener habe ein title Attribut) ermöglicht es uns, flexiblen Code zu schreiben, der den spezifischen Objekttyp, mit dem er arbeitet, nicht überprüfen muss.

Der whereas True Die Schleife unten fordert so lange auf, bis der Spieler eine gültige Nummer angibt. Wenn sie etwas eingeben, das nicht in eine Ganzzahl konvertiert werden kann, wird die besides Block fängt es und fragt erneut.

Teil 4: Die Gärtnerklasse

Nun zum Spieler. Der Gardener Die Klasse stellt alles dar, was die Individual, die das Spiel spielt, tun kann.

class Gardener:
    plant_dict = {"tomato": Tomato, "lettuce": Lettuce, "carrot": Carrot}

    def __init__(self, title):
        self.title = title
        self.planted_plants = ()
        self.stock = {}

Beachten plant_dict wird vorher definiert __init__außerhalb einer Methode. Dies macht es zu einem Attribut auf Klassenebene, das von allen gemeinsam genutzt wird Gardener Objekte und nicht spezifisch für eine einzelne Instanz. Es ordnet String-Namen den Klassenplänen selbst zu, nicht den Objekten. Keine Klammern bedeuten, dass hier keine einzelne Pflanze erstellt wird, sondern lediglich ein Verweis auf das Rezept.

Die drei Instanzattribute sind: der Identify des Gärtners, eine leere Liste der derzeit im Boden befindlichen Pflanzen und ein leeres Inventarverzeichnis, in dem Schlüssel Elementnamen und Werte Mengen sind.

Der plant() Verfahren

    def plant(self):
        selected_plant = select_item(self.stock)
        if selected_plant in self.stock and self.stock(selected_plant) > 0:
            self.stock(selected_plant) -= 1
            if self.stock(selected_plant) == 0:
                del self.stock(selected_plant)
            new_plant = self.plant_dict(selected_plant)()
            self.planted_plants.append(new_plant)
            print(f"{self.title} planted a {selected_plant}!")
        else:
            print(f"{self.title} does not have any {selected_plant} to plant!")

Wenn der Spieler „plant“ eingibt, wird diese Methode aufgerufen select_item() auf ihr Inventar, um eine Auswahl zu erhalten, verringert den Inventarzähler um 1, löscht den Eintrag, wenn er Null erreicht, und erstellt dann ein neues Pflanzenobjekt mit plant_dict.

Das self.plant_dict(selected_plant)() Linie ist die schwierigste im gesamten Projekt. Lassen Sie es uns aufschlüsseln. self.plant_dict(selected_plant) sucht den Bauplan in unserem Wörterbuch nach (z. B. der Tomato Klasse). Der () am Ende instanziiert es dann und erstellt ein tatsächliches Tomato Objekt. Die Klammern machen aus einem Bauplan ein Objekt.

Der have a tendency() Verfahren

    def have a tendency(self):
        for plant in self.planted_plants:
            if plant.harvestable:
                print(f"{plant.title} is able to be harvested!")
            else:
                plant.develop()
                print(f"{plant.title} is now a {plant.current_growth_stage}!")

Die Pflege durchläuft jede gepflanzte Pflanze und erinnert den Spieler entweder daran, dass sie zur Ernte bereit ist, oder ruft ihn an plant.develop() um es eine Stufe voranzubringen. Dies ist die OOP-Komposition am Werk: die Gardener Die Methode delegiert die eigentliche Wachstumslogik an die Plant Methode der Klasse. Der Gardener muss nicht wissen, wie Wachstumsphasen funktionieren, es ruft einfach ab develop() und vertraut darauf, dass die Plant Objekt weiß, was zu tun ist.

Der harvest() Verfahren

    def harvest(self):
        selected_plant = select_item(self.planted_plants)
        if selected_plant.harvestable == True:
            if selected_plant.title in self.stock:
                self.stock(selected_plant.title) += selected_plant.harvest()
            else:
                self.stock(selected_plant.title) = selected_plant.harvest()
            print(f"You harvested a {selected_plant.title}!")
            self.planted_plants.take away(selected_plant)
        else:
            print(f"You'll be able to't harvest a {selected_plant.title}!")

Der Spieler wählt eine Pflanze aus seiner Pflanzliste aus. Wenn es erntefähig ist, rufen wir an plant.harvest() Dadurch wird der Ertrag zurückgegeben und die Markierung „Erntbar“ zurückgesetzt. Anschließend wird diese Zahl dem Inventar hinzugefügt. Die Pflanze wird entfernt planted_plants seit es ausgewählt wurde. Wenn die Pflanze noch nicht fertig ist, informieren wir den Spieler.

Der forage_for_seeds() Verfahren

    def forage_for_seeds(self):
        seed = random.alternative(all_plant_types)
        if seed in self.stock:
            self.stock(seed) += 1
        else:
            self.stock(seed) = 1
        print(f"{self.title} discovered a {seed} seed!")

Hier ist random kommt rein. random.alternative() wählt nach dem Zufallsprinzip einen Gegenstand aus einer Liste aus, wodurch jeder Futtersuchversuch ein anderes Ergebnis liefert. Wenn das gefundene Saatgut bereits im Inventar ist, addieren wir die Anzahl; Wenn nicht, erstellen wir einen neuen Eintrag. Der all_plant_types Die Liste wird im Abschnitt zum Spiel-Setup unten definiert.

Teil 5: Die Hauptspielschleife

Nachdem beide Klassen und die Hilfsfunktion definiert sind, können wir das Spiel zusammenstellen.

all_plant_types = ("tomato", "lettuce", "carrot")
valid_commands = ("plant", "have a tendency", "harvest", "forage", "assist", "give up")

print("Welcome to the backyard! You'll act as a digital gardener.nForage for brand spanking new seeds, plant them, after which watch them develop!nStart by coming into your title.")

gardener_name = enter("What's your title? ")
print(f"Welcome, {gardener_name}! Let's get gardening!nType 'assist' for a listing of instructions.")

gardener = Gardener(gardener_name)

all_plant_types ist die Liste, die forage_for_seeds() schöpft aus. valid_commands ist der vollständige Satz an Dingen, die der Spieler eingeben kann. Wir erfassen den Namen des Spielers als Eingabe und geben ihn an weiter Gardener() um unsere spezifische Spielinstanz zu erstellen.

Jetzt die Spielschleife:

whereas True:
    player_action = enter("What would you love to do? ")
    player_action = player_action.decrease()

    if player_action in valid_commands:
        if player_action == "plant":
            gardener.plant()
        elif player_action == "have a tendency":
            gardener.have a tendency()
        elif player_action == "harvest":
            gardener.harvest()
        elif player_action == "forage":
            gardener.forage_for_seeds()
        elif player_action == "assist":
            print("*** Instructions ***")
            for command in valid_commands:
                print(command)
        elif player_action == "give up":
            print("Goodbye!")
            break
    else:
        print("Invalid command.")

Der whereas True Die Schleife läuft auf unbestimmte Zeit, bis der Spieler „give up“ eingibt, was eine auslöst break. Jeder andere gültige Befehl ruft die entsprechende Methode auf unserem auf gardener Objekt. Der .decrease() Ein Aufruf der Eingabe bedeutet, dass Spieler „PLANT“, „Plant“ oder „plant“ eingeben können und das Spiel damit korrekt umgeht.

Beachten Sie, wie kompakt diese Schleife ist, trotz allem, was sie tut. Die ganze Pflanzlogik lebt darin Gardener.plant()die ganze Wachstumslogik lebt darin Plant.develop()die gesamte Elementauswahllogik ist darin enthalten select_item(). Die Hauptschleife ist nur ein Dispatcher: Sie liest Eingaben und ruft die richtige Methode auf. Das ist der Vorteil des Bauens mit OOP.

Zuschauen, wie es läuft

So sieht eine kurze Spielsitzung aus:

Welcome to the backyard! You'll act as a digital gardener.
Forage for brand spanking new seeds, plant them, after which watch them develop!
Begin by coming into your title.
What's your title?  Jane Doe
Welcome, Jane Doe! Let's get gardening!
Kind 'assist' for a listing of instructions.
What would you love to do?  assist
*** Instructions ***
plant
have a tendency
harvest
forage
assist
give up
What would you love to do?  forage
Jane Doe discovered a lettuce seed!
What would you love to do?  plant
1. lettuce
Choose an merchandise:  1
Jane Doe planted a lettuce!
What would you love to do?  have a tendency
Lettuce is now a sprout!
What would you love to do?  have a tendency
Lettuce is now a mature!
What would you love to do?  have a tendency
Lettuce is now a harvest-ready!
What would you love to do?  harvest
1. Lettuce
Choose an merchandise:  1
You harvested a Lettuce!
What would you love to do?  give up
Goodbye!

Alles funktioniert zusammen: das Plant Blaupause, die Gardener Methoden, die select_item() Helfer und die Spielschleife. Jedes Teil hat eine Verantwortung und sie kommunizieren sauber miteinander über Methodenaufrufe und Rückgabewerte.

Nächste Schritte

Es gibt viel Raum, dieses Spiel zu erweitern, und die Erweiterung ist eine der besten Möglichkeiten, Ihre OOP-Fähigkeiten zu vertiefen.

Fügen Sie ein hinzu get_inventory() Verfahren. Das Spiel bietet derzeit keine Möglichkeit, anzuzeigen, was sich in Ihrem Inventar befindet, ohne zu pflanzen oder zu ernten. Dies hinzuzufügen ist eine gute Einstiegsherausforderung, da es denselben Mustern folgt, die wir bereits verwendet haben.

Führen Sie ein Währungssystem ein. Fügen Sie ein hinzu worth Attribut für jede Pflanzenklasse, a foreign money Attribut zu GardenerUnd purchase() Und promote() Methoden. Spieler könnten Geld verdienen, indem sie ernten und es für Samen ausgeben, anstatt es zu suchen.

Fügen Sie Schädlinge und zufällige Ereignisse hinzu. Im Second ist jede Aktion erfolgreich. Versuchen Sie, eine kleine Probability hinzuzufügen (mit random), dass eine Pflanze nicht wächst oder dass ein Schädling dazu führt, dass sie ein Wachstumsstadium zurückgeht. Spiele werden interessanter, wenn es um etwas geht.

Fügen Sie ein Zeitmesssystem hinzu. Derzeit kann ein Spieler in Sekundenschnelle Spam senden, „ernten“ und ernten. Sie könnten Pythons verwenden time Modul, um eine Verzögerung zwischen den Pflegeaktionen zu erfordern oder jede Pflanze nur einmal professional Spielrunde wachsen zu lassen.

Fügen Sie Erfolgsmeldungen hinzu. Belohnen Sie Spieler für Meilensteine ​​wie das Ernten von 10 Pflanzen, das Pflanzen aller drei Pflanzenarten oder das Entdecken eines seltenen Samens. Ein einfacher Zähler und ein paar bedingte Druckanweisungen genügen.

Ressourcen

Wenn Sie das Spiel erweitern, teilen Sie es in der Neighborhood und markieren Sie @Anna_strahl. Dieses Projekt kann in viele Richtungen gehen und es ist wirklich interessant zu sehen, was andere bauen.

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert