Wie kann ich Benutzereingaben in einem Text-Adventure-Spiel analysieren?

16

Das Parsen von Benutzerbefehlen in einem Textabenteuer ist ein Spektrum von Abenteuers einfachem " Nach Norden gehen" bis zu einigen verblüffend cleveren Befehlen in hhgttg .

Ich erinnere mich an nette Anleitungen in Computerzeitschriften in den 80ern, aber jetzt finde ich fast nichts im Netz außer einer kurzen Wikipedia-Referenz .

Wie würdest du das machen?


Update : Ich habe in meinem Ludum Dare-Eintrag den einfachsten Ansatz gewählt .

Wille
quelle
3
Gibt es ein bestimmtes Problem, das Sie lösen möchten?
Trevor Powell
@ Trevor Powell überlegt, ein kleines Textabenteuer zum Spaß zu machen und möchte mich nur mit dem Stand der Technik vertraut machen, anstatt nur einzutauchen und es auf meine Art und Weise zu lösen
Will
1
Verwenden Sie Informieren ; Das ist die beste Strategie, die Sie jemals anwenden konnten. Es gibt heutzutage praktisch keinen Grund, ein Textabenteuer von Hand zu programmieren.
Nicol Bolas
@NicolBolas es sei denn, Ludum Dare nähert sich? ;)
Will
1
Es ist nicht so sehr defätistisch als pragmatisch. Es liegt wahrscheinlich außerhalb des Rahmens einer einzigen Antwort, auf alle Techniken des erweiterten Text-Parsings einzugehen (jenseits der offensichtlichen Dinge, die sich jeder ausdenken kann).
Tetrad

Antworten:

10

Haben Sie in der interaktiven Fiction-Community gesucht? Sie schreiben immer noch Parser und einige versuchen, den Rahmen zu erweitern, indem sie neue Techniken wie die Verarbeitung natürlicher Sprachen implementieren.

Siehe zum Beispiel diesen Link für Artikel, die die verwendeten Ansätze beschreiben:

http://ifwiki.org/index.php/Past_raif_topics:_Development:_part_2#Parsing

krolth
quelle
4
Aha, "Textabenteuer" wird zu "interaktiver Fiktion" und plötzlich ist es viel besser zu verstehen! Wer hätte gedacht, dass sich der Name ändern würde, seit ich es gespielt habe? :) Trotzdem, wenn man sich diese Hinweise ansieht, wird eigentlich nicht viel erklärt
Will
9

Der von Ihnen gewünschte Begriff ist "Natural Language Processing" (NLP). Bedenken Sie jedoch, dass formale Methoden entworfen wurden, um reale Texte zu verstehen, während Sie normalerweise nur etwas benötigen, das für eine begrenzte Teilmenge Ihrer natürlichen Sprache funktioniert.

Normalerweise können Sie mit einer einfachen Grammatik und einem einfachen Wortschatz beginnen und dann einen Parser dafür schreiben. Eine Grammatik könnte so etwas Einfaches sein:

sentence = verb [preposition] object
verb = "get" | "go" | "look" | "examine"
preposition = "above" | "below"
object = ["the"] [adjective] noun
adjective = "big" | "green"
noun = "north" | "south" | "east" | "west" | "house" | "dog"

Das Obige ist eine Variante der Backus-Naur-Form, der Standardmethode zur Darstellung von Grammatiken. Wie auch immer, Sie können einen Parser-Generator verwenden, um Code zum Parsen dieser Grammatik zu generieren, oder Sie können ziemlich einfach Ihren eigenen Code schreiben, wenn Ihre Sprache eine anständige Zeichenfolgenverarbeitung aufweist. (Suchen Sie nach "Parsern rekursiver Abstammung", die eine Funktion für jede Zeile der Grammatik verwenden.)

Einmal analysiert, können Sie herausfinden, ob der Satz Sinn macht - "nach Norden gehen" mag Sinn machen, "den grünen Norden bekommen" nicht. Sie können dies auf zwei Arten lösen; die Grammatik formaler gestalten (z. B. unterschiedliche Arten von Verben haben, die nur für bestimmte Arten von Substantiven gültig sind) oder die Substantive anschließend mit dem Verb vergleichen. Der erste Weg kann Ihnen helfen, bessere Fehlermeldungen an den Player auszugeben, aber Sie müssen den zweiten Weg ohnehin immer bis zu einem gewissen Grad ausführen, da Sie immer den Kontext überprüfen müssen - z. "take the green key" ist grammatikalisch und syntaktisch korrekt, Sie müssen jedoch noch prüfen, ob der grüne Schlüssel vorhanden ist.

