# Tutorial OOP ## Setup ### Hausaufgabe 1. Downloade und installiere den Editor/ die IDE **Virtual Studio Code**, kurz **VS Code**, :
Es ist sehr empfohlen, bei der Installation die folgenden Optionen anzuwählen: * Aktion "Mit Code öffnen" dem Dateikontextmenu von Windows-Explorer hinzufügen * Aktion "Mit Code öffnen" dem Verzeichniskontextmenu von Windows-Explorer hinzufügen * Zu PATH hinzufügen Auch empfohlen ist die Option: * Code als Editor für unterstützte Dateitypen registrieren Diese Option macht VS Code zu deinem Standardeditor. Entscheide selbst, ob du dies möchtest. Mein Tipp: Mache es, VS Code ist wirklich toll! 2. Erstelle einen Account auf **GitHub**: 3. Falls du unter Windows arbeitest: Installiere **Git**. Lade Git hier herunter und installiere es . Bei der Installation wirst du einige Entscheidungen treffen müssen. Wähle die folgendenOption: * "Use Git from Git Bash only" * "Use the OpenSSL library" * "Checkout Windows-style, commit Unix-style line endings" * "Use MinTTY" * Wähle bei den "Configuring extra options" alle Optionen an 1. Du kannst gerne bereits mit den Schritten beschrieben in [In Lektion](#In_Lektion) und in [Auftrag nach erster Lektion](#Auftrag_nach_erster_Lektion). ### In Lektion 1. Erstelle auf GitHub ein neues Repository: * Namen: *tutorial_oop_voci* * private Repo * mit Gitignore-File (Programmiersprache: Python) *Achtung:* Bitte achte darauf, alle Vorgaben, was das Benennen von Ordnern, Repositories und Files angeht, zu 100% zu befolgen, inkl. Gross- & Kleinschreibweise 2. **clone** dieses Repo auf deinen Computer. 3. Öffne diesen Ordner in VS Code (*File / Open Folder*) 4. Erstelle in VS Code ein File `helloworld.py` mit dem üblichen Inhalt print("Hello World!") 5. Speichere, füge das File dem Git-Repo hinzu, committe und pushe nach GitHub. 6. Vergewissere dich, dass die Änderung auf GitHub angekommen ist. 7. Füge *anschae* als Collaborator dieses Repos hinzu. ### Auftrag nach erster Lektion Studiere im Tutorial **Python Setup** die folgenden Kapitel. Die Schritte, die du explizit machen sollst, werden explizit genannt: * Kapitel 1 (Abstract) * Kapitel 2 (Terminal) * Kapitel 3 (System-Installation von Python): * Nimm eine System-Installation von Python vor. * Kapitel 4 (Ausführen von Python Programmen): * Führe ein Python-Programm in der Konsole aus. * Kapitel 6 (Git und GitHub): * In der Lektion haben wir ein Repo auf GitHub erstellt und dieses auf den Computer geklont. Nimm an diesem Repo auf deinem Computer Änderungen vor: Ändere das vorhandene File, füge weitere Files hinzu, committe deine Änderungen und pushe diese nach GitHub. Stelle sicher, dass alles funktioniert. * Kapitel 7 (7 IDE (Integrated Development Environment) * Kapitel 8 (Visual Studio Code) * Richte VS Code für das Programmieren mit Python ein. ### Dauerauftrag In diesem Tutorial wirst du mehrere Python-Programme schreiben. Füge diese alle *unaufgefordert* dem Git-Repository *tutorial_oop_voci* hinzu und pushe alle Änderungen nach GitHub. Hast du den Auftrag, eine gewisse Aufgabe bis Datum X zu erledigen, so wird erwartet, dass bis dann das zugehörige Python-File auf deinem GitHub-Account zu finden ist. ## Theorie OOP I (Einführung) Es gibt verschiedene, fundamental unterschiedliche Stile, wie man Programmieren kann. Diese Programmierstile nennt man auch **Programmierparadigmen**. Python ist eine Multiparadigmensprache. Das bedeutet, Python zwingt den Programmierer nicht zu einem einzigen Programmierstil, sondern erlaubt, das für die jeweilige Aufgabe am besten geeignete Paradigma zu wählen. Den Code, den wir bisher geschrieben haben, ordnet man der **prozeduralen Programmierung** betrieben. Der Code wird von oben nach unten ausgeführt. Dabei durchläuft der Code Schlaufen und es werden Funktionen aufgerufen. Mit dieser Art der Programmierung kann man eigentlich alles umsetzen. Allerdings wird der Code, sobald er eine gewisse Länge und Komplexität aufweisst, sehr unübersichtlich. Der Code beinhaltet dann viele verschiedene Funktionen, die wild durcheinander aufgerufen werden. Das macht es sehr schwwierig, die Übersicht zu behalten. Noch schlimmer ist es, wenn man einen solchen Code, der eine andere Person geschrieben hat, lesen und verstehen möchte. Solchen Code nennt man **Spaghetti Code**. Eine Abhilfe hier bietet ein anderes Programmierparadigma, die **objektorientierte Programmierung (OOP)**. Eine schöne Motivation und Einführung in die objektorientierte Programmierung findest du hier: In diesem Tutorial wirst du ein Konsolenprogramm (also ohne graphische Oberfläche) schreiben, und zwar ein Voci Trainer. Dabei die objektorientierte Programmierung kennenlernen. Zentral in der OOP sind **Klassen**. In unserem Programm wollen wir ein Wort z.B. in Englisch anzeigen lassen und der Anwender tippt dann das Wort in einer anderen Sprache, z.B. Deutsch, ein. Wir haben also ganz viele Wortpaare (z.B. One - Eins, Two - Zwei, Three - Drei, ...), die gespeichert werden müssen. Jedes Wortpaar ist gleich aufgebaut: Es beinhaltet zwei **Attribute** und zwar `wordL1` und `wordL2` (steht für *word language 1* und *word language 2*). Die genauen Inhalte der Attribute sind für jeses Wortpaar unterschiedlich, die Struktur ist aber immer gleich. Man kann sich Klassen also *Vorlagen* für Objekte vorstellen. Wir definieren deshalb eine **Klasse** `WordPair`. Wir befolgen die Konvention, dass Klassennamen mit Grossbuchstaben beginnen: class WordPair: def __init__(self,w1,w2): self.wordL1 = w1 self.wordL2 = w2 Hat man eine Klasse definiert, kann man **Instanzen** dieser Klasse erzeugen. Diese Instanzen werden **Objekte** genannt - deshalb der Name *objektorientierte* Programmierung. Dies macht man wie folgt: wp1 = WordPair("Hello", "Hallo") wp2 = WordPair("World", "Welt") Wir erzeugen zwei Wortpaare (`wp1` und `wp2`). Beachte, dass die Klasse die `__init__` Methode beinhaltet. Diese wird **Konstruktor** genannt: Sie wird jedesmal (und nur dann) aufgerufen, wenn eine neue Instanz der Klasse erzeugt (oder konstruiert) wird. Das erste Argument im Konstruktor ist immer `self`. Danach kommen, falls vorhanden, die Argumente, die man dem Konstruktor übergeben möchte, um Instanzen zu erzeugen. Wir wollen ja Wortpaare erzeugen, also müssen wir zwei Worte für die Erzeugung einer Instanz angeben (`w1` und `w2`). Hat man eine Instanz einer Klasse erzeugt, kann man ganz einfach auf deren Attribute zugreifen: print(wp1.wordL2) Output: Hallo Wir können jetzt eine Voci-Liste erstellen, die lauter Wortpaare beinhaltet: Voci = [ WordPair("One", "Eins"), WordPair("Two", "Zwei"), WordPair("Three", "Drei"), WordPair("Four", "Vier"), WordPair("Fife", "Fuenf") ] ## Aufgabe 1 (voci_trainer_v01) Erstelle nun die erste Version deines Voci-Trainers. Erstelle ein leeres Python-File `voci_trainer_v01.py`. 1. Dein Code soll nun in einer Endlosschlaufe laufen. 2. Nun soll per Zufall ein Wortpaar aus der Voci-Liste ausgewählt und das Wort in der einen Sprache angezeigt werden. 3. Der Benutzer soll nun aufgefordert werden, die Übersetzung einzutippen. 4. Der Code überprüft, ob die Übersetzung stimmt oder nicht. Es wird eine Rückmeldung angegeben. Der Benutzer wird aufgefordert, Enter zu drücken, um fortzufahren. 5. Ist die Übersetzung falsch, muss es der Benutzer weiter probieren, bis er es schafft. Ist die Übersetzung richtig, so wird per Zufall das nächste Wortpaar ausgewählt. Für jede neue Eingabe (egal ob zuvor richtig oder falsch), soll die Konsole geleert werden. *Tipps zu den Schritten oben:* 1. Die Endlosschlaufe erziehlst du mit: while True: # MEIN CODE 2. Benutze dazu das `random` Modul. Füge in der ersten Zeile deines Codes die folgende Zeile `import random` ein. Um aus einer Liste `L` ein zufälliges Element auszuwählen, tippe `random.choice(L)`. 3. Text in Konsole eintippen `inp = input("Text, der in Konsole angezeigt wird")`. `inp` (oder welchen Variablennamen man auch immer wählt) ist dann ein *String*, der im Code verwendet werden kann. 4. Kein Tipp notwendig 5. Die Konsole leeren kannst du mit `os.system('cls||clear')`. Dazu musst du am Anfang das deines Code das `os` Modul importieren: `import os`. Für den Code bisher würde es sich auch anbieten, ein *dictionary* anstelle einer Klasse für die Wortpaare zu erstellen. Ein dictionary ist eine Sammlung von Schlüssel-Werte Paaren. Im nächsten Codeblock siehst du, wie ein solches dictionary aussehen könnte. Doch warum verwenden wir Klassen und Objekte anstelle eines dictionary? Einerseits handelt es sich hier um ein OOP-Tutorial, v.a. wollen wir unsere Klasse aber noch erweitern. Zum Beispiel würde es Sinn machen, ein Attribute `self.progress` hinzuzufügen. Dies könnte eine ganze Zahl sein, die den Lernfortschritt angibt: 0 (beherrsche Wort nicht), 1 (beherrsche Wort einigermassen), 2 (beherrsche Wort gut). Auch werden wir sehen, dass Klassen Methoden (Funktionen) beinhalten können. wordPairsDict = { 'One': 'Eins', 'Two': 'Zwei', 'Three': 'Drei' } ## Aufgabe 2 (voci_trainer_v02) Erstelle eine Kopie deiner ersten Version des Programs: `voci_trainer_v02.py` Erweitere nun dein Programm um die folgenden Funktionalitäten: * Tippt man ein falsches Wort ein, soll man mehrere Auswahlmöglichkeiten zum weiteren Vorgehen haben: (1) noch einma versuchen, (2) richtige Übersetzung anzeigen lassen, (3) nächstes Wortpaar * Nach jeder Eingabe soll man die Möglichkeit haben, in der Konsole ein weiteres Wortpaar der Voci-Liste hinzuzufügen. [[talit:tutorial_oop_lsg|Tutorial OOP Lösungen]] ## Aufgabe 3 (voci_trainer_v03) Erstelle eine Kopie deiner letzten Version des Programs: `voci_trainer_v03.py`. Erweitere nun dein Programm um die folgenden Funktionalitäten: * Wir wollen für jedes Wort den Lernfortschritt speichern. Erweitere die Klasse um das Attribut *progress*. Wird ein WordPair kreiert, soll der Fortschritt 0 sein (`self.progress = 0`): class WordPair: def __init__(self,w1,w2): self.wordL1 = w1 self.wordL2 = w2 self.progress = 0 * Übersetzt man ein Wort (im ersten Anlauf) korrekt, so soll der Fortschritt um 1 erhöht werden. * Gibt man ein Wort falsch ein, so soll der Fortschritt wieder um 1 zurückgestuft werden. Allerdings soll der Fortschritt nie kleiner als 0 sein. * Sobald ein WordPair den Fortschritt `PROGRESS_MAX` erreicht hat, soll es nicht mehr abgefragt werden. * Füge dazu am Anfang deines Codes eine globale Konstante `PROGRESS_MAX = 3`. Wir verwenden hier Grossbuchstaben. Damit wollen wir andeuten, dass es sich hier um eine Konstante handelt, die *nie verändert* werden soll im Code. ## Aufgabe 4 (Datenformate) Im nächsten Schritt wollen wir die WordPairs aus Daten erzeugen, die wir aus Datenfiles einlesen. Typische Formate um Textdaten zu speichern, sind: * CSV * JSON * (XML) Bildet Gruppen (je 2 SuS) und arbeitet euch in eines der beiden Datenformate (CSV oder JSON) ein. **Auftrag:** - Arbeitet euch in euer Datenformat ein: Wie werden Daten damit gespeichert? Was sind die Besonderheiten? ... - Findet heraus, wie man mit Python solche Datenformate einlesen und schreiben kann. - Erstellt ein Tutorial für eure Kollegen über euer Datenformat. Dieses soll einerseits eine allgemeine Einführung und Übersicht über euer Datenformat beinhalten. Andererseits soll genau erklärt werden, wie man mit Python mit diesem Format arbeiten kann. - Euer Tutorial soll ein repräsentatives Beispiel einer Datei von Eurem Format beinhalten. Stellt dieses in eurem Eintrag den Anderen zur Verfügung (siehe Beispiel unten). - Ebenso soll euer Tutorial ein Python-File beinhalten, mit dem dieses File gelesen und in dieses geschrieben werden kann (siehe ebenfalls Beispiel unten). - Findet eine Website, auf der man Dateien eures Formats auf Gültigkeit prüfen kann. - Ladet euer Tutorial auf dieses DokuWiki in eurem [[talit_2018_2022:start|SuS Arbeitsbereich]] - **Erste (aber trotzdem gute) Version: Sonntag 20.09.2019** - **Deadline: Montag 21.09.2019** Ein Codeblock im DokuWiki Editor wird wie folgt geschrieben: ``` for i in range(3): print(i) ``` Dies sieht dann wie folgt aus: for i in range(3): print(i) Mit einem Klick auf den Filenamen kann das File dann direkt heruntergeladen werden. Wird kein Filenamen angegeben, so wird einfach der Code angezeigt, ohne dass man diesen hinunterladen kann. Für andere Programmiersprachen oder Datenformate muss man die Definition entsprechend anpassen. Beispiel JSON: ``` // füge hier die Daten im JSON Format ein ` ``` Beispiel C#: ``` // C#: Console.WriteLine("Hallo Welt."); ` ``` ## Aufgabe 5 (voci_trainer_v04) * Erstelle im Ordner `tutorial_oop_voci` einen Unterordner `dicts`. \\ \\ * Erstelle darin mindestens vier Files, je zwei im CSV- und zwei im JSON-Format. Jedes File soll eine Wörtliliste zu einem Thema beinhalten, z.B. `animals_en_de.csv`, `sport_fr_de.json`, ... Jeder Eintrag soll beihalten: - Wort in Sprache 1 \\ - Wort in Sprache 2 \\ - Progress (z.B. Zahlen von 0 bis 3, wobei 0 für 'noch gar nicht gelernt' und 3 für 'komplett gelernt' stehen) \\ \\ * *Tipp:* Mache eine Sicherheitskopie dieser Files, falls du sie aus Versehen überschreibst. \\ \\ * Mache eine Kopie von deinem aktuellsten Voci Trainer und speichere diesen unter dem Namen `voci_trainer_v04.py` und bearbeite nun diese Datei. \\ \\ * Erstelle nun eine neue Klasse *Voci*. Diese hat das Attribut *WordPairList*. Dies ist eine Liste, die alle zugehörigen WordPairs enthält. In seiner ursprünglichsten Form könnte diese Klasse so aussehen: class Voci: def __init__(self,wpList): self.WordPairList = wpList * Beim Programmstart soll dein Programm nun automatisch den Ordner `dicts` nach CSV, JSON und XML Files durchsuchen. Jedes gefundene solche File soll dann gelesen werden. Erstelle für jedes File ein Objekt der Klasse *Voci* und fülle die im File enthaltenen WordPairs in die zugehörige WordPairList. \\ Es gibt verschiedene Möglichkeiten, Files in einem Ordner zu suchen, z.B.: import glob glob.glob("dicts/*.csv") * Sobald die Daten geladen wurden, sollst du die Auswahl haben, welche Vociliste du lernen möchtest. \\ \\ * *Tipps:* * Das Programm soll funktionieren, egal wie viele Files du zum einlesen hast. Wie kannst du eine unbekannte Anzahl an Objekten der Klasse Voci kreieren? * Das Programm wird allmählich lange und kompliziert. Definiere sinnvolle Funktionen, damit nicht aller Code im Hauptteil definiert ist. ## Aufgabe 6 (voci_trainer_v04) Erweitere deinen Code (voci_trainer_v04.py) nun so, dass der Fortschritt in den Files gespeichert wird. Dein Code soll die Option haben, das Programm zu beenden. Bevor das Programm geschlossen wird, soll der Fortschritt gespeichert werden. Überschreibe dazu die bisherigen Voci-Wörtli-Files. Achte also darauf, dass die Files genau gleich geschrieben werden, wie sie eingelesen werden. *Tipp:* Mache Kopien von deinen Voci-Listen, falls diese falsch oder leer überschrieben werden. ## Vorbereitung Console Game **Auftrag:** Als kleine Abschlussarbeit für dieses Semester (nach dem Voci-Programm) sollt ihr ein Konsolen Game alleine oder in 2er Gruppen programmieren. Überlege dir auf nächste Woche (a) mit wem du arbeiten möchtest (oder alleine) und (b) was für ein Game es sein soll. Die Bedingungen an das Game sind: * Spielt in Konsole. * Ihr dürft ein bereits bekanntes Spiel implementieren. Ihr könnt aber auch ein eigenes erfinden oder ein bestehendes abändern/erweitern. * Gibt nach erfolgreichem Spielen einen Score zurück: absolviertes Level und Anzahl Punkte (z.B. gesammelte Punkte, Spieldauer in Sekunden, Anzahl Versuche, ...) * Es ist auch möglich (und sogar wünschenswert), verschiedene Levels zu haben. Das Ziel wäre dann, ein möglichst hohes Level mit einem möglichst guten Score zu erzielen. * Das Game wird selbständig und komplett von euch programmiert. ## Theorie OOP II (Methoden, Vererbung) Quellen: * [[https://www.python-kurs.eu/klassen.php]] Bisher haben wir Klassen und deren Objekte ausschliesslich dafür benutzt, um Daten zu speichern. Zum Beispiel haben wir alle Informationen, die zu einem Wortepaar (also das Wort in 2 Sprachen und den Progress) in einem Objekt vom Typ //WordPair// gespeichert. Klassen können aber noch viel mehr! Wir haben gesehen, dass man in einer Klasse alle Informationen, welche zusammen gehören, speichern kann. Man kann aber einer Klasse auch **Funktionalität** hinzufügen. OOP könnte man wie folgt kurz **zusammenfassen**: //Das Grundkonzept der objektorientierten Programmierung besteht darin, Daten und deren Funktionen (Methoden), - d.h. Funktionen, die auf diese Daten angewendet werden können - in einem Objekt zusammenzufassen.// ### Methoden Zum Beispiel könnten wir unsere Klasse //WordPair// wie folgt erweitern: class WordPair: def __init__(self,w1,w2): self.wordL1 = w1 self.wordL2 = w2 self.progress = 0 def show_info(self): print(f"word language 1: {self.wordL1}, word language 2: {self.wordL2}, progress: {self.progress}") def increase_progress(self,max_progress): if self.progress < max_progress: self.progress += 1 def translation_correct_wordL1(self,value): if value == self.wordL1: return True else: return False def translation_correct_wordL2(self,value): if value == self.wordL2: return True else: return False Die erste Methode (//show_info//) zeigt einfach alle Attribute der Klasse an. Nachdem wir ein Objekt dieser Klasse erstellt haben, können wir diese Methode aufrufen: wp = WordPair("hubbub","Tohuwabohu",1) wp.show_info() Die zweite Methode (//increase_progress//) ist spannender. Diese rufen wir auf, wenn wir den Progress um 1 erhöhen wollen. Dies soll aber nur dann geschehen, wenn nicht bereits das Maximum erreicht wurde. Beachte, dass dann jeweils noch der max. Progress übergeben werden muss, da diese Information nicht für jedes //WordPair// gespeichert wurde sondern ein Attribut in der //Voci//-Klasse ist. wp = WordPair("hubbub","Tohuwabohu",1) wp.increase_progress(3) Warum wollen wir den Progress mittels einer Klassen-Methode erhöhen und nicht direkt dort im Code wo das Wort abgefragt wird? Weil es Sinn macht, wenn man alle Logik, die WordPairs betrifft in die entsprechende Klasse hinein packt. Die letzten beiden Methoden werden aufgerufen, wenn ein Input überprüft werden soll. Hat der Benutzer die korrekte Übersetzung eingegeben, so gibt diese Methode den Boolean //True// und ansonsten //False// zurück. Überlege dir: Gibt es weitere Methoden, mit denen du die //WordPair//-Klasse sinnvoll erweitern kannst? ### Vererbung Ein wichtiges Konzept der OOP ist die **Vererbung**. Machen wir ein Beispiel: In unserem Code haben wir eine Klasse //Voci//. Für jedes Datenfile, welches eine Wörtliliste enthält, erstellen wir ein //Voci//-Objekt. Nachdem wir die Wörtli gelernt haben, wollen wir den Inhalt des //Voci//-Objekts zurück in die Datei schreiben. Es macht also Sinn, wenn die //Voci//-Klasse zwei Methoden //read\_file// und //save\_file// hat. Die Klasse könnte dann so aussehen: class Voci: def __init__(self,file_path,max_progress): self.file_path = file_path self.max_progress = max_progress self.name = None self.WordPairList = [] self.language1 = None self.language2 = None def read_file(self): pass def save_file(self): pass Das Problem ist nun aber: Wir haben insgesamt drei verschiedene Dateiformate. Das bedeutet also, dass die //read\_file// und //save\_file//-Methoden komplett anders aussehen, wenn wir ein CSV- oder ein JSON-File einlesen oder schreiben wollen. Wir erstellen deshalb drei verschiedene Klassen: CSV\_Voci, XML\_Voci und JSON\_Voci. Dies sollen drei eigene Klassen sein, alle sollen aber gleich aufgebaut sein, und zwar genau so wie die allgemeine //Voci//-Klasse. Deshalb sagen wir diesen drei neuen Klassen, dass sie von der //Voci//-Klasse **erben** sollen: class CSV_Voci(Voci): def __init__(self, file_path,max_progress=3): super().__init__(file_path,max_progress) self.read_file() def save_file(self): pass def read_file(self): pass * In der ersten Zeile geben wir eben an, dass //CSV\_Voci// von //Voci// erben soll. //Voci// wird dann die **parent class** und CSV\_Voci die **child class** genannt.\\ \\ * Mit `super()` ist jeweils die **parent class** einer Klasse gemeint. Mit `super().__init__(file_path,max_progress)` sagen wir deshalb, dass die `__init__()`-Metode aus //Voci// verwendet werden soll.\\ \\ * Instanziieren wir ein neues //CSV\_Voci// Objekt, müssen wir den Pfad zum File und den max. Progress angeben: `voci = CSV_Voci('my_super_voci.csv',3)`.\\ \\ * Mit `self.read_file()` lesen wir dann automatisch die Daten aus dem File ein. \\ \\ * **Speichern** können wir dann einfach mit `voci.save_file()`.\\ \\ * Betrachten wir nun die //read_file//-Methode. Beachte, dass diese sowohl in der parent class //Voci// wie auch in der child class //CSV\_Voci// vorkommt: def read_file(self): pass * In der //Voci//-Klasse lassen wir diesen Code unverändert. Damit zeigen wir an, dass alle Klassen, die von //Voci// erben, eine solche Methode haben sollen. Da diese Methode für jede dieser child Klassen sehr unterschiedlich ist, kann diese Methode in der parent class noch nicht implementiert werden, wir schreiben einfach `pass`. Hingegen muss diese Methode in jeder child class wie //CSV\_Voci// explizit implementiert werden.\\ \\ * Analog für //save\_file// ## Aufgabe 7 Nun wollen wir uns an die finale Version unseres Codes machen. Bisher wurde der gesamte Code in ein einzelnes File hinein geschrieben. Damit der Code übersichtlich bleibt, macht es aber Sinn, diesen auf mehrere Files aufzuteilen. Erstelle dazu im Repo einen neuen Ordner, z.B. //final//, in dem wir die finale Version ablegen. Erstelle darin einen Unterordner //source// und einen Unterordner //dicts// für alle Files mit Wörtlilisten. Erstelle dann folgende Files in der folgenden Struktur: * app.py * source/voci.py * source/wordpair.py * source/view.py Schreibe deinen Code möglichst objektorientiert um. //Tipp:// Kopiere deinen bestehenden Code nur in kleinen Päckchen in die neue Struktur. Stelle immer sicher, dass der Code keine Fehler beinhaltet, bevor du dich um weiteren Code kümmerst. ## Aufgabe 8 Finalisiere deinen Code. * soll auswählen können, in welche Richtung man übersetzen möchte: wordL1 -> wordL2 oder wordL2 -> wordL1 * soll im Menu //Editieren//-Option haben, in der man jeweils für die WordPairs eines Vocis ...: * neue WordPairs hinzufügen kann * bestehende WordPairs ändern kann * bestehende WordPairs löschen kann * den Progress aller WordPairs auf 0 zurücksetzen kann * Soll eine neue Voci-Liste erstellen können. Diese wird dann im gewünschten Dateiformat gespeichert * Überlege dir selbst, wie du dein Programm sinnvoll erweitern kannst.