                              > 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 Gegenstnde und Attribute
       5.2 Behlter
       5.3 Fortbewegungsmittel
       5.4 Weitere Rume
       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 mten,
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
angepat werden. Textopia ermglicht 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 Rume, Tren, Fahrzeuge, Nichtspielercharaktere (NPCs),
   Behlter, ein- und ausschaltbare Dinge, Lichtquellen, die Unterscheidung
   zwischen Licht und Dunkelheit, Zeitschalter und Hintergrundprozesse (Timer
   & Daemons), die Verwaltung mehrerer Spielstnde, Automapping, eine
   Protokolldatei sowie einen Debugmodus.

>> verfgt ber einen Parser, der 40 vordefinierte Verben und Kommandos
   beherrscht und sich einfach erweitern lt. 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 knnen.

Dieser Text soll Ihnen als Einstieg in Textopia und als Nachschlagewerk bei
der Programmierung eigener Adventures dienen. Grundkenntnisse in Pascal sind
zu seinem Verstndnis sicher von Vorteil. Am besten lesen Sie diesen Text vor
Ihrem Computer, um die Beispielprogramme besser nachvollziehen zu knnen.

Programmiert, getestet und dokumentiert wurde Textopia von Oliver Berse,
dem Schreiber dieser Zeilen. Textopia darf selbstverstndlich 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
zugehrigen 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 mglich 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 zunchst 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 unlsbaren 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.
Mglich wird dies durch die Compilerdirektive {$DEFINE xxx} am Anfang der
Quelltexte. Hier knnen 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 knnen 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 bentigten
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 knnen 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
vollstndiger 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 knnen 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 verhlt FPC sich
bei der bersetzung kompatibel zu TP6/7, -B entspricht dem Build-Befehl des
TP-Compilers. FPC untersttzt mehr Compileroptionen als TP, so knnen Sie
auch fr den Prozessortyp Ihres Computers optimierten Code erzeugen. Wenn Sie
nur ppc386 eingeben, erhalten Sie eine Liste aller mglichen Optionen.

Whrend der bersetzung gibt FPC einige Hinweise aus, die Sie allesamt
ignorieren knnen. Meldungen ber nicht verwendete Parameter erzeugt FPC
flschlicherweise, 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 spter einen Segmentation Fault
verursachen. Im Console-Modus mit 80x25 oder mehr Zeichen funktioniert
Textopia dagegen einwandfrei.

Unter dem KDE-Desktop knnen Sie das K-Terminal oder das in der SuSE
Distribution enthaltene Programm Konsole von Lars Doelle verwenden.
Bei K-Terminal whlen Sie unter Einstellungen eine Fenstergre 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 gefllt) 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 Mglichkeiten 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 Vollstndigkeit, 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, groe
   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 vollstndige Methode wird dann auerhalb 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), knnen 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 knnen 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 knnen so durch Aufruf der gleichen Methode
   erledigt werden.

>> virtuelle Methoden - ermglichen 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 gewhnlich 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 lschen und somit den Speicher aufrumen. In jedem
   Objekttyp sollte ein Destruktor definiert werden. In Turbo Pascal erhalten
   Destruktoren gewhnlich den Namen Done.

Lassen Sie sich von diesen Definitionen nicht abschrecken. Sie werden schnell
herausbekommen, wie Sie Textopia und OOP fr eigene Adventures nutzen knnen.

3.2 Adventures und Objekte
--------------------------
Die Spielwelt fast aller Adventures besteht aus Elementen wie Rumen, simplen
Goldmnzen, komplizierten Maschinen und NPCs, die sprechen und sich bewegen
knnen. Neben der Programmierung eines Parsers stellt die Verwaltung dieser
Spielwelt das grte Problem bei der Adventureprogrammierung dar. Textopia
lst 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 gehrenden
   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 Gebude oder um einen Ort unter freiem Himmel
   handeln.

>> TLink - verbindet jeweils zwei Rume und kann in Verbindung mit einem
   Objekt vom Typ TLock verschiedene Durchgnge 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 Rume,
Verbindungen und Gegenstnde 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 - enthlt den Parser und verschiedene Methoden zur Kontrolle des
   Spielverlaufs.

Die genannten Objekttypen besitzen zahlreiche virtuelle Methoden, die Sie
in abgeleiteten Typen berschreiben knnen, 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 gehren. Wenn Sie den Quelltext
durchstbern, werden Sie zudem feststellen, da sich einige Sachen noch eine
Spur objektorientierter htten 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 bentigt, ber die spter 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), knnen wir also deklarieren:

CONST
  forest_id = 10;

In der Typendeklaration knnen neue Objekttypen definiert werden, um einzelne
Methoden neu zu definieren. Mit Ausnahme einiger interner Typen knnen Sie
von allen Objekttypen in Textopia Nachfolger ableiten. Fr unser Beispiel
knnen 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 bentigten Objekttypen deklarieren, um von
ihnen Instanzen erzeugen zu knnen. Wir knnen 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 bentigen 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 bentigt. Wenn
Sie spter im Spiel einen Zeiger auf ein bestimmtes Objekt bentigen,
verwenden Sie die Funktion

Adr(_id : WORD) : POINTER

Mit einer expliziten Typumwandlung der Form PRoom(Adr(forest_id)) knnen Sie
diesen Pointer in einen Zeiger auf einen Nachfolger von TBasic umwandeln.
Verwechseln Sie diese Funktion nicht mit der Pascal-Funktion Addr.

Htten wir Nachfolger von Textopia-Objekten deklariert, mten wir nun ihre
Methoden schreiben. So aber knnen 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:

Zunchst 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 gehrenden 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 whrend 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 verndern knnen, 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 hufig verwendet und
knnen mit den bereits deklarierten Konstanten t und f abgekrzt werden.

Als nchstes initialisiert Prepare eine interne Liste, die alle von Ihnen
definierten Objekte aufnimmt. Dann erzeugt Prepare eine Instanz von PRoom
mit der ID 0 und trgt sie als erstes Element in die Liste ein. Dieser
Raum 0 dient als einfache Alternative fr die Definition und Beseitigung von
von Objekten whrend des Spielverlaufs, die in Textopia nicht vorgesehen ist.
It der Spieler beispielsweise einen Keks, wird dieser nicht mehr bentigt
und landet im Raum 0. Umgekehrt knnen Sie aus diesem Raum auch bislang
verborgene Objekte hervorzaubern. Daher knnen 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 zunchst 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 zustndige 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 erwhnt 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 Lautvernderung im Plural: %Apfel -> pfel.

