Kurzdokumentation zu deform

Einbinden der Dateien

Im Quelltext des Spiels müssen die Dateien wie folgt eingebunden werden:

!... Story und Headline
!... Ersetzen von Routinen wenn gewünscht	

Include "Parser";
Include "VerbLib";	

!... Hauptteil der Definitionen	

Include "GermanG";	

!... Eigene Grammatikdefinitionen

Obwohl nur drei Dateien direkt mit Include eingebunden werden, werden fast alle *.h-Dateien benutzt; sie werden aus den anderen drei aufgerufen:

Datei Inhalt
Parser.h Hauptmodul
linklpa.h Definition von Properties und Attributen
→ (linklv.h) (nur beim Verlinken vorkompilierter Module)
parserm.h Parser (Analyse der Eingabe)
German.h Ausgaberoutinen und Lib-Texte
   
VerbLib.h Spielwelt
verblibm.h Aktionen und Listen
   
GermanG.h Grammatik
→ (infix.h) Infix-Debugger, nur mit -X (Siehe §7 im DM4)

Die Schreibweise sollte so sein, wie hier angegeben: Die drei direkt eingebundenen Dateien sowie German.h haben Großbuchstaben, alle anderen sind durchweg klein geschrieben. (Das ist unter Windows egal, aber andere Systeme unterscheiden Groß- und Kleinschreibung bei Dateien.)

In deform wird nicht language_name, sondern immer German.h eingebunden. (Wer die Library Messages komplett oder zum großen Teil ersetzen möchte, sollte daher LanguageLM mit Replace ersetzen und neu definieren.)

In der Vergangenheit ist es öfters zu Konfusionen gekommen, da sich die Dateien der deutschen Lib von den enlischen unterscheiden, aber bis auf German.h und GermanG.h, die im Original English.h und Grammar.h heißen, die selben Namen haben. Daher wird empfohlen, die Lib-Dateien in einem separaten Ordner aufzubewahren, und die Lib-Erweiterungen in einem zweiten. Der Compiler wird dann aufgerufen mit

inform +include_path=./lib/deform/,./lib/opt/ spiel

(Der Compiler muss übrigens nicht unbedingt inform heißen.) Mit dem neuen Compiler Inform 6.30 ist es möglich, die ICL-Kommandos (Inform Control Language) mit einer Art Shebang, dem Kommentarzeichen ! gefolgt von einem Prozentzeichen, in den Kopf der Datei zu schreiben:

!% -G                             ! Glulx, bitte
!% -DS                            ! Volles Debug-Programm
!% +include_path=./lib/deform/    ! Lib-Path deform
!% $MAX_OBJECTS=600               ! Viele Objekte

Diese Anweisungen müssen die ersten im Quelltext sein.

In jedem Fall wird am Ende von GermanG.h sichergestellt, dass nur deform- Dateien eingebunden wurden und dass alle Dateien von derselben Release sind.

Low Strings

Diese Lib macht Gebrauch von den so genannten Low Strings, die mit der Notation @xx in Texten eingebunden werden können. Es gibt 32 dieser Strings, folgende werden von der Lib benutzt:

String Bedeutung
@00 Adjektivendung
@01 Endung -n für Plural-Substantive im Dativ
@02 Endung -en für männl. Substantive
@03 Endung -s für Substantive im Genitiv
@04 Endung -es für Substantive im Genitiv
   
@30 ›ss‹ für schweizerische Spiele,
Eszett für alle anderen
@31 Eszett für alte Rechtschreibung,
›ss‹ für neue Rechtschreibung
und schweizerische Spiele

Die ersten Strings ändern sich ständig, sie werden bei jedem Aufruf von (den), (dem), (eine) usw. angepasst, siehe unten.

Die Eszett-Strings bestimmen das Erscheinungsbild der Texte. Wenn man sich für eine Variante entscheidet, kann man seine eigenen Texte einfach mit dieser Variante schreiben. Wenn man es zulassen möchte, dass der Spieler selbst zwischen den Varianten umschalten kann, sollten die eigenen Texte ebenfalls die Low Strings verwenden, etwa:

print_ret "Du reibst ein wenig Ru@30 von dem
          Kessel und mu@31t niesen.";

Das ist allerdings viel Arbeit.

Veröffentlichte Erweiterungen der Lib sollten diesen Mechanismus aber implementieren, damit sie universell einsetzbar bleiben.

Schema für die Ausgabe von Objektnamen

Jedes Objekt, das im Spiel sichtbar ist, sollte einen short_name und eines (und nur eines) der Attribute pluralname, male, female oder neuter besitzen, um seinen Genus zu kennzeichnen. Der short_name kann eine Routine oder ein String sein.

Objekte ohne Adjektive kommen in der Regel mit einem einfachen String aus. Für kompliziertere Objekte gibt es zwei Methoden:

Traditionell

Zu jedem Objekt wird eine Property dekl angegeben, die den Deklinationsmodus festlegt. Die folgende Deklinationstypen, die in der offiziellen Lib verwendet werden, werden auch von deform verstanden:

No. Genera Singular Plural
1 m, n -(e)s im Genitiv -n im Dativ
2 m, n -s im Genitiv -n im Dativ
3 m, n -(e)s im Genitiv
4 m, n -(e)s im Genitiv -n im Dativ
5 m, n -(e)s im Genitiv
6 m -en außer im Nominativ
7 f -n im Dativ
8 f -n im Dativ
9 f
10 f

Die Deklinationen, die hier als gleich beschrieben werden, unterscheiden sich in der Pluralbildung. Da Inform aber nie den Plural selbständig bilden muss, ist diese Unterscheidung praktisch nutzlos. Daher gibt es jetzt vier neue Deklinationsformen:

Dekl. Beispiel
Dativ_n die Zwerge, den Zwerge-n
Akkusativ_en der Student, den/dem/des Studenten
Genitiv_s der/den/dem Apfel, des Apfel-s
Genitiv_es das/dem Haus, des Haus-es

Wenn short_name ein String ist, werden passende Endungen einfach angehängt:

Object with short_name "Regale", dekl 2,
  has pluralname;

Wenn short_name eine Routine ist, müssen die beiden Routinen print_adj und print_subst verwendet werden, um die passenden Endungen anzuhängen:

Object
  with short_name [;
           print (print_adj) "alt",
               (print_subst) " Regale",
               " aus Buchenholz";
        ], dekl 2, has pluralname;

Die Property dekl kann auch (analog zum alten suffix) ein Feld mit vier Strings für die Endungen im Nominativ, Genitiv. Dativ und Akkusativ oder eine Routine, der der Fall übergeben wird, sein. Für die Regale:

dekl "" "" "n" "", ...

oder

dekl [kasus; if (kasus==Dat) print "n"; ], ...

Bei Pluralobjekten muss der short_name im Plural angegeben werden, der Plural wird nicht, wie in der offiziellen Lib, durch dekl bestimmt. Wenn es ein Objekt »Häuser« gibt, ist short_name "Häuser", nicht "Häus"! (Der Singular wird eh nie verwendet und die Umlautumwandlung hat die offizielle Lib auch nicht beherrscht.)

Vereinfacht

Vereinfacht: Der short_name enthält Low strings für Endungen (siehe oben), typischerweise @00 für Adjektive. Die Property dekl wird nicht benötigt:

Object
  with short_name "rot@00 Grütze",
   has female;	

Object
  with short_name "leblos@00 Körper des Drachen",
   has male;

Meistens wird nur @00 benötigt. @01 und @02 werden gelegentlich bei männlichen und sächlichen Substantiven im Plural und bei männlichen Substantiven verwendet:

Object
  with short_name "alt@00 Regale@01 aus Holz",
   has pluralname;	

Object
  with short_name "Student@02",
   has male animate;

@03 und @04 werden nur benötigt, wenn Objekte im Genitiv ausgegeben werden sollen, was die Lib aber nie macht, obwohl es die Routinen (des) und (eines) gibt.

Diese Methode entspricht den ^ und ~ in T.A.G. (Und sieht wegen der kruden @-Syntax etwas hässlich aus. Die paar Zahlen lassen sich aber wohl besser merken als die Deklinationstypen.)

Besonderheiten

Es gibt ein paar Sonderkonstanten für die Objektausgabe. Die Eigenschaft article, die nur bei der Ausgabe mit unbestimmtem Artikel, zum Beispiel bei (ein) oder (einen), herangezogen wird, kann außer einem String oder einer Routine zur Textausgabe folgende Werte haben:

yours

Dem Objekt wird die passende Form von »dein« vorangestellt, zum Beispiel: »dein original elbisches Schwert«

definite

Das Objekt hat immer einen bestimmten Artikel, wie zum Beispiel »das Amulett der Ewigen Verdammnis ™«

no_article

Das Objekt wird in unbestimmten Fall ohne Artikel ausgegeben, wie bei »frische Milch« oder »Brennholz«. Der Unterschied zu proper ist, dass bei proper nie Artikel verwendet werden, auch bei der Ausgabe mit bestimmten Artikel nicht. Der Unterschied zu einem leeren articleist, dass mit no_article kein ungewolltes Leerzeichen vorangestellt wird.

Außerdem kann short_name den Wert no_short_name haben, was bedeutet, dass die Ausgabe des short_name unterdrückt wird. Artikel werden aber ausgegeben, ebenso adj und post, und nur im Zusammenhang mit diesen beiden Properties ist no_short_name sinnvoll, zum Beispiel:

Object -> Binder_Mann
  with name 'blind' 'mann',
       adj "Blind",
       short_name no_short_name,
   has male animate;

Im vereinfachten System reicht hierzu der short_name "Blind@00".

Routinen für die Ausgabe der Objektnamen

Die englische Lib definiert folgende Ausgaberoutinen:

(The) Objekt mit bestimmtem Artikel, groß
(the) Objekt mit bestimmtem Artikel
(a) Objekt mit unbestimmtem Artikel
(name) Objektname ohne Artikel

Diese Routinen stehen auch im deutschen zur Verfügung, sollten aber nicht benutzt werden. Dazu kommen Ausgaberoutinen für Verben, wie (ThatOrThose) oder (TheyreOrThats), die in dieser Lib nicht definiert sind.

In der deutschen Lib muss der Fall der Ausgabe berücksichtigt werden. Es stehen folgende Routinen zur Ausgabe zur Verfügung:

(der/des/dem/den) o

Objekt mit bestimmtem Artikel. Diese Routinen rufen DefArt(o, Fall) auf.

(GDer/GDes/GDem/GDen) o

Objekt mit bestimmtem Artikel, der erste Buchstabe wird groß ausgegeben, ruft DefArt(o, Fall) auf

(ein/eines/einem/einen) o

Objekt mit unbestimmtem Artikel, ruft IndefArt(o, Fall) auf

(kein/keines/keinem/keinen) o

Negiertes Objekt, umgeleitet zu NegativeArt(o, Fall)

(er/seiner/ihm/ihn) o

Personalpronomen, das zum Objekt passt, Wird umgeleitet zu PersonalPron(o, Fall)

(WithoutArt) o

Objekt ohne Artikel im Nominativ. Dies ersetzt (name) o und kann mit WithoutArt(o, Fall) in anderen Fällen benutzt werden.

Weiterhin gibt es einige Routinen, um Verben an ein Subjekt angepasst auszugeben:

(ist) o »ist« oder »sind«
(hat) o »hat« oder »haben«
(wird) o »wird« oder »werden«
(___t) o »t« oder »en« (das sind drei Unterstriche)
(___et) o »et« oder »en«
plur(p, s, o) String p oder String s, zum Beispiel plur ("müssen", "muss", noun);

In der Lib kann man nur Objekte mit bestimmtem Artikel groß ausgeben, da dies wohl am häufigsten werwendet wird. Manchmal will man aber auch anderes groß ausgeben. dann kann man die Routine RunCapitalised(r, o); verwenden, die die Ausgabe einer Routine groß ausgibt, zum Beispiel:

RunCapitalised(einen, noun);
" ohne richtiges Werkzeug zu schälen ist echt knifflig.";

Natürlich steht es jedem Autoren frei, sich Routinen wie GEin, GEs usw. zu definieren.

Synonyme

Um die Kontraktionen aus Präpositionen und Artikeln (ins, zum usw.) effizient zu behandeln, ohne alle mühselig von Hand mit LTI_Insert zu implementieren, gibt es ein Synonym-System. Der Autor kann eigene Synonyme definieren, indem er vor dem Einbinden der Lib einen Table Synonyms definiert, in dem zu ersetzende Vokabeln und die ersetzten Strings paarweise stehen, zum Beispiel:

Array Synonyms table
    'fuers'    "fuer das"
    'doch'     ""
    ;

Achtung, der zweite Eintrag in jedem Paar ist ein String in doppelten Anführungszeichen, der natürlich keine Großbuchstaben und Umlaute enthalten darf.

Ein ähnliches System, bei dem immer zwei aufeinanderfolgende Wörter ersetzt werden sind die »Zwillinge«, die analog dazu im Feld Twins in Dreiergruppen angegeben werden:

Array Twins table
    'prof' './/'     "prof"
    '10' './/'       "zehnte"
    ;

Endungen

Der Parser schneidet Endungen ab und berücksichtigt weder den Fall des Tokens (es gibt nur noun) noch den Genus des untersuchten Worts. Dies ist eine praktikable Vorgehensweise. Im Einzelnen:

  1. Es werden nur folgende Endungen abgeschnitten:

    Endung Vorkommen
    'e' Verben und Adjektive
    'em' Adjektive*
    'en' Adjektive* und Substantive wie »Student«
    'er' Adjektive*
    'es' Adjektive* und Genitiv-es
    's' Genitiv-s
    'n' im Dativ mancher Plurale
      *) und Possesiv- und Demonstrativpronomen

    Endungen wie ›-ern‹ werden nicht berücksichtigt, da der deform-Parser verlangt, dass Wörter sowohl für die Eingabe als auch für die Ausgabe im Plural in ihrer kompletten Form angegeben werden. Vokabeln wie 'haeus' wenn eigentlich 'haeuser' gemeint ist, sind damit hinfällig.

    Verben werden nicht per se beschnitten, sondern auch nur nach den hier beschriebenen Regeln. Die meisten Verben haben wie in der offiziellen Lib ihr End-e in der Definition abgeschnitten, das erkennt 'leg' und 'lege'. ('lage' hat in deform aber ein e, da 'lag' kein sinnvoller Imperativ ist.) Diese Vereinfachung umgeht die Suche nach dem richtigen Verb, die zwar meist trivial ist, aber in Fällen wie »fuchs, gute nacht« oder »setz dich hin. steh auf. leg dich hin« umständlich sein kann.

  2. Es wird abgeschnitten, wenn das momentane Wort unbekannt ist. Wenn der Parser also das Wort ›zwergen‹ findet, macht er folgendes:

    1. Wenn es 'zwergen' gibt, nichts.

    2. Wenn es 'zwerge' gibt, wird ein 'n' abgeschnitten.

    3. Wenn es 'zwerg' gibt, wird ein 'en' abgeschnitten.

    4. Wenn es keines der drei Wörter gibt, nichts.

  3. In jedem Fall hat der Spieler Zugriff auf die Original-Eingabe, die in orig_buffer und orig_parse steht. Mit den Routinen OriginalLength(w) und OriginalAddress(w) kann man darauf zugreifen. Diese beiden Routinen berücksichtigen, dass sich die Wörter durch Synonymbildung verschieben können: »gehe ins haus« wird zu »gehe in das haus«, OriginalAddress(4) schaut sich das dritte Wort des Originaleintrags an.

Durch das Abschneiden kann es zu Konflikten kommen. Wenn es in einem Spiel im Krankenhaus eine 'trage' gibt, so wird das Verb 'trage' nicht mehr als 'trag' erkannt. In diesem Fall sollte man die Trage als 'trag' definieren, damit beides funktioniert.

In komplexeren (aber auch selteneren) Fällen kann man das Originalwort in parse_name heranziehen und die abgeschnittene Endung betrachten.

Wenn der Debug-Modus aktiv ist, kann man mit »echo on/off« jede Zeile so ausgeben lassen, wie der Parser sie sieht, oder das wieder abstellen.

Die Konstanten zum Festegen der Beschneidung aus der offiziellen Lib, USE_OLD_GERMAN_SUFFIX_ROUTINE und GERMAN_SUFFIXES_PEDANTIC haben hier keine Bedeutung, da der deform-Parser von Haus aus pedantischer ist als die pedantische Version der offiziellen Lib (und trotzdem bessere Ergebnisse liefert, behaupte ich mal).

Satzzeichen