Schließlich erhält Ihr Programm einen validierten Befehl, bei dem alle verschiedenen Teile überprüft werden. Dann müssen Sie nur die richtige Funktion mit den Argumenten aufrufen, um die Aktion auszuführen.

Kylotan
quelle
6

Der Stand der Technik für Textabenteuer ist heute Inform 7 . Inform 7 liest sich "wie Englisch", genauso wie Sie in informbasierten Spielen "Englisch schreiben" können. Zum Beispiel aus Emily Shorts Bronze :

Eine Sache hat einen Text namens Duft. Der Geruch einer Sache ist normalerweise "nichts".
Die Block-Geruchsregel ist in keinem Regelbuch aufgeführt.
Etwas riechen:
    Sagen Sie "Von [dem Substantiv] riechen Sie [Duft des Substantivs]."
Anstatt einen Raum zu riechen:
    Wenn ein duftendes Objekt vom Spieler berührt werden kann, sagen Sie "Sie riechen [die Liste der duftenden Objekte, die vom Spieler berührt werden können].";
    Ansonsten sagen Sie "Der Ort ist selig geruchlos."

Der Inform 7-Parser ist eng in die Inform 7-IDE integriert, und der gesamte Quellcode ist noch nicht für das Studium verfügbar:


quelle
5

Die beiden derzeit besten Quellen für das Erlernen der Erstellung eines Text-Adventure-Parsers sind (wie bereits erwähnt) die IF-Community und die Mud-Community. Wenn Sie die wichtigsten Foren nach diesen durchsuchen (Intfiction.org/forum, die Newsgroup rec.arts.int-fiction, Mud Connector, Mudbytes, Mudlab, Top Mud Sites), werden Sie einige Antworten finden, aber wenn Sie nur suchen Für Artikel würde ich Richard Bartles Erklärung des Parsers in MUD II empfehlen:

http://www.mud.co.uk/richard/commpars.htm

Und diese Erklärung auf rec.arts.int-fiction:

http://groups.google.com/group/rec.arts.int-fiction/msg/f545963efb72ec7b?dmode=source

Keine Respektlosigkeit gegenüber den anderen Antworten, aber die Erstellung einer CF-Grammatik oder die Verwendung von BNF ist keine Lösung für dieses Problem. Das heißt nicht, dass es keine Lösung für ein anderes Problem sein könnte, nämlich einen fortgeschritteneren Parser für natürliche Sprachen zu erstellen, aber das ist Gegenstand umfangreicher Untersuchungen und nicht von IMO im Rahmen eines Textabenteuers.

Georgek
quelle
4

In meinem ersten Jahr an der Universität haben wir ein Abenteuerspiel in Prolog gemacht, und für die Benutzereingabe mussten wir eine bestimmte Klauselgrammatik oder DCG verwenden. Ein Beispiel für die Verwendung als Befehlssprache finden Sie unter http://www.amzi.com/manuals/amzi/pro/ref_dcg.htm#DCGCommandLanguage . Es schien eine prinzipielle (es war immerhin eine einheitliche) und flexible Herangehensweise zu sein.

Eric
quelle
1

Sie müssen eine domänenspezifische Sprache definieren, die alle Sätze enthält, die in Ihrem Spiel korrekt sind. Zu diesem Zweck müssen Sie eine Grammatik für Ihre Sprache definieren (Wortschatz und Syntax). Die Art der Grammatik, die Sie benötigen, ist eine kontextfreie Grammatik. Es gibt Tools, die ausgehend von einer synthetischen Beschreibung der Grammatik wie ANTLR (www.antlr.org) automatisch einen Parser generieren. Der Parser prüft nur, ob ein Satz korrekt ist oder nicht, und erzeugt einen abstrakten Syntaxbaum (AST) des Satzes, der eine navigierbare Darstellung des Satzes ist, wobei jedes Wort die von Ihnen in der Grammatik angegebene Rolle hat. Wenn Sie im AST navigieren, müssen Sie den Code hinzufügen, der feststellt, welche Semantik jedes Wort annimmt, wenn es diese Rolle in Bezug auf die anderen Wörter im Satz spielt, und prüfen, ob die Semantik korrekt ist.

Zum Beispiel ist der Satz "Der Stein isst den Mann" syntaktisch korrekt, aber nicht notwendigerweise semantisch korrekt (es sei denn, in Ihrer Welt können Steine, vielleicht magische Steine, Männer essen).