# markiert den Buchstaben oder die Silbe, die im Plural angehngt wird:
Flasche#n -> die Flaschen

* markiert ein vorgestelltes Adjektiv. Vor einem Namen darf hchstens ein
Adjektiv stehen. Beachten Sie, da Minus-, Plus- und Dollarzeichen vor dem
Adjektiv stehen mssen: +rot* Drache#n -> der rote Drache.

Sie knnen 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 erwhnt Textopia immer nur den ersten, whrend der Spieler alle
Namen benutzen kann, um sich auf das Objekt zu beziehen. Schlielich knnen
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, knnen Sie mit der Konstruktion

p^.name.GetText

auf den Objektnamen zugreifen.

Fr die Deklination von Objektnamen ist die in tstring.pas definierte
Stringfunktion Noun() zustndig. Ihr werden folgende Parameter bergeben:

>> n1:String - ein Objektname mit seinen Sonderzeichen.

>> casus:TCasus - einer der vier grammatischen Flle, fr den der Name
   dekliniert werden soll. Mgliche 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
bentigt 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. Grobuchstaben
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 aufhlt. Jedem Objekt
eines Nachfolgers von PBasic knnen 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 lt Sie nur mhsam '+
          'weiterkommen. Weiter westlich scheinen die Bume 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 Lnge 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 selbstndig um den Zeilenumbruch.
Wird ein zusammengehriger Text lnger 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 knnen,
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 erhhter Intensitt ausgegeben. Dies
   entspricht der TP-Anweisung HighVideo.

\l Der folgende Text wird mit normaler Intensitt ausgegeben. Dies
   entspricht der TP-Anweisung NormVideo.

\r Schaltet die inverse Textdarstellung ein und aus.

\b Gibt das folgende Zeichen in Groschrift aus.

Einige weitere Steuerzeichen sind fr die systemunabhngige Darstellung
deutscher Umlaute vorgesehen (s. 8.8).

Mit AddText() angegebene Objektbeschreibungen sind statisch. Das heit, sie
lassen sich whrend des Spiels nicht mehr verndern. Wenn aber z.B. whrend
des Spiels ein Teil der Decke eines Raums einstrzt und eine Tr versperrt,
mu natrlich auch die entsprechende Raumbeschreibung gendert werden. Um
eine Objektbeschreibung an bestimmte Spielsituationen anpassen zu knnen,
mu ein neuer Objekttyp von einem der Nachfolger von TBasic abgeleitet
werden und die Methode TBasic.MyText neu definiert werden. Wird whrend
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 gewhnlich nichts und ist nur definiert, um in einem
Enkel von TBasic berschrieben zu werden. In MyText knnen 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. Zunchst 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
   Behlter 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 gewhlte Anrede halten. Entsprechende
   Personalpronomen und Verbformen knnen noch nicht automatisch erzeugt
   werden.

>> _gender - bestimmt das Geschlecht von Spielers Alter-Ego und kann whrend
   des Spiels mit der PPlayer-Methode ChangeGender gewechselt werden.
   Mgliche 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 trgt.

>> _wmax - bestimmt das Maximalgewicht des Spielers. Da sich auch allen
   mglichen Gegenstnden ein Gewicht zuordnen lt, knnen Sie mit _wmax
   leicht bestimmen, wieviele Dinge der Spieler mit sich herumtragen darf.

Einen Abenteurer knnen 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 whrend
   des Spiels ein- und ausgeschaltet werden.

>> _upsize:BOOLEAN - bestimmt, ob die Spielereingaben in Grobuchstaben
   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 vollstndige Raumbeschreibung aus, auch wenn der
   Spieler den Raum bereits besucht hat. Andernfalls werden in bereits
   bekannten Rumen 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 knnen wir jetzt mit

NEW(game,Init(t,f,t,10));

initialisieren. Bevor wir das Programm starten, knnen 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 Straen den '+
            'alten Wald. Hier irgendwo liegen die mit Schtzen gefllten '+
            'Grber der Zwergenknige, die einst diese Lnder '+           'beherrschten.\n\n');

Der mit AddProlog() dem Spiel hinzugefgte Text erscheint immer zu
Spielbeginn. Diese Methode verhlt sich ganz hnlich wie AddText(). Mit der
Methode TGame.Run knnen 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 aufzurumen:

  Run;
  Done;
END;

Vergessen Sie nicht, das Hauptprogramm mit END. abzuschlieen.

4.6 Ein erster Dialog
---------------------
Wenn sich demo1.pas problemlos kompilieren und starten lt, sehen Sie
jetzt die Statuszeile mit dem Namen des aktuellen Raums sowie den Punkten und
Zgen des Spielers. In der unteren Bildschirmhlfte 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 Fllen 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 knnen Sie einige Debugkommandos
eingeben, die Ihnen helfen, das Verhalten Ihrer Objekte zu kontrollieren.
Die Debugkommandos werden in 7.6 erlutert. Wenn Ihr Spiel fertig ist und
fehlerfrei luft, sollten Sie den Debugmodus mit der Zeile

debug:=f;

im Hauptprogramm ausschalten.

Viel lt sich mit nur einem Raum noch nicht anfangen. Ein erster Dialog
knnte so aussehen:

Das Zwergengrab
Eine Textopia-Demo von Oliver Berse

Drei Tage schon durchstreifen Sie abseits der Straen den alten Wald. Hier 
irgendwo liegen die mit Schtzen gefllten Grber der Zwergenknige, die
einst diese Lnder beherrschten.