Satzzeichen wie Kommas und Punkte werden in Inform als word separators bezeichnet, das heißt ein solches Zeichen gilt immer als eigenes Wort, auch wenn es direkt, ohne trennendes Leerzeichen an einem anderen Wort klebt, wie es bei nachgestellten Zeichen üblich ist. Das ist nütztlich für die Lib, auch wenn es in einzelnen Fällen, wie etwa Abkürzungen (»Dr. Müller«) etwas ärgerlich sein kann. Dies kann man aber mit Synonymen beheben. Der dritte word separator in Inform ist das Anführungszeichen (").

Frage- und Ausrufezeichen, Strichpunkte und Doppelpunkte werden in Inform nicht als word separators betrachtet, so dass in 'was ist ein graus?' das vierte Wort 'graus?' inklusive des Fragezeichens ist, und nicht etwa nur 'graus'.

Zur besonderen Behandlung dieser Satzzeichen gibt es unter deform drei Möglichkeiten, die durch verschiedene Konstanten aktiviert werden:

IGNORE_PUNCTUATION

Die Satzzeichen werden einfach durch Leerzeichen ersetzt. Dieser pragmatische Ansatz wird auch in der offiziellen Lib verwendet, wenn man die Konstante NO_PUNCTUATION setzt. (Diese Konstante wird von deform als Synonym zu IGNORE_PUNCTUATION verstanden.)

REPLACE_PUNCTUATION

Hier werden die Satzzeichen durch Punkte ersetzt. (Was im Fall von Frage- und Ausrufezeichen sinnvoll ist, bei den Anführungszeichen wohl weniger.)

SEPARATE_PUNCTUATION

Hier werden die Satzzeichen so von den angrenzenden Wörtern abgerückt, dass sie eigene Wörter sind, wie Punkt und Komma. Dann muss man aber auch die Tokens anpassen, also

Verb 'was' 'wer'
    * 'ist/'sind' scope=Topic          -> WhatIs
    * 'ist/'sind' scope=Topic '?'      -> WhatIs
    ;

In deform werden Frage- und Ausrufezeichen als Satzzeichen verstanden und damit so behandelt wie oben beschrieben wenn man die passende Konstante definiert hat. Die offizielle Lib berücksichtigt auch das Anführungszeichen. Was genau ein Satzzeichen ist, bestimmt die Routine Is_Punctuation, die man ersetzen kann. Diese Routine bekommt ein ZSCII-Zeichen als Argument übergeben und gibt wahr oder falsch zurück, je nachdem ob das Zeichen als Satzzeichen betrachet werden soll oder nicht. Man kann also auch Doppel- und Strichpunkte besonders behandeln, wenn man möchte.

Man kann nur eine der drei Konstanten sinnvoll definieren. (Wenn man mehrere definiert, hat man eine Kombination der drei Methoden, was genausogut ist wie IGNORE_PUNCTUATION.)

Verben

Die Verben müssen im Imperativ der zweiten Person Singular angegeben werden, Die Konvention ist hier, dass der Spieler das Spiel duzt. Die regelmäßigen Verben lassen hier mist zwei Forme zu, die sich nur durch ein angehängtes 'e' unterscheiden, etwa 'schau' und 'schaue'.

In der offizielle Lib wird das 'e' automatisch vom Parser abgeschnitten, daher dürfen dort Verb-Definitionen kein 'e' am Ende haben. Hier dürfen Verben ein 'e' am Ende haben, was besonders bei »Verben«, die keine Verben, sondern Substantive oder englische Debug-Kommandos sind sinnvoll ist. Wenn ein Verb aber zwei Formen hat und beide erkannt werden sollen, sollte man nur die Form ohne e definieren, also 'schau', 'geh', 'spring' usw.

Die Verben dürfen wie alle Vokabeln keine Umlaute enthalten, es müssen die Umschreibungen 'ae', 'oe', 'ue' und 'ss' verwendet werden.

Verbkonstruktionen sind im Deutschen nicht immer eindeutig. In GermanG.h wird zum Beispiel »zieh ... auf« als ##Wear interpretiert, etwa um einen Hut aufzuziehen. Wenn es nun aber im Spiel eine Spieluhr gibt, die man auch aufziehen kann, so sollte man nach dem Einbinden von GermanG.h das Verb ertweitern, entweder über ein Attribut, das die Spieluhr besitzt oder uber ein noun=Routine-Token:

Extend 'zieh' first
    * clocklike 'auf' -> WindUp;

mit dem neu definierten Attribut clocklike (für ein Spiel mit vielen Uhren) oder:

[ is_Clocklike;
    if (noun == Spieluhr or Standuhr) rtrue; rfalse;
];	

Extend 'zieh' first
    * noun=is_Clocklike 'auf' -> WindUp;

Wichtig ist, dass diese Bedingung in einem Satzmuster vor der üblichen Zeile, die auf ##Wear verweist steht. Es muss also die Option first angegeben werden.

Die einschränkenden Tokens attribute, noun=Routine und in gewissem Maße auch creature sollten nur dazu verwendet werden, verschiedene Bedeutungen gleicher Satzmuster zu unterscheiden, wie im Beispiel oben gezeigt.

Wenn man die Gültigkeit eines Tokens bereits in der Grammatikdefinition einschränkt, so kann dies zu ungewollten Fehlermeldungen führen. Wenn der Autor zum Beispiel ein neues Verb einführt, das nur für Musikinstrumente gilt:

[ is_Instrument;
    if (noun ofclass Instrument) rtrue; rfalse;
];	

Verb 'spiel'
    * noun=is_Instrument                -> Play
    * 'auf' noun=is_Instrument          -> Play;

dann scheint das auf den ersten Blick logisch – man kann nur Instrumente spielen. Gibt der Spieler jedoch »spiele Karten« ein, so wird CANTSEE_PE, also »Du siehst hier so etwas nicht« ausgegeben, auch wenn ein Kartenspiel direkt vor dem Spieler auf dem Tisch liegt. Deshalb ist es hier besser, alle Objekte zuzulassen und in der Aktionsroutine eine Absage zu erteilen:

[ PlaySub;
    print_ret (GDer) noun , " ",
        (ist) noun, "nichts zum Spielen.";
];	

Verb 'spiel'
    * noun         -> Play
    * 'auf' noun   -> Play;

Der Code für das Spielen würde dann in before der Klasse für Musikinstrumente definiert.

Lange Wörter

Im Deutschen gibt es viele zusammengesetzte Wörter, die sehr lang sind. Im z-Code können die Vokabeln aber nur neun Zeichen lang sein. (Das heißt in den Versionen fünf und später. In Version drei ist ein Eintrag ins Wörterbuch nur sechs Zeichen lang. In Glulx kann man die Länge der Vokabeln mit einer Konstante definieren, aber die Voreinstellung ist auch hier neun Zeichen.)

In vielen Fällen ist es egal, ob nach dem neunten Zeichen noch etwas Sinnvolles steht. Wenn der Spieler »Kaffeetasche« eingibt, und das als 'kaffeetas' mit dem Ergebnisobjekt Kaffeetasse geparst wird, ist das sicherlich kein Beinbruch. In manchen Fällen muss man aber das ganze Wort parsen. Dazu stehen in deform zwei Methoden zur Verfügung.

Genaue Wortanalyse in parse_name

In Inform kann man mit der Property parse_name die übliche Analyse über name ersetzen oder erweitern. Um lange Wörter parsen zu können, steht in deform die Routine WordMatch(s) zur Verfügung. s ist hier ein String zwischen doppelten Anführungszeichen, der so aussehen muss wie eine Vokabel, das heißt er darf keine Umlaute oder Großbuchstaben besitzen.

Die Routine untersucht das Wort an der Stelle wn und gibt false zurück, wenn das Wort nicht passt oder die Länge des Strings (die einen wahren Wert hat), wenn das Wort übereinstimmt. In diesem Fall wird auch der Wortmarker wn um eins weitergerückt. Auf diese Weise sind in parse_name Prüfungen wie folgende möglich:

Object -> Hauptsicherung "Sicherungsschalter"
  with article definite,
       name 'schalter' 'notstrom' 'hebel',
       parse_name [n;
            while (WordMatch("sicherungsschalter")
                || WordMatch("sicherungshebel")
                || WordInProperty(NextWord(),
                   self, name)) n++;
            return n;
        ],
    has male static;

Bitte beachten: Die reine Oder-Klausel ist wahr, wenn eines ihrer Glieder wahr ist. Es wird von links ausgewertet, und die erste wahre Bedingung zählt wn um eins weiter – NextWord() macht dies auch – und springt in den Ausführungsblock der Schleife. Hier wird n hochgezählt. Es rückt also in diesem Schema immer nur ein Aufruf den Wortmarker vor.

WordMatch prüft nur über die Länge des Worts; alles, was danach kommt, wird ignoriert. Mit WordMatch(s, true) kann man eine genaue Übereinstimmung erwzingen.

Class  -> Streichholz "Streichholz"
  with name 'holz', 'hoelzer//p',
       parse_name [n b;
           do {
               b = false;
               if (WordMatch("streichholz", 1)) {
                   b = true;
                   n++;
               }
               if (WordMatch("streichhoelzer", 1) {
                   b = true;
                   parser_action = ##PluralFound;
               }
           } until (~~b);
           if (n) return n;
           return -1;
       ],
       plural "Streichhölzer",
   has neuter;

Abtrennen der Köpfe und Schwänze

Oben im Quelltext, bevor Parser.h eingebunden wird, kann man ein Feld CompundHeads definieren. Es enthält »Köpfe« von Wörtern die abgetrennt werden und dann, mit einem angehängten Bindestrich, eigene Wörter sind.

Die Streichhölzer oben sähen mit dieser Methode so aus:

Array CompoundHeads table
    "streich" 0
    "zuend" 0
    ;	

Include "Parser.h";
Include "VerbLib.h";	

Class  -> Streichholz "Streichholz"
  with name 'streich-' 'zuend-' 'holz', 'hoelzer//p',
       plural "Streichhölzer",
   has neuter;

Nun wird die Eingabe »streichholz« umgewandelt in »streich- holz«, und damit kann der Inform-Parser gut umgehen.

Analog zu den CompoundHeads gibt es die CompoundTails, die ein Wort von hinten beschneiden. Mit der Definition

Array CompoundTails table
    "schluessel" 0
    "karte" 0
    ;

könnte man nun die ganzen Holz-, Eisen-, Stahl-, Gold-, Molybdän- und was weiß ich nicht noch für Schlüssel unterscheidbar machen, indem man überall das Präfix 'eisen-' usw. und das Wort 'schluessel' angibt. Hierbei werden die üblichen Endungen brücksichtigt, das heißt auch 'landkarten' würde in 'land- karte' umgewandelt. (Es sei denn, es gibt das Wort 'karten' auch, dann würde es 'land- karten' heißen – es wird zunächst geprüft, ob das Wort inklusive einer der möglichen Endungen passt, dann wird getrennt, und dann nach den üblichen Regeln das zweite Wort beschnitten. Die Angabe von "karte" in CompoundTails ist also noch keine Garantie dafür, dass das zweite Wort auch 'karte' ist.

Jeder »Kopf« und jeder »Schwanz« in CompoundHeads und CompoundTails muss zwei Feldeinträge haben. Der zweite ist üblicherweise Null, kann aber Eind sein, um anzuzeigen, dass kein Bindestrich eingefügt werden soll. Statt 'haus- tuer' hieße es dann 'haus tuer'. (Wozu das genau nützlich sein kann, weiß ich nicht, aber es ist implementiert, und irgendwie schien es auch eine gute Idee zu sein.)

Eine Sache, die man beachten sollte, ist, dass die Köpfe immer abgeschnitten werden. Wenn man also die Vorsilbe "oel" abtrennt, so kann man nicht 'oelbild' oder 'oelung' oder 'oeler' als name für ein Objekt definieren. Diese Vokabeln werden durch die Heads untypable. Das Wort 'oel', ohne weitere Endung, kann man aber definieren, sei es als Verb oder als Substantiv. Im Zweifelsfall sollte man sich mit dem »Echo« das Resultat anschauen und überlegen, ob das abtrennen der Wortteile sinnvoll ist.

Wer selbst noch Änderungen im Textpuffer vornehmen möchte, kann dies mit den beiden Einhängern PreInformese und PostInformese tun, die vor und nach den Informisierungsmechanismen von deform aufgerufen werden.

Descriptors

Als Descriptors bezeichnet Inform Wörter, die vor einem Objekt stehen können und dies näher beschreiben. Dazu gehören Artikel, Zahlenangaben, Possessiv- und Demonstrativpronomen und allgemein gültige Adjektive.

In deform werden nur die Artikel und die selten benutzen Demonstrativpronomen 'dies' und 'jene' (ohne Endungen, die soll der Parser abschneiden, sie werden eh nicht betrachtet) definiert. Die Personalpronomen werden meiner Meinung nach sowieso fast nie benutzt, und dann nicht so, wie der Spieler es will. Auf diese Weise sind Possessivpronomen keine Descriptors und können als Vokabeln angegeben werden:

Object -> meine_Tasse "Kaffeetasse"
  with article yours,
       name 'mein' 'tasse' 'kaffeetasse' 'rot',
       description
           "Wann ist eigentlich die Sitte aufgekommen,
           bunte Tassen mit blöden Sprüchen im Büro zu
           benutzen? Diese Tasse ist rot und behauptet
           ~Ich bin hier der Boss~.",
   has female container;	

Object -> Bernds_Tasse "Bernds Kaffeetasse"
  with name 'sein' 'bernds' 'tasse'
           'kaffeetasse' 'weiss',
       description
           "Bernd ist in seinem Kaffetassengeschmack
           (und auch sonst) langweiliger als du.
           Seine Tasse ist weiß mit einem winzig
           kleinen Logo des Deutschen Roten Kreuzes
           neben dem Henkel.",
   has female proper container;

Wer das gesamte Spektrum der Possessivpronomen benutzen will wie im Original der kann die Konstante TRADITIONAL_DESCRIPTORS definieren.

Genera für Vokabeln

Normalerweise betrachtet Inform den Genus von Vokabeln nicht. Das ist ärgerlich, wenn man ein Objekt anders nennt, dieser andere Name auch erkannt wird, aber nachfolgende Pronomen nicht:

> u die jacke
Der Anorak ist marineblau mit einem Besatz aus künstlichem Eisbärenfell. Auf dem rechten Ärmel steht »go nw«.

> zieh sie an
Mir ist nicht klar, worauf sich »sie« bezieht.

In der alten deutschen Lib war es noch ärgerlicher, da das Pronomen oft zur Ausgabe verwendet wurde. deform ist hier etwas expliziter und gibt immer den ganzen Namen des Objekts aus, also »Die rostige Tür ist zu.« statt nur »Sie ist zu.«

In der ofiziellen deutschen Lib gibt es hierzu den Mechanismus changing gender, der auch in deform implementiert ist, wenn auch etwas einfacher. Vokabeln, deren Genus nicht dem des Objekts entspricht, werden als Attribut hintenangestellt:

Object -> Beutel "Beutel"
  with name 'beutel' 'tasche' female 'tuete' female,
   has male;

Jetzt werden ›tasche‹ und ›tuete‹ als weiblich erkannt und Eingabe wie »öffne die Tasche und leere sie« verstanden. Wenn man ein Pronomen mit (er), (ihn) usw. ausgibt, wird auch der Genus aus der Eingabe verwendet – bis eine neue Eingabe geparst wird oder eine andere Art der Ausgabe mit dem »richtigen« Genus des Objekts aufgerufen wird, zum Beispiel (der) oder (einer).

Etwas ärgerlich ist es, dass der Compiler anmeckert, dass in der name-Property Attribute stehen. name ist eine besondere Property, die nur Vokabeln enthalten soll. Andere Werte sind legal, aber es wird gewarnt. (Vokabeln können in name, und nur da, auch in doppelten Anführungszeichen stehen. Diese Praxis ist aber veraltet und sollte nicht verwendet werden.)

In deform kann man anstelle der Attribute auch die untypable words 'm.', 'f.', 'n.' und 'p.' verwenden, also:

with name 'beutel'   'tasche' 'f.'   'tuete' 'f.',

(»Untypable words« sind Wörter, die niemals erkannt werden, weil sie Trennzeichen wie Punkt oder Komma enthalten. Die Eingabe »f.« würde vom Interpreter in die beiden Wörter 'f//' und './/' aufgeteilt.)

Wenn man in parse_name den changing gender setzen möchte, benutzt man dazu die Routine GenderNotice(obj, attr):

Class Lampion
  with short_name [;
           if (self has light) print "hell";
           else print "dunkl"; print "@00 Lampion";
           rtrue;
       ],
       parse_name [ wd n adj;
           if (self has light) adj = 'hell';
           else adj = 'dunkel';
           wd = NextWordStopped();
           while (wd == 'lampion' or 'laterne' or adj) {
               n++;
               if (wd == 'laterne')
                   GenderNotice(self, female);
               wd = NextWordStopped();
           }
           return n;
       ],
       ...,
   has male ~light;

Wenn man WordInProperty benutzt, wird der changing_gender automatisch gesetzt:

Class Lampion
  with name 'lampion' 'laterne' 'f.'
       parse_name [ wd n adj;
           if (self has light) adj = 'hell';
           else adj = 'dunkel';
           wd = NextWordStopped();
           while (wd == adj
               || WordInProperty(wd, self, name)) {
               n++; wd = NextWordStopped();
           }
           return n;
       ],
       ...,
   has male ~light;

Dieser Code ist äquivalent zum Code oben.

Früher musste man zu jedem Objekt, das vom Hauptgenus abweichende Genera verwendet hat, das Attribut changing_gender definieren. Neuere Versionen von deform verwenden ein Feld, auf dem die geänderten Genera abgelegt werden. Man kann also zu jedem Objekt ohne Weiteres andere Genera in name definieren. (Die Angabe von Genera funktioniert aber auch in anderen Properties, die man dann in parse_name mit WordInProperty untersuchen kann.)

Der changing gender funktioniert ganz gut, es gibt aber Einschränkungen, wenn man Pronomen verwendet:

Du siehst hier ein Beil.

> frotz die Axt
Sie leuchtet nun hell.

> frotz sie
Es leuchtet nun hell.

Das Beil wird zwar richtig erkannt, weil sich »sie« auf das vorher erwähnte Synonym »Axt« bezieht, aber in der Ausgabe nicht richtig angesprochen. Die Information, welchen Genus das Pronomen hatte, wird im Pronomen-System von Inform nicht abgelegt.

Keine der Standardantworten von deform verwendet jedoch die reine Ausgabe mit Pronomen. Wenn Pronomen verwendet werden, geht immer eine normale Ausgabe voran, die den changing gender zurücksetzt.

Erweitertes Parsen

Mit der Eigenschaft parse_name und dem Einhänger ParseNoun(obj) kann der Autor eigene Regeln zum Parsen schreiben. Manche Regeln kommen aber öfter vor, so dass es nützlich ist, den Parser zu erweitern.

Wenn die Konstante EXTENDED_PARSER zu Spielbeginn definiert wird, stehen neben den üblichen Möglichkeiten der Wortanalyse folgende Besonderheiten zur Verfügung:

  1. Ein Objektname enthält eine Präposition, zum Beispiel »Postkarte« aus Australien». Wenn die Präposition in name enthalten ist, wird bei einem Satz wie «nimm die Karte aus dem Postkasten» die Präposition «aus" der Karte zugeschlagen, und der Parser läuft ins Leere.

    Daher kann man in der Eigenschaft prep eine Liste von Präpositionen angeben, die zusätzlich zu name verstanden wird, aber nicht als letztes Wort einer Kette. Außerdem werden Descriptors, also Artikel und Demonstrativpronomen verstanden. Die Karte sähe dann so aus:

     Object -> -> 
            Karte_aus_Australien 
            "Karte aus Australien"
       with name 
                'karte' 'postkarte' 'post' 'gruss' 
                'australien' 'christine' 'mein' 
                'dein' 'nachbarin' 'sydney',
            prep 'von' 'aus',
            description
                "Von deiner Nachbarin Christine, mit 
                Sylyestergrüßen aus Sydney."
        has female;

    Dann wird »Karte aus Sydney«, »Karte von meiner Nachbarin« usw. verstanden.

  2. Ein Objekt bezieht sich auf ein anderes Objekt, wie zum Beispiel ein Loch im Zaun oder ein Fleck auf dem Mantel. In diesem Fall definiert man wieder eine oder mehrere Präpositionen mit prep. Zusätzlich gibt man eine Eigenschaft parse_ref an, die ein Objekt enthält:

     Object -> Loch "Loch im Bauzaun"
       with name 'loch' 'guckloch' 'oeffnung',
            prep 'in' 'von',
            parse_ref Bauzaun,
        has scenery neuter;

    Dann wird zunächst das Objekt selbst untersucht. Wenn eine der Präpositionen folgt, wird das Objekt in parse_ref untersucht, das auch eine parse_name haben oder von ParseNoun() erkannt werden kann.

    Da hier die Präposition vorhanden sein muss, müssen Artikel im Dativ, also 'des' und 'der' als Präposition angegeben werden, damit »Logbuch des Kapitäns« oder »Tasche der Frau« verstanden werden. (Bei »der« kann es aber wieder zu Problemen kommen: »gib Tasche der Frau« wird hier nicht richtig verstanden, da sich die komplette Phrase auf die Tasche bezieht.)

    Die Eigenschaft parse_ref kann auch eine Routine sein, die ein Objekt zurückgibt.

Diese beiden Fälle funktionieren auch beim Disambiguisieren, wenn nicht allzutief verschachtelt wird.

Disambiguisierung

Oft muss der Parser zwischen mehreren Objekten mit gleichen Namen auswählen oder fehlende Objeke erraten. Diese Methode wird in der Original-Lib (und auch in der offiziellen deutschen Lib) sehr rudimentär vorgenommen und führt oft zu seltsamen Annahmen.

In deform wird ein neues System verwendet. Beim Disambiguisieren – das ist der Fall flag==2 in ChooseObjects – wird wie folgt vorgegangen:

  1. Die Eigenschaft disambig des Objekts wird herangezogen. Sie kann eine Priorität zwischen −3 (niedrig) und 10 (hoch) zurückgeben.

  2. Sonst wird ChooseObjects(obj, 2) aufgerufen, wie es in Abschnitt 33 des DM4 beschrieben ist. Bitte beachten: Der Defaultwert für die Priorität ist drei, das heißt wenn man einen Wert kleiner als drei zurückgibt, wird das Objekt heruntergestuft!

  3. Die Routine Disambiguate der Lib macht allgemeine Annahmen über Objekte im Zusammenhang mit den Standard-Verben. Zum Beispiel werden beim Öffnen Objekte mit den Attributen openable und ~open bevorzugt. Diese Routine kann mit Replace ersetzt werden. Objekte mit den Attributen scenery und concealed werden leicht benachteiligt (2).

Wenn in (a) oder (b) ein von Null verschiedener Wert zurückgegeben wird, werden die nachfolgenden Punkte nicht mehr betrachet.

Die Property disambig kann herangezogen werden, um einzelne Objekte hervor- zuheben, damit der Spieler nicht immer alles eingeben muss. So könnte zum Beispiel ein Lexikon folgendes definieren:

disambig [; Consult: return 5; ],

um dem Spieler die Nachfrage nach dem Nachschlagewerk zu ersparen:

> schlage erinnyen nach
(in Baxters Kompakter Ezyklopädie der Antiken Mythologie)

Erinnyen [grch. »die Zürnenden«]: Die drei griechischen Rachegöttinnen →Tisiphone, →Allekto und →Megaira. Beschönigend auch Eumeniden [grch. »die Wohlgesinnten«] genannt.

Eine andere Anwendung ist das Bevorzugen oder Benachteiligen bestimmter Objekte je nach durchgeführter Aktion:

Object -> Warnschild "Warnschild"
  with name 'schild' 'warnschild' 'warnung',
       description "~ Bitte viermal klingeln! ~",
       disambig [;
           Examine: return 3;
           default: return -1;
       ],
   has neuter scenery;	

Object -> Buckelschild "Buckelschild"
  with name 'bronzen' 'schild' 'buckelschild'
          'bronzeschild' 'buckel',
       description
           "Ein bronzener Schild mit Buckel, der
           einige Schläge abhält wenn du ihn an
           deinem Schildarm trägst.",
   has male clothing;

Hier wird das Warnschild nur beim Lesen, beziehungsweise Untersuchen bevorzugt, das dies die Haupteigenschaft eines Schildes ist: beachtet zu werden. Trotzdem sollte man hier zusätzlich zu 'schild' weitere Vokabeln angeben, um die beiden Objekte eindeutig unterscheidbar zu machen, wenn der Spieler es so will. (Die Benachteiligung mit −1 ist redundant, da das Warnschild als scenery-Objekt eh schlechter abschneidet.)

Um unterscheiden zu können, ob gerade noun oder second betrachtet wird, kann man die globale Variable parameters benutzen, die 0 für noun und 1 für second ist:

Object -> Taschenmesser
  with name 'messer' 'taschenmesser'
       disambig [;
           Cut: if (parameters==1) return 5;
       ],
       before [;
           Cut:
           if (noun hasnt cuttable)
               "Das kann man nicht schneiden.";
           give noun cut_up ~cuttable;
           "Du schneidest ", (den) noun, " durch.";
       ],
   has neuter;

Wer die von der Lib vorgenommenen Annahmen nicht verwenden möchte, der kann die Konstante TRADITIONAL_CHOOSE_OBJECTS definieren. Ein Überschreiben der Annahmen mit disambig funktioniert dann allerdings weiterhin.

Wer ändern möchte, wie sich der Parser bei »nimm alles« verhält, kann die Routine DisambiguateAll ersetzen.

Explizite Fehlermeldungen

Wenn der Inform-Parser ein Wort nicht kennt, sagt er das üblicherweise nicht, sondern hält die Fehlermeldungen eher vage: »So etwas kannst du hier nicht sehen«, lautet die Fehlermeldung oder »Ich habe nur Folgendes verstanden: ...«.

Die offizielle Erklärung ist, dass der Spieler Hinweise auf implementierte Objekte, die er nocht nicht kennt, bekommen kann, wenn die Fehlermeldungen explizit sind. Ich finde das sehr verwirrend:

> stecke die Codekarte in das Lesgerät
So etwas kannst du hier nicht sehen.

Na schön, was kann ich nicht sehen? Wahrscheinlich das falsch eingegebene »Lesgerät«, aber sicher kann ich mir da nicht sein. Oder:

> u gelbe Bank
Ich habe nur folgendes verstanden: die gelben Blumen betrachten.

Diese Art von Fehler tritt auf, wenn es die Vokabeln 'gelb' und 'bank' zwar gibt, sie aber nicht beim selben Objekt definiert sind.

Wer dem Spieler etwas mehr Information geben möchte, kann vor dem Einbinden des Parsers die Konstante EXPLICIT_ERROR_MESSAGES definieren. Dann werden die nicht oder falsch verstandenen Wörter genannt:

> u gelbe Bank
Ich verstehe das Wort »bank« in diesem Zusammenhang nicht.

> stecke die Codekarte in das Lesgerät
Ich kenne das Wort »Lesgeraet« nicht.

Die beiden neuen Meldungen sind ##Miscellany Nummer 98 und 99, die wie üblich mit der Eigenschaft before im Objekt LibraryMessages überschrieben werden können.

Die erste Meldung kommt, wenn das Wort dem Spiel bekannt ist, das heißt, wenn es im Wörterbuch des Spiels steht. Lange Wörter, die als Strings mit WordMatch untersucht werden, werden nicht berücksichtigt. Der Einhänger WordIsKnown bietet dem Autor die Möglichkeit, den Sprachschatz des Spiels zu erweitern oder einzuschränken. In dieser Routine wird das Wort an der Stelle wn untersucht. Wenn diese Routine true oder false zurüpckgibt, gilt das Wort als bekannt oder unbekannt. Wenn die Routine −1 zurückgibt, entscheidet der Parser.

Listen

Der List Writer funktioniert wie in der Original-Lib. Um die Liste in einem bestimmten Fall auszugeben, kann man WriteListFromCase benutzen, dem man den Fall als drittes Argument übergibt:

WriteListFromCase(obj, style, case)

Das ISARE_BIT ist nur sinnvoll, wenn die Liste im Nominativ ausgegeben wird. Ist dieses Bit gesetzt und die Liste soll in einem anderen Fall ausgegeben werden, so wird der Fall automatisch auf den Nominativ gesetzt. Der jeweils zur Ausgabe benutzte Fall steht in der globalen Variable short_name_case. Die Fälle sind von null bis drei surchnummeriert und entsprechen den Konstanten Nom, Gen, Dat, Akk.

In der Original-Lib werden Listen ineinandergeschachtelt, so dass man leicht den Überblick verliert:

Du hast einen Wanderrucksack (der offen ist), darin eine durchsichtige Plastikdose, darin ein Butterbrot mit Käse, ein Butterbrot mit Wurst, ein hartgekochtes Ei und eine Tomate, eine Flasche, darin etwas Apfelschorle und einen Anorak bei dir.

Mit dem APPEND_BIT (das zusätzlich zu RECURSE_BIT gesetzt werden kann oder nicht) werden Listen nacheinander ausgegeben. Die Inhalte der vorher erwähnten Objekte werden in einem jeweils eigenen Satz ausgegeben.

Du hast einen Wanderrucksack (der offen ist) bei dir.

In dem Wanderrucksack sind eine durchsichtige Plastikdose, eine Flasche und ein Anorak. In der Flasche ist etwas Apfelschorle. In der durchsichtigen Plastikdose sinds ein Butterbrot mit Käse, ein Butterbrot mit Wurst, ein hartgekochtes Ei und eine Tomate.

Jedes Objekt auf der oberen Ebene, das eigenen Inhalt hat, bekommt einen Absatz, alle Objekte darunter werden mit im selben Absatz behandelt. Lange Listen von Objekte sind nie sehr schön, aber ich finde sie in eigenen Sätzen übersichtlicher. Mit der Konstante NO_NESTED_LISTS kann man erwzingen, dass alle Listen, die ENGLISH_BIT und RECURSE_BIT haben auch das APPEND_BIT bekommen.

Die angehängten Sätze werden mit der neu eingefügten Library Message (##Look, -1) geschrieben, die natürlich ersetzt werden kann. Wer selbst WriteListFrom aufruft und dabei das APPEND_BIT setzt oder NO_NESTED_LISTS definiert hat, sollte auf jeden Fall nach dem Satz, der den List-Writer aufruft, die Routine WriteSubLists() aufrufen. Diese Routine gibt die Anzahl der geschriebenen Sätze zurück, so dass man Zeilennumbrüche und Leerzeichen schreiben kann, je nachdem, ob etwas augegeben wurde oder nicht. (Es erfordert meist etwas Herumspielen, bis es funktioniert. Am besten, man schaut sich die passenden Lib-Messages einmal an.)

Die Einrückung von Listen mit INDENT_BIT kann man über die Konstante INVENTORY_INDENT steuern, Default ist eine Einrückung um zwei Leerzeichen in jeder Ebene. Mit der Konstante INVENTORY_BULLET kann man jedem Listen- eintrag ein Aufzählungszeichen voranstellen. Um z.B. einen Spiegelstrich zu verwenden definiert man am Anfang des Quelltexts:

Constant INVENTORY_BULLET = "- ";

(Ziemliche Spielerei, ich weiß. Aber ich finde die Listen oft zu wenig eingerückt und mit Aufzählungszeichen besser lesbar.)

Initialisierung

Jedes Spiel muss die Routine Initialise definieren. Dort wird meist ein Anfangstext ausgegeben. Auf jeden Fall muss die Variable location auf den Startraum gesetzt werden.

Hier werden auch andere Dinge gemacht, wie zum Beispiel Dämonen gestartet oder Objekte ins Inventar des Spielers verschoben. Das ist unübersichtlich, da diese objektbezogenen Initialisierungen nicht beim Objekt definiert werden. Mit der Property init kann man bei jedem Objekt Initialisierungen vornehmen:

init [;
    move self to player;
    StartDaemon(self);
    self.colour = random("kadmiumgelb",
        "malvenfarben", "himmelblau", "kirschrot");
], ...

In init-Properties sollte kein Text ausgegeben werden.

Besonderheiten beim Umstieg

Normalerweise sollten sich Dateien, die für die offizielle deutsche Lib erstellt wurden, auch mit deform kompilieren lassen. Einige Dinge gibt es jedoch zu beachten:

Diese Punkte kann man umgehen, indem man die Konstante COMPATIBILITY_MODE definiert. Die folgenden Punkte muss man allerdings von Hand nachziehen: