Einführung in C++ Kapitel 12 — Abflug — Das Spiel

So, jetzt haben wir eine Menge über C++ gelernt und wissen ziemlich genau, wie wir eine einzelne Klasse zu schreiben und zu verwenden haben. Wie aber erstellen wir ein Programm, in dem verschiedene Klassen nebeneinander und zusammen arbeiten, um eine Aufgabe zu erfüllen? Ein Abenteuerspiel scheint sich für ein größeres Beispielprogramm recht gut zu eignen. Wir haben es mit viel Ein- und Ausgabe zu tun und das Programm muß ziemlich flexibel sein, da so viele Dinge das Spiel als Hindernisse, Rätsel, Überraschungen beeinflussen können.

Ort dieses einzigartigen Spieles ist ein Flughafen. Wichtiger als der Flughafen ist der Code, den wir brauchen, um heil durch den Flughafen zu kommen. Du solltest das Spiel zunächst einmal spielen, bis Du weißt, was der Code tut, um dann im Code selbst nachzuschauen, wie er das tut. Schließlich bekommst Du noch eine Aufgabe, den Code zu erweitern. Dann siehst Du, ob Du wirklich alles verstanden hast.

Spielispieli

Erst das Spiel, dann das Vergnügen. Zuerst also solltest Du ein bißchen Abflug spielen, bevor Du Dich auf den Quellcode stürzt.

Wenn Du schon einmal ein "Adventure" gespielt hast (und also rechtschaffen enttäuscht bist), solltest Du einfach ein paar Kommandos ausprobieren, um Deinen Weg durch den Flughafen zum richtigen Flieger zu finden. Ist Dir diese Art von Spiel völlig fremd, sollen einige Hinweise weiterhelfen.

Ziel des Spieles ist es, rechtzeitig das richtige Flugzeug zu besteigen und in den wohlverdienten Urlaub zu fliegen. Selbstredend stellen sich Dir einige Hindernisse und Probleme in den Weg. Die Lösung mußt Du selbst finden. Um das ganze ein bißchen spannender zu machen, hast Du nur ungefähr 25 Minuten Zeit. Jedes Kommando dauert eine Minute, Du mußt Dich also sputen! Nur das Flugzeug rechtzeitig zu erreichen ist aber auch noch nicht genug, es müssen einige zusätzliche Bedingungen erfüllt sein. Diese werden im Laufe des Spieles klar werden (spätestens am Schluß).

Du hast weniger als fünf Versuche gebraucht, um das Spiel zu schaffen (ohne den Code zu kennen)? Respekt.

Der Spielablauf

Die Spielweise ist extrem einfach. Du rennst einfach am Flughafen herum, immer auf der Suche nach Dingen, die Du tun könntest. Du bewegst dich, indem Du dem Programm mitteilst, in welche der vier Himmelsrichtungen (Norden, Süden, Osten oder Westen) Du gehen willst. Alle diese Richtungen können durch den ersten Buchstaben abgekürzt werden und entweder in Groß- oder in Kleinbuchstaben eingegeben werden. Es kann auch vorkommen, daß Du in eine bestimmte Richtung nicht gehen kannst. Starte das Programm jetzt, versuche es einmal mit den vier Richtungen und schau, was passiert. Wenn das noch unklar ist, dann gib das Wort "Hilfe" ein.

Du kannst auch Dinge nehmen oder Dich in den einzelnen Räumen umsehen. Sag dem System jetzt, daß Du Dich in diesem Raum umsehen willst, indem Du Schau eingibst. Dahinter verbirgt sich die Lösung zu so manchem Rätsel. Ein weiteres wichtiges Kommando ist Rucksack, das eine Liste der Dinge ausgibt, die Du bei Dir trägst. Was haben wir im Rucksack?

Alle anderen Kommandos bestehen aus zwei Wörtern, einem Verb und einem Hauptwort. Die Reihenfolge ist dabei unerheblich, da das Programm den Unterschied erkennt. Wenn Du ein Kommando eingibst, das das Programm nicht kennt, wird es Dir sagen, daß es Dich nicht versteht. Lege jetzt etwas hin, das Du besitzt, oder Nimm etwas, das sich im selben Raum befindet wie Du selbst.