WALD
Fast undurchdringliches Unterholz lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume 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 auslsen, 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 Gegenstnde und Attribute
-----------------------------
Bevor wir unserem Programm "Zwergengrab" weitere Rume hinzufgen, geben wir
dem Spieler einige ntzliche Dinge mit auf den Weg. Mit Ausnahme von Rumen
und Tren knnen 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, knnen 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 zunchst 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 bentigten Zeiger liefert
   die Funktion Adr(). Wenn das Objekt im Inventar des Spielers plaziert
   werden soll, mu dem Konstruktor der Wert NIL bergeben werden. Whrend
   des Spiels knnen Sie die Position eines Objekt vom Typ PItem mit der
   Methode MoveItemTo() verndern. 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 erwhnt haben,
   knnen Sie hier False bergeben. Diese Voreinstellung lt sich im Spiel
   mit der Methode ListMe() verndern, der True oder False bergeben wird.
   So knnen 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, lt sich hiermit bestimmen, wieviele
   Gegenstnde es maximal enthalten darf. Soll das Objekt nicht als
   Behlter dienen, knnen 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 ermglicht 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)

knnen Sie abhngig vom Parameter _on ein Attribut hinzufgen (_on=True)
oder entfernen (_on=False). Ob ein Objekt ber ein bestimmtes Attribut
verfgt oder nicht, knnen Sie im Spiel mit der Methode

TItem.Has(a : BYTE) : BOOLEAN

prfen. Zehn Attribute sind bereits als Konstanten deklariert und knnen
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 Prposition,
   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 Rumen (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 knnen Sie im Spiel mit den Methoden TItem.IsOn und
   TItem.IsOff berprfen. Die Methoden TItem.SwitchOn und TItem.SwitchOff
   verndern 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. Nheres ber NPCs und Dialoge erfahren
   Sie in 8.1.

>> transparent_at - das Objekt ist durchsichtig (aber nicht unsichtbar),
   was fr Behlter bedeutet, da ihr Inhalt auch im geschlossenen Zustand
   sichtbar bleibt.

Einige Kombinationen der Attribute ergeben sicher sehr surrealistische
Objekte. Neben diesen zehn Attributen knnen Sie auch eigene Attribute
deklarieren. Fr deren Konstanten knnen Sie Werte zwischen 10 und 255
whlen. 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 nheres ber das Schwert erfahren will. Fr die Beschreibung
von Gegenstnden knnen 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 ntigen Taler erst noch in den Grbern finden '+
          'mten, 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 Behlter
------------
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;

Zustzlich soll der Rucksack noch als Behlter dienen. Einen Behlter kann
der Spieler ffnen, schlieen und abschlieen. Natrlich kann er in einen
Behlter auch andere Gegenstnde hineinlegen (und wieder herausnehmen). Um
aus einem Gegenstand einen Behlter zu machen, mu ein Objekt vom Typ TLock
erzeugt und dem Gegenstand zugeordnet werden. TLock verwaltet einige
Informationen, die alle Objekte bentigen, die sich ffnen und schlieen
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 Behlter und keine Tr ist).
ber diesen Zeiger knnen 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 Behlter oder als Tr dienen soll. Einen Zeiger auf das
   entsprechende Objekt liefert Ihnen whrend des Spiels die Methode
   TLock.GetOwner.

>> _openable: BOOLEAN - bestimmt, ob der Spieler das Objekt ffnen kann.
   Im Spiel knnen 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 schlieen kann.
   Diese Voreinstellung knnen 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
   whrend des Spiels die Methode TLock.GetState. Wenn Sie das Schlo einer
   Verbindung zugeordnet haben, knnen 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 abschlieen.
   Wird hier NIL angegeben, kann der Spieler das Objekt nur ffnen (wenn es
   nicht abgeschlossen ist) und schlieen, nicht aber abschlieen. 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 whrend des Spiel auf einen Behlter zugreifen wollen, sollten Sie
noch folgende Dinge wissen: Um festzustellen, ob ein Behlter ein bestimmtes
Objekt enthlt, verwenden Sie die Methode

TItem.Contains(x : PItem) : BOOLEAN.

Umgekehrt liefert die Methode TItem.GetContainer einen Zeiger vom Typ PItem
auf den Behlter, der das Objekt enthlt. Objekte vom Typ TItem und TPlayer
enthalten ein statisches Objekt namens weight. Dessen Methoden GetCont,
GetSum und GetMax liefern die Zahl der im Behlter enthaltenen Objekte, das
Gesamtgewicht des Behlters und sein zulssiges Maximalgewicht.

Wenn Sie jetzt das Programm demo2.pas starten, knnen 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 enthlt:
  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 erhhen, 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 Behlter nicht
aufgenommen werden kann, weil es zu schwer oder weil es zu gro ist. Fr
diese Unterscheidung mte neben dem Gewicht eines Objekts auch noch dessen
Gre festgelegt werden (was die Verwaltung der Behlter wesentlich komplexer
machen wrde). In den meisten Spielen wird die Gewichtsangabe sicher
ausreichen.

5.3 Fortbewegungsmittel
-----------------------
Eine weitere Gruppe ntzlicher Gegenstnde sind Fortbewegungsmittel wie
Reittiere und Fahrzeuge aller Art. Wenn der Spieler sie betreten hat,
bewegen sie sich mit ihm durch die Rume. 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
knnen. 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 lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume weniger dicht beieinander zu stehen.

>steig vom pferd
Ok

>schau
WALD
Fast undurchdringliches Unterholz lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume weniger dicht beieinander zu stehen.
Ein Pferd.

>steig aufs pferd
Ok

>schau
WALD (auf einem Pferd)
Fast undurchdringliches Unterholz lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume 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 knnen, wird ein Fortbewegungsmittel nicht in der
Raumbeschreibung erwhnt, 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 auerhalb des den
Spieler enthaltenen Objekts befinden (In einem Auto oder einer Telefonzelle
kann man z.B. schlecht eine auf der Strae liegende Mnze aufheben).

5.4 Weitere Rume
-----------------
Einen Wald haben wir bereits in 4.3 erzeugt. Fr unser Mini-Adventure
brauchen wir noch vier weitere Rume: 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 Hhle liegen. Im Osten plazieren wir eine
Schatzkammer, die durch eine Tr zunchst versperrt wird. Die Karte sieht
dann wie folgt aus:

   Hgelgrab/Tor.....Wald
       .
   Treppe nach unten
       .
   Grabkammer...Tr/Schatzkammer
  .
Hhle

Beginnen wir mit der Deklaration der Konstanten fr die neuen Rume:

hill_id    = 14;
grave_id   = 15;
chamber_id = 16;
cave_id    = 17;

Nach der Initialisierung des Waldes knnen jetzt die neuen Rume 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 Hhle.\n');
END;
NEW(room,Init(chamber_id,'-Schatzkammer#n'));
WITH room^ DO
BEGIN
  AddText('In dieser Kammer lagerten die Grabbeigaben des Knigs.\n');
END;
NEW(room,Init(cave_id,'-Hhle#n'));
WITH room^ DO
BEGIN
  AddText('Sie befinden sich in der kleinen Hhle im Sdwesten der '+
          'Grabkammer. Diese Hhle wurde offensichtlich nicht von den '+
          'Zwergen angelegt, als sie das Grab schufen. Die Wand der '+
          'Grabkammer wurde fr die Hhle durchbrochen. Ihre Wnde ');
  AddText('sind mit scheulichen Symbolen berst, 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 erwhnen soll,
ob das Tor geffnet oder geschlossen ist. Diese Beschreibung folgt in 5.6.
Wenden wir uns zunchst den Dingen in der Grabkammer zu. Der Sarkophag stellt
einen Behlter dar. Er enthlt 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 '+
          'Knig geblieben ist. Wenn Sie genau hinschauen, entdecken Sie '+
          'an dem Knochen tiefe Kratzer, die nur von sehr scharfen '+
          'Zhnen stammen knnen.\n');
END;

5.5 Variable Objektbeschreibungen
---------------------------------
Der Knochen soll bei der Beschreibung des Sarkophags erwhnt 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 gendert werden:

item : PnItem;

Jetzt folgt die Neudefinition von TnItem.MyText, die variable
Beschreibung des Sarkophags enthlt. 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 gemeielt. 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 Zwergenknig 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 Hhle finden kann. Als Schlssel dienende Objekte mssen vor
der Initialisierung der entsprechnenden Schlsser 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 Rumen. Jede von ihnen
braucht eine eigene ID. Bei umfangreicheren Karten sollten den Verbindungen
aussagekrftigere 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 zunchst 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. Zustzlich zu den zehn
   Himmelsrichtungen (north, northeast usw.) knnen 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 erwhnt 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 schlieen 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 geffnet oder geschlossen ist. Hierbei wird hnlich wie
bei der Neudefinition von TItem.MyText vorgegangen, mit dem Unterschied, da
diesmal ein Nachfolger von TRoom bentigt wird. Im Type-Abschnitt wird die
Deklaration

TnRoom = OBJECT(TRoom)
           PROCEDURE MyText; VIRTUAL;
         END;
PnRoom = ^TnRoom;

hinzugefgt. Im Var-Abschnitt wird der Zeiger room gendert:

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 Zwergenknigs. ');
                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 enthlt Zeiger auf die
Verbindungen in den entsprechenden Himmelsrichtungen. Mit der Abfrage

IF gate[south]<>NIL

knnen 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 knnen Sie ber lock auf
die Methoden von TLock zugreifen.

Wenn Sie jetzt einmal demo4.pas starten, ist folgender Dialog mglich:

WALD (auf einem Pferd)
Fast undurchdringliches Unterholz lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume weniger dicht beieinander zu stehen.

>w
LICHTUNG (auf einem Pferd)
Ein kleiner Hgel erhebt sich in der Mitte einer Lichtung ber das Grab eines 
Zwergenknigs. 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 Hhle.

>us sarkophag
In der Ihnen unbekannten Zwergensprache ist ein Text in die Seiten des 
Sarkophags gemeielt. 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 Knig geblieben 
ist. Wenn Sie genau hinschauen, entdecken Sie an dem Knochen tiefe Kratzer, 
die nur von sehr scharfen Zhnen stammen knnen.

>sw
HHLE
Sie befinden sich in der kleinen Hhle im Sdwesten der Grabkammer. Diese 
Hhle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab 
schufen. Die Wand der Grabkammer wurde fr die Hhle durchbrochen und die 
Wnde der Hhle sind mit scheulichen Symbolen berst, 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 Hhle.

>ffne die tr mit dem schlssel
Sie ffnen die Eichentr.

>o
SCHATZKAMMER
In dieser Kammer lagerten die Grabbeigaben des Knigs.

Wenn Sie wollen, knnen Sie in der Schatzkammer noch verschiedene Schtze
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 Rume knnen 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 auerhalb des Behlters oder Fahrzeugs befinden, in dem der
Spieler sich befindet. Auch in anderen Fllen kann ein Objekt fr den Spieler
unerreichbar sein. Jeder Nachfolger von TBasic verfgt ber die Methode

TBasic.Scope : BYTE

An ihrem Rckgabewert knnen 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 auer 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 Behlter 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) lt dem Spieler
eine etwas zu groe Bewegungsfreiheit: Nachdem er vom Pferd gestiegen ist
und das Tor geffnet 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 zunchst 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 Behlter 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 knnen.

Die einzelnen Ereignisse knnen von einer normalen Spielereingabe, einem
Metaverb (s. 7.5) oder einem Debugkommando (s. 7.6) ausgelst werden und
haben folgende Bedeutung:

close_ev     - Ein Schlo schlieen
dance_ev     - Spieler tanzt
dec_ev       - Debugkommando .dec
drink_ev     - Spieler trinkt Objekt
eat_ev       - Spieler it Objekt
enter_ev     - Spieler betritt Objekt
examine_ev   - Spieler untersucht Objekt
give_ev      - Spieler gibt NPC Objekt
go_ev        - Spieler verlt Raum
inv_ev       - Metaverb #i
drop_ev      - Spieler legt Objekt weg
jump_ev      - Spieler springt in die Luft
kill_ev      - Spieler ttet NPC
kiss_ev      - Spieler kt NPC
leave_ev     - Spieler verlt zuvor betretenes Objekt
lista_ev     - Debugkommando .browse
lists_ev     - Debugkommando .list
load_ev      - Metaverb #laden
lock_ev      - Schlo abschlieen
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 schlft
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 knnen bis zu zwei Objekte beteiligt sein. Die betroffenen
Objekte erwhnt 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 erwhnt, knnen 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 zunchst 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 zustndig ist, wird die Methode TGame.HandleEvent() aufgerufen.
Folgende Tabelle zeigt, welcher Objekttyp fr welches Ereignis zustndig
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
gehren 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 gehrende 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 abhngt. 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 zustndigen Objekttyps berschreiben, in einer
Case-Anweisung das Ereignis abfangen und entscheiden, ob die Ausfhrung in
HandleEvent() fortgesetzt werden soll. Sie knnen 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 knnen Sie hier nicht hinein.\n');
              END;
    END;
  END;
  BeforeAction:=ok;