Wenn auch die Semantik stimmt, können Sie zum Beispiel die Welt danach verändern. Dies könnte den Kontext ändern und somit könnte derselbe Satz nicht mehr semantisch korrekt sein (zum Beispiel könnte es keinen Menschen geben, der etwas isst).

www.Sillitoy.com
quelle
1

Ich habe die Tads3-Engine (www.tads3.org) für einige der von mir geschriebenen Textabenteuer verwendet. Es ist zwar mehr für Computerprogrammierer, aber eine sehr mächtige Sprache. Wenn Sie ein Programmierer sind, wird Tads3 viel einfacher zu programmieren sein als Inform7, das ich zuvor auch verwendet habe. Das Problem mit Inform7 für Programmierer ist so berühmt wie "rate das Verb" für Spieler von Text-Abenteuern, denn wenn Sie Ihre Sätze nicht SEHR sorgfältig schreiben, brechen Sie das Spiel. Wenn Sie die Geduld dazu haben, können Sie mit der Tokenizer-Klasse problemlos einen Parser in Java schreiben. Beispiel Ich habe mit einem globalen JTextArea und einem globalen String [] -Array geschrieben. Es werden unerwünschte Zeichen entfernt, mit Ausnahme von AZ und 0-9 sowie des Fragezeichens (für eine "Hilfe" -Befehlsverknüpfung):

// put these as global variables just after your main class definition
public static String[] parsed = new String[100];
// outputArea should be a non-editable JTextArea to display our results
JTextArea outputArea = new JTextArea();
/*
 * parserArea is the JTextBox used to grab input
 * and be sure to MAKE sure somewhere to add a 
 * java.awt.event.KeyListener on it somewhere where
 * you initialize all your variables and setup the
 * constraints settings for your JTextBox's.
 * The KeyListener method should listen for the ENTER key 
 * being pressed and then call our parseText() method below.
 */
JTextArea parserArea = new JTextArea();

public void parseText(){
    String s0 = parserArea.getText();// parserArea is our global JTextBox
    s0 = s0.replace(',',' ');
    s0 = s0.replaceAll("[^a-zA-Z0-9? ]","");
    // reset parserArea back to a clean starting state
    parserArea.setCaretPosition(0);
    parserArea.setText("");
    // erase what had been parsed before and also make sure no nulls found
    for(int i=0;i < parsed.length; i++){
      parsed[i] = "";
    }
    // split the string s0 to array words by breaking them up between spaces
    StringTokenizer tok = new StringTokenizer(s0, " ");
    // use tokenizer tok and dump the tokens into array: parsed[]
    int iCount = 0;
    if(tok.countTokens() > 0){
      while(tok.hasMoreElements()){
        try{
          parsed[iCount] = tok.nextElement().toString();
          if(parsed[iCount] != null && parsed[iCount].length()>1){
            // if a word ENDS in ? then strip it off
            parsed[iCount] = parsed[iCount].replaceAll("[^a-zA-Z0-9 ]","");
           }
        }catch(Exception e){
          e.printStackTrace();
        }
          iCount++;
        }


      /*
       * handle simple help or ? command.
       * parsed[0] is our first word... parsed[1] the second, etc.
       * we can use iCount from above as needed to see how many...
       * ...words got found.
       */
      if(parsed[0].equalsIgnoreCase("?") || 
        parsed[0].equalsIgnoreCase("help")){
          outputArea.setText("");// erase the output "screen"
          outputArea.append("\nPut help code in here...\n");
        }
      }

      // handle other noun and verb checks of parsed[] array in here...

    }// end of if(tok.countTokens() > 0)... 

}// end of public void parseText() method

... Ich habe die Hauptklassendefinition und die Variable initialize () - Methode usw. weggelassen, da davon ausgegangen wird, dass Sie, wenn Sie Java kennen, bereits wissen, wie Sie das einrichten. Die Hauptklasse hierfür sollte wahrscheinlich JFrame erweitern und in Ihrer öffentlichen statischen void main () -Methode nur eine Instanz davon erstellen. Hoffentlich hilft ein Teil dieses Codes.