Ein paar Freunde haben Abflug mit genau den Angaben gespielt, die ich auch Dir jetzt gegeben habe. Einer hat nur 40 Minuten gebraucht, die meisten aber eine Stunde, bis sie es geschafft haben, in Urlaub zu fliegen. Der gesamte Code für das Spiel ist in CPPSRC.ZIP enthalten, das Du für die übrigen Kapitel dieser Einführung schon heruntergeladen haben solltest. Das Spiel ist absichtlich relativ einfach und kurz, damit es auch einfach und schnell verstanden werden kann. Es gibt keinen Grund, warum das Programm nicht durch zusätzliche Räume, Gegenstände und Fallen viel größer gemacht werden könnte. Vielleicht machst Du ja gerade das, um weitere Erfahrungen im Programmieren in C++ zu sammeln.

Einige spezielle Konstanten

Beispielprogramm: ABFLUG.H

Die Datei ABFLUG.H beinhaltet die Definitionen von TRUE und FALSE sowie den Aufzählungstypen, die unseren Wortschatz für das Spiel definieren. Wir beginnen die List mit 1, damit wir den Wert 0 später verwenden können, um anzuzeigen, daß ein Wort nicht im Wörterbuch aufscheint.

Das #ifndef in Zeile 4 ist notwendig, da diese header-Datei in vielen anderen Dateien importiert wird. Wir müssen also eine multiple Definition vermeiden. Eine Klasse muß nur einmal definiert werden und wenn dies durch das Importieren dieser Datei geschehen ist, wird ABFLUG_H definiert und damit eine nochmalige Definition verhindert. Genauer haben wir das am Ende des Kapitel 7 beschrieben.

Die erste Klasse — Uhr

Beispielprogramm: UHR.H

In der Datei UHR.H definieren wir die gleichnamige Klasse, Uhr. Dies ist die Klasse für die Spieluhr und wir werden nur eine Instanz dieser Klasse verwenden, und zwar Tageszeit, definiert in Zeile 23 von ABFLUG.CPP.

Die Klasse ist sehr einfach und besteht lediglich aus zwei Variablen, Stunde und Minute, und vier Methoden. Die erste Methode ist der Konstruktor, mit dem wir die Uhr mit 8:51 initialisieren, wie aus der Implementation der Klasse in UHR.CPP ersichtlich ist. Die nächsten beiden Methoden dienen dazu, die Werte der Variablen zu bekommen. Die letzte Methode ist da schon interessanter. Sie aktualisiert die Tageszeit und gibt die Eingabeaufforderung aus. Dies ist wahrscheinlich nicht der beste Platz, um die Eingabeaufforderung auszugeben, da es sich in dieser Klasse nur um die Tageszeit dreht. Wir haben sie aber dafür gewählt, weil die Zeitangabe Teil der Eingabeaufforderung ist. Es ist Dir sicher aufgefallen, daß wir zwar die Uhr mit 8:51 initialisieren, die erste Ausgabe aber auf 8:52 lautet. Um es uns später leichter zu machen, wenn wir bei jedem Spielzug überprüfen müssen, ob das Flugzeug rechtzeitig erreicht wurde, zählen wir die Zeit schon am Beginn eines jeden Spielzuges hinauf. Deshalb ist die Zeit bei der Eingabe des Kommandos und dessen Abwicklung dieselbe und deshalb wird auch schon vor der ersten Ausgabe hinaufgezählt. Die Klasse Uhr ist die einfachste unseres Spieles und Du solltest mit dem Verständnis keine Probleme haben.

Kommandos

Beispielprogramm: WOERTER.CPP

Die Routinen, mit denen wir die Eingabe bearbeiten, werden in der Klasse Woerter definiert. Der Code für diese Klasse findet sich in WOERTER.CPP. Der Code ist relativ einfach zu verstehen, wir beschränken uns also auf einige Kommentare.

Die Methode HoleAnweisung liest mit der Funktion LiesEineZeile zwei Wörter und speichert sie in den Klassenelementen Verb und Substantiv. Die Methode speichert null für eines oder beide Wörter, wenn sie kein gültiges Verb oder Substantiv findet.

Zwei Methoden machen das Verb oder das Substantiv der letzten Eingabe verfügbar. Damit können wir in jedem Code, in dem das Objekt, das wir mit dieser Klasse erstellen, sichtbar ist, herausfinden, was die Spielerin will.

Die vier Methoden, die mit Ist beginnen, stellen fest, ob sich um ein Verb, ein Substantiv, eine Richtung oder eine Handlung handelt. Diese Methoden werden wir des öfteren aufrufen.

Schließlich und endlich setzt die einfachste der Methoden, StoppeSpiel das Verb auf Aus, damit das Gespiel ein Ende hat. Dies bewerkstelligt der Code im Hauptprogramm ABFLUG.CPP.

Die Implementation dieser Klasse findest Du in der Datei WOERTER.CPP. Der Code ist sehr einfach und reichlich kommentiert, Du darfst ihn Dir also alleine ansehen.

Die zweite Klasse — Gegenstaende

Beispielprogramm: GGSTDE.CPP

In den Dateien GGSTDE.H und GGSTDE.CPP findest Du die komplette Definition und das Handling aller Gegenstände, die Du beim Spielen herumgeschleppt hast. Es gibt genau vier mobile Gegenstände, die entweder in einem Raum oder im Rucksack sind: die Schluessel, das Konfekt (perfekt! und ich frage mich, warum ich keinen Müesliriegel daraus gemacht habe...nur ob des Namens.), das Ticket und das Geld. Mit Geld oder Schlüssel kommt man nicht durch die Sicherheitskontrolle und Ticket und Konfekt benötigen wir, damit wir ins richtige Flugzeug und auch bis ans Ziel kommen.

Die vier Gegenstände werden in der Klasse mit dem Namen Gegenstaende in der Form TRUE oder FALSE gespeichert. Dies reicht völlig aus; ein TRUE bedeutet, daß der Gegenstand hier ist, ein FALSE das Gegenteil. Die Werte TRUE und FALSE definieren wir in ABFLUG.H. Schließlich haben wir noch sechs Methoden, um mit den Gegenständen zu arbeiten.

Die erste Methode setzt alle Gegenstände auf FALSE. Die nächsten beiden werden dazu verwendet, den angegeben Gegenstand hinzulegen oder zu nehmen. Die vierte Methode sagt uns, ob sich ein Gegenstand hier befindet und die letzten beiden sagen uns, welche Gegenstände bei der Hand sind.

Auch diese header-Datei schützen wir vor mehrfachem Importieren durch das #ifndef Konstrukt.

Einführung in C++ bringt Dich in Partnerschaft mit Amazon.de von Null auf Programmieren in ein paar Klicks.

Zum Beispiel Zweit- oder Erstbuch:

Effektives modernes C++
So schwer ist das Programmieren nicht, wie Du hoffentlich auch an und in dieser Einführung siehst. Und auch bessere Programme schreiben ist leichter als man glaubt, wenn man den Meyers intus hat.
›› Mehr C++-Bücher

In Zeile 24 von ABFLUG.CPP verwenden wir diese Klasse, um ein Objekt für die Spielerin mit dem Namen PersoenlicheGegenstaende zu definieren, das die List der Gegenstände speichert, die die Spielerin spazierenträgt. Auch in der Klasse Ort verwenden wir diese Klasse als eingebettetes Objekt, um die Gegenstände zu speichern, die an jedem der 19 Orte liegen.

Die Implementation dieser Klasse ist wiederum so einfach, daß Du keine Schwierigkeiten beim Verständnis haben solltest.

Die Klasse der Flüge und Gates — Plan

Beispielprogramm: PLAN.H

Die Dateien PLAN.H und PLAN.CPP sind unser erstes Beispiel einer etwas größeren Klasse. Sie kümmert sich um die Flüge und die Gates. In dieser Klasse findest Du eine Vielzahl von Variablen und Anzahl von 8 Methoden, die mit der Vielzahl arbeiten. Anstelle einer detaillierten Beschreibung jeder einzelnen Variable und Methode wollen wir uns auf einen Überblick über die Klasse beschränken.

Nur ein Objekt dieser Klasse, FlugInfo, wird in Zeile 22 des Hauptprogrammes ABFLUG.CPP deklariert. Der Konstruktor initialisiert die möglichen Flüge. Die Methode mit dem Namen AendereGates() (ein Schelm, wer da and'res denkt!) ändert alle Gates, wenn die Spielerin an ihrem richtigen Gate ankommt, ohne den Monitor in der Wartezone gelesen zu haben. Wenn letzteres aber passiert ist, wird die Variable FluegeStehen auf TRUE gesetzt. Die Destination wird von der Methode AendereFluege() bei jedem Spielzug geändert bis die Spielerin ihr Ticket liest und damit die Methode ZeigeDestination() aufruft.

Diese Klasse enthält die Methoden für die Daten der Monitoranzeige und die Daten, die angezeigt werden, wenn die Spielerin an einem der Gates schaut. Schließlich enthält diese Klasse noch die Methode mit dem Namen UeberpruefeFlug(), die die List der Voraussetzungen durchsucht, um festzustellen, ob die Spielerin alle erfüllt hat.

Einführung in C++ bringt Dich in Partnerschaft mit Amazon.de von Null auf Programmieren in ein paar Klicks.

Zum Beispiel guter Plan:

Langlebige Software-Architekturen: Technische Schulden analysieren, begrenzen und abbauen
Die Komplexität ist eine Tochter von Software und Zeit. Dass man alten Code nicht mehr angreifen will (weil man ihn nicht versteht), ist allerdings nicht notwendig so. "Langlebige Software-Architekturen" liefert sehr praxisorientiert — mit realen Fallbeispielen — und theoretisch fundiert — mit Einblicken in die kognitiven Prozesse, die uns komplizierte System verstehen lernen — brauchbares Wissen zum spannenden Thema Softwarearchitektur.
›› Mehr Softwareentwicklung-Bücher

Einige der Ortsobjekte mußten in dieser Klasse verfügbar sein, deshalb scheinen sie in den Zeilen 11 bis 20 der Implementation der Klasse als extern auf. Erwähnenswert wäre dann noch das Problem, dessen Du dich erleichtern mußt, bevor Du abfliegen kannst. In Zeile 27 definieren und initialisieren wir die globale Variable. Auf TRUE setzen wir sie dann in Zeile 76, wenn der momentane Aufenthaltsort die Toilette ist. Schließlich überprüfen wir den Wert der Variable in Zeile 229 dieser Datei und machen davon den weiteren Spielverlauf (respektive Nicht-Verlauf) abhängig. Das Hauptprogramm weiß von der Existenz dieser Variable nichts und auch nicht davon, daß sie das Spielgeschehen beeinflußt.

Du hast zwar eine relativ große und komplexe Klasse vor Dir, sie ist aber durchgehend kommentiert und wir werden uns deshalb nicht länger mit ihr aufhalten.

Die meistbenutzte Klasse — Ort

Beispielprogramm: ORT.H

Die Datei mit dem Namen ORT.H ist die header-Datei der Klasse mit dem Namen Ort. Diese Klasse kontrolliert alle Bewegungen von einem Ort zu einem anderen.

Diese Klasse ist insofern ein wenig unüblich, als die meisten Daten als Zeiger gespeichert sind. Die ersten vier sind Orte, zu denen wir kommen, wenn wir uns von unserem momentanen Aufenthaltsort in eine der vier Richtungen bewegen. Es handelt sich dabei um Zeiger auf diese vier Orte. Dann kommen Zeiger auf zwei verschiedene Zeichenketten, die zu diesem Raum gehören. Schließlich, in Zeile 22, definieren wir das Objekt GegenstaendeListe, ein Objekt der Klasse Gegenstaende, die wir zuvor definiert haben. Es handelt sich also um eine eingebettete Klasse. Das ist keine Elternklasse, von der wir etwas erben können. Wir erzeugen ein Objekt der Klasse Gegenstaende, das wir im Raum verwenden.

In dieser Klasse verwenden wir keinen Konstruktor, da wir die Orte einzeln initialisieren. Die Methode mit dem Namen Init() hat 6 Parameter, allesamt Zeiger, mit denen sie die ersten sechs Variablen des Objektes initialisiert. Die letzte Variable, ein Objekt der Klasse Gegenstaende, wird mit dem Konstruktor dieser Klasse initialisiert. In den Zeilen 39 bis 171 der Implementation der Klasse Karte findest Du den Code für die Initialisierung aller 19 Objekte der Klasse Ort. Da das Auto wegfährt, sobald Du es verläßt, kannst Du auch nicht dorthin zurückgehen.

Einführung in C++ bringt Dich in Partnerschaft mit Amazon.de von Null auf Programmieren in ein paar Klicks.

Zum Beispiel meistbenutzte Bücher:

Der C++-Programmierer: C++ lernen - professionell anwenden - Lösungen nutzen
Eines der jedenfalls besseren Bücher zur Einführung in C++ ist umfassend und verständlich, übersichtlich und gelungen. Halbwegs hübsch anzuschauen ist es auch.
›› Mehr C++-Bücher

Die nächste Methode mit Namen Geh() gibt einen Zeiger auf den neuen Ort zurück, wenn die Bewegung erfolgreich war [??], sonst NULL. Du hast sicher bemerkt, daß das Verlassen der Snackbar und das Passieren der Sicherheitskontrolle etwas Besonderes darstellen. Der Code dafür findet sich hier, weil es sich um Teile des Geh!-Kommandos handelt.

Die restlichen Methoden sind einfach und wir gehen nicht weiter auf sie ein.

Die Nachrichten

Beispielprogramm: NACHR.TXT

In der Datei NACHR.TXT findet sich eine komplette List der Nachrichten, die am Bildschirm erscheinen, wenn einer der Orte betreten wird. Auch die Nachrichten für das Kommando Schau findest Du hier. Die Nachrichten haben wir in einer eigenen Datei gesammelt, um den Umfang der Implementation der Klasse Plan zu reduzieren. Die Kompilationszeit können wir so nicht reduzieren, da sich NACHR.TXT ja nicht separat kompilieren läßt, sondern einfach in die Datei PLAN.CPP inkludiert wird. Einige der Nachrichten bestehen nur aus (leeren) Anführungszeichen, wir führen sie aber trotzdem an, um Argumente für die Initialisierung zu haben.

In den Zeilen 5 bis speichern wir noch weitere 3 Nachrichten in dieser Datei. Was sie bedeuten und wozu sie gut sind, sollte klar sein.

Das Hauptprogramm

Beispielprogramm: ABFLUG.CPP

Schlußendlich kommen wir also zum Hauptprogramm. Schau Dir die Datei ABFLUG.CPP an, wir werden einige interessante Dinge entdecken.

Wir beginnen mit main(), und nach einem Aufruf der Methode Flughafen.Initialisiere() beginnen wir eine do...while Schleife, die dann endet, wenn die Spielerin "Aus" eingibt oder das Programm selbst dieses Wort generiert.

Die Schleife besteht aus 5 Methodenaufrufen. Zuerst rufen wir in Zeile 30 die Funktion EingabeWoerter.HoleAnweisung() auf, um die Eingabe zu holen. Dann senden wir zwei Nachrichten an das Objekt FlugInfo, um die Flüge und Gates zu ändern, wenn dies notwendig ist, um anschließend Flughafen.TuHandlung() aufzurufen, worauf wir in Kürze eingehen werden. Schließlich senden wir eine Nachricht an das Objekt mit dem Namen FlugInfo, um zu überprüfen, ob die Spielerin eines der Gates erreicht hat. In den meisten Methoden, die wir hier aufrufen, überprüfen wir, ob überhaupt Handlungsbedarf gegeben ist und führen demgemäß entweder die Handlung aus oder kehren einfach zum Hauptprogramm zurück.

Die Arbeitsmethode

Beispielprogramm: PLAN.H

Die einzige Methode, die wir noch nicht implementiert haben, ist die interessanteste. Die Funktion TuHandlung() beginnt in Zeile 182 der Datei PLAN.CPP. Sie schaut sich Verb und Substantiv, so es sie gibt, an und läßt die adäquate Aktion ablaufen. Wenn Du die else if-Anweisungen der Reihe nach durchgehst, wird es sogleich klar sein, welche Eingabe welche Aktion bewirkt. Für viele der Aktionen müssen noch gewisse Voraussetzungen erfüllt sein, bevor sie durchgeführt werden. So ist es etwa nicht möglich, Konfekt zu kaufen, wenn Du kein Geld hast, am Ort kein Konfekt ist oder der Ort nicht die Snackbar ist.

Am Ende dieser Methode, in Zeile 276, findet sich die Standardaktion, wenn sonst nichts passiert ist. Wir nehmen an, daß die Spielerin Gebrauch von ihrer Kreativkraft gemacht hat und die Snackbar nehmen wollte oder trocken wie treffend "Aus -einz!" eingegeben hat.

Schlußbemerkungen zu Abflug

Nun, da Du das Spiel gespielt und den Code dazu studiert hast, solltest Du verstanden haben, wie ein solches Programm geschrieben werden kann. Natürlich gibt es unzählige Variationen dieses Themas.

Du hast jetzt vielleicht das Gefühl, daß diese Methode der Implementation fix und fertig vom Himmel gefallen oder doch zumindest durch spirituelle Eingebung offenbart wurde. Das ist überraschenderweise nicht so. Es gibt einige (unrühmliche) Vorgängerversionen dieses Programmes, die wieder verworfen wurden. Beim Update von Version 2.0 des Tutorials auf 2.2 wurde das Programm restrukturiert. In Version 2.0 waren ungefähr 50% des Code in Klassen, jetzt aber sind es circa 98%.

Für objektorientiertes Programmieren ist dasselbe vorausschauende Denken vonnöten wie auch für nicht objektorientiertes Programmieren, der objektorientierte Compiler hilf Dir aber beim Schreiben des Codes und bei der Fehlerbereinigung, da er viele der Fehler findet, die in unseren Programmen zu verstecken wir wahre Meister sind.

Dein Projekt

Diese Aufgabe soll Dir ein wenig Erfahrung im Umgang mit einem relativ großen Projekt vermitteln.

Erweitere das Programm um einen Koffer, der bei der Ankunft am Flughafen im Auto liegt. Der Koffer muß eingechecked werden, bevor die Sicherheitskontrolle passiert werden kann. Davon sind einige Klassen betroffen. So mußt du etwa das folgende tun:

  1. Erweitere die Wortliste um "Koffer" und "check". Das muß natürlich an der richtigen Stelle passieren.
  2. Füge der Klasse Gegenstaende den Koffer hinzu.
  3. Initialisiere die Gegenstände im Auto (mit dem Koffer).
  4. Überprüfe beim Durchschreiten der Sicherheitskontrolle, daß die Spielerin auch den Koffer nicht bei sich trägt. Die Strafe bleibt Dir überlassen (aber muß es immer ein Massaker sein?)
  5. Überprüfe im Flieger, ob die Spielerin auch den Koffer eingechecked hat. Sollte sie das nicht getan haben, sei kreativ!

Wenn Du das Spiel erfolgreich um den Koffer erweitert hast, kannst Du Dich einmal zurücklehnen und befriedigt feststellen, daß es (alles) doch einen Sinn hat (nur: welchen? Zwar ist alles egal, das Wunderbare aber ist, daß es (uns) egal ist (sein muß), daß dem so ist); Du hast verstanden, wie das Programm arbeitet und wie Objekte miteinander Aufgaben erfüllen.

Einführung in C++ bringt Dich in Partnerschaft mit Amazon.de von Null auf Programmieren in ein paar Klicks.

Zum Beispiel Projekte:

Basiswissen für Softwareprojektmanager im klassischen und agilen Umfeld: Aus- und Weiterbildung zum ASQF® Certified Professional for Project Management (CPPM) (iSQI-Reihe)
Was gibt es schöneres als ASQF, CPPM und CMMI? Ja, wissen was all das bedeutet und wie es helfen kann, das Wort "Scheitern" aus dem Softwareprojektwortschatz zu verbannen.
›› Mehr Projektmanagement-Bücher

Dann solltest Du Dir ein Projekt überlegen, das objektorientierte Programmiertechniken verwendet und sogleich damit beginnen. Der beste Weg, etwas zu lernen, ist immer noch, es zu tun.

Viel Spaß!

Einführung in C++ bringt Dich in Partnerschaft mit Amazon.de von Null auf Programmieren in ein paar Klicks.

Zum Beispiel Spiele:

Einstieg in Unity: 2D- und 3D-Spiele entwickeln. Ideal für Programmieranfänger ohne Vorwissen. Mit 15 kompletten Games aus allen Spielegenres.
Wer will schon nur text adventures schreiben? Eben. "Einstieg in Unity" führt mit 15 Spielen (vom simplen Platformer zum — zugegebenermaßen auch recht simplen — Rennspiel) in die Spieleprogrammierung mit Unity und C# ein.
››Mehr Spieleentwicklung-Bücher

[ — Ludwig Wittgenstein ]