END;

Ausprobieren knnen 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 verndern 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 whrend der Ausfhrung eines Ereignisses bereits ein Text ausgegeben
wurde. Fhrt ein Ereignis nicht zur Ausgabe irgendeines Textes, so gibt der
Parser anschlieend 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 Fllen kann AfterAction() die Ausfhrung eines Ereignisses
also verhindern oder ndern.

Whrend der Ausfhrung eines Ereignisses knnen Sie auch weitere Ereignisse
auslsen, indem Sie die Methode HandleEvent() eines beliebigen Objekts mit
der Variable event und einem vernderten 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 zunchst das Ereignis
open_ev ausgelst. 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 knnen. 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 Grobuchstaben erfolgt. Es gilt die
   in TGame.Init() (s. 4.5) mit dem Parameter _upsize angegebene
   Voreinstellung.

>> lmem - Gre 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 durchblttert 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 Wrter enthalten, weitere Wrter werden ignoriert. Diese Zahl knnen Sie
mit der in tstring.pas deklarierten Konstante maxbuf erhhen. Die Satzzeichen
? . und ! knnen 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 knnen 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, aufwrts, rauf, hoch, unten, abwrts und runter.

Der Parser versteht die Numerale von zwei bis zehn. Mit ihnen knnen 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 unverstndlich sind Bindewrter (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, knnten 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 ausgelst. Der Konstruktor von
TGame ruft die Methode TGame.ReadLib auf, die eine Liste mit Verben, das sog.
Wrterbuch, anlegt. Zustzlich zu jedem Verb werden im Wrterbuch auch eine
zugehrige Grammatik sowie das auszulsende Ereignis gespeichert. Parse()
vergleicht nun den Eingabestring mit jedem Eintrag im Wrterbuch. Wenn in
der Eingabe ein Verb aus dem Wrterbuch gefunden wird und die Eingabe auch
mit der zum Verb gehrenden Grammatik bereinstimmt, wird das entsprechende
Ereignis bei den beteiligten Objekten ausgelst.

Das Wrterbuch 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 auslsen soll. Sie
   knnen auch mehrere synonyme Wrter 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 gehrende Grammatik, mit der die Eingabe
   bereinstimmen mu, um ein Ereignis auszulsen. Es knnen auch mehrere
   durch ein Semikolon getrennte Grammatiken angegeben werden. Wenn Sie nur
   einen Leerstring angeben, gengt die Eingabe des Verbs, um ein Ereignis
   auszulsen.

>> _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 Wrter dem Verb im Eingabestring folgen mssen. Sie knnen 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 Wrter 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 Prposition. Das Wort mu nur in der Grammatik in
   eckigen Klammern gefat sein. Wenn der Parser in der Eingabe auf ein Wort
   stt, 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. Grere Zahlen knnen 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 knnen 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 auslsen
knnen. Umgekehrt knnen gleiche Verben auch zu unterschiedlichen Ereignissen
fhren, wenn ihre Grammatiken sich unterscheiden.

Einige Verben bentigen zwei Substantive (oder knnen wahlweise mit zwei
Substantiven verwendet werden). Um ein Objekt in einen Behlter 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 enthlt, wird das Ereignis grundstzlich
von dem zum ersten Substantiv gehrenden 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 Behlter y legt. In
einigen Fllen knnen die Substantive x und y jedoch vertauscht werden, ohne
da sich die Bedeutung der Eingabe verndert. Ein Beispiel dafr sind die
gleichbedeutenden Eingaben "verschliee die Truhe mit dem Schlssel" und
"verschliee mit dem Schlssel die Truhe". In diesen Fllen mssen zwei
Grammatiken, jeweils eine fr eine der beiden mglichen Reihenfolgen,
angegeben werden. Wenn in einer Grammatik das zweite Objekt das Ereignis
ausfhren soll, so mu dem entsprechenden Token das Klammeraffenzeichen (@)
vorangestellt werden:

AddVerb('verschliee','[mit]+held+@reachable',lock_ev);
AddVerb('verschliee','reachable+[mit]+@held',lock_ev);

Bevor Sie in Ihrem Programm TGame.Run aufrufen, knnen Sie dem Wrterbuch mit
AddVerb() eigene Verben, Grammatiken und Ereignisse hinzufgen. Ein ganz
neues Wrterbuch knnen Sie anlegen, wenn Sie vor AddVerb() die von ReadLib
angelegte Liste mit der Methode

TGame.ClearDictionary

lschen. Alle Verben eines bestimmten Ereignisses knnen Sie mit der Methode

TGame.DelVerb(_event : BYTE)

lschen.

In Readlib werden Sie vielleicht die Himmelsrichtungen fr die Bewegung
(go_ev) vermissen. Diese werden von Parse() unabhngig von der Verbenliste
verarbeitet, da es mit dem System des Wrterbuchs nicht mglich 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
enthlt 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 - besttigt 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 ausgelst
   wurde. Ansonsten zeigt who auf das als NPC dienende Objekt (s. 8.1).

>> first:PNoun - ein Zeiger auf einen Record vom Typ TNoun, dieser enthlt
   weitere Informationen ber das erste Substantiv der Eingabe.

>> second:PNoun - ein Zeiger auf einen Record vom Typ TNoun, dieser enthlt
   weitere Informationen ber das zweite Substantiv der Eingabe.

>> data:Pointer - einen Zeiger auf weitere Daten, die fr die Ausfhrung des
   Ereignisses bentigt werden. Dieser Zeiger wird nur bei den Ereignissen
   go_ev und tell_ev (s. 8.1) bentigt, 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 enthlt 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 enthlt ein Adjektiv, wenn das Feld True ist.

>> number:Integer - eine vom Spieler angegebene Menge.

>> xroom:PRoom - ein Zeiger auf ein zum Substantiv gehrendes Objekt vom
   Typ TRoom.

>> xlink:PLink - ein Zeiger auf ein zum Substantiv gehrendes Objekt vom
   Typ TLink.

>> xitem:PItem - ein Zeiger auf ein zum Substantiv gehrendes 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 mglichen Meldungen, informiert diese Methode
den Spieler, da seine Eingabe nicht ausgefhrt werden kann. Mgliche Grnde
fr den Aufruf von ParserError() sind z.B. unbekannte Wrter in der Eingabe,
der Versuch des Spielers, mit einem nicht erreichbaren Objekt zu hantieren
oder eine bereits abgeschlossene Tr erneut zu verschlieen. 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 lt. Die Bedeutung dieses Strings hngt
   vom jeweiligen Fehlercode ab.

>> i:Byte - Anzahl der vom fehlgeschlagenen Ereignis betroffenen Objekte.
   Der entsprechende Objektname wird abhngig 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 mchten, 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 unabhngige 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, knnen 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 Rumen, 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 Rume an. Im Debugmodus
   werden alle Rume angezeigt, egal ob sie bereits besucht wurden. Der
   aktuelle Raum erscheint dabei immer in der Mitte der Karte, deren Gre
   von der Zahl der Bildschirmzeilen und Spalten begrenzt wird.

>> #laden - ldt einen zuvor gespeicherten Spielstand. Wurden bereits
   mehrere Spielstnde gespeichert, wird ein Men mit den gespeicherten
   Spielstnden angezeigt, von denen einer ausgewhlt 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
   zustzlich 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 knnen Sie alle Objekte anzeigen
   lassen, mit ESC beenden Sie die Ausgabe.

>> .dec - gibt alle Deklinationen des angegebenen Objektnamens aus. Sie knnen
   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 Vernderung
   des Scopes im Spiel beobachtet werden kann. Wenn Sie die Methode Inspect,
   die alle Nachfahren von TBasic besitzen, berschreiben, knnen Sie auch
   die Vernderung 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 zunchst 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 Zustnden entsprechen die
Ausprgungen 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)