BEARBEITET - Okay, jetzt müssen Sie eine Actions-Klasse erstellen und nach einer Action suchen (z. B. "Lampe holen" oder "Schwert fallen lassen"). Zur Vereinfachung benötigen Sie ein RoomScan-Objekt oder eine RoomScan-Methode, um alle im Bereich sichtbaren Objekte und nur die Objekte in dieser Aktion zu scannen. Das Objekt selbst handhabt die Aktionsbehandlung. Standardmäßig sollte eine Item-Klasse alle bekannten Aktionen auf eine Standardweise handhaben, die überschrieben werden kann. Wenn ein Gegenstand, den Sie "bekommen" möchten, beispielsweise von einem Nicht-Spieler-Charakter gehalten wird, sollte die Standardantwort für das Halten dieses Gegenstands durch seinen Besitzer ungefähr so ​​lauten: "Das lässt Sie nicht zu." Jetzt müssten Sie in der Item- oder Thing-Klasse eine Menge Standardaktionsantworten dafür erstellen. Dies kommt im Grunde genommen aus der Sicht von Tads3 auf das gesamte Design. Denn in Tads3 hat jedes Element seine eigene Standardroutine für die Aktionsbehandlung, die der Parser aufruft, wenn eine Aktion darauf initialisiert wird. Also ... ich sage Ihnen nur, Tads3 hat all dies bereits implementiert, so dass es SEHR einfach ist, in einem Textabenteuer in dieser Sprache zu programmieren. Aber wenn Sie es von Grund auf neu machen möchten, wie in Java (oben), dann würde ich persönlich damit genauso umgehen, wie Tads3 entworfen wurde. Auf diese Weise können Sie Standardaktionen überschreiben, die Routinen für verschiedene Objekte selbst verarbeiten. Wenn Sie also beispielsweise "Lampe abrufen" möchten und der Butler diese festhält, kann dies eine Antwort in der Standardaktionsmethode "Get" für Item auslösen oder Wenden Sie sich an und sagen Sie: "Der Butler weigert sich, die Messinglampe abzugeben." Ich meine ... wenn Sie lange genug wie ich Programmierer waren, dann ist das alles SEHR einfach. Ich bin über 50 Jahre alt und mache dies seit meinem siebten Lebensjahr. Mein Vater war in den siebziger Jahren ein Hewlett Packard-Ausbilder, also lernte ich von ihm zunächst eine TON in Computerprogrammierung. Ich bin jetzt auch als Serveradministrator in den US Army Reserves. Ähm ... ja, also gib nicht auf. Es ist nicht so schwer, wenn Sie es auf das herunterbrechen, was Ihr Programm tun soll. Manchmal ist Versuch und Irrtum der beste Weg, um solche Dinge zu tun. Einfach testen und sehen und niemals aufgeben. Okay? Codierung ist eine Kunst. Dies kann auf viele verschiedene Arten geschehen. Lassen Sie sich von der einen oder anderen Seite nicht vom Design abhalten. Ich bin auch in den US Army Reserves als Server Administrator tätig. Ähm ... ja, also gib nicht auf. Es ist nicht so schwer, wenn Sie es auf das herunterbrechen, was Ihr Programm tun soll. Manchmal ist Versuch und Irrtum der beste Weg, um solche Dinge zu tun. Einfach testen und sehen und niemals aufgeben. Okay? Codierung ist eine Kunst. Dies kann auf viele verschiedene Arten geschehen. Lassen Sie sich von der einen oder anderen Seite nicht vom Design abhalten. Ich bin auch in den US Army Reserves als Server Administrator tätig. Ähm ... ja, also gib nicht auf. Es ist nicht so schwer, wenn Sie es auf das herunterbrechen, was Ihr Programm tun soll. Manchmal ist Versuch und Irrtum der beste Weg, um solche Dinge zu tun. Einfach testen und sehen und niemals aufgeben. Okay? Codierung ist eine Kunst. Dies kann auf viele verschiedene Arten geschehen. Lassen Sie sich von der einen oder anderen Seite nicht vom Design abhalten.

William Chelonis
quelle
Dies lässt leider den schwierigsten Teil eines Textparsers aus, nämlich das Identifizieren des Verbs, des Subjekts und des Objekts der Benutzereingabe und die Zuordnung zu einer Aktion.
Philipp
Richtig, aber wie ich es machen würde, würde ich eine Aktionsklasse erstellen und eine Reihe von Aktionen beispielsweise in einer Wörterbuchklasse speichern und dann nach Aktionswörtern suchen. Wenn es sich bei der Aktion um ein zweites Wort handelt (wie bei einer "Take" -Aktion, vielleicht "Take Lamp"), lassen Sie eine Reihe von Gegenständen (oder Substantiven) nach Objekten durchsuchen, bei denen diese Objekte selbst über ein Skript verfügen, mit dem Aktionen ausgeführt werden können. Dies alles setzt voraus, dass Sie das Ganze direkt in Java codieren und nicht versuchen, eine tatsächliche externe Datei zum Kompilieren von Textabenteuern zu lesen.
William Chelonis