deform inoffizielle deutsche Inform-Library auf dem Stand von Inform-Lib 6/11 KURZDOKUMENTATION 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: 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 für 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 das 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: @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 reformierte und schweizerische Spiele Die ersten Strings ändern sich ständig, sie werden bei jedem Aufruf von (den), (dem) 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: "Du reibst ein wenig Ru@30 vom 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. Die neue deutsche Rechtschreibung ist der Default: string 30 "ß"; string 31 "ss"; Constant DIALECT_TRADITIONAL: alte Rechtschreibung: string 30 "ß"; string 31 "ß"; Constant DIALECT_SWISS: schweizerische Rechtschreibung: string 30 "ss"; string 31 "ss"; 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: (1) Traditionell: Zu jedem Objekt wird eine Property 'dekl' angegeben, die den Deklinationsmodus festlegt. Es gibt folgende Deklinationstypen: # 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 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: 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.) (2) 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 benötigt: Object with short_name "alt@00 Regale@01 aus Holz", has pluralname; Object with short_name "Student@02", has male animate; Object with short_name "klein@00 Junge@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 Deklinationbstypen.) Es gibt ein paar Sonderkonstanten für die Objektausgabe. Die Eigenschaft article, die nur bei (ein), (einen) usw. 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 [tm]" no_article: Das Objekt wird in unbestimmten Fall ohne Artikel ausgegeben. Der Unterschied zu proper ist, dass bei proper nie Artikel verwendet werden, auch bei der Ausgabe mit bestimmten Artikel nicht. Ist das nützlich? Kaum, aber was soll's? 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 art und poat, 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 shop assistant") (the) Objekt mit bestimmtem Artikel ("the quick brown fox") (a) Objekt mit unbestimmtem Artikel ("an apple", "some marbles") (name) Objektname ohne Artikel ("red rice-paper lantern") 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) o, (des) o, (dem) o, (den) o Objekt mit bestimmtem Artikel. Diese Routinen rufen DefArt(o, Fall) auf. (GDer) o, (GDes) o, (GDem) o, (GDen) o Objekt mit bestimmtem Artikel, der erste Buchstabe wird groß ausgegeben, ruft DefArt(o, Fall) auf (ein) o, (eines) o, (einem) o, (einen) o Objekt mit unbestimmtem Artikel, ruft IndefArt(o, Fall) auf (kein) o, (keines) o, (keinem) o, (keinen) o Negiertes Objekt, umgeleitet zu NegativeArt(o, Fall) (er) o, (seiner) o, (ihm) o, (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 CEin, CEs 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 'fürs' "fuer das" 'doch' "" ; Achtung, der zweite Eintrag in jedem Paar ist ein String in Gänsefüßchen, 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 Dreier- gruppen angegeben werden: Array Twins table 'prof' './/' "prof" '10' './/' "zehnte" ; ENDUNGEN Der Parser schneidet Endungen ab und berücksichtigt werden den Fall des Tokens (es gibt nur [noun]) noch den Genus des untersuchten Worts. Dies ist eine praktikable Vorgehensweise. Im Einzelnen: (a) Es werden nur folgende Endungen abgeschnitten: '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 (den Zwerge-n) *) 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 Ende-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. (b) Es wird abgeschnitten, wenn dan 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 (c) 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 Gänsefüßchen ("). 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, entwerden ü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 Musikinstru- mente 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. (1) 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 Gänsefüßchen, der so aussehen muss wie eine Vokabel, das heißt er darf keine Umlaute oder Großbuchstaben besitzen. Die Routine untersucht das Wort 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 des Schiffs" 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 kann also in diesem Schema immer nur *ein* Aufruf den Wortmarker vorrücken. WordMatch prüft nur über die Länge des Worts, alles, was danach kommt, wird ignoriert. Mit WordMatch(s, true) kann man eine genaue Überein- stimmung erwzingen. Class -> Streichholz "Streichholz" with name 'holz', 'hoelzer//p', parse_name [n b; do { b = false; if (WordMatch("streichholz", 1)) b = true; if (WordMatch("streichhoelzer", 1) { b = true; parser_action = ##PluralFound; } } while (b); if (n) return n; return -1; ], plural "Streichhölzer", has neuter; (2) 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 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 Wort- teile sinnvoll ist. Wer selbst noch Änderungen im Textpuffer vornehmen möchte, kann dies mit den beiden Einmhä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 Demonstrativ- pronomen '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 Inform 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 manein 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 gab es hierzu den Mechanismus "changing gender", der auch in deform implementiert ist, wenn auch etwas einfacher. Wenn ein Objekt Vokabeln mit verschiedenen Genera hat, muss es die Property changing_gender definieren. Vokabeln, deren Genus nicht dem des Objekts entspricht, werden als Attribut hintenangestellt: Object -> Beutel "Beutel" with name 'beutel' 'tasche' female 'tuete' female, changing_gender, 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. (Vokalen können in name, und nur da, auch in doppelten Anführungszeichen stehen. Diese Praxis ist aber veraltet und sollte nicht verwendet werden.) Max Kalus' Ansatz, die Warnung aus dem Compiler zu schmeißen, war etwas kurzsichtig. Sie galt nur für eine Plattform, und neuere Versionen des Inform-Compilers haben diese Warnung natürlich wieder. 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; Diese Routine setzt im Moment einfach obj.changing_gender = attr. (Die globale Variable changed_gender gibt es nicht mehr.) Bitte aber trotzdem die Routine verwenden, da sich vielleicht irgendwann einmal etwas an der Methode ändert. (Zum Beispiel, dass nicht mehr die Property changing_gender explizit bei jedem Objekt angegeben werden mus.) 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. DISAMBIGUISIERUNG Oft muss der Parser zwischen mehrerer 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: (a) Die Eigenschaft disambig des Objekts wird herangezogen. Sie kann eine Priorität zwischen -3 (niedrig) und 10 (hoch) zurückgeben. (b) 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! (c) 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 Nachschalgewerk 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 Annehmen 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. 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 Non, 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 einen Anorak. In der Flasche ist etwas Apfelschorle. In der durchsichtigen Plastikdose ist 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 Konstente 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ärze 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: * Einige (wenige) Bezeichner haben sich geändert, insbesondere: (endT) -> (___t) (endET) -> (___et) definit -> definite; Diese Bezeichner kommen nicht allzu oft vor, und man kann sie leicht per Hand ausbessern, oder folgendes nach dem Einbinden von Parser.h definieren: [ endT o; ___t(o); ]; [ endET o; ___et(o); ]; Constant definit = definite; * Die Deklinationsformen können unter Umständen im Genitiv abweichen. Da deform nicht auf die Endung der Wörter schaut, können -es und -s schon einmal vertauscht werden. (Habe ich noch nicht beobachtet, ist aber denkbar.) In diesem Fall bitte dekl auf Genitiv_s oder Genitiv_es setzen. * Die offizielle Lib definiert eine LiftSub, die allerdings nur auf LookUnder umlenkt. Diese Punkte kann man umgehen, indem man die Konstante COMPABILITY_MODE definiert. Die folgenden Punkte muss man allerdings von Hand nachziehen: * Plurale werden nicht von deform, sondern vom Autor gebildet. Deshalb muss man, wenn ein Objekt im Plural steht, auch den Plural angeben. Statt Object -> "Frau" with dekl 9, name 'frau' has pluralname female; heißt es Object -> "Frauen" with name 'frauen' has pluralname; Attribut 'pluralname' nicht vergessen! (Der Plural wird wie in T.A.G. als eigener Genus betrachtet, es wird nicht zwischen Plural männlich, weiblich und sächlich unterschieden, und jedes Objekt sollte nur eins der Attribute 'male', 'female', 'neuter' und 'pluralname' haben.) * Wer direkt auf die ta_irgandwas-Routinen zugegriffen hat, muss jetzt umstricken. Die ta-Routinen sind zusammen mit tgerman.h verschwunden. Wie man was am besten macht, kann man sich in den Library Messages abgucken. Oder fragen. Gute Orte um nachzuschauen sind die Felder LanguageArticles, LanguageSuffixes und PersonalPronouns.