lt sich der Zustand eines Objekts ndern, mit

TItem.GetMatter : TMatter

lt 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 Fangzhnen '+
          'und Klauen haben Sie bisher nur in Tavernen gehrt 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 Hhle:

HHLE
Sie befinden sich in der kleinen Hhle im Sdwesten der Grabkammer. Diese 
Hhle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab 
schufen. Die Wand der Grabkammer wurde fr die Hhle durchbrochen und die 
Wnde der Hhle sind mit scheulichen Symbolen berst, die sich stark von 
den Schriftzeichen der Zwerge auf dem Sarkophag unterscheiden.
Ein Schlssel.
Ein Troll.

>troll, "wer sind sie?"
Der Troll hrt Ihnen nicht zu.

Die Meldung "x hrt 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 knnen:

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 Zhne und '+
                                'schaut Sie mit einem sehr beunruhigenden '+
                                'Blick an.\n');
                END;
    END;
  END;
  BeforeAction:=ok;
END;

Um den String mit der wrtlichen Rede des Spielers zu erreichen, mssen Sie
die Pointer-Variable data des Records event in einen String umwandeln. In
unserem Fall hngt 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 knnen. 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:

HHLE
Sie befinden sich in der kleinen Hhle im Sdwesten der Grabkammer. Diese 
Hhle wurde offensichtlich nicht von den Zwergen angelegt, als sie das Grab 
schufen. Die Wand der Grabkammer wurde fr die Hhle durchbrochen und die 
Wnde der Hhle sind mit scheulichen Symbolen berst, 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 knnen Sie entnehmen, ob ein
Ereignis vom Spieler oder von einem NPC ausgelst wurde. Wurde ein Ereignis
vom Spieler ausgelst, ist dieser Zeiger gleich NIL, ansonsten zeigt er auf
den handelnden NPC. Wenn der Troll fr die Anweisungen des Spielers taub
sein soll, knnen 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 knnen. Um es dem Spieler nicht zu einfach zu machen, ttet 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 tten, einen Schlssel finden und die
Schatzkammer ausrumen (wenn Sie da Schtze reinlegen). Fr die Bewltigung
bestimmter Aufgaben knnen Sie dem Spieler Punkte geben. Benutzen Sie hierfr
die Methode

TPlayer.IncScores(ds : WORD)

Mit der Methode

TPlayer.GetScores : WORD

knnen Sie die aktuelle Punktzahl abfragen. Das Ereignis kill_ev knnen Sie
mit dem Programm demo7.pas ausprobieren.

8.2 Leben, Tod und Sieg des Spielers
------------------------------------
In dem Mini-Adventure knnen Sie dem Troll auch die Chance geben, den Spieler
zu tten. Wenn es Ihnen gefllt, knnen 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
knnen Sie mit der Methode

TPlayer.SetState(s : BYTE)

ndern. Tten 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 Rtsel gelst oder eine bestimmte Punktzahl
erreicht, knnen Sie das Spiel mit SetState(victory_ps) beenden. Hiernach
wird die Methode

TPlayer.Victory

aufgerufen, die normalerweise ebenfalls die erreichte Punktzahl ausgibt.
Beide Methoden knnen 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 Dmon 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 Dmon einmal aktiv, wird nach jeder
Spielrunde die Methode

RunDaemon; VIRTUAL;

aufgerufen, die Sie berschreiben mssen, damit sie etwas tut. In dieser
Methode knnen 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 knnen Sie mit StopTimer
auch vorzeitig abschalten (wenn es dem Spieler z.B. gelingt, eine Zeitbombe
zu entschrfen). Zeitschalter und Dmon 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 Dmonen und Zeitschalter auch bercksichtigen
knnen. 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 knnen 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 whrend einer Runde vergehen.

