> T E X T O P I A Einfhrung in die Programmierung von Text-Adventures mit Textopia 1.0 ,\=/ oo___@ ! {,,,___) \ /\ /\ (_ \/^ (/^\ ( (_ \/^\ ( (_ \ _ ( (_ \ !! \/\( (_( ! !! (__( ( )! \ /(___(/`` \ // ___( (___( )____// ;;;__ ;;;_______)_____/ Geschrieben von Oliver Berse Juni 1999 INHALT ------ I Einleitung II Kompilieren der Units 2.1 Der Quelltext 2.2 Kompilieren mit Turbo Pascal unter DOS 2.3 Kompilieren mit Delphi unter Windows 2.4 Kompilieren mit Free Pascal unter Linux 2.5 Worauf Sie unter Linux achten sollten III Textopia und OOP 3.1 Einige wichtige Begriffe 3.2 Adventures und Objekte IV Das erste Programm 4.1 Units, Konstanten, Typen und Variablen 4.2 Vorbereitungen mit Prepare 4.3 Objekte und ihre Namen: Ein erster Raum 4.4 Statische Objektbeschreibungen und Sonderzeichen 4.5 Spieler und Spiel 4.6 Ein erster Dialog V Die Spielwelt 5.1 Gegenst„nde und Attribute 5.2 Beh„lter 5.3 Fortbewegungsmittel 5.4 Weitere R„ume 5.5 Variable Objektbeschreibungen 5.6 Verbindungen und Tren 5.7 Der Scope VI Aktionen und Reaktionen 6.1 Ereignisse 6.2 BeforeAction und AfterAction VII Der Parser 7.1 Die Eingabe 7.2 Verben und Grammatik 7.3 TEvent und TNoun 7.4 Fehlermeldungen 7.5 Metaverben 7.6 Debugkommandos VIII Verschiedenes 8.1 Nichtspielercharaktere 8.2 Leben, Tod und Sieg des Spielers 8.3 Hintergrundprozesse, Zeitschalter und Spielzeit 8.4 Licht und Dunkel 8.5 Die Spielstandverwaltung 8.6 Statuszeile, Farben und Funktionstasten 8.7 Kommandozeilenoptionen 8.8 Umlaute 8.9 Warnungen IX Die Units 9.1 Funktionen und Prozeduren von tstring.pas 9.2 Funktionen und Prozeduren von tio.pas 9.3 Datentypen, Funktionen und Prozeduren von tmain.pas 9.4 Methoden von TBasic 9.5 Methoden von TItem 9.6 Methoden von TRoom 9.7 Methoden von TLink 9.8 Methoden von TLock 9.9 Methoden von TPlayer 9.10 Methoden von TGame ***************************************************************************** I Einleitung ***************************************************************************** Textopia ist eine Toolbox fr die Programmierung klassischer Text-Adventures. Wie andere Systeme auch, die fr diese Aufgabe entwickelt wurden, nimmt Textopia Ihnen viel Routinearbeit ab, um die Sie sich kmmern máten, wenn Sie Ihre Adventures ohne Hilfsmittel in einer Sprache wie C oder Pascal schreiben wollten. Viele Entwicklungssysteme verlangen das Erlernen einer speziellen Programmiersprache und/oder mssen erst an die deutsche Grammatik angepaát werden. Textopia erm”glicht die Adventureprogrammierung mit Hilfe der vielen Programmierern vertrauten Sprache Pascal und wurde von vornherein fr die deutsche Sprache entworfen. Textopia >> arbeitet mit Turbo Pascal unter DOS, mit dem kostenlosen Free Pascal (http://www.tfdec1.fys.kuleuven.ac.be/~michael/fpc/fpc.htm) unter Linux und mit Delphi 4 unter Windows. Mit Textopia geschriebene Spiele mssen daher nur neu kompiliert werden, um sie zwischen DOS, Windows und Linux auszutauschen. >> untersttzt R„ume, Tren, Fahrzeuge, Nichtspielercharaktere (NPCs), Beh„lter, ein- und ausschaltbare Dinge, Lichtquellen, die Unterscheidung zwischen Licht und Dunkelheit, Zeitschalter und Hintergrundprozesse (Timer & Daemons), die Verwaltung mehrerer Spielst„nde, Automapping, eine Protokolldatei sowie einen Debugmodus. >> verfgt ber einen Parser, der 40 vordefinierte Verben und Kommandos beherrscht und sich einfach erweitern l„át. Der Parser untersttzt die Deklination fast aller deutschen Substantive und erkennt Pluralformen, Adjektive, Reflexivpronomen und natrlich Umlaute. >> ahmt das "Look & Feel" klassischer Text-Adventures der 80er Jahre nach. Die Statuszeile, Bildschirmfarben und Funktionstastenbelegung sind nur einige der Dinge, die Sie in Textopia an Ihre eigenen Vorstellungen anpassen k”nnen. Dieser Text soll Ihnen als Einstieg in Textopia und als Nachschlagewerk bei der Programmierung eigener Adventures dienen. Grundkenntnisse in Pascal sind zu seinem Verst„ndnis sicher von Vorteil. Am besten lesen Sie diesen Text vor Ihrem Computer, um die Beispielprogramme besser nachvollziehen zu k”nnen. Programmiert, getestet und dokumentiert wurde Textopia von Oliver Berse, dem Schreiber dieser Zeilen. Textopia darf selbstverst„ndlich von jedem interessierten Programmierer benutzt und weiterverbreitet werden. Das gilt auch fr damit entwickelte Programme. Beachten Sie aber bitte folgende Punkte: (a) Wenn Sie Textopia weiterverbreiten, dann nur mit allen zugeh”rigen Dateien, auch den Quelltexten. Eigene Programme, die Textopia verwenden, mssen Sie aber nicht im Quelltext weitergeben. (b) Wenn Sie Žnderungen (es sollten Verbesserungen sein) an Textopia vornehmen, drfen Sie natrlich auch Ihren Namen in den Kommentarkopf der Quelltexte setzen. Meinen lassen Sie da bitte stehen. (c) Eine kommerzielle Verwendung, wenn sie denn berhaupt m”glich ist, bedarf meiner Zustimmung. Textopia findet sich im IF-Archiv (ftp.gmd.de) und auf der Textopia-Seite meiner Homepage (http://homepage.ruhr-uni-bochum.de/Oliver.Berse/comp1.htm). Dort finden Sie immer die aktuellsten Informationen ber Textopia. Berichte ber Bugs, Kommentare und Anregungen senden Sie bitte an Oliver.Berse@rz-ruhr-uni-bochum.de. Oliver Berse Juni 1999 ***************************************************************************** II Kompilieren der Units ***************************************************************************** 2.1 Der Quelltext ----------------- Die Quelldateien fr die drei Textopia Units sind tstring.pas, tio.pas und tmain.pas. Diese Dateien sind fr jedes Textopia-Programm notwendig und mssen zun„chst kompiliert werden. Textopia wurde mit Turbo Pascal 6.0 unter DOS, mit Delphi 4 unter Windows 95/98 und mit Free Pascal unter Linux 6.1 programmiert und getestet. Mit Delphi 3 und Windows NT drfte es auch funktionieren, getestet habe ich das aber nicht. Der Free Pascal Compiler (FPC) untersttzt neben Linux auch OS/2 und das AmigaOS. Mangels entsprechender Soft- und Hardware konnte ich Textopia unter diesen Systemen noch nicht testen. Eine Anpassung der Units an andere TP-kompatible Compiler, wie z.B. das kommerzielle Virtual Pascal, sollte keine unl”sbaren Probleme verursachen. Delphi 4 und FPC sind nicht wirklich 100% kompatibel zu Turbo Pascal. Das liegt an der 32-Bit Struktur beider Compiler und der frhen Versionsnummer von FPC, das noch ein paar Kinderkrankheiten hat. Die Quelltexte der Textopia-Units werden aber von allen drei Compilern problemlos geschluckt. M”glich wird dies durch die Compilerdirektive {$DEFINE xxx} am Anfang der Quelltexte. Hier k”nnen die Symbole tp, fpc oder delphi definiert werden. An den wenigen kritischen Stellen teilt ein Konstrukt wie {$IFDEF tp} ..code.. {$ELSE} ..code.. {$ENDIF} dem entsprechenden Compiler dann mit, ob er den Code fr DOS, Linux oder Windows bersetzen soll. 2.2 Kompilieren mit Turbo Pascal unter DOS ------------------------------------------ Lassen Sie den Compiler mit Build aus der Datei tmain.pas eine Unit-Datei erstellen, die anderen drei Quelldateien werden automatisch mitkompiliert. Von den am Dateianfang gesetzten Compileroptionen sollten die I/O-Kontrolle {$I} und die strenge Stringkontrolle {$V} immer ausgeschaltet, die erweiterte Syntax {$X} immer eingeschaltet bleiben. 2.3 Kompilieren mit Delphi unter Windows ---------------------------------------- Sie k”nnen die Units sowohl in der Entwicklungsumgebung (IDE) als auch ber die Kommandozeile in einem DOS-Fenster kompilieren. Wenn Sie in der IDE kompilieren, ”ffnen Sie tmain.pas als Pascal-Projekt, um die ben”tigten Dateien mitzubersetzen. Mit der Kommandozeilenversion von Delphi starten Sie die šbersetzung durch Eingabe von dcc32 -cc -b tmain.pas. Die Hinweise und Warnungen, die Delphi beim kompilieren ausgibt, sind nur falscher Alarm und k”nnen unbercksichtigt bleiben. Neben den Dateien tmain.pas, tio.pas und tstring.pas bersetzt Delphi auch die Datei dcrt.pas. Diese Unit ersetzt die unter Delphi 4 fehlende Crt-Unit von Turbo Pascal, ohne die Textopia nicht auskommt. DCrt ist jedoch kein vollst„ndiger Ersatz fr alle Funktionen der originalen Crt-Unit. Sie simuliert nur die von Textopia verwendeten Routinen und arbeitet in DOS-Fenstern noch recht langsam (zumindest auf meinen 200Mhz Pentium). Wenn Sie Spiele unter Verwendung der Textopia-Units mit Delphi kompilieren, achten Sie darauf, daá sie als Anwendungen fr den Textmodus kompiliert werden. Sie k”nnen am Anfang des Quelltextes ein {$AppType Console} einfgen, in der IDE unter Projekt/Optionen/Linker den Textmodus einstellen oder den Kommandozeilencompiler mit der Option -cc aufrufen. 2.4 Kompilieren mit Free Pascal unter Linux ------------------------------------------- Geben Sie im Verzeichnis mit den Quelldateien einfach ppc386 -So -B tmain.pas ein, um die šbersetzung zu starten. Mit dem Schalter -So verh„lt FPC sich bei der šbersetzung kompatibel zu TP6/7, -B entspricht dem Build-Befehl des TP-Compilers. FPC untersttzt mehr Compileroptionen als TP, so k”nnen Sie auch fr den Prozessortyp Ihres Computers optimierten Code erzeugen. Wenn Sie nur ppc386 eingeben, erhalten Sie eine Liste aller m”glichen Optionen. W„hrend der šbersetzung gibt FPC einige Hinweise aus, die Sie allesamt ignorieren k”nnen. Meldungen ber nicht verwendete Parameter erzeugt FPC f„lschlicherweise, wenn eine Prozedur einen Parameter entgegennimmt und ihn ohne Žnderungen an eine zweite Prozedur weiterreicht. 2.5 Worauf Sie unter Linux achten sollten ----------------------------------------- Textopia-Programme sind Textanwendungen und funktionieren im Linux-Textmodus oder unter einem Windowmanager in einem Terminalfenster. Letztere kennen meist verschiedene Textmodi, die sich nicht alle mit der Crt-Unit von FPC vertragen. Wenn sich Ihr Terminalfenster im VT100-Modus befindet, wird die von Textopia benutzte Crt-Unit frher oder sp„ter einen Segmentation Fault verursachen. Im Console-Modus mit 80x25 oder mehr Zeichen funktioniert Textopia dagegen einwandfrei. Unter dem KDE-Desktop k”nnen Sie das K-Terminal oder das in der SuSE Distribution enthaltene Programm Konsole von Lars Doelle verwenden. Bei K-Terminal w„hlen Sie unter Einstellungen eine Fenstergr”áe von mindestens 80x52 Zeichen und schalten unter Einstellungen/Terminal den Console-Modus und die Backspace-Taste ein. Im Programm Konsole stellen Sie den 80x25 Zeichen-Modus ein und schalten die Option BS sendet DEL aus. Wenn Sie beim Programmieren unter einem Windowmanager in einem Editorfenster (XEmacs oder was Ihnen sonst so gef„llt) den Quelltext bearbeiten und in einem Terminalfenster kompilieren und testen, erhalten Sie eine „hnlich einfach zu bedienende Entwicklungsumgebung wie unter Delphi oder Turbo. ***************************************************************************** III Textopia und OOP ***************************************************************************** 3.1 Einige wichtige Begriffe ---------------------------- Textopia nutzt die M”glichkeiten der objektorientierten Programmierung (OOP) mit Turbo Pascal. Um mit Textopia Adventures zu programmieren, mssen Sie kein Experte in Object Pascal, C++ oder gar Smalltalk sein. Einige Begriffe der OOP sollten Sie aber kennen. Daher habe ich hier, natrlich ohne Anspruch auf Vollst„ndigkeit, einige formale Definitionen aufgefhrt. Eine sehr viel genauere "Einfhrung in objektorientierte Programmierung mit Turbo Pascal" stammt von Joseph Mittendorfer und sei jedem Interessierten zur Lektre empfohlen. >> Objekt - bildet eine Einheit aus Daten (Variablen) und den sie verarbeitenden Prozeduren und Funktionen, die auch die Methoden des Objekts genannt werden. In Pascal „hnelt ein Objekt einem Record, der auch Prozeduren und Funktionen aufnehmen kann. Ziel der OOP ist es, groáe Softwareprojekte in mehrere kleine, berschaubare und wiederverwertbare Objekte zu unterteilen. >> Klasse - die Definition der Daten und Methoden eines Objekts. In Turbo Pascal werden Klassen Objekttypen genannt und „hnlich wie Records definiert. Mit der Anweisung TObjekt=OBJECT erzeugen Sie den neuen Objekttyp TObjekt. Wie im Interface-Abschnitt einer Unit, werden in Objektdefinitionen nur die Methodennamen und ihre Parameter aufgefhrt, die vollst„ndige Methode wird dann auáerhalb der Objektdefinition geschrieben. >> Instanz - Objekt eines Objekttyps. Instanzen und Objekte sind in Pascal Variablen eines Objekttyps. Die Deklaration objekt:TObjekt erzeugt eine Instanz des Objekttyps TObjekt. Wenn Sie einen Zeiger auf einen Objekttyp definieren (pobjekt:=^TObjekt), k”nnen Sie Instanzen auch dynamisch mit NEW() erzeugen. >> Vererbung - Eine neue Objektdefinition kann auf einem bereits bestehenden Objekttyp aufbauen. Bei der Objektdefinition wird dazu der Vorfahre in Klammern hinter das Schlsselwort geschrieben (TNeu=OBJECT(TObjekt)). Der neue Objekttyp erbt dabei alle Variablen und Methoden des Vorfahren und kann diesen neue hinzufgen. Durch Vererbung kann ein Stammbaum voneinander abgeleiteter Objekttypen (die sog. Objekthierarchie) gebildet werden. >> Polymorphismus - von einem Vorfahren geerbte Methoden k”nnen in einem neuen Objekttyp berschrieben, d.h. neu definiert werden. Der Name und die Parameterzahl der betreffenden Prozedur oder Funktion bleiben dabei gleich. Verschiedene Aufgaben k”nnen so durch Aufruf der gleichen Methode erledigt werden. >> virtuelle Methoden - erm”glichen dem Compiler zur Laufzeit festzustellen, welche der berschriebenen Versionen dieser Methode bei ihrem Aufruf abgearbeitet wird. Virtuelle Methoden werden mit dem nachgestellten Schlsselwort VIRTUAL gekennzeichnet. >> Konstruktoren - sind Methoden, die die Daten von Instanzen bei ihrer ersten Verwendung initialisieren, d.h. mit bestimmten Werten vorbelegen. In jedem Objekttyp sollte ein Konstruktor definiert werden. In Turbo Pascal erhalten Kontruktoren gew”hnlich den Namen Init. Bei der Erzeugung dynamischer Instanzen kann Init schon bei der Erzeugung mit NEW() aufgerufen werden: NEW(pobjekt,Init). >> Destruktoren - sind Methoden, die dynamische Daten von nicht mehr benutzten Objekten l”schen und somit den Speicher aufr„umen. In jedem Objekttyp sollte ein Destruktor definiert werden. In Turbo Pascal erhalten Destruktoren gew”hnlich den Namen Done. Lassen Sie sich von diesen Definitionen nicht abschrecken. Sie werden schnell herausbekommen, wie Sie Textopia und OOP fr eigene Adventures nutzen k”nnen. 3.2 Adventures und Objekte -------------------------- Die Spielwelt fast aller Adventures besteht aus Elementen wie R„umen, simplen Goldmnzen, komplizierten Maschinen und NPCs, die sprechen und sich bewegen k”nnen. Neben der Programmierung eines Parsers stellt die Verwaltung dieser Spielwelt das gr”áte Problem bei der Adventureprogrammierung dar. Textopia l”st dieses Problem, indem es alle in einem Spiel vorkommenden Dinge als Objekte darstellt. In tmain.pas werden der Objekttyp TBasic und seine drei Nachfolger TRoom, TLink und TItem definiert. Diese Objekttypen stellen die Grundbausteine jedes Textopia-Programms dar: >> TBasic - verwaltet die Informationen, die alle zur Spielwelt geh”renden Objekte gemeinsam haben, u.a. einen Namen. >> TRoom - stellt einen einzelnen Ort in der Spielwelt dar. Dabei kann es sich um einen Raum in einem Geb„ude oder um einen Ort unter freiem Himmel handeln. >> TLink - verbindet jeweils zwei R„ume und kann in Verbindung mit einem Objekt vom Typ TLock verschiedene Durchg„nge und Tren darstellen. >> TItem - besitzt eine Reihe von Attributen, die sich ein- und ausschalten lassen, um die unterschiedlichsten Dinge in einem Adventure zu simulieren. Objekte der Typen TRoom, TLink und TItem werden im folgenden Text auch R„ume, Verbindungen und Gegenst„nde genannt. Aber nicht nur die Spielwelt wird durch Objekte dargestellt. Auch der Spieler (genauer: sein Alter Ego im Spiel) und das Spiel selbst sind Objekte. Spieler und Spiel stellen jeweils ein Objekt der Typen TPlayer und TGame dar: >> TPlayer - verwaltet verschiedene Informationen ber den Zustand des Spielers, z.B. seine Position in der Spielwelt und seine Punktzahl. >> TGame - enth„lt den Parser und verschiedene Methoden zur Kontrolle des Spielverlaufs. Die genannten Objekttypen besitzen zahlreiche virtuelle Methoden, die Sie in abgeleiteten Typen berschreiben k”nnen, um ihr Verhalten in einem Spiel an Ihre Vorstellungen anzupassen. Textopia ist weitgehend objektorientiert, besitzt aber auch viele Prozeduren und Funktionen, die nicht zu einem Objekttyp geh”ren. Wenn Sie den Quelltext durchst”bern, werden Sie zudem feststellen, daá sich einige Sachen noch eine Spur objektorientierter h„tten umsetzen lassen. Textopia entwickelte sich aus Experimenten mit einem deutschsprachigen Parser. Daher beansprucht es nicht, ein Lehrbuchbeispiel fr OOP zu sein. ***************************************************************************** IV Das erste Programm ***************************************************************************** In den folgenden Kapiteln soll schrittweise ein erstes Textopia-Programm entwickelt werden, das ein Mini-Adventure darstellt. Textopia-Programme sind Pascal-Programme und folgen natrlich deren bekanntem Schema: PROGRAM programmname; USES { Einbinden von Units } CONST { Konstantendeklaration } TYPE { Typendeklaration } VAR { Variablendeklaration } ... { Funktionen und Prozeduren } BEGIN { Hauptprogramm } END. 4.1 Units, Konstanten, Typen und Variablen ------------------------------------------- In jedes Textopia-Programm werden die drei Textopia-Units eingebunden: PROGRAM demo1; USES TString,TIO,TMain; Fr jeden Raum, jede Verbindung und fr jeden Gegenstand im Programm wird nun eine numerische Konstante ben”tigt, ber die sp„ter jedes Objekt eindeutig zu identifizieren ist. Die Konstanten drfen (eindeutige) Werte zwischen 1 und 65535 annehmen und mssen in keiner Weise geordnet sein. Eine solche Konstante soll im weiteren Text die ID eines Objekts der Spielwelt genannt werden. Fr das denkbar einfachste Textopia-Programm, die Simulation eines einzigen leeren Raums (hier ein Wald), k”nnen wir also deklarieren: CONST forest_id = 10; In der Typendeklaration k”nnen neue Objekttypen definiert werden, um einzelne Methoden neu zu definieren. Mit Ausnahme einiger interner Typen k”nnen Sie von allen Objekttypen in Textopia Nachfolger ableiten. Fr unser Beispiel k”nnen wir aber bei TRoom bleiben und die Typendeklaration berspringen. Alle Objekte werden in Textopia dynamisch angelegt. Im Variablen-Abschnitt mssen wir daher Zeiger auf die ben”tigten Objekttypen deklarieren, um von ihnen Instanzen erzeugen zu k”nnen. Wir k”nnen hierbei auf die vordefinierten Zeigertypen PBasic, PRoom, PLink, PItem, PPlayer und PGame zurckgreifen, die einen Zeiger auf die entsprechenden Objekttypen darstellen. Es mssen immer auch zwei Zeiger fr das Spieler- und das Spielobjekt (oder fr ihre Nachfolger) deklariert werden. Zur Darstellung eines Raums ben”tigen wir also drei Zeiger: VAR room : PRoom; player : PPlayer; game : PGame; Die im Var-Abschnitt deklarierten Zeiger vom Typ PRoom, PLink, und PItem dienen der Erzeugung aller im Spiel vorkommenden Objekte des entsprechenden Typs. Es wird also nicht fr jedes Objekt ein eigener Zeiger ben”tigt. Wenn Sie sp„ter im Spiel einen Zeiger auf ein bestimmtes Objekt ben”tigen, verwenden Sie die Funktion Adr(_id : WORD) : POINTER Mit einer expliziten Typumwandlung der Form PRoom(Adr(forest_id)) k”nnen Sie diesen Pointer in einen Zeiger auf einen Nachfolger von TBasic umwandeln. Verwechseln Sie diese Funktion nicht mit der Pascal-Funktion Addr. H„tten wir Nachfolger von Textopia-Objekten deklariert, máten wir nun ihre Methoden schreiben. So aber k”nnen wir gleich mit dem Hauptprogramm loslegen. 4.2 Vorbereitungen mit Prepare ------------------------------ Auf das BEGIN des Hauptprogramms muá in Textopia immer zuerst die Anweisung Prepare(_filename: STRING; _new: BOOLEAN); folgen. Prepare erfllt folgende Aufgaben: Zun„chst legt es den Dateinamen fr die Dateien fest, die im Spielverlauf angelegt werden. Das sind *.sav und *.ttx fr die Spielstandverwaltung und *.txt fr eine Textdatei, die automatisch das Spielgeschehen protokolliert. Eine Datei *.dat wird nur in der DOS Version angelegt. Sie nimmt die zu den Objekten geh”renden Texte in codierter Form auf. Diese Datei wird nur angelegt, um unter DOS Speicher zu sparen. Unter Windows und Linux bleiben alle Texte im Speicher. Der Parameter _new bestimmt, ob die Datei *.dat bei jedem Programmstart neu angelegt wird, was w„hrend der Programmierung sehr zu empfehlen ist, oder eine bereits bestehende Datei verwendet wird. Unter Linux und Windows hat dieser Parameter also keine Bedeutung. Wenn Sie verhindern wollen, daá Spieler die den Objekten zugeordneten Texte (und nur diese) im Programmcode per HexEditor ver„ndern k”nnen, setzen Sie _new im fertigen Programm auf False (ein hundertprozentiger Schutz ist das aber auch nicht). Ein Hinweis: Die Werte True und False werden in Textopia h„ufig verwendet und k”nnen mit den bereits deklarierten Konstanten t und f abgekrzt werden. Als n„chstes initialisiert Prepare eine interne Liste, die alle von Ihnen definierten Objekte aufnimmt. Dann erzeugt Prepare eine Instanz von PRoom mit der ID 0 und tr„gt sie als erstes Element in die Liste ein. Dieser Raum 0 dient als einfache Alternative fr die Definition und Beseitigung von von Objekten w„hrend des Spielverlaufs, die in Textopia nicht vorgesehen ist. Iát der Spieler beispielsweise einen Keks, wird dieser nicht mehr ben”tigt und landet im Raum 0. Umgekehrt k”nnen Sie aus diesem Raum auch bislang verborgene Objekte hervorzaubern. Daher k”nnen wir diesen Raum auch Top (den Zylinder eines Zauberknstlers) nennen. 4.3 Objekte und ihre Namen: Ein erster Raum ------------------------------------------- Nun aber weiter mit dem Hauptprogramm. Unsere Spielwelt soll zun„chst aus nur einem Raum bestehen. Ein Objekt vom Typ TRoom mit der ID room_id erzeugen wir wie folgt: NEW(room,Init(forest_id,'+W%ald#er')); Mit NEW wird eine neue Instanz erzeugt und gleichzeitig der fr die Initialisierung ihrer Daten zust„ndige Konstruktor Init ausgefhrt. Die ersten beiden Parameter der Konstruktoren von TRoom, TLink und TItem sind die ID und der Name der neuen Instanz. šber die ID identifizieren Sie als Programmierer eine Instanz und ber ihren Namen bezieht der Spieler sich auf ein Objekt in der Spielwelt. Die Sonderzeichen im Namen zeigen Textopia an, wie der Name zu deklinieren ist und welcher Artikel ihm vorgestellt werden muá, wenn er in einer Ausgabe erw„hnt wird. Textopia kennt fr diesen Zweck folgende Zeichen: - markiert ein feminines Nomen: -Flasche -> die Flasche. + markiert ein maskulines Nomen: +Troll -> der Troll. $ markiert einen Eigennamen: $Tom -> Tom Steht vor dem Namen kein Minus-, Plus- oder Dollarzeichen, so gilt der Name als Neutrum: Fenster -> das Fenster. % markiert vor einem Vokal eine Lautver„nderung im Plural: %Apfel -> Žpfel. # markiert den Buchstaben oder die Silbe, die im Plural angeh„ngt wird: Flasche#n -> die Flaschen * markiert ein vorgestelltes Adjektiv. Vor einem Namen darf h”chstens ein Adjektiv stehen. Beachten Sie, daá Minus-, Plus- und Dollarzeichen vor dem Adjektiv stehen mssen: +rot* Drache#n -> der rote Drache. Sie k”nnen einem Objekt auch mehrere synonyme Namen geben, diese mssen Sie mit einem Semikolon trennen: Raumschiff#e;Raumfahrzeug#e;Sternenschiff#e. Zwischen den Namen drfen keine Leerzeichen stehen. Hat ein Objekt mehrere Namen, so erw„hnt Textopia immer nur den ersten, w„hrend der Spieler alle Namen benutzen kann, um sich auf das Objekt zu beziehen. Schlieálich k”nnen Sie den Namen auch ganz weglassen, was bei Verbindungen oft sinnvoll ist. Der Objektname wird von einem Objekt vom Typ TMemText gespeichert. Wenn p ein Zeiger auf einen Nachfolger von TBasic ist, k”nnen Sie mit der Konstruktion p^.name.GetText auf den Objektnamen zugreifen. Fr die Deklination von Objektnamen ist die in tstring.pas definierte Stringfunktion Noun() zust„ndig. Ihr werden folgende Parameter bergeben: >> n1:String - ein Objektname mit seinen Sonderzeichen. >> casus:TCasus - einer der vier grammatischen F„lle, fr den der Name dekliniert werden soll. M”gliche Werte sind: nom (Nominativ), acc (Akkusativ), gen (Genitiv) und dat (Dativ). >> def:Boolean - bestimmt, ob der Objektname mit einem bestimmten (True) oder unbestimmten (False) Artikel zurckgegeben wird. >> num:Byte - bestimmt, ob der Objektname im Singular (num=1) oder im Plural (num>1) zurckgegeben wird. Manchmal ist ein Blick in den Duden hilfreich, um mit Noun() das gewnschte Ergebnis zu erzielen. Wenn ein Objektname nur im Nominativ Singular ohne seine Sonderzeichen ben”tigt wird, kann auch die ebenfalls in tstring.pas definierte Funktion ShortName(str : STRING; mode : BYTE) : STRING verwendet werden. Sie nimmt den Objektnamen mit Sonderzeichen und einen Byte-Parameter entgegen und gibt den Namen ohne Sonderzeichen zurck. Ist der Byte-Parameter gleich 0, wird der Objektname wie bei der Initialisierung angegeben zurckgegeben, bei 1 und 2 wird er in Klein- bzw. Groábuchstaben umgewandelt. 4.4 Statische Objektbeschreibungen und Sonderzeichen ---------------------------------------------------- Wir haben jetzt einen Raum erzeugt und ihm eine ID und einen Namen gegeben. Das reicht natrlich noch nicht. Wir brauchen einen Text, der dem Spieler mitteilt, was in dem Raum los ist, wenn er sich in ihm aufh„lt. Jedem Objekt eines Nachfolgers von PBasic k”nnen wir mit der Methode AddText(str : STRING) eine ausfhrliche Beschreibung zuordnen. Diese Methode erreichen wir ber den Zeiger, mit dem wir das Objekt auch erzeugt haben: WITH room^ DO BEGIN AddText('Fast undurchdringliches Unterholz l„át Sie nur mhsam '+ 'weiterkommen. Weiter westlich scheinen die B„ume weniger '+ 'dicht beieinander zu stehen.\n'); END; Der an AddText() bergebene Text darf maximal 255 Zeichen lang sein. Sie drfen aber beliebig viele AddText() hintereinander schreiben, womit auch die L„nge des Textes nur von Speicher begrenzt wird. Unter DOS werden die mit AddText() hinzugefgten Texte in der Datei *.dat gespeichert. Bei der Textausgabe kmmert sich Textopia selbst„ndig um den Zeilenumbruch. Wird ein zusammengeh”riger Text l„nger als eine Bildschirmseite, wartet Textopia nach der Ausgabe einer Seite automatisch auf einen Tastendruck des Spielers, um den Rest anzuzeigen. Textopia kennt eine Reihe von Steuerzeichen, die Sie im Text einfgen k”nnen, um die Textausgabe zu beeinflussen. Eingeleitet werden diese Anweisungen mit dem Zeichen \ (Backlash): \n Erzwingt einen Zeilenumbruch: AddText('Zeile eins\nZeile zwei'); \h Der folgende Text wird mit erh”hter Intensit„t ausgegeben. Dies entspricht der TP-Anweisung HighVideo. \l Der folgende Text wird mit normaler Intensit„t ausgegeben. Dies entspricht der TP-Anweisung NormVideo. \r Schaltet die inverse Textdarstellung ein und aus. \b Gibt das folgende Zeichen in Groáschrift aus. Einige weitere Steuerzeichen sind fr die systemunabh„ngige Darstellung deutscher Umlaute vorgesehen (s. 8.8). Mit AddText() angegebene Objektbeschreibungen sind statisch. Das heiát, sie lassen sich w„hrend des Spiels nicht mehr ver„ndern. Wenn aber z.B. w„hrend des Spiels ein Teil der Decke eines Raums einstrzt und eine Tr versperrt, muá natrlich auch die entsprechende Raumbeschreibung ge„ndert werden. Um eine Objektbeschreibung an bestimmte Spielsituationen anpassen zu k”nnen, muá ein neuer Objekttyp von einem der Nachfolger von TBasic abgeleitet werden und die Methode TBasic.MyText neu definiert werden. Wird w„hrend des Spiels eine Objektbeschreibung ausgegeben, prft Textopia immer zuerst, ob dem Objekt ein Text mit AddText() hinzugefgt wurde. Ist das nicht der Fall, wird die Methode MyText, die TRoom, TLink und TItem von TBasic erben, aufgerufen. MyText tut gew”hnlich nichts und ist nur definiert, um in einem Enkel von TBasic berschrieben zu werden. In MyText k”nnen nun bedingte Objektbeschreibungen erfolgen. Fr die Textausgabe muá nicht nur in MyText immer die Prozedur Print(line : STRING) statt Writeln verwendet werden, da sie sich auch um die Bildschirmverwaltung kmmert. In unserem Beispiel bleiben wir aber erstmal bei einer statischen Beschreibung. 4.5 Spieler und Spiel --------------------- Nachdem die Objekte der Spielwelt, hier nur ein Wald, erschaffen sind, mssen wir uns um einige Eigenschaften des Spielers und des eigentlichen Spiels kmmern. Zun„chst erzeugen wir eine Instanz von TPlayer (oder eines Nachfolgers davon). Um sie zu initialisieren, mssen wir den entsprechenden Konstruktor mit fnf Parametern aufrufen: Init(_where: WORD; _adress: TAdress; _gender: TGender; _wmin,_wmax: BYTE) >> _where - ist die ID eines Raums oder eines Items, das dem Spieler als Beh„lter dient (wir kommen darauf zurck), und bestimmt, wo das Spiel beginnt. >> _adress - kann den Wert du, sie oder ihr annehmen und bestimmt die von Textopia benutzte Anrede fr den Spieler. In den von Ihnen vorgegeben Texten mssen Sie sich an die gew„hlte Anrede halten. Entsprechende Personalpronomen und Verbformen k”nnen noch nicht automatisch erzeugt werden. >> _gender - bestimmt das Geschlecht von Spielers Alter-Ego und kann w„hrend des Spiels mit der PPlayer-Methode ChangeGender gewechselt werden. M”gliche Werte sind natrlich male und female. Dies ist noch eine experimentelle Option, die von Textopia nur bei einer einzigen Ausgabe bercksichtigt wird. Wenn Sie aber ein Spiel im Stil von Infocoms "Leather Goddesses of Phobos" schreiben wollen, ist _gender sicher ntzlich. >> _wmin - gibt das Normalgewicht von Spielers Alter-Ego an, wenn er nichts bei sich tr„gt. >> _wmax - bestimmt das Maximalgewicht des Spielers. Da sich auch allen m”glichen Gegenst„nden ein Gewicht zuordnen l„át, k”nnen Sie mit _wmax leicht bestimmen, wieviele Dinge der Spieler mit sich herumtragen darf. Einen Abenteurer k”nnen wir also wie folgt in unserem Wald aussetzen: NEW(player,Init(forest_id,sie,male,1,3)); Im letzten Schritt initialisieren wir jetzt das eigentliche Spiel. Der Konstruktor von TGame verlangt folgende Parameter: >> _player:PPlayer - ein Zeiger auf die erzeugte Instanz von TPlayer. >> _statusline:BOOLEAN - bestimmt, ob zu Spielbeginn eine Statuszeile in der ersten Bildschirmzeile angezeigt wird. Die Statuszeile kann auch w„hrend des Spiels ein- und ausgeschaltet werden. >> _upsize:BOOLEAN - bestimmt, ob die Spielereingaben in Groábuchstaben erscheinen sollen, um sie optisch von den Spieltexten zu unterscheiden. Textopia unterscheidet aber nicht zwischen Groá- und Kleinschreibung. Intern werden alle Eingaben in Kleinschrift umgewandelt. >> _verbose:BOOLEAN - ist _verbose wahr, so gibt Textopia in jedem Raum, den der Spieler betritt, die vollst„ndige Raumbeschreibung aus, auch wenn der Spieler den Raum bereits besucht hat. Andernfalls werden in bereits bekannten R„umen nur deren Namen ausgegeben. >> _history:BYTE - der Zeileneditor von Textopia erzeugt automatisch eine Liste der letzten n Spielereingaben, die der Spieler mit den Tasten CursorOben/CursorUnten editieren kann. _history bestimmt die Anzahl der darin gespeicherten Eingabezeilen. Unsere Waldszene k”nnen wir jetzt mit NEW(game,Init(t,f,t,10)); initialisieren. Bevor wir das Programm starten, k”nnen wir mit der Methode AddProlog(_str : STRING) noch Informationen wie den Titel, eine Copyrightmeldung und eine Einleitung oder Vorgeschichte hinzufgen: WITH game^ DO BEGIN AddProlog('Das Zwergengrab\nEine Textopia-Demo von Oliver Berse\n\n'+ 'Drei Tage schon durchstreifen Sie abseits der Straáen den '+ 'alten Wald. Hier irgendwo liegen die mit Sch„tzen gefllten '+ 'Gr„ber der Zwergenk”nige, die einst diese L„nder '+ 'beherrschten.\n\n'); Der mit AddProlog() dem Spiel hinzugefgte Text erscheint immer zu Spielbeginn. Diese Methode verh„lt sich ganz „hnlich wie AddText(). Mit der Methode TGame.Run k”nnen wir unsere kleine Demonstration jetzt starten. Dabei bernimmt Textopia die Kontrolle ber das Spiel, bis der Spieler es beendet. Nach Beendigung des Spiels sollte noch der Destruktor von TGame aufgerufen werden, um belegten Speicherplatz aufzur„umen: Run; Done; END; Vergessen Sie nicht, das Hauptprogramm mit END. abzuschlieáen. 4.6 Ein erster Dialog --------------------- Wenn sich demo1.pas problemlos kompilieren und starten l„át, sehen Sie jetzt die Statuszeile mit dem Namen des aktuellen Raums sowie den Punkten und Zgen des Spielers. In der unteren Bildschirmh„lfte erscheint zuerst der mit AddProlog() zugewiesene Text, die aktuelle Raumbeschreibung und das Zeichen > als Eingabeaufforderung. Haben Sie bei der Initialisierung der Objekte irgendeinen Fehler gemacht, gibt Textopia eine Warnung (s. 8.9) aus und fragt Sie, ob Sie abbrechen oder fortfahren wollen (in einigen besonders harten F„llen wird das Programm auch ohne Rckfrage beendet). In diesem Fall sollten Sie immer abbrechen und die Fehlerursache beseitigen. Setzen Sie das Programm nur fort, wenn Sie genau wissen, daá der gemeldete Fehler keine Katastrophe verursachen kann. Das kleine d vor der Eingabeaufforderung weist darauf hin, daá der Debugmodus aktiv ist. In diesem Modus k”nnen Sie einige Debugkommandos eingeben, die Ihnen helfen, das Verhalten Ihrer Objekte zu kontrollieren. Die Debugkommandos werden in 7.6 erl„utert. Wenn Ihr Spiel fertig ist und fehlerfrei l„uft, sollten Sie den Debugmodus mit der Zeile debug:=f; im Hauptprogramm ausschalten. Viel l„át sich mit nur einem Raum noch nicht anfangen. Ein erster Dialog k”nnte so aussehen: Das Zwergengrab Eine Textopia-Demo von Oliver Berse Drei Tage schon durchstreifen Sie abseits der Straáen den alten Wald. Hier irgendwo liegen die mit Sch„tzen gefllten Gr„ber der Zwergenk”nige, die einst diese L„nder beherrschten. WALD Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. d>w Hier geht es nicht weiter. d>#i Sie tragen nichts mit sich 0 Item(s) Gewicht=1 d>warte Die Zeit vergeht... d>#ende Wollen Sie das Spiel wirklich beenden? (j/n) j Die Mit dem Zeichen # beginnenden Kommandos sind sog. Metaverben, die einige Spieloptionen ausl”sen, das Spielgeschehen selbst aber nicht beeinflussen. Metaverben werden im Abschnitt 7.5 behandelt. Anzahl und Gewicht der Items im Inventar werden nur im Debugmodus angezeigt. ***************************************************************************** V Die Spielwelt ***************************************************************************** 5.1 Gegenst„nde und Attribute ----------------------------- Bevor wir unserem Programm "Zwergengrab" weitere R„ume hinzufgen, geben wir dem Spieler einige ntzliche Dinge mit auf den Weg. Mit Ausnahme von R„umen und Tren k”nnen mit dem Objekttyp TItem fast alle in einem typischen Adventure vorkommenden Objekte dargestellt werden. Um einen Gegenstand zu erzeugen, brauchen wir zuerst wieder eine Konstante fr die ID und einen Zeiger vom Typ PItem. Wenn wir dem Spieler ein Schwert und einen Rucksack mitgeben wollen, k”nnen wir unser Programm um folgende Zeilen erweitern (ich fhre hier nur die neuen Zeilen auf, die demo1.pas an den entsprechenden Stellen hinzugefgt werden): CONST sword_id = 11; rucksack_id = 12; VAR item : PItem; Der Konstruktor TItem.Init() verlangt zun„chst wieder eine ID und einen Namen fr das neue Objekt (s. 4.3). Danach mssen folgende vier Parameter angegeben werden: >> _location: PBasic - einen Zeiger auf den Ort, an dem sich das Objekt zu Spielbeginn befinden soll. Dieser Ort kann ein Raum, das Inventar des Spielers oder ein anderer Gegenstand sein. Den ben”tigten Zeiger liefert die Funktion Adr(). Wenn das Objekt im Inventar des Spielers plaziert werden soll, muá dem Konstruktor der Wert NIL bergeben werden. W„hrend des Spiels k”nnen Sie die Position eines Objekt vom Typ PItem mit der Methode MoveItemTo() ver„ndern. Die neue Position wird durch einen Zeiger vom Typ PBasic bergeben, der die gleiche Bedeutung wie _location hat. >> _show: BOOLEAN - bestimmt, ob das Objekt automatisch in der Raumbeschreibung aufgefhrt wird. Nach Ausgabe des von Ihnen geschrieben Textes werden bei einer Raumbeschreibung alle im Raum befindlichen Objekte aufgelistet. Wenn Sie das Objekt bereits in Ihrem Text erw„hnt haben, k”nnen Sie hier False bergeben. Diese Voreinstellung l„át sich im Spiel mit der Methode ListMe() ver„ndern, der True oder False bergeben wird. So k”nnen Sie Objekte bis zu einem bestimmten Ereignis vor dem Spieler verstecken. >> _wmin: BYTE - bestimmt das Eigengewicht des Objekts. Wenn Sie das Objekt im Inventar des Spielers plazieren, achten Sie darauf, daá sein Gewicht nicht das Maximalgewicht des Spielers bersteigt. >> _wmax: BYTE - bestimmt das Maximalgewicht des Objekts. Wenn das Objekt andere Objekte aufnehmen kann, l„át sich hiermit bestimmen, wieviele Gegenst„nde es maximal enthalten darf. Soll das Objekt nicht als Beh„lter dienen, k”nnen Sie hier 0 angeben. Folgende Zeile erzeugt ein Schwert im Inventar des Spielers: NEW(item,Init(sword_id,'Schwert#er',NIL,t,1,0)); Damit wird dem Spiel zwar ein neues Objekt hinzugefgt, aber woher soll das Spiel wissen, was der Spieler damit anfangen kann? Um Textopia mitzuteilen, daá der Spieler den neuen Gegenstand z.B. aufnehmen und weglegen kann, mssen dem Objekt sog. Attribute hinzugefgt werden. Ein Attribut erm”glicht es dem Spieler, mit dem Objekt eine bestimmte Aktionen durchzufhren und ist entweder vorhanden oder nicht vorhanden. Nach seiner Initialisierung besitzt ein Objekt keine Attribute. Mit der Methode TItem.SetAttrib(a : BYTE; _on : BOOLEAN) k”nnen Sie abh„ngig vom Parameter _on ein Attribut hinzufgen (_on=True) oder entfernen (_on=False). Ob ein Objekt ber ein bestimmtes Attribut verfgt oder nicht, k”nnen Sie im Spiel mit der Methode TItem.Has(a : BYTE) : BOOLEAN prfen. Zehn Attribute sind bereits als Konstanten deklariert und k”nnen beliebig kombiniert werden. Die einzelnen Attribute haben folgende Bedeutung: >> drinkable_at - das Objekt kann getrunken werden. >> edable_at - das Objekt kann gegessen werden. >> enterable_at - das Objekt kann vom Spieler betreten werden. Betreten meint hierbei sowohl hineingehen (Telefonzelle) als auch draufsetzen (Fahrrad). Wenn Sie dieses Attribut setzen, sollten Sie daher mit der Methode TItem.SetPraepos(pp:STRING) auch die entsprechende Pr„position, meistens "in" oder "auf", angeben, damit Textopia den Umstand, daá der Spieler ein Objekt betreten hat, korrekt ausdrcken kann. >> moveable_at - der Spieler kann sich mit dem Objekt herumbewegen, wenn er es betreten hat. Ein Objekt mit den Attributen moveable_at und enterable_at kann somit ein Fahrzeug simulieren. >> readable_at - das Objekt kann gelesen werden (Bcher, Schilder etc.) >> shining_at - das Objekt ist eine Lichtquelle. Eine besondere Aktion kann der Spieler hiermit nicht ausfhren. Lichtquellen sind aber in ansonsten dunklen R„umen (s. 8.4) sehr praktisch. >> switchable_at - das Objekt kann ein- und ausgeschaltet werden. Nach der Initialisierung ist ein Objekt immer ausgeschaltet. Den aktuellen Schalterzustand k”nnen Sie im Spiel mit den Methoden TItem.IsOn und TItem.IsOff berprfen. Die Methoden TItem.SwitchOn und TItem.SwitchOff ver„ndern den Schalterzustand. >> takeable_at - das Objekt kann vom Spieler weggelegt, aufgenommen und im Inventar mit herumgetragen werden. >> talkable_at - das Objekt kann angeredet werden (NPCs, Mikrophone etc.). Um ein Objekt anzusprechen, muá der Spieler den Objektnamen gefolgt von einem Komma, einem Leerzeichen und einem Text in doppelten Anfhrungszeichen eingeben. N„heres ber NPCs und Dialoge erfahren Sie in 8.1. >> transparent_at - das Objekt ist durchsichtig (aber nicht unsichtbar), was fr Beh„lter bedeutet, daá ihr Inhalt auch im geschlossenen Zustand sichtbar bleibt. Einige Kombinationen der Attribute ergeben sicher sehr surrealistische Objekte. Neben diesen zehn Attributen k”nnen Sie auch eigene Attribute deklarieren. Fr deren Konstanten k”nnen Sie Werte zwischen 10 und 255 w„hlen. Natrlich mssen Sie dann auch einige Methoden an die neuen Attribute anpassen, lesen Sie also erstmal weiter. Das grade erzeugte Schwert braucht der Spieler nur mit sich herumzutragen: WITH item^ DO BEGIN SetAttrib(takeable_at,t); Jetzt braucht muá noch der Text geschrieben werden, der erscheint, wenn der Spieler n„heres ber das Schwert erfahren will. Fr die Beschreibung von Gegenst„nden k”nnen wir wieder AddText() verwenden: AddText('Das Schwert hat der Waffenschmied Ihnen kurz vor Ihrer '+ 'Abreise gegeben. Es schimmert und ist sehr scharf. Den '+ 'Schmied zu berreden, Ihnen das Schwert anzuschreiben, weil '+ 'Sie die n”tigen Taler erst noch in den Gr„bern finden '+ 'máten, war nicht einfach.\n'); END; Wenn Sie einem Gegenstand weder mit AddText() noch mit der Methode MyText eine Beschreibung hinzufgen und der Spieler das Objekt untersucht, gibt Textopia die Meldung "An xxx ist nichts Besonderes zu entdecken" aus. 5.2 Beh„lter ------------ Nach dem Schwert erzeugen wir noch einen Rucksack: NEW(item,Init(rucksack_id,'+Rucks%ack#e',NIL,t,1,5)); Den Rucksack kann der Spieler wie das Schwert mit sich herumtragen: WITH item^ DO BEGIN SetAttrib(takeable_at,t); END; Zus„tzlich soll der Rucksack noch als Beh„lter dienen. Einen Beh„lter kann der Spieler ”ffnen, schlieáen und abschlieáen. Natrlich kann er in einen Beh„lter auch andere Gegenst„nde hineinlegen (und wieder herausnehmen). Um aus einem Gegenstand einen Beh„lter zu machen, muá ein Objekt vom Typ TLock erzeugt und dem Gegenstand zugeordnet werden. TLock verwaltet einige Informationen, die alle Objekte ben”tigen, die sich ”ffnen und schlieáen lassen. Daher wird TLock Ihnen auch bei der Erzeugung von Tren wiederbegegnen. Objekte vom Typ TItem und TLink besitzen einen Zeiger namens lock, der auf das ihnen zugeordnete TLock-Objekt zeigt (und der natrlich NIL ist, wenn das entsprechende Objekt kein Beh„lter und keine Tr ist). šber diesen Zeiger k”nnen Sie auf die Methoden von TLock zugreifen. Der Konstruktor von TLock verlangt fnf Parameter: >> _owner: PBasic - einen Zeiger auf ein Objekt vom Typ TItem oder TLink, das als Beh„lter oder als Tr dienen soll. Einen Zeiger auf das entsprechende Objekt liefert Ihnen w„hrend des Spiels die Methode TLock.GetOwner. >> _openable: BOOLEAN - bestimmt, ob der Spieler das Objekt ”ffnen kann. Im Spiel k”nnen Sie diese Voreinstellung mit der Methode TLock.SetOpenable(s:BOOLEAN) „ndern. Den aktuellen Zustand dieses Parameters liefert die Methode TLock.IsOpenable. >> _closeable: BOOLEAN - bestimmt, ob der Spieler das Objekt schlieáen kann. Diese Voreinstellung k”nnen Sie im Spiel mit der Methode Tlock.SetCloseable(s:BOOLEAN) „ndern. Den aktuellen Zustand dieses Parameters liefert die Methode TLock.IsCloseable. >> _state: TState - Zustand des Objekts bei Spielbeginn, kann die Werte open, closed oder locked annehmen. Den aktuellen Zustand liefert Ihnen w„hrend des Spiels die Methode TLock.GetState. Wenn Sie das Schloá einer Verbindung zugeordnet haben, k”nnen Sie alternativ auch die Methode TRoom.IsGate() benutzen, die Sie informiert, ob die Verbindung in der bergebenen Richtung offen, geschlossen oder verschlossen ist. >> _key: PItem - einen Zeiger auf einen Gegenstand, der als Schlssel dient. Nur mit diesem Schlssel kann der Spieler das Objekt auf- und abschlieáen. Wird hier NIL angegeben, kann der Spieler das Objekt nur ”ffnen (wenn es nicht abgeschlossen ist) und schlieáen, nicht aber abschlieáen. Der als Schlssel dienende Gegenstand kann natrlich auch eine Magnetkarte oder Brechstange sein. Im Spiel gelangen Sie ber TLock.GetKey wieder an den Schlssel. Um eine Instanz von TLock zu erzeugen, brauchen wir einen neuen Zeiger. Fgen Sie daher im Var-Abschnitt die Zeile lock : PLock; ein. Mit item wurde der Rucksack erzeugt, dieser Zeiger kann daher gleich dem Konstruktor von TLock bergeben werden: NEW(lock,Init(item,t,t,closed,NIL)); Auf eine besondere Beschreibung des Rucksacks soll einmal verzichtet werden. Wenn Sie w„hrend des Spiel auf einen Beh„lter zugreifen wollen, sollten Sie noch folgende Dinge wissen: Um festzustellen, ob ein Beh„lter ein bestimmtes Objekt enth„lt, verwenden Sie die Methode TItem.Contains(x : PItem) : BOOLEAN. Umgekehrt liefert die Methode TItem.GetContainer einen Zeiger vom Typ PItem auf den Beh„lter, der das Objekt enth„lt. Objekte vom Typ TItem und TPlayer enthalten ein statisches Objekt namens weight. Dessen Methoden GetCont, GetSum und GetMax liefern die Zahl der im Beh„lter enthaltenen Objekte, das Gesamtgewicht des Beh„lters und sein zul„ssiges Maximalgewicht. Wenn Sie jetzt das Programm demo2.pas starten, k”nnen Sie mit unserem Mini-Adventure schon etwas mehr anfangen: >#i Sie tragen folgende Dinge mit sich: Ein Schwert. Einen Rucksack. 2 Item(s) Gewicht=3 >”ffne den rucksack Sie ”ffnen den Rucksack. >lege das schwert in den rucksack Sie legen ein Schwert in den Rucksack. >#i Sie tragen folgende Dinge mit sich: Einen Rucksack, der Rucksack enth„lt: Ein Schwert. 2 Item(s) Gewicht=3 >nimm das schwert aus dem rucksack Sie nehmen ein Schwert. >#i Sie tragen folgende Dinge mit sich: Ein Schwert. Einen Rucksack. 2 Item(s) Gewicht=3 Wenn Sie meinen, ein Schwert sei zu groá, um in einen Rucksack gelegt zu werden, brauchen Sie nur das Gewicht des Schwerts zu erh”hen, so daá es mehr wiegt als der Rucksack aufnehmen kann. Hierbei werden Sie auch merken, daá Textopia nicht unterscheiden kann, ob ein Objekt von einem Beh„lter nicht aufgenommen werden kann, weil es zu schwer oder weil es zu groá ist. Fr diese Unterscheidung máte neben dem Gewicht eines Objekts auch noch dessen Gr”áe festgelegt werden (was die Verwaltung der Beh„lter wesentlich komplexer machen wrde). In den meisten Spielen wird die Gewichtsangabe sicher ausreichen. 5.3 Fortbewegungsmittel ----------------------- Eine weitere Gruppe ntzlicher Gegenst„nde sind Fortbewegungsmittel wie Reittiere und Fahrzeuge aller Art. Wenn der Spieler sie betreten hat, bewegen sie sich mit ihm durch die R„ume. Geben Sie einem TItem-Objekt, das dem Spieler zur Fortbewegung dienen soll, die Attribute enterable_at und moveable_at. Der Spieler im "Zwergengrab" soll mit einem Pferd durch den Wald reisen k”nnen. Wir deklarieren die Konstante horse_id und erzeugen vor der Initialisierung des Spielers ein Pferd: horse_id = 13; NEW(item,Init(horse_id,'Pferd#e',Adr(forest_id),t,1,10); WITH item^ DO BEGIN SetAttrib(enterable_at,t); SetAttrib(moveable_at,t); SetPraepos('auf'); END; Mit SetPraepos() bestimmen Sie, ob der Spieler sich in oder auf einem Fahrzeug befindet (s. 5.1). Um den Spieler zu Spielbeginn gleich aufs Pferd zu setzen, muá im Konstruktor von TPlayer als Startposition jetzt horse_id statt forest_id angegeben werden. Mit demo3.pas lernen Sie den Umgang mit einem Pferd: WALD (auf einem Pferd) Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. >steig vom pferd Ok >schau WALD Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. Ein Pferd. >steig aufs pferd Ok >schau WALD (auf einem Pferd) Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. >us pferd An dem Pferd ist nichts Besonderes zu entdecken. Statt "steig aufs pferd" versteht der Parser natrlich auch "steige auf das pferd". Wie Sie erkennen k”nnen, wird ein Fortbewegungsmittel nicht in der Raumbeschreibung erw„hnt, wenn der Spieler sich darin oder darauf befindet. Beachten Sie auch, daá der Spieler, wenn er sich in einem betretbaren Objekt befindet, nicht an Objekte herankommt, die sich auáerhalb des den Spieler enthaltenen Objekts befinden (In einem Auto oder einer Telefonzelle kann man z.B. schlecht eine auf der Straáe liegende Mnze aufheben). 5.4 Weitere R„ume ----------------- Einen Wald haben wir bereits in 4.3 erzeugt. Fr unser Mini-Adventure brauchen wir noch vier weitere R„ume: Vom Wald aus soll der Spieler in westlicher Richtung auf eine Lichtung mit einem Hgelgrab gelangen. In dem Hgel befindet sich ein Tor, ber eine dahinterliegende Treppe gelangt der Spieler in die unter dem Hgel liegende Grabkammer. Im Sdwesten dieser Kammer soll eine weitere H”hle liegen. Im Osten plazieren wir eine Schatzkammer, die durch eine Tr zun„chst versperrt wird. Die Karte sieht dann wie folgt aus: Hgelgrab/Tor.....Wald . Treppe nach unten . Grabkammer...Tr/Schatzkammer . H”hle Beginnen wir mit der Deklaration der Konstanten fr die neuen R„ume: hill_id = 14; grave_id = 15; chamber_id = 16; cave_id = 17; Nach der Initialisierung des Waldes k”nnen jetzt die neuen R„ume hinzugefgt werden: NEW(room,Init(hill_id,'-Lichtung#en')); NEW(room,Init(grave_id,'-Grabkammer#n')); WITH room^ DO BEGIN AddText('In der Mitte der engen Grabkammer steht ein Sarkophag. In der '+ 'Ostwand befindet sich eine schwere, mit Eisen beschlagene '+ 'Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine '+ 'hinter der Grabkammer liegende H”hle.\n'); END; NEW(room,Init(chamber_id,'-Schatzkammer#n')); WITH room^ DO BEGIN AddText('In dieser Kammer lagerten die Grabbeigaben des K”nigs.\n'); END; NEW(room,Init(cave_id,'-H”hle#n')); WITH room^ DO BEGIN AddText('Sie befinden sich in der kleinen H”hle im Sdwesten der '+ 'Grabkammer. Diese H”hle wurde offensichtlich nicht von den '+ 'Zwergen angelegt, als sie das Grab schufen. Die Wand der '+ 'Grabkammer wurde fr die H”hle durchbrochen. Ihre W„nde '); AddText('sind mit scheuálichen Symbolen bers„t, die nichts mit den '+ 'Schriftzeichen der Zwerge auf dem Sarkophag zu tun haben.\n'); END; Die Lichtung braucht eine variable Beschreibung (s. 4.4), die erw„hnen soll, ob das Tor ge”ffnet oder geschlossen ist. Diese Beschreibung folgt in 5.6. Wenden wir uns zun„chst den Dingen in der Grabkammer zu. Der Sarkophag stellt einen Beh„lter dar. Er enth„lt einen Knochen und wird von einer unbeweglichen Steinplatte halb verschlossen: sarko_id = 18; stone_id = 19; bone_id = 20; NEW(item,Init(sarko_id,'+Sarkophag',Adr(grave_id),f,1,5)); NEW(lock,Init(item,f,f,open,NIL)); NEW(item,Init(stone_id,'-Steinplatte#n',Adr(grave_id),f,1,0)); NEW(item,Init(bone_id,'+Knochen',Adr(sarko_id),t,1,0)); WITH item^ DO BEGIN SetAttrib(takeable_at,t); AddText('Dies ist der einzige Knochen, der von dem hier bestatteten '+ 'K”nig geblieben ist. Wenn Sie genau hinschauen, entdecken Sie '+ 'an dem Knochen tiefe Kratzer, die nur von sehr scharfen '+ 'Z„hnen stammen k”nnen.\n'); END; 5.5 Variable Objektbeschreibungen --------------------------------- Der Knochen soll bei der Beschreibung des Sarkophags erw„hnt werden. Da der Spieler den Knochen auch herausnehmen kann, sollte der Sarkophag eine variable Raumbeschreibung erhalten. Statt des statischen AddText() muá also die Methode MyText verwendet werden. Hierzu deklarieren wir einen Nachfolger von TItem, der ja auch ein Nachfolger von TBasic ist, und bringen die Beschreibung des Sarkophags in der berschriebenen Methode MyText unter: TYPE TnItem = OBJECT(TItem) PROCEDURE MyText; VIRTUAL; END; PnItem = ^TnItem; Im Var-Abschnitt muá der Typ des Zeigers item in PnItem ge„ndert werden: item : PnItem; Jetzt folgt die Neudefinition von TnItem.MyText, die variable Beschreibung des Sarkophags enth„lt. Beachten Sie, daá eine berschriebene Methode von allen Instanzen des entsprechenden Objekttyps verwendet werden kann. Daher muá in TnItem.MyText mit der Methode TBasic.GetID : WORD festgestellt werden, fr welches Objekt die Beschreibung ausgegeben werden soll: PROCEDURE TnItem.MyText; BEGIN CASE GetID OF sarko_id : BEGIN Print('In der Ihnen unbekannten Zwergensprache ist ein '+ 'Text in die Seiten des Sarkophags gemeiáelt. Die '+ 'schwere Steinplatte, die den Sarkophag einmal '+ 'verschloá, ist halb zur Seite geschoben. '); IF Contains(Adr(bone_id)) THEN Print('Im Sarkophag liegt ein einzelner Knochen.\n') ELSE Print('Von dem Zwergenk”nig ist im Sarkophag '+ 'nichts mehr zu entdecken.\n'); END; END; END; 5.6 Verbindungen und Tren -------------------------- Die Tr zur Schatzkammer soll sich mit einem Schlssel ”ffnen lassen, den der Spieler in der H”hle finden kann. Als Schlssel dienende Objekte mssen vor der Initialisierung der entsprechnenden Schl”sser initialisiert werden: key_id = 21; NEW(item,Init(key_id,'+Schlssel',Adr(cave_id),t,1,0)); item^.SetAttrib(takeable_at,t); Auf der Karte gibt es vier Verbindungen zwischen den R„umen. Jede von ihnen braucht eine eigene ID. Bei umfangreicheren Karten sollten den Verbindungen aussagekr„ftigere Namen gegeben werden, hier numerieren wir einfach: link1_id = 22; link2_id = 23; link3_id = 24; link4_id = 25; Im Var-Abschnitt mssen jetzt noch Zeiger vom Typ PLink und PLock deklariert werden: link : PLink; lock : PLock; Der Konstruktor von TLink verlangt zun„chst wieder nach einer ID und einem Namen fr die Verbindung. Der Name kann entfallen, wenn die Verbindung keine Tr oder ein „hnliches Hindernis darstellen soll. Geben Sie dann einfach einen Leerstring ('') an. Nach ID und Namen mssen noch folgende vier Parameter angegeben werden: >> _from:Word - die ID des ersten Raums. >> _dirto:TDir - Himmelsrichtung (in englisch), in der der Spieler den ersten Raum verlassen kann, um den zweiten zu erreichen. Zus„tzlich zu den zehn Himmelsrichtungen (north, northeast usw.) k”nnen Sie auch up und down angeben. >> _to:Word - die ID des Raums, den der Spieler aus angegebener Himmelsrichtung vom ersten Raum aus erreicht. >> _show:Boolean - bestimmt, ob die Verbindung automatisch nach der Raumbeschreibung erw„hnt wird. Die Zeile NEW(link,Init(link1_id,'',forest_id,west,hill_id,f)); verbindet den Wald in westlicher Richtung mit dem Hgelgrab auf der Lichtung. Die Verbindung zwischen dem Hgel und der Grabkammer wird mit NEW(link,Init(link2_id,'Tor',hill_id,down,grave_id,f)); erzeugt. Damit der Spieler das Tor ”ffnen und schlieáen kann, muá dieser Verbindung noch ein Objekt vom Typ TLink zugeordnet werden: NEW(lock,Init(link,t,t,closed,NIL)); Entsprechend werden die brigen Verbindungen und Tren erzeugt: NEW(link,Init(link3_id,'-Eichentr#en;-Tr#en',grave_id,east,chamber_id,f)); NEW(lock,Init(link,t,t,locked,Adr(key_id))); NEW(link,Init(link4_id,'',grave_id,southwest,cave_id,f)); Es fehlt noch die Beschreibung der Lichtung, die dem Spieler mitteilen soll, ob das Tor im Hgel ge”ffnet oder geschlossen ist. Hierbei wird „hnlich wie bei der Neudefinition von TItem.MyText vorgegangen, mit dem Unterschied, daá diesmal ein Nachfolger von TRoom ben”tigt wird. Im Type-Abschnitt wird die Deklaration TnRoom = OBJECT(TRoom) PROCEDURE MyText; VIRTUAL; END; PnRoom = ^TnRoom; hinzugefgt. Im Var-Abschnitt wird der Zeiger room ge„ndert: room : PnRoom; Die variable Raumbeschreibung der Lichtung sieht wie folgt aus: PROCEDURE TnRoom.MyText; BEGIN CASE GetID OF hill_id : BEGIN Print('Ein kleiner Hgel erhebt sich in der Mitte einer '+ 'Lichtung ber das Grab eines Zwergenk”nigs. '); IF gate[down]^.lock^.GetState=closed THEN Print('Ein steinernes Tor in der Nordseite versperrt '+ 'Ihnen den Weg ins Innere.\n') ELSE Print('Das steinerne Tor in der Nordseite steht '+ 'offen. Dahinter fhrt eine Treppe in '+ 'das Grab hinab.\n'); END; END; END; Das Array Gate[] ist ein Datenfeld von TRoom und enth„lt Zeiger auf die Verbindungen in den entsprechenden Himmelsrichtungen. Mit der Abfrage IF gate[south]<>NIL k”nnen Sie z.B. feststellen, ob im Sden eine Verbindung zu einem anderen Raum besteht. Ob einer Verbindung ein Schloá zugeordnet ist, teilt Ihnen die Abfrage IF gate[down]^.lock<>NIL mit. Wenn der Verbindung ein Schloá zugeordnet ist k”nnen Sie ber lock auf die Methoden von TLock zugreifen. Wenn Sie jetzt einmal demo4.pas starten, ist folgender Dialog m”glich: WALD (auf einem Pferd) Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. >w LICHTUNG (auf einem Pferd) Ein kleiner Hgel erhebt sich in der Mitte einer Lichtung ber das Grab eines Zwergenk”nigs. Ein steinernes Tor in der Nordseite versperrt Ihnen den Weg ins Innere. >steig vom pferd Ok >”ffne das tor Sie ”ffnen das Tor. >runter GRABKAMMER In der Mitte der engen Grabkammer steht ein Sarkophag. In der Ostwand befindet sich eine schwere, mit Eisen beschlagene Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine hinter der Grabkammer liegende H”hle. >us sarkophag In der Ihnen unbekannten Zwergensprache ist ein Text in die Seiten des Sarkophags gemeiáelt. Die schwere Steinplatte, die den Sarkophag einmal verschloá, ist halb zur Seite geschoben. Im Sarkophag liegt ein einzelner Knochen. >nimm den knochen Sie nehmen einen Knochen. >us knochen Dies ist der einzige Knochen, der von dem hier bestatteten K”nig geblieben ist. Wenn Sie genau hinschauen, entdecken Sie an dem Knochen tiefe Kratzer, die nur von sehr scharfen Z„hnen stammen k”nnen. >sw H™HLE Sie befinden sich in der kleinen H”hle im Sdwesten der Grabkammer. Diese H”hle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab schufen. Die Wand der Grabkammer wurde fr die H”hle durchbrochen und die W„nde der H”hle sind mit scheuálichen Symbolen bers„t, die sich stark von den Schriftzeichen der Zwerge auf dem Sarkophag unterscheiden. Ein Schlssel. >nimm den schlssel Sie nehmen einen Schlssel. >no GRABKAMMER In der Mitte der engen Grabkammer steht ein Sarkophag. In der Ostwand befindet sich eine schwere, mit Eisen beschlagene Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine hinter der Grabkammer liegende H”hle. >”ffne die tr mit dem schlssel Sie ”ffnen die Eichentr. >o SCHATZKAMMER In dieser Kammer lagerten die Grabbeigaben des K”nigs. Wenn Sie wollen, k”nnen Sie in der Schatzkammer noch verschiedene Sch„tze wie Mnzen, Kronen und Waffen unterbringen. Denken Sie daran, daá mehrfach vorhandene Dinge (z.B. mehrere Goldmnzen) unterschiedliche IDs aber identische Namen bekommen mssen und maximal zehnfach vorhanden sein drfen. Den Weg des Spielers durch Ihre R„ume k”nnen Sie mit den Methoden TRoom.FromDir : TDir und TRoom.ToDir : TDir verfolgen. Aus der Richtung FromDir hat der Spieler den Raum betreten, in die Richtung ToDir hat er ihn verlassen. Wenn der Spieler den Raum noch nicht betreten oder verlassen hat, liefern diese Methoden den Wert nowhere. 5.7 Der Scope ------------- In 5.3 haben Sie bereits erfahren, daá der Spieler Objekte nicht erreichen kann, die sich auáerhalb des Beh„lters oder Fahrzeugs befinden, in dem der Spieler sich befindet. Auch in anderen F„llen kann ein Objekt fr den Spieler unerreichbar sein. Jeder Nachfolger von TBasic verfgt ber die Methode TBasic.Scope : BYTE An ihrem Rckgabewert k”nnen Sie erkennen, inwieweit das entsprechende Objekt fr den Spieler erreichbar ist. Die einzelnen Werte von Scope sind in tmain.pas als Konstanten mit der Endung *_sd deklariert und haben folgende Bedeutung: >> notinroom_sd - Das Objekt befindet sich nicht mit dem Spieler in einem Raum, es ist daher auáer Sicht- und Reichweite. >> visible_sd - Das Objekt ist nur sichtbar, kann vom Spieler aber nicht berhrt oder aufgenommen werden. Dies tritt z.B. ein, wenn das Objekt sich in einem verschlossenen, transparenten Beh„lter befindet. >> reachable_sd - Das Objekt befindet sich mit dem Spieler in einem Raum, aber nicht im Inventar des Spielers oder eines NPC. >> bynpc_sd - Das Objekt wird von einem NPC (s. 8.1) gehalten. >> held_sd - Das Objekt befindet sich im Inventar des Spielers. Die Methode Scope ist in allen Objekttypen unterschiedlich definiert, so kann z.B. der Scope eines Raums niemals held_sd sein. ***************************************************************************** VI Aktionen und Reaktionen ***************************************************************************** 6.1 Ereignisse -------------- Die bisherige Version unseres Mini-Adventures (demo4.pas) l„át dem Spieler eine etwas zu groáe Bewegungsfreiheit: Nachdem er vom Pferd gestiegen ist und das Tor ge”ffnet hat, kann er sich wieder auf das Pferd setzen und in die Grabkammer hineinreiten. Wir wollen aber annehmen, daá das Tor und das Grab zu eng und zu klein sind, um sich auf einem Pferd hineinzubegeben. Der Spieler sollte also daran gehindert werden, das Grab zu betreten, wenn er sich noch auf dem Pferd befindet. Um auf Aktionen des Spielers (hier: das Betreten der Grabkammer mit einem Pferd) Einfluá zu nehmen, mssen Sie zun„chst wissen, wie diese von Textopia ausgefhrt werden. Der Parser versucht, jeder Eingabe des Spielers ein ihm bekanntes Ereignis zuzuordnen. Ein Ereignis tritt z.B. ein, wenn der Spieler ein Objekt untersucht, einen Beh„lter ”ffnet oder den Spielstand speichert. Textopia kennt insgesamt 40 Ereignisse, die im Interface-Abschnitt von tmain.pas als Konstanten mit der Endung *_ev deklariert sind. In Abschnitt 7.2 werden Sie erfahren, wie Sie eigene Ereignisse hinzufgen k”nnen. Die einzelnen Ereignisse k”nnen von einer normalen Spielereingabe, einem Metaverb (s. 7.5) oder einem Debugkommando (s. 7.6) ausgel”st werden und haben folgende Bedeutung: close_ev - Ein Schloá schlieáen dance_ev - Spieler tanzt dec_ev - Debugkommando .dec drink_ev - Spieler trinkt Objekt eat_ev - Spieler iát Objekt enter_ev - Spieler betritt Objekt examine_ev - Spieler untersucht Objekt give_ev - Spieler gibt NPC Objekt go_ev - Spieler verl„át Raum inv_ev - Metaverb #i drop_ev - Spieler legt Objekt weg jump_ev - Spieler springt in die Luft kill_ev - Spieler t”tet NPC kiss_ev - Spieler kát NPC leave_ev - Spieler verl„át zuvor betretenes Objekt lista_ev - Debugkommando .browse lists_ev - Debugkommando .list load_ev - Metaverb #laden lock_ev - Schloá abschlieáen look_ev - Raumbeschreibung map_ev - Metaverb #karte open_ev - Schloá ”ffnen press_ev - Spieler drckt Objekt quit_ev - Metaverb #ende restart_ev - Metaverb #neu read_ev - Spieler liest Objekt score_ev - Metaverb #punkte save_ev - Metaverb #sichern set1_ev - Metaverb #status set2_ev - Metaverb #beschreibung show_ev - Spieler zeigt NPC Objekt sleep_ev - Spieler schl„ft smell_ev - Spieler riecht an Objekt switchoff_ev - Spieler schaltet Objekt aus switchon_ev - Spieler schaltet Objekt ein take_ev - Spieler nimmt Objekt auf tell_ev - Spieler spricht zu NPC touch_ev - Spieler berhrt Objekt trace_ev - Debugkommando .trace wait_ev - Spieler wartet An einem Ereignis k”nnen bis zu zwei Objekte beteiligt sein. Die betroffenen Objekte erw„hnt der Spieler meist in der Eingabe: "Lege das Schwert in den Rucksack" oder "™ffne das Tor". Auch Eingaben, in denen der Spieler kein Objekt der Spielwelt ausdrcklich erw„hnt, k”nnen sich auf ein bestimmtes Objekt beziehen. Das Kommando "schau" bezieht sich z.B. auf den aktuellen Raum und bewirkt die Ausgabe der Beschreibung eines Objekts vom Typ TRoom. Die an einem Ereignis beteiligten Objekte werden vom Parser benachrichtigt und fhren die vom Spieler gewnschte Aktion aus. Dabei ruft der Parser die Methode HandleEvent(VAR event : TEvent); VIRTUAL; auf, die alle Nachfolger von TBasic und TGame besitzen. Der Methode wird ein Record vom Typ TEvent bergeben, in der der Parser einige Informationen ber die Eingabe speichert. Von den Datenfeldern dieses Records soll zun„chst nur das Feld action vom Typ Byte interessieren. In action ist der Wert der dem Ereignis zugeordneten Konstante gespeichert. In der Methode HandleEvent() kann so festgestellt werden welches Ereignis ausgefhrt werden soll. Die HandleEvent()-Methoden der einzelnen Objekttypen kmmern sich nur um die Ereignisse, die mit dem jeweiligen Objekttyp auch einen Sinn ergeben. Die von TBasic abgeleiteten Objekttypen leiten ein Ereignis an ihren Vorfahren weiter, wenn sie es nicht selbst abarbeiten. Wenn auch TBasic nicht fr das Ereignis zust„ndig ist, wird die Methode TGame.HandleEvent() aufgerufen. Folgende Tabelle zeigt, welcher Objekttyp fr welches Ereignis zust„ndig ist: TItem TRoom TLink TLock TBasic TGame close_ev go_ev close_ev close_ev dance_ev lista_ev drink_ev look_ev lock_ev lock_ev dec_ev sleep_ev drop_ev open_ev open_ev examine_ev wait_ev eat_ev jump_ev enter_ev lists_ev give_ev touch_ev kill_ev trace_ev kiss_ev leave_ev lock_ev open_ev read_ev show_ev switchoff_ev switchon_ev press_ev take_ev tell_ev TItem und TLink leiten die Ereignisse close_ev, lock_ev und open_ev an das ihnen zugeordnete Objekt vom Typ TLock weiter. Die Ereignisse inv_ev, load_ev, map_ev, quit_ev, restart_ev, save_ev, score_ev, set1_ev und set2_ev geh”ren zu den Metaverben und werden in Abschnitt 7.4 behandelt. 6.2 BeforeAction und AfterAction -------------------------------- Da jedes Objekt mehrere Ereignisse abarbeiten kann, erfolgt die Verarbeitung der einzelnen Ereignisse in der Methode HandleEvent() immer in einer Case-Anweisung: PROCEDURE Txxx.HandleEvent(VAR event : TEvent); BEGIN WITH event DO BEGIN CASE action OF yyy_ev : { Code... } zzz_ev : { Code... } ELSE vorgaenger_von_Txxx.HandleEvent(event); END; END; END; Die Methode kann auch einen Var-Abschnitt mit lokalen Variablen enthalten. Wenn Sie die Verarbeitung eines einzelnen Ereignisses beeinflussen wollen, mssen Sie nicht die ganze Methode mit allen Ereignissen berschreiben. Der zu einem Ereignis geh”rende Codeblock beginnt immer mit dem Aufruf der Methode BeforeAction(VAR event : TEvent) : BOOLEAN; VIRTUAL; BeforeAction() liefert einen Wahrheitswert zurck (normalerweise immer True), von dem die Ausfhrung der weiteren Anweisungen abh„ngt. Wenn diese Methode False zurckgibt, wird das betreffende Ereignis nicht weiter ausgefhrt. Wenn Sie ein bestimmtes Ereignis beeinflussen wollen, mssen Sie also nur die Methode BeforeAction() des zust„ndigen Objekttyps berschreiben, in einer Case-Anweisung das Ereignis abfangen und entscheiden, ob die Ausfhrung in HandleEvent() fortgesetzt werden soll. Sie k”nnen so auch den kompletten Code fr die Ausfhrung eines Ereignisses durch eigene Anweisungen ersetzen. In unserem Beispiel mssen wir verhindern, daá der Spieler auf seinem Pferd in das Grab eindringt. Dafr mssen wir das Ereignis go_ev abfangen und prfen, ob der Spieler auf der Lichtung mit dem Pferd nach unten in den Hgel gehen will. Der Deklaration von TnRoom fgen Sie den Methodenkopf von BeforeAction() hinzu und schreiben diese Methode neu: FUNCTION TnRoom.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN CASE action OF go_ev : IF ToDir=down THEN BEGIN ok:=player^.GetContainer=NIL; IF NOT(ok) THEN Print('Mit dem Pferd k”nnen Sie hier nicht hinein.\n'); END; END; END; BeforeAction:=ok; END; Ausprobieren k”nnen Sie diese Erweiterung mit dem Programm demo5.pas. Wenn ein Ereignis ausgefhrt wurde, gibt HandleEvent() eine entsprechende Mitteilung aus, die den Spieler ber die erfolgreiche Ausfhrung seiner Aktion informiert (z.B. "Sie nehmen x" nach der Eingabe "nimm x"). Wenn Sie diese Meldung ver„ndern oder unterdrcken wollen, mssen Sie die zu BeforeAction() analoge Methode AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; berschreiben, die nach der Ausfhrung eines Ereignisses und vor Ausgabe einer Meldung aufgerufen wird. Liefert AfterAction() den Wert False zurck, so wird die Meldung unterdrckt. Die in tio.pas deklarierte globale Variable replay vom Typ Boolean wird bei jeder Textausgabe mit Print() auf True gesetzt. An ihr erkennt der Parser, ob w„hrend der Ausfhrung eines Ereignisses bereits ein Text ausgegeben wurde. Fhrt ein Ereignis nicht zur Ausgabe irgendeines Textes, so gibt der Parser anschlieáend ein schlichtes "Ok" aus. Manche Ereignisse bestehen nur in der Ausgabe einer Meldung, z.B. bewirkt das Kommando "warte" die Ausgabe "Die Zeit vergeht...", ohne daá irgendwelche weiteren Methoden ausgefhrt werden. In solchen F„llen kann AfterAction() die Ausfhrung eines Ereignisses also verhindern oder „ndern. W„hrend der Ausfhrung eines Ereignisses k”nnen Sie auch weitere Ereignisse ausl”sen, indem Sie die Methode HandleEvent() eines beliebigen Objekts mit der Variable event und einem ver„nderten Wert fr action aufrufen. Textopia tut dies z.B. bei der Ausfhrung des Ereignisses go_ev in der Methode TRoom.HandleEvent(). Wenn der Spieler mit einem Schlssel durch eine mit diesem Schlssel verschlossene Tr gehen will, wird zun„chst das Ereignis open_ev ausgel”st. Schauen Sie sich hierzu einmal den Quelltext von TRoom.HandleEvent() an. ***************************************************************************** VII Der Parser ***************************************************************************** 7.1 Die Eingabe --------------- Der Parser (von engl. to parse: einen Satz grammatisch analysieren) ist der wichtigste Bestandteil jedes Text-Adventures. Er nimmt die Spielereingabe entgegen, analysiert sie und sorgt dafr, daá die Befehle des Spielers ausgefhrt werden. Eine Schleife in der Methode TGame.Run wartet in jedem Spielzug auf eine Spielereingabe und bergibt sie dem Parser. Die Spielereingabe wird von der in tmain.pas definierten Prozedur Scan(VAR line : STRING; prompt,upsize : BOOLEAN; lmem : BYTE); entgegengenommen. Scan() bildet einen kompletten Zeileneditor, mit dem Sie auch selber Eingaben des Spielers anfordern k”nnen. Ihre Parameter haben golgende Bedeutung: >> line - in diesem String wird die Spielereingabe eingetragen. >> prompt - bestimmt, ob das Eingabeaufforderungszeichen > ausgegeben wird. >> upsize - bestimmt, ob die Eingabe in Groábuchstaben erfolgt. Es gilt die in TGame.Init() (s. 4.5) mit dem Parameter _upsize angegebene Voreinstellung. >> lmem - Gr”áe des Zeilenspeichers. Es gilt die in TGame.Init() (s. 4.5) mit dem Parameter _history angegebene Voreinstellung. Die Eingabe kann mit den Tasten [<-], [Einfg], [Pos1], [Ende] und [Esc] editiert werden. Mit den Tasten CursorOben/CursorUnten kann der Zeilenspeicher durchbl„ttert werden. Groá- und Kleinschreibung werden nicht unterschieden. Intern wird die Eingabe in Kleinbuchstaben umgewandelt. Der Eingabestring kann maximal eine Bildschirmzeile lang sein und darf maximal zehn W”rter enthalten, weitere W”rter werden ignoriert. Diese Zahl k”nnen Sie mit der in tstring.pas deklarierten Konstante maxbuf erh”hen. Die Satzzeichen ? . und ! k”nnen eingegeben werden, werden vom Parser aber ignoriert. Das Komma und das doppelte Anfhrungszeichen (") haben eine besondere Funktion beim Umgang mit NPCs (s. 8.1) und sollten nur in diesem Zusammenhang verwendet werden. Umlaute k”nnen wahlweise auch ausgeschrieben werden. Hat der Spieler sich in der letzten Eingabe mit einem Substantiv auf ein Objekt bezogen, kann er sich mit einem Reflexivpronomen erneut auf das selbe Objekt beziehen: z.B. "lege das Schwert weg" und "nimm es" statt "nimm das Schwert". Fr die Bewegung durch die Spielwelt stehen dem Spieler folgende Kommandos zur Verfgung: n, norden, no, nordosten, o, osten, so, sdosten, s, sden, sw, sdwesten, w, westen, nw, nordwesten. Nach oben und unten gelangt der Spieler mit oben, aufw„rts, rauf, hoch, unten, abw„rts und runter. Der Parser versteht die Numerale von zwei bis zehn. Mit ihnen k”nnen Mengen gleichartiger Dinge angegeben werden. Wenn in einem Raum z.B. fnf Mnzen vorhanden sind, kann der Spieler folgende Eingaben machen: "nimm eine Mnze", "nimm drei Mnzen" oder "nimm alle Mnzen". Statt eines Numerals kann der Spieler auch die entsprechende Ziffer eingeben. Fr den Parser bislang unverst„ndlich sind Bindew”rter (z.B. "und", "oder") und Verneinungen mit "nicht" und "kein". Nach der Eingabe wird der Eingabestring an die Methode TGame.BeforeParsing(VAR input : STRING); VIRTUAL; bergeben. Diese Methode macht normalerweise nichts mit dem String, kann aber berschrieben werden. Wenn der Spieler in ihrem Spiel z.B. von einem Fluch getroffen wird und seinen Orientierungssinn verliert, k”nnten Sie in dieser Methode die Himmelsrichtungen im Eingabestring vertauschen. 7.2 Verben und Grammatik ------------------------ Der Eingabestring wird von der Methode TGame.Parse() analysiert, die versucht, das zu der Eingabe passende Ereignis zu finden. Die einzelnen Ereignisse werden von verschiedenen Verben ausgel”st. Der Konstruktor von TGame ruft die Methode TGame.ReadLib auf, die eine Liste mit Verben, das sog. W”rterbuch, anlegt. Zus„tzlich zu jedem Verb werden im W”rterbuch auch eine zugeh”rige Grammatik sowie das auszul”sende Ereignis gespeichert. Parse() vergleicht nun den Eingabestring mit jedem Eintrag im W”rterbuch. Wenn in der Eingabe ein Verb aus dem W”rterbuch gefunden wird und die Eingabe auch mit der zum Verb geh”renden Grammatik bereinstimmt, wird das entsprechende Ereignis bei den beteiligten Objekten ausgel”st. Das W”rterbuch wird in Readlib durch wiederholten Aufruf der Methode TGame.AddVerb(_verb,_syntax : STRING; _event : BYTE) angelegt. Ihre Parameter haben folgende Bedeutung: >> _verb:String - ein beliebiges Wort, das ein Ereignis ausl”sen soll. Sie k”nnen auch mehrere synonyme W”rter angeben, die durch ein Semikolon getrennt werden mssen. Wenn das Wort ein Metaverb oder ein Debugkommando sein soll, so muá ihm ein Filezeichen (#) bzw. ein Punkt vorangestellt werden. >> _syntax:String - die zum Verb geh”rende Grammatik, mit der die Eingabe bereinstimmen muá, um ein Ereignis auszul”sen. Es k”nnen auch mehrere durch ein Semikolon getrennte Grammatiken angegeben werden. Wenn Sie nur einen Leerstring angeben, gengt die Eingabe des Verbs, um ein Ereignis auszul”sen. >> _event:Byte - die Konstante eines Ereignisses. Geben Sie hier eine der in tmain.pas deklarierten Konstanten mit der Endung *_ev an. Was hat es nun mit der Grammatik auf sich? Die Grammatik eines Verbs gibt an, welche W”rter dem Verb im Eingabestring folgen mssen. Sie k”nnen sich die Grammatik als eine Schablone vorstellen, in die die Eingabe hineinpassen muá. Eine Grammatik besteht aus einzelnen Token, von denen jedes fr ein einzelnes Wort oder eine Wortgruppe steht. Die Token werden in der Grammatik in der Reihenfolge angeordnet, in der die entsprechenden W”rter auch in der Eingabe auftauchen sollen, dabei mssen die einzelnen Token durch ein Pluszeichen voneinander getrennt werden. Textopia kennt sieben verschiedene Token: >> [wort] - ein beliebiges Wort, das nicht Bestandteil eines Objektnamens ist, meistens eine Pr„position. Das Wort muá nur in der Grammatik in eckigen Klammern gefaát sein. Wenn der Parser in der Eingabe auf ein Wort st”át, das weder in einem Objektnamen noch in einer Grammatik auftaucht, gibt er die Meldung "Ich verstehe das Wort 'xxx' nicht" aus. >> held - ein Substantiv, das sich auf ein Objekt im Inventar des Spielers bezieht. Der Scope (s. 5.7) dieses Objekts muá also dem Wert held_sd entsprechen. >> noun - ein Substantiv, das sich auf irgendein beliebiges Objekt in der Spielwelt bezieht. >> npc - ein Substantiv, das sich auf einen NPC bezieht, der sich mit dem Spieler in einem Raum befindet. >> number - ein Numeral zwischen zwei und zehn oder eine Nummer zwischen 0 und 32767. Beachten Sie, daá fr Mengenangaben nur Zahlen bis 10 erlaubt sind. Gr”áere Zahlen k”nnen natrlich als Telefonnummern oder Geheimcodes verwendet werden. >> reachable - ein Substantiv, das sich auf ein Objekt mit dem Scope reachable_sd bezieht. >> routine - ein Substantiv, das sich auf ein Objekt bezieht, fr das die zu berschreibende Methode TGame.MyScope(_id:Word;_action:Byte):BOOLEAN den Wert True liefert. In dieser Methode k”nnen Sie bestimmen, ob das Objekt mit der ID _id das Ereignis _action ausfhren kann. Eines der Verben, mit dem der Spieler sich auf das Pferd setzen kann, ist z.B. wie folgt definiert: AddVerb('steige;steig','[auf]+reachable;[aufs]+reachable',enter_ev); Wenn Sie sich ReadLib anschauen, werden Sie sehen, daá unterschiedliche Verben gleiche Grammatiken haben und auch das gleiche Ereignis ausl”sen k”nnen. Umgekehrt k”nnen gleiche Verben auch zu unterschiedlichen Ereignissen fhren, wenn ihre Grammatiken sich unterscheiden. Einige Verben ben”tigen zwei Substantive (oder k”nnen wahlweise mit zwei Substantiven verwendet werden). Um ein Objekt in einen Beh„lter zu legen, kann der Spieler z.B. "lege das Schwert in den Rucksack" eingeben. Die Definition des entsprechenden Verbs sieht wie folgt aus: AddVerb('lege;wirf;werfe;schmeiá','held+[in]+reachable',drop_ev); Wenn eine Grammatik zwei Substantive enth„lt, wird das Ereignis grunds„tzlich von dem zum ersten Substantiv geh”renden Objekt ausgefhrt. Die Eingabe "lege x in y" fhrt also immer dazu, daá die Methode HandleEvent() des Objekts x das Ereignis drop_ev ausfhrt und das Objekt in den Beh„lter y legt. In einigen F„llen k”nnen die Substantive x und y jedoch vertauscht werden, ohne daá sich die Bedeutung der Eingabe ver„ndert. Ein Beispiel dafr sind die gleichbedeutenden Eingaben "verschlieáe die Truhe mit dem Schlssel" und "verschlieáe mit dem Schlssel die Truhe". In diesen F„llen mssen zwei Grammatiken, jeweils eine fr eine der beiden m”glichen Reihenfolgen, angegeben werden. Wenn in einer Grammatik das zweite Objekt das Ereignis ausfhren soll, so muá dem entsprechenden Token das Klammeraffenzeichen (@) vorangestellt werden: AddVerb('verschlieáe','[mit]+held+@reachable',lock_ev); AddVerb('verschlieáe','reachable+[mit]+@held',lock_ev); Bevor Sie in Ihrem Programm TGame.Run aufrufen, k”nnen Sie dem W”rterbuch mit AddVerb() eigene Verben, Grammatiken und Ereignisse hinzufgen. Ein ganz neues W”rterbuch k”nnen Sie anlegen, wenn Sie vor AddVerb() die von ReadLib angelegte Liste mit der Methode TGame.ClearDictionary l”schen. Alle Verben eines bestimmten Ereignisses k”nnen Sie mit der Methode TGame.DelVerb(_event : BYTE) l”schen. In Readlib werden Sie vielleicht die Himmelsrichtungen fr die Bewegung (go_ev) vermissen. Diese werden von Parse() unabh„ngig von der Verbenliste verarbeitet, da es mit dem System des W”rterbuchs nicht m”glich ist, zu bestimmen, in welche Richtung der Spieler sich bei dem Ereignis go_ev bewegt. 7.3 TEvent und TNoun -------------------- Wenn der Parser eine Eingabe erkannt hat, bergibt er der Methode HandleEvent() eine Recordvariable vom Typ TEvent (s. 6.1). Dieser Record enth„lt folgende Informationen: >> action:Byte - der Wert einer Ereigniskonstante. HandleEvent() kann hiermit feststellen, welches Ereignis ausgefhrt werden soll. >> exec:Byte - wenn das Ereignis von mehreren gleichartigen Objekten ausgefhrt wird (z.B. "nimm drei Mnzen"), gibt dieses Feld an, von wievielen Objekten es bereits ausgefhrt wurde. >> maxexec:Byte - bestimmt, von wievielen Objekten das Ereignis maximal ausgefhrt wird. >> return:Boolean - best„tigt die erfolgreiche Ausfhrung eines Ereignisses. Diesem Feld wird bei der Ausfhrung eines Ereignisses in HandleEvent() der Wert True zugewiesen. Wenn ein Ereignis von BeforeAction() unterbrochen wird, gengt eine Textausgabe, um die Variable replay (s. 6.2) auf True zu setzen und dem Parser eine erfolgreiche Ausfhrung zu signalisieren. >> who:PItem - ist immer gleich NIL, wenn das Ereignis vom Spieler ausgel”st wurde. Ansonsten zeigt who auf das als NPC dienende Objekt (s. 8.1). >> first:PNoun - ein Zeiger auf einen Record vom Typ TNoun, dieser enth„lt weitere Informationen ber das erste Substantiv der Eingabe. >> second:PNoun - ein Zeiger auf einen Record vom Typ TNoun, dieser enth„lt weitere Informationen ber das zweite Substantiv der Eingabe. >> data:Pointer - einen Zeiger auf weitere Daten, die fr die Ausfhrung des Ereignisses ben”tigt werden. Dieser Zeiger wird nur bei den Ereignissen go_ev und tell_ev (s. 8.1) ben”tigt, ansonsten ist er gleich NIL. Die Methode TGame.SearchNoun() wird von Parse() aufgerufen und sucht in der Eingabe nach einem Objektnamen. In ihr werden die Recordvariablen vom Typ TNoun ausgefllt, auf die first und second zeigen. TNoun enth„lt folgende Informationen ber die in der Eingabe vorkommenden Substantive: >> detect:Boolean - das Substantiv konnte einem Objekt zugeordnet werden, wenn dieses Feld True ist, ansonsten wird kein Ereignis ausgefhrt. >> adj:Boolean - das Substantiv enth„lt ein Adjektiv, wenn das Feld True ist. >> number:Integer - eine vom Spieler angegebene Menge. >> xroom:PRoom - ein Zeiger auf ein zum Substantiv geh”rendes Objekt vom Typ TRoom. >> xlink:PLink - ein Zeiger auf ein zum Substantiv geh”rendes Objekt vom Typ TLink. >> xitem:PItem - ein Zeiger auf ein zum Substantiv geh”rendes Objekt vom Typ TItem. Von den Zeigern xroom, xlink und xitem ist immer nur einer ungleich NIL: 7.4 Fehlermeldungen ------------------- Wenn bei der Analyse der Eingabe oder der Ausfhrung eines Ereignisses ein Problem auftritt, wird die Methode TGame.ParserError(n : BYTE; str : STRING; i : BYTE; VAR error : BOOLEAN); aufgerufen. Mit einer von 37 m”glichen Meldungen, informiert diese Methode den Spieler, daá seine Eingabe nicht ausgefhrt werden kann. M”gliche Grnde fr den Aufruf von ParserError() sind z.B. unbekannte W”rter in der Eingabe, der Versuch des Spielers, mit einem nicht erreichbaren Objekt zu hantieren oder eine bereits abgeschlossene Tr erneut zu verschlieáen. Die einzelnen Parameter haben folgende Bedeutung: >> n:Byte - ein Fehlercode zwischen 1 und 37. >> str:String - ein Wort aus der Eingabe oder der Name des Objekts, mit dem sich ein Ereignis nicht ausfhren l„át. Die Bedeutung dieses Strings h„ngt vom jeweiligen Fehlercode ab. >> i:Byte - Anzahl der vom fehlgeschlagenen Ereignis betroffenen Objekte. Der entsprechende Objektname wird abh„ngig von dieser Variable im Singular oder Plural ausgegeben. >> error:Boolean - ist ein Var-Wert und wird von ParserError() auf True gesetzt. Der Aufruf von ParserError() setzt in Parse() und HandleEvent() eine lokale Error-Variable auf True, was die weitere Analyse und Ausfhrung verhindert. Wenn Sie eine der Meldungen von ParserError() „ndern oder weitere Meldungen hinzufgen m”chten, mssen Sie diese Methode berschreiben, die gewnschte Meldung abfangen und fr andere Fehlercodes wieder TGame.ParserError() aufrufen. Wenn Sie von TGame den Objekttyp TnGame abgeleitet haben, kann das so aussehen: PROCEDURE TnGame.ParserError(n : BYTE; str : STRING; i : BYTE; VAR error : BOOLEAN); BEGIN CASE n OF 5 : Print('Ihre Meldung\n'); ELSE TGame.ParserError(n : BYTE; str : STRING; i : BYTE; VAR error : BOOLEAN); END; END; 7.5 Metaverben -------------- Metaverben beeinflussen einige von der Spielhandlung unabh„ngige Optionen und verbrauchen keine Spielzeit. Sie beziehen sich nicht auf Objekte in der Spielwelt. Daher werden sie nicht von einer HandleEvent-Methode ausgefhrt, sondern fhren zum Aufruf verschiedener Methoden von TGame. Bevor der Parser ein Metaverb ausfhrt, wird die Methode TGame.MetaVerb(mv : BYTE) : BOOLEAN; VIRTUAL; mit dem Wert des entsprechenden Ereignisses aufgerufen, deren Rckgabewert ber die weitere Bearbeitung des Ereignisses entscheidet. Wenn Sie MetaVerb() berschreiben, k”nnen Sie also die Ausfhrung von Metaverben beeinflussen. Um eine Verwechslung mit anderen Verben zu verhindern, muá den Metaverben das Filezeichen (#) vorangestellt werden. Folgende Metaverben stehen zur Verfgung: >> #beschreibung - wechselt zwischen ausfhrlichen und kurzen Beschreibungen bereits bekannter R„umen, „ndert also die bei der Initialisierung des Spiels getroffene Voreinstellung. Kann mit #b abgekrzt werden. >> #ende - beendet das Spiel nach einer Sicherheitsabfrage. >> #inventar - zeigt das Inventar des Spielers an. Im Debugmodus werden noch die Anzahl der Objekte im Inventar und das Gesamtgewicht des Spielers angezeigt. Kann mit #inv und #i abgekrzt werden. >> #karte - zeigt eine Karte der bereits bekannten R„ume an. Im Debugmodus werden alle R„ume angezeigt, egal ob sie bereits besucht wurden. Der aktuelle Raum erscheint dabei immer in der Mitte der Karte, deren Gr”áe von der Zahl der Bildschirmzeilen und Spalten begrenzt wird. >> #laden - l„dt einen zuvor gespeicherten Spielstand. Wurden bereits mehrere Spielst„nde gespeichert, wird ein Men mit den gespeicherten Spielst„nden angezeigt, von denen einer ausgew„hlt werden kann. >> #neu - stellt den Ausgangszustand des Spiels wieder her. >> #punkte - zeigt Punkte- und Zugzahl des Spielers an. Die hierbei aufgerufene Methode TPlayer.RankStr kann berschrieben werden, um zus„tzlich zu der Punktzahl einen sich hieraus ergebenden Status des Spielers anzuzeigen. >> #status - schaltet die Statuszeile ein und aus. >> #sichern - speichert einen Spielstand. Nach Eingabe dieses Kommandos wird der Spieler nach einem Namen fr den Spielstand gefragt. 7.6 Debugkommandos ------------------ Vier Debugkommandos sollen Ihnen helfen, šberblick ber die Objekte der Spielwelt zu behalten. Debugkommandos beeinflussen wie die Metaverben nicht das Spielgeschehen, beziehen sich aber auf einzelne Objekte und werden daher von einer HandleEvent-Methode ausgefhrt. Debugkommandos mssen mit einem Punkt beginnen und werden vom Parser nur im Debugmodus (s. 4.6) verstanden. Folgende Debugkommandos stehen zur Verfgung: >> .browse - zeigt die Eigenschaften (Namen, Attribute, Position etc.) einzelner Objekte an. Die Eigenschaften eines Objekts werden von der Methode View ausgegeben, die jeder Nachfolger von TBasic besitzt. Mit den Tasten CursorOben/CursorUnten k”nnen Sie alle Objekte anzeigen lassen, mit ESC beenden Sie die Ausgabe. >> .dec - gibt alle Deklinationen des angegebenen Objektnamens aus. Sie k”nnen so prfen, ob Sie die Sonderzeichen fr die Deklination (s. 4.4) richtig angegeben haben. >> .list - zeigt die gleichen Informationen wie browse, jedoch nur fr das angegebene Objekt. >> .trace - schaltet die Anzeige des Namens und des Scopes eines Objekts an oder aus. Diese Anzeige erfolgt nach jeder Eingabe, so daá die Ver„nderung des Scopes im Spiel beobachtet werden kann. Wenn Sie die Methode Inspect, die alle Nachfahren von TBasic besitzen, berschreiben, k”nnen Sie auch die Ver„nderung anderer Eigenschaften eines Objekts verfolgen. ***************************************************************************** VIII Verschiedenes ***************************************************************************** 8.1 Nichtspielercharaktere -------------------------- Kehren wir noch einmal zu unserem Mini-Adventure zurck. Damit es dem Spieler in dem Zwergengrab nicht zu langweilig wird, soll ihm ein Troll Gesellschaft leisten. Der Troll soll ein Nichtspielercharakter (kurz NPC) sein. Der Spieler kann mit einem NPC reden und ihm Anweisungen geben, die der NPC ausfhrt. NPCs werden von Objekten des Typs TItem dargestellt. Um unserem Adventure einen Troll hinzuzufgen, muá demo5.pas also zun„chst um die Zeilen troll_id = 26; NEW(item,Init(troll_id,'+Troll#e',Adr(cave_id),t,1,5)); erweitert werden. Jedes Objekt vom Typ TItem ist entweder unbelebt, lebendig oder tot. Als NPCs dienende Objekte sind lebendig oder tot, alle anderen Objekte vom Typ TItem sind immer unbelebt. Diesen Zust„nden entsprechen die Auspr„gungen des in tmain.pas deklarierten Variablentyps TMatter: inanimate, dead und alive. Jedes Objekt wird automatisch mit dem Zustand inanimate initialisiert. Mit der Methode TItem.SetMatter(state : Tmatter) l„át sich der Zustand eines Objekts „ndern, mit TItem.GetMatter : TMatter l„át er sich abfragen. Ein "lebendes" Objekt kann nicht automatisch angesprochen werden. Damit der Spieler den Troll auch anreden kann, muá noch das Attribut talkable_at gesetzt werden. Folgende Zeilen machen aus dem eben erzeugten Objekt einen NPC: WITH item^ DO BEGIN SetMatter(alive); SetAttrib(talkable_at); AddText('Berichte ber Trolle mit ihren langen, scharfen Fangz„hnen '+ 'und Klauen haben Sie bisher nur in Tavernen geh”rt und nie '+ 'ernst genommen. Jetzt sollten Sie Ihre Meinung „ndern.\n'); END; Um einen NPC anzusprechen, muá der Spieler dessen Namen gefolgt von einem Komma, einem Leerzeichen und einem Text in doppelten Anfhrungszeichen eingeben. Starten Sie jetzt einmal demo6.pas und gehen in die H”hle: H™HLE Sie befinden sich in der kleinen H”hle im Sdwesten der Grabkammer. Diese H”hle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab schufen. Die Wand der Grabkammer wurde fr die H”hle durchbrochen und die W„nde der H”hle sind mit scheuálichen Symbolen bers„t, die sich stark von den Schriftzeichen der Zwerge auf dem Sarkophag unterscheiden. Ein Schlssel. Ein Troll. >troll, "wer sind sie?" Der Troll h”rt Ihnen nicht zu. Die Meldung "x h”rt Ihnen nicht zu" ist die Standardantwort auf jede Anrede. Um eine sinnvollere Reaktion des NPC hervorzurufen, mssen Sie die Methode TItem.BeforeAction() berschreiben und das Ereignis tell_ev abfangen. Den Objekttyp TnItem haben wir bereits von TItem abgeleitet, so daá wir seiner Deklaration nur noch die Zeile FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; hinzufgen mssen, um folgende Reaktion des Trolls auf eine Anrede bestimmen zu k”nnen: FUNCTION TnItem.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN CASE action OF tell_ev : BEGIN ok:=f; IF POS('zwerg',STRING(data^))>0 THEN Print('Der Troll sagt: "Die Zwergenknochen sind mir '+ 'ausgegangen, du kommst genau richtig..."\n') ELSE Print('Der Troll leckt nur seine Z„hne und '+ 'schaut Sie mit einem sehr beunruhigenden '+ 'Blick an.\n'); END; END; END; BeforeAction:=ok; END; Um den String mit der w”rtlichen Rede des Spielers zu erreichen, mssen Sie die Pointer-Variable data des Records event in einen String umwandeln. In unserem Fall h„ngt die Antwort des Trolls davon ab, ob in der Anrede des Spielers der Teilstring "zwerg" auftaucht. Diese Typumwandlung funktioniert nur bei dem Ereignis tell_ev. Beachten Sie, daá wenn in Ihrem Spiel mehrere NPCs auftauchen, Sie erst mit der Methode GetID feststellen mssen, welcher NPC angesprochen wurde. Bevor Sie den Spieler mit seinem Schwert den Troll meucheln lassen, schauen Sie sich noch an, wie NPCs Befehle ausfhren k”nnen. Um einem NPC ein Kommando zu geben, muá der Spieler nur den Namen des NPC gefolgt von einem Komma, einem Leerzeichen und der gewnschten Anweisung eingeben: H™HLE Sie befinden sich in der kleinen H”hle im Sdwesten der Grabkammer. Diese H”hle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab schufen. Die Wand der Grabkammer wurde fr die H”hle durchbrochen und die W„nde der H”hle sind mit scheuálichen Symbolen bers„t, die sich stark von den Schriftzeichen der Zwerge auf dem Sarkophag unterscheiden. Ein Troll. Ein Schlssel. >troll, nimm den schlssel Der Troll nimmt einen Schlssel. >troll, gib mir den schlssel Der Troll reicht Ihnen einen Schlssel. >gib den schlssel dem troll Sie geben dem Troll einen Schlssel. >troll, lege den schlssel weg Der Troll legt einen Schlssel weg. Der Variable who vom Typ PItem im Record event k”nnen Sie entnehmen, ob ein Ereignis vom Spieler oder von einem NPC ausgel”st wurde. Wurde ein Ereignis vom Spieler ausgel”st, ist dieser Zeiger gleich NIL, ansonsten zeigt er auf den handelnden NPC. Wenn der Troll fr die Anweisungen des Spielers taub sein soll, k”nnen Sie die obige Methode wie folgt erweitern: FUNCTION TnItem.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN IF who<>NIL THEN BEGIN ok:=f; Print('Der Troll denkt nicht daran, Ihre Befehle auszufhren.\n'); END; CASE action OF ... END; END; BeforeAction:=ok; END; Der Versuch, den Troll zu erschlagen, fhrt zu der wenig befriedigenden Meldung "Sie sind ein friedliebender Spieler" (bzw. "Spielerin", dies ist die einzige Stelle, an der Textopia das Spielergeschlecht bercksichtigt). Das ist natrlich nur eine Standardantwort, die Sie in BeforeAction() „ndern k”nnen. Um es dem Spieler nicht zu einfach zu machen, t”tet nur jeder zweite Schwertstreich den Troll: FUNCTION TnItem.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN IF who<>NIL THEN BEGIN ... END; CASE action OF tell_ev : BEGIN ... END; kill_ev : BEGIN ok:=f; IF RANDOM(2)=0 THEN BEGIN SetMatter(dead); Print('Mit einem schrecklichen Schrei stirbt der '+ 'Troll, als Ihr Schwert ihn trifft.\n'); player^.IncScores(5); END ELSE Print('Sie haben noch nie ein Schwert in der '+ 'Hand gehabt, stimmts?\n'); END; END; END; BeforeAction:=ok; END; Der Spieler kann jetzt den Troll t”ten, einen Schlssel finden und die Schatzkammer ausr„umen (wenn Sie da Sch„tze reinlegen). Fr die Bew„ltigung bestimmter Aufgaben k”nnen Sie dem Spieler Punkte geben. Benutzen Sie hierfr die Methode TPlayer.IncScores(ds : WORD) Mit der Methode TPlayer.GetScores : WORD k”nnen Sie die aktuelle Punktzahl abfragen. Das Ereignis kill_ev k”nnen Sie mit dem Programm demo7.pas ausprobieren. 8.2 Leben, Tod und Sieg des Spielers ------------------------------------ In dem Mini-Adventure k”nnen Sie dem Troll auch die Chance geben, den Spieler zu t”ten. Wenn es Ihnen gef„llt, k”nnen Sie das Alter-Ego des Spielers natrlich auch auf andere Weise umkommen lassen. Der Spieler verfgt ber einen Status, der die Werte der Konstanten alive_ps, dead_ps und victory_ps annehmen kann. Bei Spielbeginn bekommt der Spielerstatus automatisch den Wert alive_ps zugeordnet. Der Dialog zwischen Spieler und Parser endet, wenn der Spielerstatus den Wert dead_ps oder victory_ps annimmt. Den Spielerstatus k”nnen Sie mit der Methode TPlayer.SetState(s : BYTE) „ndern. T”ten Sie den Spieler mit SetState(dead_ps), so wird die Methode TPlayer.AfterLife aufgerufen, die normalerweise die erreichte Punktzahl ausgibt. Hat der Spieler hingegen das finale R„tsel gel”st oder eine bestimmte Punktzahl erreicht, k”nnen Sie das Spiel mit SetState(victory_ps) beenden. Hiernach wird die Methode TPlayer.Victory aufgerufen, die normalerweise ebenfalls die erreichte Punktzahl ausgibt. Beide Methoden k”nnen Sie berschreiben, um einen Nachruf oder Glckwnsche auszugeben. Nach Beendigung dieser Methoden kann der Spieler das Spiel erneut beginnen, einen gespeicherten Spielstand laden oder das Programm ganz beenden. 8.3 Hintergrundprozesse, Zeitschalter und Spielzeit --------------------------------------------------- Ein Hintergrundprozeá, auch D„mon genannt, ist eine Methode mit beliebigem Programmcode, die jedem Objekt vom Typ TRoom, TItem und TLink zugeordnet werden kann und nach jeder Spielrunde einmal ausgefhrt wird. Ein Hintergrundprozeá wird mit der Methode StartDaemon gestartet. Die Methode StopDaemon schaltet ihn wieder aus. Ist ein solcher D„mon einmal aktiv, wird nach jeder Spielrunde die Methode RunDaemon; VIRTUAL; aufgerufen, die Sie berschreiben mssen, damit sie etwas tut. In dieser Methode k”nnen Sie z.B. NPCs von Raum zu Raum bewegen (s. 5.1) oder berprfen, ob eine bestimmte Spielsituation eingetreten ist, auf die irgendein Objekt reagieren soll. Im Gegensatz zu Hintergrundprozessen fhren Zeitschalter nicht nach jeder Spielrunde zur Ausfhrung einer Routine, sondern erst nach Ablauf einer bestimmten Anzahl von Runden. Gestartet wird ein Zeitschalter (oder Timer) mit der Methode StartTimer(rounds : BYTE) Ihr mssen Sie die Zahl der Runden bergeben, nach deren Ablauf die Methode TimeOut; VIRTUAL; aufgerufen wird. TimeOut mssen Sie berschreiben, um zu bestimmen, was nach der angegeben Rundenzahl geschehen soll. Einen Timer k”nnen Sie mit StopTimer auch vorzeitig abschalten (wenn es dem Spieler z.B. gelingt, eine Zeitbombe zu entsch„rfen). Zeitschalter und D„mon eines Objekts drfen auch gleichzeitig aktiv sein, beachten Sie dann, daá RunDaemon immer vor TimeOut ausgefhrt wird. Textopia verfolgt auch die Spielzeit (die nichts mit der Systemzeit Ihres Computers zu tun hat), die Ihre D„monen und Zeitschalter auch bercksichtigen k”nnen. Die Spielzeit wird mit einer 24 Stunden Uhr ab Spielbeginn gemessen und kann mit der Methode TGame.GetTime(VAR hour,min : BYTE) ermittelt werden. Die Spielzeit wird zu Spielbeginn auf 0:00 Uhr gesetzt. Die Uhrzeit k”nnen Sie zu Spielbeginn oder irgendwann im Spiel mit der Methode TGame.SetTime(_time,_rate : WORD) „ndern. Der Parameter _time gibt die Uhrzeit als Zahl der seit Mitternacht vergangenen Minuten an und berechnet sich nach der Formel 60*Stunden+Minuten. Der Parameter _rate gibt an, wieviele Minuten w„hrend einer Runde vergehen. Mit einem D„mon k”nnen wir das Mini-Adventure noch mal erweitern. Bislang steht der Troll nur regungslos in der H”hle herum, jetzt soll er die Initiative ergreifen, sobald der Spieler die H”hle betritt. Der Spieler wird in der H”hle vom Troll angegriffen und get”tet, wenn er flchten will. Erschl„gt der Spieler den Troll, so findet er den Schlssel, der dem toten Troll aus dem Wams f„llt. Zun„chst muá statt der statischen Beschreibung der H”hle eine variable Beschreibung in TnRoom.MyText erfolgen. Hier wird festgestellt, ob der Troll noch lebt. Ist er tot, wird die normale Beschreibung der H”hle ausgegeben. Lebt der Troll noch, so wird dem Spieler der Angriff mitgeteilt und der D„mon des Trolls gestartet, um den Troll nun jede Runde den Spieler angreifen zu lassen. In TnRoom.BeforeAction() wird der Spieler an der Flucht gehindert und get”tet. Wenn der Spieler den Troll erschl„gt, was nur mit dem Schwert m”glich ist, wird der D„mon des Trolls abgeschaltet und der Schlssel in die H”hle gelegt. Der Deklaration von TnItem wird die Zeile PROCEDURE RunDaemon; VIRTUAL; hinzugefgt. Definiert wird die Methode wie folgt: PROCEDURE TnItem.RunDaemon; BEGIN CASE RANDOM(3) OF 0 : Print('Die furchtbaren Klauen des Trolls verfehlen Sie um '+ 'Haaresbreite.\n'); 1 : Print('Der Troll schl„gt Ihnen beinahe das Schwert aus der '+ 'Hand.\n'); 2 : Print('Sie k”nnen grade noch dem Schlag des Trolls ausweichen.\n'); END; END; Echten Schaden fgt der Troll dem Spieler also nur zu, wenn dieser zu flchten versucht. Auf eine Prfung, ob der Spieler tats„chlich ein Schwert h„lt, soll bei der zweiten Ausgabe einmal verzichtet werden. Beachten Sie, daá Sie in RunDaemon mit GetID feststellen mssen, welcher D„mon grade ausgefhrt wird, wenn mehrere Hintergrundprozesse aktiv sind. Eingeschaltet wird der D„mon des Trolls in TnRoom.MyText, wenn der Spieler die H”hle zum ersten Mal betritt: PROCEDURE TnRoom.MyText; BEGIN CASE GetID OF hill_id : BEGIN ... END; cave_id : IF PItem(Adr(troll_id))^.GetMatter=alive THEN BEGIN Print('Als Sie die H”hle betreten springt Ihnen mit '+ 'markerschtterndem Gebrll ein Troll entgegen!\n'); PItem(Adr(troll_id))^.StartDaemon; END ELSE BEGIN Print('Sie befinden sich in der kleinen H”hle im '+ 'Sdwesten der Grabkammer. Diese H”hle '+ 'wurde offensichtlich nicht von den Zwergen '+ 'angelegt, als sie das Grab schufen. Die '+ 'Wand der Grabkammer wurde fr die H”hle '+ 'durchbrochen und die W„nde '); Print('der H”hle sind mit scheuálichen Symbolen '+ 'bers„t, die sich stark von den '+ 'Schriftzeichen der Zwerge auf dem '+ 'Sarkophag unterscheiden.\n'); END; END; END; Bei der Initialisierung des Troll wurde der Parameter show auf False gesetzt, damit der Troll nach dem ersten Angriff nicht noch mal erw„hnt wird. Erst wenn er stirbt, wird seine Leiche mit ListMe(True) wieder in die automatische Raumbeschreibung aufgenommen. Wenn der D„mon nun aktiv ist, prfen wir, ob der Spieler die H”hle verlassen will und der Troll noch lebt: FUNCTION TnRoom.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN CASE action OF go_ev : IF ToDir=down THEN BEGIN ... END ELSE IF (ToDir=northeast) AND (PItem(Adr(troll_id))^.GetMatter=alive) THEN BEGIN Print('Sie spren kurz eine Klaue im Nacken, '+ 'dann wird es um Sie dunkel...\n'); player^.SetState(dead_ps); END; END; END; BeforeAction:=ok; END; Schlieálich soll der Spieler den Troll mit dem Schwert t”ten, dessen D„mon damit ausschalten und einen Schlssel finden: FUNCTION TnItem.BeforeAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN IF who<>NIL THEN BEGIN ... END; CASE action OF tell_ev : BEGIN ... END; kill_ev : BEGIN ok:=f; IF PItem(Adr(sword_id))^.GetLocation=NIL THEN BEGIN IF RANDOM(2)=0 THEN BEGIN SetMatter(dead); ListMe(t); StopDaemon; Print('Mit einem schrecklichen Schrei stirbt der '+ 'Troll, als Ihr Schwert ihn trifft. Als er zu '+ 'Boden strzt, f„llt ein Schlssel aus seinem '+ 'Wams.\n'); PItem(Adr(key_id))^.MoveItemTo(Adr(cave_id)); player^.IncScores(5); END ELSE Print('Sie haben noch nie ein Schwert in der '+ 'Hand gehabt, stimmts?\n'); END ELSE Print('Mit bloáen H„nden??\n'); END; END; END; BeforeAction:=ok; END; 8.4 Licht und Dunkel -------------------- R„ume werden von Textopia automatisch beleuchtet, so daá der Spieler in ihnen immer sehen kann. Mit der Methode TRoom.SetLight(_light : BOOLEAN) k”nnen Sie die automatische Beleuchtung aus- und wieder einschalten. Mit der Methode TRoom.HasLight : BOOLEAN k”nnen Sie feststellen, ob der Spieler in einem Raum sehen kann. Wenn der Spieler ein Item mit dem Attribut shining_at bei sich tr„gt, kann er in jedem Raum sehen. Befindet der Spieler sich in einem dunklen Raum, so wird in der Statuszeile statt dem Raumnamen das Wort "Finsternis" ausgegeben und die Raumbeschreibung erfolgt ber die Methode TRoom.MyDarkness, die Sie berschreiben mssen, um dem Spieler mitzuteilen, was bzw. was er nicht sehen kann. Bis auf das Kommando "schau" bleiben alle Aktionen des Spielers von der Dunkelheit unbeeinfluát, solange Sie das fehlende Licht nicht in irgendeiner berschriebenen Methode bercksichtigen. In der letzten Version unseres Mini-Adventures (demo9.pas) statten wir den Spieler jetzt mit einer Fackel aus und schalten das Licht im Grab ab, wenn das Tor von innen geschlossen wird. Zun„chst wird die Fackel erzeugt, dazu wird im Abschnitt Const die Zeile torch_id = 27; hinzugefgt. Da zum Anznden und L”schen der Fackel zwei neue Verben und Ereignisse gebraucht werden, k”nnen hier auch gleich die neuen Ereigniskonstanten deklariert werden: fireon_ev = 140; fireoff_ev = 141; Jetzt initialisieren wir eine nicht brennende Fackel und legen Sie in den Rucksack: NEW(item,Init(torch_id,'-Fackel#n',Adr(rucksack_id),t,1,0)); item^.SetAttrib(takeable_at,t); Nach der Initialisierung des Spiels mssen die neuen Verben "anznden" und "l”schen" dem W”rterbuch bekanntgemacht werden: AddVerb('znde;znd','held+[an]',fireon_ev); AddVerb('l”sch;l”sche;mache;mach','held;held+[aus]',fireoff_ev); Die neuen Verben werden von TItem.HandleEvent() nicht bercksichtigt und k”nnen daher nicht in TnItem.BeforeAction() abgehandelt werden. Daher berschreiben wir HandleEvent() fr die neuen Ereignisse und geben via Else alle anderen Ereignisse an den Vorg„nger weiter. Nachdem die Zeile PROCEDURE TnItem.HandleEvent(VAR event : TEvent); VIRTUAL; der Definition von TnItem hinzugefgt wurde, kann jetzt der Code fr die Fackel geschrieben werden: PROCEDURE TnItem.HandleEvent(VAR event : TEvent); BEGIN WITH event DO BEGIN CASE action OF fireon_ev : IF GetID=torch_id THEN BEGIN IF NOT(Has(shining_at)) THEN BEGIN IF player^.GetLocation^.GetID IN [grave_id,cave_id,chamber_id] THEN BEGIN PnRoom(Adr(grave_id))^.SetLight(t); PnRoom(Adr(cave_id))^.SetLight(t); PnRoom(Adr(chamber_id))^.SetLight(t); END; SetAttrib(shining_at,t); return:=t; END ELSE Print('Die Fackel brennt bereits.\n'); END ELSE Print('Das geht nicht.\n'); fireoff_ev : IF GetID=torch_id THEN BEGIN IF Has(shining_at) THEN BEGIN IF (player^.GetLocation^.GetID IN [grave_id,cave_id,chamber_id]) AND (PnRoom(Adr(grave_id))^.IsGate(up)=closed) THEN BEGIN PnRoom(Adr(grave_id))^.SetLight(f); PnRoom(Adr(cave_id))^.SetLight(f); PnRoom(Adr(chamber_id))^.SetLight(f); END; SetAttrib(shining_at,f); return:=t; END ELSE Print('Die Fackel ist bereits aus.\n'); END ELSE Print('Das geht nicht.\n'); ELSE TItem.HandleEvent(event); END; END; END; Bevor die Fackel ein- und ausgeschaltet wird, wird ihr aktueller Status und die Position des Spielers berprft, dieser muá sich irgendwo im Grab befinden. Wenn die Fackel gel”scht wird, wird die Beleuchtung nur abgeschaltet, wenn das Tor geschlossen ist und kein Sonnenlicht ins Grab dringt. Die auf True gesetzte Variable return teilt dem Parser die erfolgreiche Ausfhrung der neuen Ereignisse mit. Die Ereignisse open_ev und close_ev werden von TLock behandelt. Hiervon wird ein Nachfolger ben”tigt, um die Beleuchtung im Grab ein- und auszuschalten, wenn der Spieler das Tor ”ffnet und schlieát. šberschrieben werden mssen die Methoden BeforeAction() und AfterAction(): TnLock = OBJECT(TLock) FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; END; PnLock = ^TnLock; Entsprechend muá der generische Zeiger lock nun vom Typ PnLock sein: lock : PnLock; Das Tor wird von TnLock wie folgt behandelt: FUNCTION TnLock.BeforeAction(event : TEvent) : BOOLEAN; BEGIN WITH event DO BEGIN CASE action OF close_ev : IF (GetOwner=Adr(link2_id)) AND (player^.GetLocation=Adr(grave_id)) AND (NOT(PItem(Adr(torch_id))^.Has(shining_at))) THEN BEGIN PnRoom(Adr(grave_id))^.SetLight(f); PnRoom(Adr(cave_id))^.SetLight(f); PnRoom(Adr(chamber_id))^.SetLight(f); Print('Es wird dunkel im Grab...\n'); END; open_ev : IF (GetOwner=Adr(link2_id)) AND (player^.GetLocation=Adr(grave_id)) THEN BEGIN PnRoom(Adr(grave_id))^.SetLight(t); PnRoom(Adr(cave_id))^.SetLight(t); PnRoom(Adr(chamber_id))^.SetLight(t); Print('Sonnenlich str”mt wieder ins Grab.\n'); END; END; END; BeforeAction:=t; END; Da Schl”sser keine ID besitzen, muá hier mit GetOwner geprft werden, ob es sich um das der Torverbindung zugeordnete Schloá handelt. In BeforeAction() wird bereits ein Text ausgegeben, wenn der Spieler den Zustand des Tors ver„ndert, daher sollte AfterAction() weitere Meldungen des Parsers ("Sie ”ffnen/schlieáen das Tor") verhindern: FUNCTION TnLock.AfterAction(event : TEvent) : BOOLEAN; VAR ok : BOOLEAN; BEGIN ok:=t; WITH event DO BEGIN CASE action OF close_ev : IF (GetOwner=Adr(link2_id)) AND (player^.GetLocation=Adr(grave_id)) THEN ok:=f; open_ev : IF (GetOwner=Adr(link2_id)) AND (player^.GetLocation=Adr(grave_id)) THEN ok:=f; END; END; AfterAction:=ok; END; Schlieálich muá noch TnRoom.MyDarkness berschrieben werden. Fgen Sie den Methodenkopf in die Definition von TnRoom ein und schreiben Sie etwas wie PROCEDURE TnRoom.MyDarkness; BEGIN Print('Sie k”nnen hier kaum etwas sehen...\n'); END; Wenn Sie jetzt demo9.pas starten, sollte der folgende Dialog m”glich sein: WALD (auf einem Pferd) Fast undurchdringliches Unterholz l„át Sie nur mhsam weiterkommen. Weiter westlich scheinen die B„ume weniger dicht beieinander zu stehen. >w LICHTUNG (auf einem Pferd) Ein kleiner Hgel erhebt sich in der Mitte einer Lichtung ber das Grab eines Zwergenk”nigs. Ein steinernes Tor in der Nordseite versperrt Ihnen den Weg ins Innere. >steig vom pferd Ok >”ffne das tor Sie ”ffnen das Tor. >gehe nach unten GRABKAMMER In der Mitte der engen Grabkammer steht ein Sarkophag. In der Ostwand befindet sich eine schwere, mit Eisen beschlagene Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine hinter der Grabkammer liegende H”hle. >”ffne den rucksack Sie ”ffnen den Rucksack. >nimm die fackel aus dem rucksack Sie nehmen eine Fackel. >znde die fackel an Ok >schlieáe das tor Ok >schau GRABKAMMER In der Mitte der engen Grabkammer steht ein Sarkophag. In der Ostwand befindet sich eine schwere, mit Eisen beschlagene Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine hinter der Grabkammer liegende H”hle. >l”sche die fackel Ok >schau Finsternis Sie k”nnen hier kaum etwas sehen... >znde die fackel an Ok >sw H™HLE Als Sie die H”hle betreten springt Ihnen mit markerschtterndem Gebrll ein Troll entgegen! >t”te den troll mit dem schwert Mit einem schrecklichen Schrei stirbt der Troll, als Ihr Schwert ihn trifft. Als er zu Boden strzt, f„llt ein Schlssel aus seinem Wams. >nimm den schlssel Sie nehmen einen Schlssel. >no GRABKAMMER In der Mitte der engen Grabkammer steht ein Sarkophag. In der Ostwand befindet sich eine schwere, mit Eisen beschlagene Eichentr. Im Sdwesten fhrt ein schmaler Gang weiter in eine hinter der Grabkammer liegende H”hle. >”ffne die tr (mit dem Schlssel) Sie ”ffnen die Eichentr. >o SCHATZKAMMER In dieser Kammer lagerten die Grabbeigaben des K”nigs. >#ende šbrigens: Taschenlampen lassen sich leichter programmieren als Fackeln, weil sie ein- und ausgeschaltet werden und daher keine neuen Verben ben”tigen. Ich fand aber, daá Fackeln sich besser zu Demonstrationszwecken eignen. Das Mini-Adventure ist natrlich noch nicht perfekt. Fgen Sie ihm weitere R„ume, NPCs und vor allem Aufgaben fr den Spieler hinzu, die das Spiel interessanter machen. 8.5 Die Spielstandverwaltung ---------------------------- Mit den Metaverben #sichern und #laden kann der Spieler einen Spielstand speichern bzw. laden (s. 7.5). Wenn Sie in eine neue Objektdefinition auch neue Variablen aufnehmen, sollten Sie wissen, wie deren Werte dabei gesichert und wiederhergestellt werden. Die Verben #sichern und #laden fhren zum Aufruf der Methoden TGame.UserSave bzw. TGame.UserLoad, in der vom Spieler der Name des betreffenden Spielstands angefordert wird. Die Namen und Anzahl der gespeicherten Spielst„nde werden in der Datei *.sav gespeichert, was das Auslesen aus dem Plattenverzeichnis erspart. Die einzelnen Spielst„nde werden in untypisierten Dateien mit der Blockl„nge 1 und der Endung *.ttx gespeichert, wobei das x fr die Nummer eines Spielstands in der Datei *.sav steht. Wenn ein Spielstand gespeichert werden soll, legt die Methode TGame.SaveGame() eine Datei *.ttx an und ruft die Methode Store(h : FILE); VIRTUAL; auf, die alle Nachfolger von TBasic besitzen. Mit dieser Methode speichert nun jedes Objekt die Variablen, deren Werte sich im Spielverlauf „ndern k”nnen. Dabei werden alle Variablen als Byte-Wert codiert gespeichert. Ein untypisierter Dateityp wurde nur wegen eines inzwischen behobenen Bugs in FPC benutzt, eigentlich tut's auch ein File of Byte. Die Umwandlung der verschiedenen Variablentypen in Byte-Werte geschieht mit den Prozeduren WritePtr(VAR h : File; p : PBasic); WriteWord(VAR h : File; w : WORD); WriteBool(VAR h : File; v : Boolean); WriteDir(VAR h : File; d1 : TDir); Bytevariablen werden natrlich direkt mit BlockWrite() gespeichert. Der Pointer auf ein Objekt wird einfach als dessen ID gespeichert. Fr andere Datentypen mssen Sie eigene Umwandlungen programmieren. Umgekehrt kann jedes Objekt mit der Methode Load(h : FILE); VIRTUAL; einen gespeicherten Zustand wiederherstellen. Hierfr werden die Funktionen und Prozeduren ReadPtr(VAR h : File) : POINTER; ReadWord(VAR h : File; VAR w : WORD); ReadBool(VAR h : File; VAR v : Boolean); ReadDir(VAR h : File; VAR d1 : TDir); verwendet. Wenn Sie in einer Objektdefinition nun eine neue Variable definieren, die sich im Spielverlauf „ndern kann, so mssen Sie die Methoden Store() und Load() berschreiben, die neue Variable speichern bzw. laden und anschlieáend den Vorg„nger des Objekttyps aufrufen, der sich um alle weiteren Variablen kmmert. Achten Sie darauf, daá die Reihenfolge der Variablen in Store() und Load() gleich bleibt, sonst fhrt das Kommando #laden unweigerlich zu einem Absturz. 8.6 Statuszeile, Farben und Funktionstasten ------------------------------------------- Wenn der Spieler die Statuszeile nicht mit dem Kommando #status ausgeschaltet hat, wird sie immer dann neu geschrieben, wenn sich der aktuelle Raum, die Punktzahl oder die Zahl der Spielzge „ndert. Letzteres passiert nach jeder Runde, in der der Spieler kein Metaverb eingegeben hat. Den Text in der Statuszeile liefern die Methoden TPlayer.StatusBarLeftStr : STRING; VIRTUAL und TPlayer.StatusBarRightStr : STRING; VIRTUAL Links wird normalerweise der Name des aktuellen Raums ausgegeben, w„hrend rechts die Punktzahl und die Zahl der bisherigen Spielrunden von einem Schr„gstrich getrennt ausgegeben werden. Die Informationen hierber sind in TPlayer gespeichert, weshalb diese Methoden nicht zu TGame geh”ren. Sie k”nnen diese Methoden berschreiben, um beliebige andere Informationen in der Statuszeile auszugeben. Die Bildschirmfarben k”nnen Sie mit der in tio.pas definierten Prozedur SetColor(fc,bc : BYTE) einstellen. Dabei gibt fc die Text- und bc die Hintergrundfarbe an. Als Textfarben sind alle 16 in tio.pas deklarierten Farbkonstanten verfgbar, w„hrend fr die Hintergrundfarbe nur die Farben 0 (Black) bis 7 (Lightgray) verfgbar sind. Bei der Textausgabe k”nnen Sie mit den Sonderzeichen (s. 4.4) \h und \l die Farbintensit„t „ndern, das Zeichen \r vertauscht die Text- und Hintergrundfarben. Leider fhren nicht alle Farbkombinationen unter Windows, DOS und Linux zur selben Darstellung. Wenn Sie Ihr Spiel auf ein anderes System bertragen wollen, mssen Sie hier experimentieren. Mit den voreingestellten Farben gibt es keine Probleme. Um dem Spieler Tipparbeit abzunehmen, k”nnen Sie die Funktionstasten F1 bis F10 mit h„ufig ben”tigten Verben vorbelegen. Hierzu benutzen Sie die Prozedur SetFKey(n : BYTE; str : STRING) Der Parameter n gibt dabei die gewnschte Funktionstaste an. 8.7 Kommandozeilenoptionen -------------------------- Der Spieler kann das fertige Programm mit einigen Kommandozeilenoptionen aufrufen, um Voreinstellungen zu ver„ndern. Jede Option beginnt dabei mit einem Minuszeichen gefolgt von einem Buchstaben und einer Zahl () oder einem Schalter (). Schalter werden mit dem Pluszeichen ein- und mit dem Minuszeichen ausgeschaltet. Die Optionen sind: >> F - bestimmt die Textfarbe >> B - bestimmt die Hintergrundfarbe >> R - bestimmt die Zahl der Bildschirmzeilen. Diese Zahl muá am Monitor bereits eingestellt sein, Textopia „ndert keine Fenstergr”áe oder Bildschirmaufl”sung. Voreingestellt sind 25 Zeilen. >> C - bestimmt die Zahl der Zeichen pro Bildschirmzeile. Voreingestellt sind 80 Zeichen. >> S - bestimmt, ob das Spiel mit oder ohne Statuszeile gestartet wird. >> V - bestimmt, ob in bereits bekannten R„umen die ausfhrliche Raumbeschreibung oder nur der Raumname ausgegeben wird. >> U - bestimmt, ob die Eingabe in Groábuchstaben umgewandelt wird. Die Option -? gibt einen kurzen Hilfetext aus. Die Bercksichtigung der Kommandozeilenoptionen k”nnen Sie mit der Zeile useroption:=False; verhindern. 8.8 Umlaute ----------- DOS, Windows und Linux gebrauchen unterschiedliche Zeichens„tze. Daher entsteht bei der šbertragung von Programmen zwischen den Systemen das Problem, daá die deutschen Umlaute nicht als solche ausgegeben werden. Um dieses Problem zu umgehen, sind in tstring.pas die Char-Konstanten lae_kc, loe_kc, lue_kc und ss_kc fr die kleinen und uae_kc, uoe_kc, uue_kc fr die groáen Umlaute definiert. Die Werte der Konstanten sind dabei vom Compiler abh„ngig, so daá fr eine Konstante auf jedem System das richtige Zeichen ausgegeben wird. Wenn Sie Ihr Programm nur fr ein System schreiben, brauchen Sie sich um die Konstanten nicht zu kmmern und k”nnen in Ihren Texten die Umlaute direkt schreiben. Andernfalls haben Sie zwei M”glichkeiten, die Texte in Ihrem Spiel an unterschiedliche Zeichens„tze anzupassen. Sie k”nnen die Umlaute wie in den Textopia-Quelltexten gegen ihre entsprechenden Konstanten austauschen. Dadurch werden die Texte allerdings recht unleserlich. In allen Texten, die mit der Prozedur Print() ausgegeben werden, k”nnen Sie aber auch statt eines Umlauts einen Backlash gefolgt von einem Vokal eingeben. Print() tauscht diese Zeichenfolge dann automatisch gegen die Konstante des entsprechenden Umlauts aus (aus „ wird also „). Diese Ersetzung kann auch das Programm umlaut.pas fr Sie vornehmen. Mit dem Aufruf umlaut -a dateiname werden alle Umlaute gegen einen Backlash und ihren Vokal getauscht. Mit umlaut -e dateiname wird aus dem Backlash wieder ein normaler Umlaut. 8.9 Warnungen ------------- Wenn Sie bei der Initialisierung der Objekte einen Fehler machen, gibt Textopia in spitzen Klammern eine Warnung aus und fragt Sie, ob Sie das Programm fortsetzen m”chten. Folgende Warnungen sind m”glich: Keine Verbindung in xxx Sie haben in TRoom.IsGate eine Richtung angegeben, in der keine Verbindung existiert. Objekt xxx kann kein Schloá zugewiesen werden Nur Objekten vom Typ PLink und PItem k”nnen Schl”sser zugewiesen werden. xxx und yyy sind bereits verbunden Sie haben versucht, zwei R„ume doppelt zu verbinden. xxx kann nicht ein-/ausgeschaltet werden Die Methoden TItem.SwitchOn und TItem.SwitchOff funktionieren nur, wenn das Objekt das Attribut switchable_at hat. Objekt xxx kann nicht in yyy abgelegt werden Mit TItem.MoveItemTo() k”nnen Objekte nur in R„ume oder Beh„lter bewegt werden. Objekt xxx kann nicht bei Objekt yyy positioniert werden Im Konstruktor von TItem muá ein Raum oder ein Beh„lter als Position angegeben werden. Index xxx existiert nicht Es wurde auf einen ungltigen Index von TObjList zugegriffen. Ungltiger Spielerstatus Der Methode TPlayer.SetState() k”nnen nur die Konstanten dead_ps, alive_ps und victory_ps bergeben werden. Spieler kann nicht bei Objekt xxx positioniert werden TPlayer.MovePlayerTo() kann den Spieler nur in R„ume oder Objekte mit dem Attribut enterable_at versetzen. Spielerobjekt nicht initialisiert Eine Instanz von TPlayer muá vor TGame initialisiert werden. Unbekanntes Token xxx Sie haben sich in einer mit TGame.AddVerb() angegebenen Grammatik verschrieben. xxx wurde nicht bearbeitet Ein Ereignis wurde nicht verarbeitet. Es erfolgte keine Rckmeldung durch return oder replay. ***************************************************************************** IX Die Units ***************************************************************************** 9.1 Funktionen und Prozeduren von tstring.pas --------------------------------------------- Es folgt eine Liste der im Interface-Abschnitt deklarierten Datentypen, Funktionen und Prozeduren. Einige andere Routinen in tstring.pas dienen obskuren internen Zwecken und werden hier nicht aufgefhrt. *** Datentypen *** CONST {$IFNDEF fpc} { Umlaute unter Dos/Windows } lae_kc = #132; loe_kc = #148; lue_kc = #129; ss_kc = #225; uae_kc = #142; uoe_kc = #153; uue_kc = #154; {$ELSE} { Umlaute unter Linux } lae_kc = #228; loe_kc = #246; lue_kc = #252; ss_kc = #223; uae_kc = #196; uoe_kc = #214; uue_kc = #220; {$ENDIF} maxbuf = 10; { Max. Zwischenspeicher fuer Woerter } boolstr : ARRAY[BOOLEAN] OF STRING = ('Nein','Ja '); { Abkuerzungen fuer True/False } t = TRUE; f = FALSE; TYPE TAdress = (du,sie,ihr); { Anrede Spieler } TGender = (male,female); { Geschlecht Spieler/in } TCasus = (nom,acc,gen,dat); { Grammatische Faelle } TBuffer = ARRAY[1..maxbuf] OF STRING; *** Prozeduren und Funktionen *** PROCEDURE DelStr(VAR str : STRING; sub : STRING); L”scht alle Vorkommen von sub in str. FUNCTION LowCase(a : CHAR) : CHAR; Wandelt ein Zeichen in Kleinschrift. Bercksichtigt auch Umlaute. FUNCTION Lower(s1 : STRING) : STRING; Wandelt einen String in Kleinschrift. Bercksichtigt auch Umlaute. FUNCTION Noun(n1 : STRING; casus : TCasus; def : BOOLEAN; num : BYTE) : STRING; Dekliniert den Objeknamen n1 (s. 4.3). FUNCTION Numeral(n : BYTE) : STRING; Liefert ein Numeral fr eine Zahl zwischen 2 und 10. FUNCTION NumToStr(n : WORD) : STRING; Wandelt n in einen String. FUNCTION PerPron(adress : TAdress; casus : TCasus) : STRING; Liefert das zu Anrede und Kasus passende Personalpronomen (Sie, Er etc.). FUNCTION RandStr(line : STRING) : STRING; W„hlt zuf„llig einen der durch Semikolons getrennten Teilstrings von line aus und liefert ihn zurck. PROCEDURE SwpStr(VAR str : STRING; oldstr,newstr : STRING); Tauscht in str alle Vorkommen von oldstr gegen newstr. FUNCTION UpChar(a : CHAR) : CHAR; Wandelt ein Zeichen in Groáschrift. Bercksichtigt auch Umlaute. FUNCTION Upper(s1 : STRING) : STRING; Wandelt einen String in Groáschrift. Bercksichtigt auch Umlaute. 9.2 Funktionen und Prozeduren von tio.pas ----------------------------------------- Auch hier werden nur Datentypen, Prozeduren und Funktionen aufgefhrt, die nicht ausschlieálich fr interne Zwecke gedacht sind. *** Datentypen *** TYPE PChain = ^TChain; { Element der String-Liste } TChain = RECORD c : Char; next : PChain; END; {$IFDEF tp} PDiskText = ^TDiskText; { Stringverwaltung mit Textdatei. Nur fuer TP/Dos } TDiskText = OBJECT CONSTRUCTOR Init; FUNCTION HasText : BOOLEAN; PROCEDURE SetText(str : STRING; clear : BOOLEAN); PROCEDURE PrintText; FUNCTION GetText : STRING; DESTRUCTOR Done; PRIVATE start : LONGINT; { Startposition des Textes in Datei } END; {$ENDIF} PMemText = ^TMemText; { Stringverwaltung mit Liste } TMemText = OBJECT CONSTRUCTOR Init; FUNCTION HasText : BOOLEAN; PROCEDURE SetText(str : STRING; clear : BOOLEAN); PROCEDURE PrintText; FUNCTION GetText : STRING; DESTRUCTOR Done; PRIVATE pfirst : PChain; { Startadresse des Textes im Speicher } PROCEDURE DelStr; END; PMStr = ^TMStr; TMStr = RECORD line : STRING[132]; next : PMStr; END; PMap = ^TMap; { Nimmt Karte fuers Automapping auf } TMap = OBJECT start : PMStr; CONSTRUCTOR Init; PROCEDURE InsStr(x1,y1 : SHORTINT; str : STRING); PROCEDURE Show; DESTRUCTOR Done; END; CONST back_kc = #08; { Tastaturcodes } del_kc = #83; down_kc = #80; end_kc = #79; enter_kc = #13; esc_kc = #27; f1_kc = #59; f2_kc = #60; f3_kc = #61; f4_kc = #62; f5_kc = #63; f6_kc = #64; f7_kc = #65; f8_kc = #66; f9_kc = #67; f10_kc = #68; home_kc = #71; ins_kc = #82; left_kc = #75; pdown_kc = #81; pup_kc = #73; right_kc = #77; up_kc = #72; { Farben, da im Spiel keine CRT gebraucht wird } black = 0; blue = 1; green = 2; cyan = 3; red = 4; magenta = 5; brown = 6; lightgray = 7; darkgray = 8; lightblue = 9; lightgree = 10; lightcyan = 11; lightred = 12; lightmagenta = 13; yellow = 14; white = 15; VAR maxcol, { Bildschirmspalten } maxrow, { Bildschirmzeilen } row : BYTE; { aktuelle Zeile } replay, { Bestaetigung fuer Textausgabe } debug : BOOLEAN; { Debug-Modus ein/aus } *** Prozeduren und Funktionen *** PROCEDURE Print(line : STRING); Gibt line ohne Zeilenvorschub aus. Um einen Zeilenvorschub zu erzeugen, muá am Ende von line ein \n eingefgt werden. Die Textausgabe in Ihrem Spiel sollte immer ber Print() erfolgen. FUNCTION Question(str,chars : STRING) : CHAR; Gibt str aus und wartet auf einen Tastendruck, der ein in chars vorkommendes Zeichen liefern muá. Kann fr eine Menauswahl verwendet werden. PROCEDURE SetColor(fc,bc : BYTE); Setzt Text- und Hintergrundfarben, wobei fc die Text- und bc die Farbe des Hintergrunds angibt. PROCEDURE SetFKey(n : BYTE; str : STRING); Belegt die Funktionstaste n mit str. PROCEDURE SetWinTitle(title : STRING); Setzt unter Windows den Fenstertitel. Unter DOS und Linux wird diese Anweisung ignoriert. 9.3 Datentypen, Funktionen und Prozeduren von tmain.pas ------------------------------------------------------- Neben den Objekten TBasic, TItem, TRoom, TLink, TLock, TPlayer und TGame definiert tmain.pas auch die von den Objekten verwendeten Datentypen, die Funktion Adr() sowie die Prozeduren und Funktionen fr die Spielstandverwaltung. *** Datentypen *** TYPE PBasic = ^TBasic; { Zeiger auf Basisobjekt } PLink = ^TLink; { Zeiger auf Verbindung } PGame = ^TGame; { Zeiger auf Spielobjekt } PLib = ^TVerb; { Zeiger auf Eintrag im W”rterbuch } PItem = ^TItem; { Zeiger auf Gegenstand } PLock = ^TLock; { Zeiger auf Schloss fuer Tueren und Behaelter } PNoun = ^TNoun; { Zeiger auf Informationen ueber Eingabe } PPlayer = ^TPlayer; { Zeiger auf Spielerobjekt } PRoom = ^TRoom; { Zeiger auf Raum } TDir = (north,northeast, { Himmelsrichtungen } east,southeast, south,southwest, west,northwest, up,down, nowhere); T_Class = (room,link,item); { Bausteine der Spielwelt } TMatter = (inanimate,dead,alive); { moegliche Zustaende der Items } { TNoun enthaelt Informationen ueber die in der Spielereingabe ausgewaehlten Objekte } TNoun = RECORD detect, adj : BOOLEAN; number : INTEGER; xroom : PRoom; xlink : PLink; xitem : PItem; END; { Record fuer Nachricht an Objekte } TEvent = RECORD action, { Event-Konstante des Ereignisses } exec, { Wie oft ausgefuehrt? } maxexec : BYTE; { Wie oft maximal ausfuehren? } return : BOOLEAN; { erfolgreich ausgefuehrt? } who : PItem; { Akteur: Spieler oder NPC } first, { Empfaengerobjekt } second : PNoun; { Weiteres beteiligtes Objekt } data : POINTER; { Zeiger auf zusaetzliche Daten } END; { TWeight verwaltet Gewichte } TWeight = OBJECT CONSTRUCTOR Init(_owner : POINTER; _min,_max : BYTE); FUNCTION GetCont : BYTE; { Zahl enthaltener Objekte } FUNCTION GetSum : BYTE; { Gewicht } FUNCTION GetMax : BYTE; { Maximalgewicht } DESTRUCTOR Done; PRIVATE owner : POINTER; { Item oder Spieler } counter, { Anzahl enthaltener Items } wsum, { Eigengewicht + Gewicht von Items } wmax : BYTE; { Maximalgewicht } PROCEDURE Pick(x : POINTER; VAR error : BOOLEAN); PROCEDURE Drop(x : POINTER); END; { Spielerobjekt } TPlayer = OBJECT CONSTRUCTOR Init(_where : WORD; _adress : TAdress; _gender : TGender; _wmin,_wmax : BYTE); PROCEDURE AfterLife; VIRTUAL; FUNCTION GetAdress : TAdress; FUNCTION GetContainer : PItem; FUNCTION GetContent : BYTE; FUNCTION GetGender : TGender; FUNCTION GetLocation : PRoom; FUNCTION GetMaxWeight : BYTE; FUNCTION GetScores : WORD; FUNCTION GetMoves : WORD; FUNCTION GetState : BYTE; FUNCTION GetWeight : BYTE; FUNCTION HasPlayer(i : WORD) : BOOLEAN; PROCEDURE IncScores(ds : WORD); PROCEDURE Load(VAR h : File); VIRTUAL; PROCEDURE MovePlayerTo(where : PBasic); PROCEDURE SetState(s : BYTE); FUNCTION StatusBarLeftStr : STRING; VIRTUAL; FUNCTION StatusBarRightStr : STRING; VIRTUAL; PROCEDURE Store(VAR h : File); VIRTUAL; FUNCTION RankStr : STRING; VIRTUAL; PROCEDURE Victory; VIRTUAL; DESTRUCTOR Done; PRIVATE inside : PItem; { Zeiger auf Behaelter oder Fahrzeug } position : PRoom; { Zeiger auf aktuellen Raum } weight : TWeight; adress : TAdress; { Anrede fuer Spieler } gender : TGender; { Spielergeschlecht } moves, { Runden } scores : WORD; { Punkte } state : BYTE; { Status des Spielers } PROCEDURE Enter(_item : PItem); PROCEDURE IncMoves; PROCEDURE Inventory; PROCEDURE Leave; END; { Basisobjekt fuer Raeume, Durchgaenge und Items } TBasic = OBJECT name : TMemText; { Objektname als Listenobjekt } lock : PLock; { Schlosser fuer Links und Items } CONSTRUCTOR Init(_id : WORD; _name : STRING; _objclass : T_Class); PROCEDURE AddText(str : STRING); PROCEDURE DelText; FUNCTION GetClass : T_Class; FUNCTION GetCopies : BYTE; FUNCTION GetID : WORD; PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION HasDaemon : BOOLEAN; FUNCTION HasTimer : BOOLEAN; PROCEDURE Inspect; VIRTUAL; PROCEDURE Load(VAR h : File); VIRTUAL; PROCEDURE MyText; VIRTUAL; PROCEDURE RunDaemon; VIRTUAL; FUNCTION Scope : BYTE; VIRTUAL; PROCEDURE StartDaemon; PROCEDURE StopDaemon; PROCEDURE StartTimer(rounds : BYTE); PROCEDURE StopTimer; PROCEDURE Store(VAR h : File); VIRTUAL; PROCEDURE TimeOut; VIRTUAL; PROCEDURE View; VIRTUAL; DESTRUCTOR Done; VIRTUAL; PRIVATE _class : T_Class; { room, item oder link } id : WORD; { ID zur eindeutigen Identifikation } copies, { Anzahl erreichbarer Objekte mit gleichem Namen } counter : BYTE; { Zaehler fuer Timer } flag, { wichtig } daemon, { Daemon eingeschaltet? } timer, { Timer eingeschaltet? } tracing : BOOLEAN; { Objektzustand verfolgen? } {$IFDEF tp} objtext : PDiskText; { TP: Text in Datei speichern } {$ELSE} objtext : PMemText; { Delphi/FPC: Text in Liste speichern } {$ENDIF} PROCEDURE ObscureEvents; PROCEDURE RunTimer; PROCEDURE Reset; PROCEDURE TextOut; END; { TLock: Schloesser fuer Tueren und Behaelter } TState = (open,closed,locked); { Zustaende fuer Schloesser } TLock = OBJECT CONSTRUCTOR Init(_owner : PBasic; _openable, _closeable : BOOLEAN; _state : TState; _key : PItem); FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION GetKey : PItem; FUNCTION GetOwner : PBasic; FUNCTION GetState : TState; PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION HasKey(event : Tevent) : BOOLEAN; FUNCTION IsOpenable : BOOLEAN; FUNCTION IsCloseable : BOOLEAN; PROCEDURE Load(VAR h : File); VIRTUAL; PROCEDURE SetCloseable(s : BOOLEAN); PROCEDURE SetOpenable(s : BOOLEAN); PROCEDURE SetState(s : TState); PROCEDURE Store(VAR h : File); VIRTUAL; PROCEDURE View; VIRTUAL; DESTRUCTOR Done; VIRTUAL; PRIVATE key : PItem; { Instanz von TItem oder NIL } owner : PBasic; { Instanz von TItem oder TLink } openable, { kann das Schloss geoeffnet und } closeable : BOOLEAN; { geschlossen werden? } state : TState; { offen, geschlossen oder verschlossen } END; { TLink stellt eine Verbindung zwischen jeweils zwei Raeumen her } TLink = OBJECT(TBasic) r1,r2 : PRoom; CONSTRUCTOR Init(_id : WORD; _name : STRING; _from : WORD; _dirto : TDir; _to : WORD; _show : BOOLEAN); FUNCTION BeforeAction(VAR event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION AfterAction(VAR event : TEvent) : BOOLEAN; VIRTUAL; PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION HasAutoDescription : BOOLEAN; PROCEDURE Load(VAR h : File); VIRTUAL; FUNCTION Scope : BYTE; VIRTUAL; PROCEDURE Store(VAR h : File); VIRTUAL; PROCEDURE View; VIRTUAL; DESTRUCTOR Done; VIRTUAL; PRIVATE show : BOOLEAN; { Objekt in Raumbeschreibung erwaehnen? } END; { TRoom, der Objekttyp fuer alle Raeume } TRoom = OBJECT(TBasic) gate : ARRAY[north..down] OF PLink; CONSTRUCTOR Init(_id : WORD; _name : STRING); FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION FromDir : TDir; PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION HasLight : BOOLEAN; FUNCTION IsGate(d : TDir) : TState; PROCEDURE Load(VAR h : File); VIRTUAL; PROCEDURE MyDarkness; VIRTUAL; PROCEDURE SetLight(_light : BOOLEAN); PROCEDURE Store(VAR h : File); VIRTUAL; FUNCTION Scope : BYTE; VIRTUAL; FUNCTION ToDir : TDir; PROCEDURE View; VIRTUAL; DESTRUCTOR Done; VIRTUAL; PRIVATE light, { Kann Spieler hier etwas sehen? } explored : BOOLEAN; { Deja-vu? } wto,wfrom : TDir; { Spieler will wohin / kommt woher? } PROCEDURE CountLinks; PROCEDURE RoomDescription; END; { TAttrib dient der Verwaltung von Item-Attributen in einer Liste } PAttrib = ^TAttrib; TAttrib = RECORD attribute : BYTE; prev,next : PAttrib; END; { TItem stellt alle Gegenstaende im Spiel dar } TItem = OBJECT(TBasic) CONSTRUCTOR Init(_id : WORD; _name : STRING; _location : PBasic; _show : BOOLEAN; _wmin,_wmax : BYTE); FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; FUNCTION Contains(x : PItem) : BOOLEAN; FUNCTION DecAmount : BOOLEAN; FUNCTION GetAmount : BYTE; FUNCTION GetContainer : PItem; FUNCTION GetContent : BYTE; FUNCTION GetLocation : PRoom; FUNCTION GetMatter : TMatter; FUNCTION GetMaxWeight : BYTE; FUNCTION GetWeight : BYTE; PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION Has(p : BYTE) : BOOLEAN; FUNCTION HasAutoDescription : BOOLEAN; FUNCTION IsOn : BOOLEAN; FUNCTION IsOff : BOOLEAN; PROCEDURE ListMe(s : BOOLEAN); PROCEDURE Load(VAR h : File); VIRTUAL; PROCEDURE MoveCmd(where : POINTER; VAR event : TEvent); PROCEDURE MoveItemTo(where : PBasic); FUNCTION Scope : BYTE; VIRTUAL; PROCEDURE SetAttrib(a : BYTE; _on : BOOLEAN); PROCEDURE SetMatter(state : TMatter); PROCEDURE SetPraepos(pp : STRING); PROCEDURE SetAmount(n : BYTE); PROCEDURE Store(VAR h : File); VIRTUAL; PROCEDURE SwitchOn; PROCEDURE SwitchOff; PROCEDURE View; VIRTUAL; DESTRUCTOR Done; VIRTUAL; PRIVATE location : PRoom; inside : PItem; weight : TWeight; matter : TMatter; { Objektzustand? } pstart : PAttrib; { Startadresse Eigenschaften } show, { In Raumbeschreibung erwaehnen? } on : BOOLEAN; { Schalterzustand } amount : BYTE; { Menge,Tankfuellung etc. } praepos : STRING[4]; { Praeposition } PROCEDURE ClearAttributes; PROCEDURE Follow; PROCEDURE Register; END; { TVerb bildet das Woerterbuch } TVerb = RECORD verb,syntax : TMemText; { Verb und zugehoerige Tokenfolge } message : BYTE; { Vom Verb ausgeloestes Ereignis } prev,next : PLib; { vorheriges/naechstes Verb } END; { Hauptobjekt: Steuert Dialog mit Spieler, interpretiert dessen Eingabe und versendet Nachrichten an betroffene Objekte } TGame = OBJECT CONSTRUCTOR Init(_statusline,_upsize,_verbose : BOOLEAN; _history : BYTE); FUNCTION ActorWhere : PRoom; FUNCTION ActorInside(container : PItem) : BOOLEAN; PROCEDURE AddProlog(_str : STRING); PROCEDURE AddVerb(_verb,_syntax : STRING; _event : BYTE); PROCEDURE Admonition(adress : TAdress); VIRTUAL; FUNCTION AfterAction(event : TEvent) : BOOLEAN; FUNCTION BeforeAction(event : TEvent) : BOOLEAN; PROCEDURE BeforeParsing(VAR input : STRING); VIRTUAL; PROCEDURE ClearDictionary; PROCEDURE DelVerb(_event : BYTE); FUNCTION GetActor : PItem; PROCEDURE GetTime(VAR hour,min : BYTE); PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; FUNCTION MetaVerb(mv : BYTE) : BOOLEAN; VIRTUAL; FUNCTION MyScope(_id : WORD; _action : BYTE) : BOOLEAN; VIRTUAL; PROCEDURE ParserError(n : BYTE; str : STRING; i : BYTE; VAR error : BOOLEAN); VIRTUAL; PROCEDURE Run; PROCEDURE SetTime(_time,_rate : WORD); PROCEDURE WriteProlog; DESTRUCTOR Done; PRIVATE {$IFDEF tp} prologue : PDiskText; { Vorspann } {$ELSE} prologue : PMemText; {$ENDIF} statusline, { Statuszeile anzeigen? } verbose, { Ausfuehrliche Beschreibung? } upsize, { Eingabe in Grossschrift? } meta : BOOLEAN; { Ist erkanntes Verb ein Metaverb? } history, { Anzahl Zeilen im Historypuffer } quit, { Spielende erreicht? } maxwords : BYTE; { Zahl eingegebener Woerter } time, { Zeit in Minuten seit 0:00 Uhr } rate : WORD; { Minuten pro Runde } buffer : TBuffer; { Eingabepuffer } actor : PItem; { Akteuer } pverb : PLib; { Startadresse Woerterbuch } refpro : PNoun; { Substantiv fuer Reflexivpronomen } nounstr : ARRAY[1..maxbuf] OF TNoun; { wichtig } PROCEDURE Browser; PROCEDURE CheckCommandLine; FUNCTION CmpScope(index,ttype,_action : BYTE) : BOOLEAN; PROCEDURE DetectNumeral(w0 : BYTE; VAR number : INTEGER; VAR ok : BOOLEAN); PROCEDURE DrawMap; PROCEDURE DrawStatusline(location : BOOLEAN); PROCEDURE InitEvent(VAR event : TEvent; _what, _maxexec : BYTE; _who : PItem; _first, _second : PNoun; _data : POINTER); FUNCTION IsArtOrPron(str : STRING) : BOOLEAN; PROCEDURE LoadGame(n : BYTE); PROCEDURE Mapping(p : PRoom; x0,y0 : SHORTINT; VAR map : PMap); PROCEDURE Parse(line : STRING); PROCEDURE QuitGame; PROCEDURE ReadLib; PROCEDURE SaveGame(n : BYTE); PROCEDURE SearchNoun(VAR w0 : BYTE; t0 : BYTE; VAR tfound : BYTE; ttype,_action : BYTE; VAR userstr : STRING; VAR pfailed : PBasic; VAR error : BOOLEAN); FUNCTION IsVerbose : BOOLEAN; PROCEDURE UserLoad; PROCEDURE UserSave; END; CONST sdstr : ARRAY[0..4] OF STRING[12] { Namen derScope-Definitionen } = ('notinroom_sd', 'visible_sd', 'reachable_sd', 'bynpc_sd', 'held_sd'); { PlayerState: Spiel wird beendet, wenn Status des Spielers <> alive_ps } dead_ps = 0; alive_ps = 1; victory_ps = 2; { Scope-Definitionen } notinroom_sd = 0; { Objekt ist ausser Sicht und Reichweite } visible_sd = 1; { Objekt ist sichtbar } reachable_sd = 2; { Objekt ist sichtbar und erreichbar } bynpc_sd = 3; { Objekt wird von NPC gehalten } held_sd = 4; { Objekt wird vom Spieler gehalten } { Vordefinierte Ereignisse } close_ev = 100; dance_ev = 101; dec_ev = 102; drink_ev = 103; eat_ev = 104; enter_ev = 105; examine_ev = 106; give_ev = 107; go_ev = 108; inv_ev = 109; drop_ev = 110; jump_ev = 111; kill_ev = 112; kiss_ev = 113; leave_ev = 114; lista_ev = 115; lists_ev = 116; load_ev = 117; lock_ev = 118; look_ev = 119; map_ev = 120; open_ev = 121; press_ev = 122; quit_ev = 123; restart_ev = 124; read_ev = 125; score_ev = 126; save_ev = 127; set1_ev = 128; set2_ev = 129; show_ev = 130; sleep_ev = 131; smell_ev = 132; switchoff_ev = 133; switchon_ev = 134; take_ev = 135; tell_ev = 136; touch_ev = 137; trace_ev = 138; wait_ev = 139; { vordefinierte Item-Eigenschaften } drinkable_at = 0; edable_at = 1; enterable_at = 2; moveable_at = 3; readable_at = 4; shining_at = 5; switchable_at = 6; takeable_at = 7; talkable_at = 8; transparent_at = 9; VAR top : PRoom; { Zeiger auf Raum 0 } useroption : BOOLEAN; { Kommandozeilenoptionen ein/aus } *** Prozeduren und Funktionen *** FUNCTION Adr(_id : WORD) : POINTER; Liefert einen Zeiger auf das Objekt mit der ID _id. Kann mit einer expliziten Typumwandlung in einen Zeiger vom Typ PBasic, PRoom, PItem oder PLink umgewandelt werden: PRoom(Adr(forest_id)). PROCEDURE Prepare(_filename : STRING; _new : BOOLEAN); Bestimmt Dateinamen fr das Spiel und initialisiert den als Vesteck fr Objekte dienenden Raum 0 (top). PROCEDURE WritePtr(VAR h : File; p : PBasic); Speichert einen Zeiger auf einen Nachfolger von TBasic in der Spielstanddatei h. FUNCTION ReadPtr(VAR h : File) : POINTER; Liest einen Zeiger auf einen Nachfolger von TBasic aus der Spielstanddatei h. PROCEDURE WriteWord(VAR h : File; w : WORD); Speichert einen Word-Wert in der Spielstanddatei h. PROCEDURE ReadWord(VAR h : File; VAR w : WORD); Liest einen Word-Wert aus der Spielstanddatei h. PROCEDURE WriteBool(VAR h : File; v : Boolean); Speichert einen Boolean-Wert in der Spielstanddatei h. PROCEDURE ReadBool(VAR h : File; VAR v : Boolean); Liest einen Boolean-Wert aus der Spielstanddatei h. PROCEDURE WriteDir(VAR h : File; d1 : TDir); Speichert einen TDir-Wert in der Spielstanddatei h. PROCEDURE ReadDir(VAR h : File; VAR d1 : TDir); Liest einen TDir-Wert aus der Spielstanddatei h. 9.4 Methoden von TBasic ----------------------- TBasic stellt das Basisobjekte fr alle Objekte der Spielwelt dar. Von ihm werden die Objekttypen TItem, TRoom und TLink abgeleitet. Die privaten Methoden von TBasic und seiner Nachfolger werden hier nicht aufgefhrt, sind aber im Quelltext kommentiert. CONSTRUCTOR Init(_id : WORD; _name : STRING; _objclass : T_Class); Initialisiert ein Objekt. Wird bei der Initialisierung aller Nachfolger von TBasic aufgerufen. PROCEDURE AddText(str : STRING); Ordnet dem Objekt eine Beschreibung zu. Mehrere Methodenaufrufe k”nnen einen Text mit mehr als 255 Zeichen zuordnen. PROCEDURE DelText; Entfernt die mit AddText() hinzugefgte Beschreibung wieder. FUNCTION GetClass : T_Class; Liefert den Typ eines Objekts: item, room oder link. FUNCTION GetCopies : BYTE; Liefert die Anzahl der Objekte, die sich im gleichen Raum befinden und den gleichen Namen haben. FUNCTION GetID : WORD; Liefert die ID des Objekts. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Ereignisroutine (s. 6.1). Verarbeitet die Ereignisse dance_ev, dec_ev, examine_ev, jump_ev, lists_ev, touch_ev und trace_ev. Diese Ereignisse werden von den Ereignisroutinen der Nachfolger von TBasic weitergereicht und k”nnen auch dort behandelt werden. FUNCTION HasDaemon : BOOLEAN; Ist True, wenn der D„mon des Objekts aktiv ist. FUNCTION HasTimer : BOOLEAN; Ist True, wenn der Zeitschalter des Objekts aktiv ist. PROCEDURE Inspect; VIRTUAL; Gibt nach jeder Runde Informationen aus, wenn das Objekt mit .trace (s. 7.6) verfolgt wird. PROCEDURE Load(VAR h : File); VIRTUAL; L„dt gespeicherten Zustand. PROCEDURE MyText; VIRTUAL; Gibt variable Beschreibung aus, wenn dem Objekt mit AddText() kein Text zugeordnet wurde. PROCEDURE RunDaemon; VIRTUAL; Fhrt einen aktiven Hintergrundprozeá (s. 8.3) aus. FUNCTION Scope : BYTE; VIRTUAL; Liefert Information ber die Erreichbarkeit des Objekts (s. 5.7). PROCEDURE StartDaemon; Aktiviert den Hintergrundprozeá des Objekts. PROCEDURE StopDaemon; Beendet den Hintergrundprozeá des Objekts. PROCEDURE StartTimer(time : BYTE); Aktiviert den Zeitschalter des Objekts (s. 8.3). PROCEDURE StopTimer; Beendet den Zeitschalter vorzeitig. PROCEDURE Store(VAR h : File); VIRTUAL; Speichert den aktuellen Zustand. PROCEDURE TimeOut; VIRTUAL; Wird von abgelaufenem Zeitschalter ausgefhrt. PROCEDURE View; VIRTUAL; Gibt Informationen fr den von .browse und .list aktivierten Objekt-Betrachter aus. Wird von den Nachfolgern von TBasic berschrieben. DESTRUCTOR Done; VIRTUAL; L”scht dynamische Variablen. 9.5 Methoden von TItem ---------------------- TItem ist ein Nachfolger von TBasic und kann fast alle m”glichen und unm”glichen Dinge in der Spielwelt darstellen. CONSTRUCTOR Init(_id : WORD; _name : STRING; _location : PBasic; _show : BOOLEAN; _wmin,_wmax : BYTE); Initialisiert einen Gegenstand (s. 5.1). FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TItem.HandleEvent() nach Ausfhrung eines Ereignisses aufgerufen und verhindert eine Textausgabe, wenn False zurckgegeben wird. FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TItem.HandleEvent() vor Ausfhrung eines Ereignisses aufgerufen und verhindert eine weitere Bearbeitung, wenn False zurckgegeben wird. FUNCTION Contains(x : PItem) : BOOLEAN; Stellt fest, ob das Objekt enthalten ist. FUNCTION DecAmount : BOOLEAN; Reduziert die Menge des Objekts um eine Einheit. Wird bislang nur von den Ereignissen eat_ev und drink_ev verwendet (s. SetAmount()). FUNCTION GetAmount : BYTE; Liefert die Menge des Objekts. FUNCTION GetContainer : PItem; Liefert den Beh„lter des Objekts. FUNCTION GetContent : BYTE; Liefert Anzahl enthaltener Objekte. FUNCTION GetLocation : PRoom; Liefert den Raum, in dem sich das Objekt (und evtl. sein Beh„lter) befindet. FUNCTION GetMatter : TMatter; Liefert den Zustand des Objekts: inanimate, dead oder alive. FUNCTION GetMaxWeight : BYTE; Liefert das Maximalgewicht. FUNCTION GetWeight : BYTE; Liefert das Gewicht. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Ereignisroutine fr die Ereignisse cose_ev, drink_ev, drop_ev, eat_ev, enter_ev, give_ev, kill_ev, kiss_ev, leave_ev, lock_ev, open_ev, read_ev, show_ev, switchoff_ev, switchon_ev, press_ev, take_ev und tell_ev. FUNCTION Has(p : BYTE) : BOOLEAN; Stellt fest, ob das Objekt das Attribut p hat. p ist der Wert einer Attributskonstante mit der Endung *_at. FUNCTION HasAutoDescription : BOOLEAN; Stellt fest, ob das Objekt nach der Raumbeschreibungen automatisch erw„hnt wird. FUNCTION IsOn : BOOLEAN; Stellt fest, ob das Objekt eingeschaltet ist, wenn es das Attribut switchable_at hat. FUNCTION IsOff : BOOLEAN; Stellt fest, ob das Objekt ausgeschaltet ist, wenn es das Attribut switchable_at hat. PROCEDURE ListMe(s : BOOLEAN); Wenn True bergeben wird, wird das Objekt nach der Raumbeschreibung automatisch erw„hnt. Ansonsten kann es in der Raumbeschreibung erw„hnt oder vor dem Spieler versteckt werden. PROCEDURE Load(VAR h : File); VIRTUAL; s. TBasic.Load(). PROCEDURE MoveCmd(where : POINTER; VAR event : TEvent); Wird von TItem.HandleEvent() fr die Bearbeitung der Ereignisse give_ev, drop_ev und take_ev aufgerufen und bewegt das Objekt an die durch where angegebene Position. Dabei werden BeforeAction() und AfterAction() aufgerufen. Wenn Sie die Position eines Gegenstandes „ndern wollen, sollten Sie die Methode MoveItemTo verwenden(). PROCEDURE MoveItemTo(where : PBasic); Bewegt das Objekt an die durch where angegebene Position, dabei muá where ein Zeiger auf einen Raum oder einen Beh„lter sein. BeforeAction() und AfterAction() werden nicht aufgerufen. Die Positionsver„nderung kann vom Spieler unbemerkt erfolgen. FUNCTION Scope : BYTE; VIRTUAL; s. TBasic.Scope. PROCEDURE SetAttrib(a : BYTE; _on : BOOLEAN); Setzt oder entfernt das Attribut a (s. 5.1). PROCEDURE SetMatter(state : TMatter); Setzt den Zustand des Objekts auf inanimate, dead oder alive. PROCEDURE SetPraepos(pp : STRING); Bestimmt die Pr„position (meist "auf" oder "in"), wenn der Spieler das Objekt betreten kann (s. 5.1). PROCEDURE SetAmount(n : BYTE); Bestimmt eine Menge, die bislang nur von den Ereignissen eat_ev und drink_ev verringert wird. Vulgo: Diese Menge gibt an, wie oft der Spieler von einem Brot essen oder aus einer Flasche trinken kann. Die Voreinstellung ist eins. PROCEDURE Store(VAR h : File); VIRTUAL; Speichert aktuellen Objektzustand. PROCEDURE SwitchOn; Schaltet das Objekt ein. Das Attribut switchable_ev muá vorhanden sein. PROCEDURE SwitchOff; Schaltet das Objekt aus. Das Attribut switchable_ev muá vorhanden sein. PROCEDURE View; VIRTUAL; Gibt Informationen fr den mit .browse und .list aktivierten Objekt-Betrachter aus. PROCEDURE Done; VIRTUAL; s. TBasic.Done. 9.6 Methoden von TRoom ---------------------- TRoom ist ein Nachfolger von TBasic und stellt einzelne R„ume dar. CONSTRUCTOR Init(_id : WORD; _name : STRING); Initialisiert einen Raum (s. 4.3). FUNCTION FromDir : TDir; Stellt fest, aus welcher Richtung der Spieler den Raum betreten hat. Wenn die Methode den Wert nowhere liefert, war der Spieler hier noch nie oder hat das Spiel begonnen. FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TRoom.HandleEvent() nach Ausfhrung eines Ereignisses aufgerufen und verhindert eine Textausgabe, wenn False zurckgegeben wird. FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TRoom.HandleEvent() vor Ausfhrung eines Ereignisses aufgerufen und verhindert eine weitere Bearbeitung, wenn False zurckgegeben wird. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Ereignisroutine fr die Ereignisse go_ev und look_ev. FUNCTION HasLight : BOOLEAN; Stellt fest, ob der Spieler hier sehen kann (s. 8.4). FUNCTION IsGate(d : TDir) : TState; Stellt fest, ob eine Verbindung/Tr in der angegebenen Richtung ge”ffnet (open), geschlossen (closed) oder verschlossen (locked) ist. PROCEDURE Load(VAR h : File); VIRTUAL; s. TBasic.Load(). PROCEDURE MyDarkness; VIRTUAL; Gibt einen Text an Stelle der Raumbeschreibung aus, wenn der Raum nicht beleuchtet ist. PROCEDURE SetLight(_light : BOOLEAN); Schaltet die Beleuchtung ein und aus (s. 8.4). PROCEDURE Store(VAR h : File); VIRTUAL; s. TBasic.Store(). FUNCTION Scope : BYTE; VIRTUAL; Der Scope eines Raums ist visible_sd, wenn der Spieler sich in ihm aufh„lt, oder notinroom_sd, wenn er sich in einem anderen Raum aufh„lt. FUNCTION ToDir : TDir; Stellt fest, in welche Richtung der Spieler den Raum verlassen will oder verlassen hat. Wenn diese Methode nowhere zurckliefert war der Spieler hier noch nie oder hat das Spiel hier begonnen und den Raum noch nicht verlassen. PROCEDURE View; VIRTUAL; s. TBasic.View. DESTRUCTOR Done; VIRTUAL; s. TBasic.Done. 9.7 Methoden von TLink ---------------------- TLink ist ein Nachfolger von TBasic und verbindet zwei R„ume. CONSTRUCTOR Init(_id : WORD; _name : STRING; _from : WORD; _dirto : TDir; _to : WORD; _show : BOOLEAN); Initialisiert eine Verbindung (s. 5.6). FUNCTION BeforeAction(VAR event : TEvent) : BOOLEAN; VIRTUAL; Wird von TLink.HandleEvent() vor Ausfhrung eines Ereignisses aufgerufen und verhindert eine weitere Bearbeitung, wenn False zurckgegeben wird. FUNCTION AfterAction(VAR event : TEvent) : BOOLEAN; VIRTUAL; Wird von TLink.HandleEvent() nach Ausfhrung eines Ereignisses aufgerufen und verhindert eine Textausgabe, wenn False zurckgegeben wird. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Gibt die Ereignisse close_ev, lock_ev und open_ev an das zugeordnete Objekt vom Typ TLock weiter. FUNCTION HasAutoDescription : BOOLEAN; Stellt fest, ob die Verbindung (als Ausgang) nach der Raumbeschreibung erw„hnt wird. PROCEDURE Load(VAR h : File); VIRTUAL; s. TBasic.Load(). FUNCTION Scope : BYTE; VIRTUAL; s. TBasic.Scope(). PROCEDURE Store(VAR h : File); VIRTUAL; s. TBasic.Store(). PROCEDURE View; VIRTUAL; s. TBasic.View. DESTRUCTOR Done; VIRTUAL; s. TBasic.Done(). 9.8 Methoden von TLock ---------------------- TLock dient Beh„ltern und Verbindungen als Schloá. CONSTRUCTOR Init(_owner : PBasic; _openable,_closeable : BOOLEAN; _state : TState; _key : PItem); Initialisiert ein Schloá und ordnet es einem Gegenstand oder einer Verbindung zu (s. 5.2). FUNCTION AfterAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TLock.HandleEvent() nach Ausfhrung eines Ereignisses aufgerufen und verhindert eine Textausgabe, wenn False zurckgegeben wird. FUNCTION BeforeAction(event : TEvent) : BOOLEAN; VIRTUAL; Wird von TLock.HandleEvent() vor Ausfhrung eines Ereignisses aufgerufen und verhindert eine weitere Bearbeitung, wenn False zurckgegeben wird. FUNCTION GetKey : PItem; Liefert einen Zeiger auf das Objekt, mit dem sich das Schloá auf- und abschlieáen l„át. FUNCTION GetOwner : PBasic; Liefert einen Zeiger auf ein Objekt vom Typ PItem oder PLink, dem das Schloá zugeordnet ist. FUNCTION GetState : TState; Liefert den Zustand des Schlosses: open, closed oder locked. Alternativ kann auch die Methode TRoom.IsGate() verwendet werden. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Ereignisroutine fr die Ereignisse close_ev, lock_ev und open_ev. FUNCTION HasKey(event : Tevent) : BOOLEAN; Stellt fest, ob der Akteur den fr das Schloá ben”tigten Schlssel tr„gt. FUNCTION IsOpenable : BOOLEAN; Stellt fest, ob sich das Schloá ”ffnen l„át. FUNCTION IsCloseable : BOOLEAN; Stellt fest, ob sich das Schloá schlieáen und abschlieáen l„át. PROCEDURE Load(VAR h : File); VIRTUAL; Speichert aktuellen Objektzustand. PROCEDURE SetCloseable(s : BOOLEAN); Bestimmt, ob sich das Objekt schlieáen und/oder abschlieáen l„át. PROCEDURE SetOpenable(s : BOOLEAN); Bestimmt, ob sich das Objekt ”ffnen l„át. PROCEDURE SetState(s : TState); Bestimmt den Zustand des Schlosses: open, closed oder locked. PROCEDURE Store(VAR h : File); VIRTUAL; L„dt gespeicherten Zustand. PROCEDURE View; VIRTUAL; Gibt Informationen im Objekt-Betrachter aus. DESTRUCTOR Done; VIRTUAL; L”scht dynamische Variablen. 9.9 Methoden von TPlayer ------------------------ TPlayer verwaltet die den Spieler betreffenden Informationen. CONSTRUCTOR Init(_where : WORD; _adress : TAdress; _gender : TGender; _wmin,_wmax : BYTE); Initialisiert das Spielerobjekt (s. 4.5). PROCEDURE AfterLife; VIRTUAL; Wird nach dem Tod des Spielers aufgerufen, um eine beliebige Meldung auszugeben. FUNCTION GetAdress : TAdress; Liefert die im Konstruktor angegebene Anrede fr den Spieler: du, sie ihr. FUNCTION GetContainer : PItem; Liefert einen Zeiger auf das Objekt, in dem der Spieler sich befindet. FUNCTION GetContent : BYTE; Liefert die Anzahl der Objekte im Inventar des Spielers. FUNCTION GetGender : TGender; Liefert das im Konstruktor angegeben Geschlecht des Spielers: male oder female. FUNCTION GetLocation : PRoom; Liefert den aktuellen Raum. FUNCTION GetMaxWeight : BYTE; Liefert das Maximalgewicht, das der Spieler tragen kann. FUNCTION GetScores : WORD; Liefert die Punktzahl des Spielers. FUNCTION GetMoves : WORD; Liefert die Zahl der bisherigen Spielzge. FUNCTION GetState : BYTE; Liefert den Zustand des Spielers: alive_ps, dead_ps oder victory_ps. FUNCTION GetWeight : BYTE; Liefert das Gewicht des Spielers. FUNCTION HasPlayer(i : WORD) : BOOLEAN; Stellt fest, ob sich das Objekt mit der ID i im Inventar befindet. PROCEDURE IncScores(ds : WORD); Erh”ht die Punktzahl des Spielers um ds Punkte. PROCEDURE Load(VAR h : File); VIRTUAL; L„dt den Zustand des Spielers. PROCEDURE MovePlayerTo(where : PBasic); Bewegt (oder teleportiert) den Spieler zu der Position where, dieser Parameter muá ein Raum oder ein Objekt mit dem Attribut enterable_at sein. PROCEDURE SetState(s : BYTE); Ver„ndert den Spielerstatus. SetState(dead_ps) t”tet den Spieler, w„hrend ihn SetState(victory_ps) das Spiel gewinnen l„át. FUNCTION StatusBarLeftStr : STRING; VIRTUAL; Bestimmt den linksbndigen Text in der Statuszeile. FUNCTION StatusBarRightStr : STRING; VIRTUAL; Bestimmt den rechtsbndigen Text in der Statuszeile. PROCEDURE Store(VAR h : File); VIRTUAL; Speichert den Zustand des Spielers. FUNCTION RankStr : STRING; VIRTUAL; Gibt die Punktzahl und wahlweise weitere Informationen aus. PROCEDURE Victory; VIRTUAL; Wird nach dem Sieg des Spielers aufgerufen, um eine beliebige Meldung auszugeben. DESTRUCTOR Done; L”scht dynamische Variablen. 9.10 Methoden von TGame ----------------------- TGame beinhaltet den Parser und steuert den Spielverlauf. CONSTRUCTOR Init(_statusline,_upsize,_verbose : BOOLEAN; _history : BYTE); Initialisiert das Spiel (s. 4.5). FUNCTION ActorWhere : PRoom; Liefert den Aufenthaltsort des Akteurs. FUNCTION ActorInside(container : PItem) : BOOLEAN; Stellt fest, ob sich der Akteur in container befindet. PROCEDURE AddProlog(_str : STRING); Fgt dem Spiel einen Vorspann hinzu. Mit mehreren AddProlog() kann auch ein Text von mehr als 255 Zeichen zugewiesen werden. PROCEDURE AddVerb(_verb,_syntax : STRING; _event : BYTE); Fgt dem W”rterbuch ein neues Verb hinzu (s. 7.2). PROCEDURE Admonition(adress : TAdress); VIRTUAL; Fordert den Spieler auf, sich wieder auf das Spiel zu konzentrieren, wenn er eines der Ereignisse dance_ev, jump_ev und kiss_ev ausl”st. Um auf diese Ereignisse anders zu reagieren, mssen Sie diese Methode oder einen Nachfolger von TBasic.HandleEvent() berschreiben. FUNCTION AfterAction(event : TEvent) : BOOLEAN; Wird von TGame.HandleEvent() nach Ausfhrung eines Ereignisses aufgerufen und verhindert eine Textausgabe, wenn False zurckgegeben wird. FUNCTION BeforeAction(event : TEvent) : BOOLEAN; Wird von TGame.HandleEvent() vor Ausfhrung eines Ereignisses aufgerufen und verhindert eine weitere Bearbeitung, wenn False zurckgegeben wird. PROCEDURE BeforeParsing(VAR input : STRING); VIRTUAL; Erm”glicht die Ver„nderung des Eingabestrings, bevor er an den Parser bergeben wird. PROCEDURE ClearDictionary; L”scht das gesamte W”rterbuch. Die Richtungsanweisungen (norden, sden, etc.) bleiben erhalten, da sie unabh„ngig vom W”rterbuch bearbeitet werden. PROCEDURE DelVerb(_event : BYTE); Entfernt alle Verben mit dem Ereignis _event aus dem W”rterbuch. FUNCTION GetActor : PItem; Liefert einen Zeiger auf den Akteur. Wenn NIL zurckgegeben wird, ist der Spieler der Akteur. PROCEDURE GetTime(VAR hour,min : BYTE); Liefert die Spielzeit. PROCEDURE HandleEvent(VAR event : TEvent); VIRTUAL; Wird von TBasic.HandleEvent() fr die Ereignisse lista_ev, sleep_ev und wait_ev aufgerufen. FUNCTION MetaVerb(mv : BYTE) : BOOLEAN; VIRTUAL; Wird vor der Ausfhrung eines Metaverbs aufgerufen und kann dessen weitere Bearbeitung verhindern (s. 7.5). FUNCTION MyScope(_id : WORD; _action : BYTE) : BOOLEAN; VIRTUAL; Bestimmt w„hrend der Analyse der Eingabe, ob das vom Spieler genannte Objekt mit der ID _id das Ereignis _action ausfhren kann. Solange Sie diese Methode nicht berschreiben, liefert sie immer True. Diese Methode wird aufgerufen, wenn Sie in einer Grammatik das Token routine angeben. PROCEDURE ParserError(n : BYTE; str : STRING; i : BYTE; VAR error : BOOLEAN); VIRTUAL; Gibt eine Meldung des Parsers aus, wenn bei der Analyse der Eingabe oder der Ausfhrung eines Ereignisses ein Problem auftritt. PROCEDURE Run; Startet das Spiel. PROCEDURE SetTime(_time,_rate : WORD); Bestimmt die Spielzeit in Minuten seit Mitternacht. Der Parameter _rate gibt an, wieviele Minuten w„hrend einer Runde verstreichen. DESTRUCTOR Done; Ruft am Programmende die Methode Done aller Nachfolger von TBasic auf. -------------------------------------------------------------- Einen Index finden Sie in der Postscript-Version dieses Textes