Mit einem Dmon knnen wir das Mini-Adventure noch mal erweitern. Bislang
steht der Troll nur regungslos in der Hhle herum, jetzt soll er die
Initiative ergreifen, sobald der Spieler die Hhle betritt. Der Spieler wird
in der Hhle vom Troll angegriffen und gettet, wenn er flchten will.
Erschlgt der Spieler den Troll, so findet er den Schlssel, der dem toten
Troll aus dem Wams fllt. Zunchst mu statt der statischen Beschreibung der
Hhle eine variable Beschreibung in TnRoom.MyText erfolgen. Hier wird
festgestellt, ob der Troll noch lebt. Ist er tot, wird die normale
Beschreibung der Hhle ausgegeben. Lebt der Troll noch, so wird dem Spieler
der Angriff mitgeteilt und der Dmon 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 gettet. Wenn der Spieler den Troll
erschlgt, was nur mit dem Schwert mglich ist, wird der Dmon des Trolls
abgeschaltet und der Schlssel in die Hhle 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 schlgt Ihnen beinahe das Schwert aus der '+
              'Hand.\n');
    2 : Print('Sie knnen 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 tatschlich ein Schwert
hlt, soll bei der zweiten Ausgabe einmal verzichtet werden. Beachten Sie,
da Sie in RunDaemon mit GetID feststellen mssen, welcher Dmon grade
ausgefhrt wird, wenn mehrere Hintergrundprozesse aktiv sind. Eingeschaltet
wird der Dmon des Trolls in TnRoom.MyText, wenn der Spieler die Hhle
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 Hhle 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 Hhle im '+
                               'Sdwesten der Grabkammer. Diese Hhle '+
                               'wurde offensichtlich nicht von den Zwergen '+
                               'angelegt, als sie das Grab schufen. Die '+
                               'Wand der Grabkammer wurde fr die Hhle '+
                               'durchbrochen und die Wnde ');
                         Print('der Hhle sind mit scheulichen Symbolen '+
                               'berst, 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 erwhnt wird. Erst wenn
er stirbt, wird seine Leiche mit ListMe(True) wieder in die automatische
Raumbeschreibung aufgenommen. Wenn der Dmon nun aktiv ist, prfen wir, ob
der Spieler die Hhle 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;

Schlielich soll der Spieler den Troll mit dem Schwert tten, dessen Dmon
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, fllt 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 bloen Hnden??\n');
                END;
    END;
  END;
  BeforeAction:=ok;
END;

8.4 Licht und Dunkel
--------------------
Rume werden von Textopia automatisch beleuchtet, so da der Spieler in ihnen
immer sehen kann. Mit der Methode

TRoom.SetLight(_light : BOOLEAN)

knnen Sie die automatische Beleuchtung aus- und wieder einschalten. Mit der
Methode

TRoom.HasLight : BOOLEAN

knnen Sie feststellen, ob der Spieler in einem Raum sehen kann. Wenn der
Spieler ein Item mit dem Attribut shining_at bei sich trgt, 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 unbeeinflut, 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. Zunchst wird die Fackel erzeugt, dazu
wird im Abschnitt Const die Zeile

torch_id = 27;

hinzugefgt. Da zum Anznden und Lschen der Fackel zwei neue Verben
und Ereignisse gebraucht werden, knnen 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
"lschen" dem Wrterbuch bekanntgemacht werden:

AddVerb('znde;znd','held+[an]',fireon_ev);
AddVerb('lsch;lsche;mache;mach','held;held+[aus]',fireoff_ev);

Die neuen Verben werden von TItem.HandleEvent() nicht bercksichtigt und
knnen daher nicht in TnItem.BeforeAction() abgehandelt werden. Daher
berschreiben wir HandleEvent() fr die neuen Ereignisse und geben via Else
alle anderen Ereignisse an den Vorgnger 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 gelscht 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 bentigt, um die Beleuchtung im Grab ein- und auszuschalten,
wenn der Spieler das Tor ffnet und schliet. 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 strmt wieder ins Grab.\n');
                 END;
    END;
  END;
  BeforeAction:=t;
END;

Da Schlsser 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
verndert, daher sollte AfterAction() weitere Meldungen des Parsers
("Sie ffnen/schlieen 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;

Schlielich 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 knnen hier kaum etwas sehen...\n');
END;

Wenn Sie jetzt demo9.pas starten, sollte der folgende Dialog mglich sein:

WALD (auf einem Pferd)
Fast undurchdringliches Unterholz lt Sie nur mhsam weiterkommen. Weiter 
westlich scheinen die Bume weniger dicht beieinander zu stehen.
>w

LICHTUNG (auf einem Pferd)
Ein kleiner Hgel erhebt sich in der Mitte einer Lichtung ber das Grab eines 
Zwergenknigs. 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 Hhle.

>ffne den rucksack
Sie ffnen den Rucksack.

>nimm die fackel aus dem rucksack
Sie nehmen eine Fackel.

>znde die fackel an
Ok

>schliee 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 Hhle.

>lsche die fackel
Ok

>schau
Finsternis
Sie knnen hier kaum etwas sehen...

>znde die fackel an
Ok

>sw
HHLE
Als Sie die Hhle betreten springt Ihnen mit markerschtterndem Gebrll ein 
Troll entgegen!

>tte den troll mit dem schwert
Mit einem schrecklichen Schrei stirbt der Troll, als Ihr Schwert ihn trifft. 
Als er zu Boden strzt, fllt 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 Hhle.

>ffne die tr
(mit dem Schlssel)
Sie ffnen die Eichentr.

>o
SCHATZKAMMER
In dieser Kammer lagerten die Grabbeigaben des Knigs.

>#ende

brigens: Taschenlampen lassen sich leichter programmieren als Fackeln, weil
sie ein- und ausgeschaltet werden und daher keine neuen Verben bentigen. Ich
fand aber, da Fackeln sich besser zu Demonstrationszwecken eignen.

Das Mini-Adventure ist natrlich noch nicht perfekt. Fgen Sie ihm weitere
Rume, 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 Spielstnde werden
in der Datei *.sav gespeichert, was das Auslesen aus dem Plattenverzeichnis
erspart. Die einzelnen Spielstnde werden in untypisierten Dateien mit der
Blocklnge 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
knnen. 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 anschlieend den Vorgnger 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, whrend
rechts die Punktzahl und die Zahl der bisherigen Spielrunden von einem
Schrgstrich getrennt ausgegeben werden. Die Informationen hierber sind in
TPlayer gespeichert, weshalb diese Methoden nicht zu TGame gehren. Sie
knnen diese Methoden berschreiben, um beliebige andere Informationen in der
Statuszeile auszugeben.

Die Bildschirmfarben knnen 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,
whrend fr die Hintergrundfarbe nur die Farben 0 (Black) bis 7 (Lightgray)
verfgbar sind. Bei der Textausgabe knnen Sie mit den Sonderzeichen (s. 4.4)
\h und \l die Farbintensitt 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, knnen Sie die Funktionstasten F1 bis
F10 mit hufig bentigten 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 verndern. Jede Option beginnt dabei mit
einem Minuszeichen gefolgt von einem Buchstaben und einer Zahl (<n>) oder
einem Schalter (<x>). Schalter werden mit dem Pluszeichen ein- und mit dem
Minuszeichen ausgeschaltet. Die Optionen sind:

>> F<n> - bestimmt die Textfarbe

>> B<n> - bestimmt die Hintergrundfarbe

>> R<n> - bestimmt die Zahl der Bildschirmzeilen. Diese Zahl mu am
   Monitor bereits eingestellt sein, Textopia ndert keine Fenstergre oder
   Bildschirmauflsung. Voreingestellt sind 25 Zeilen.

>> C<n> - bestimmt die Zahl der Zeichen pro Bildschirmzeile. Voreingestellt
   sind 80 Zeichen.

>> S<x> - bestimmt, ob das Spiel mit oder ohne Statuszeile gestartet wird.

>> V<x> - bestimmt, ob in bereits bekannten Rumen die ausfhrliche
   Raumbeschreibung oder nur der Raumname ausgegeben wird.

>> U<x> - bestimmt, ob die Eingabe in Grobuchstaben umgewandelt wird.

Die Option -? gibt einen kurzen Hilfetext aus. Die Bercksichtigung der
Kommandozeilenoptionen knnen Sie mit der Zeile

useroption:=False;

verhindern.

8.8 Umlaute
-----------
DOS, Windows und Linux gebrauchen unterschiedliche Zeichenstze. 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
groen Umlaute definiert. Die Werte der Konstanten sind dabei vom Compiler
abhngig, 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 knnen in Ihren Texten die Umlaute direkt
schreiben. Andernfalls haben Sie zwei Mglichkeiten, die Texte in Ihrem Spiel
an unterschiedliche Zeichenstze anzupassen. Sie knnen 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, knnen 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 mchten. Folgende Warnungen sind mglich:

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 knnen Schlsser zugewiesen werden.

xxx und yyy sind bereits verbunden
Sie haben versucht, zwei Rume 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() knnen Objekte nur in Rume oder Behlter bewegt
werden.

Objekt xxx kann nicht bei Objekt yyy positioniert werden
Im Konstruktor von TItem mu ein Raum oder ein Behlter als Position
angegeben werden.

Index xxx existiert nicht
Es wurde auf einen ungltigen Index von TObjList zugegriffen.

Ungltiger Spielerstatus
Der Methode TPlayer.SetState() knnen 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 Rume 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);
Lscht 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;
Whlt zufllig 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 Groschrift. Bercksichtigt auch Umlaute.

FUNCTION Upper(s1 : STRING) : STRING;
Wandelt einen String in Groschrift. Bercksichtigt auch Umlaute.

9.2 Funktionen und Prozeduren von tio.pas
-----------------------------------------
Auch hier werden nur Datentypen, Prozeduren und Funktionen aufgefhrt, die
nicht ausschlielich 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 Wrterbuch }
  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 knnen 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 knnen auch dort behandelt werden.

FUNCTION HasDaemon : BOOLEAN;
Ist True, wenn der Dmon 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;
Ldt 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;
Lscht dynamische Variablen.

9.5 Methoden von TItem
----------------------
TItem ist ein Nachfolger von TBasic und kann fast alle mglichen und
unmglichen 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 Behlter des Objekts.

FUNCTION GetContent : BYTE;
Liefert Anzahl enthaltener Objekte.

FUNCTION GetLocation : PRoom;
Liefert den Raum, in dem sich das Objekt (und evtl. sein Behlter) 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
erwhnt 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 erwhnt. Ansonsten kann es in der Raumbeschreibung erwhnt 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 Behlter sein. BeforeAction() und
AfterAction() werden nicht aufgerufen. Die Positionsvernderung 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 Prposition (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 Rume 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 geffnet
(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
aufhlt, oder notinroom_sd, wenn er sich in einem anderen Raum aufhlt.

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 Rume.

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
erwhnt 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 Behltern 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
abschlieen lt.

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 bentigten Schlssel trgt.

FUNCTION IsOpenable : BOOLEAN;
Stellt fest, ob sich das Schlo ffnen lt.

FUNCTION IsCloseable : BOOLEAN;
Stellt fest, ob sich das Schlo schlieen und abschlieen lt.

PROCEDURE Load(VAR h : File); VIRTUAL;
Speichert aktuellen Objektzustand.

PROCEDURE SetCloseable(s : BOOLEAN);
Bestimmt, ob sich das Objekt schlieen und/oder abschlieen lt.

PROCEDURE SetOpenable(s : BOOLEAN);
Bestimmt, ob sich das Objekt ffnen lt.

PROCEDURE SetState(s : TState);
Bestimmt den Zustand des Schlosses: open, closed oder locked.

PROCEDURE Store(VAR h : File); VIRTUAL;
Ldt gespeicherten Zustand.

PROCEDURE View; VIRTUAL;
Gibt Informationen im Objekt-Betrachter aus.

DESTRUCTOR Done; VIRTUAL;
Lscht 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);
Erhht die Punktzahl des Spielers um ds Punkte.

PROCEDURE Load(VAR h : File); VIRTUAL;
Ldt 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);
Verndert den Spielerstatus. SetState(dead_ps) ttet den Spieler, whrend
ihn SetState(victory_ps) das Spiel gewinnen lt.

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;
Lscht 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 Wrterbuch 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 auslst. 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;
Ermglicht die Vernderung des Eingabestrings, bevor er an den Parser
bergeben wird.

PROCEDURE ClearDictionary;
Lscht das gesamte Wrterbuch. Die Richtungsanweisungen (norden, sden,
etc.) bleiben erhalten, da sie unabhngig vom Wrterbuch bearbeitet
werden.

PROCEDURE DelVerb(_event : BYTE);
Entfernt alle Verben mit dem Ereignis _event aus dem Wrterbuch.

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 whrend 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 whrend 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
