Как мне разобрать ввод пользователя в текстовой приключенческой игре?


16

Парсинг пользовательских команд в текстовом приключении - это спектр от простого приключения Adventure «идти на север» до невероятно умных в hhgttg .

Кажется, я помню, как читал хорошие практические руководства в компьютерных журналах еще в 80-х годах, но теперь я не нахожу почти ничего в сети, кроме краткой ссылки из Википедии .

Как бы вы это сделали?


Обновление : я пошел с самым простым подходом, возможным в моей записи Ludum Dare .


3
Есть ли конкретная проблема, которую вы пытаетесь решить?
Тревор Пауэлл

@TrevorPowell рассматривает возможность создания небольшого текстового приключения для развлечения и просто хочет познакомиться с «состоянием искусства», а не просто погружаться и решать его по-своему - все будет
Уилл

1
Используйте Информ ; это лучшая стратегия, которую вы когда-либо могли использовать. В наши дни практически нет причин писать текстовые приключения вручную.
Николь Болас

@NicolBolas разве Ludum Dare приближается? ;)
Будет

1
Это не пораженческий, а прагматичный. Разобраться во всех методах расширенного разбора текста (помимо очевидных вещей, которые кто-либо может придумать), вероятно, выходит за рамки одного ответа здесь.
Тетрад

Ответы:


10

Вы искали в интерактивном художественном сообществе? Они по-прежнему пишут парсеры, а некоторые пытаются выйти за рамки, внедряя новые методы, такие как обработка естественного языка.

Смотрите, например, эту ссылку для статей, описывающих используемые подходы:

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


4
Ага, «текстовое приключение» становится «интерактивной выдумкой», и внезапно становится гораздо более привлекательным! Кто бы мог подумать, что это даже изменит имя с тех пор, как я его сыграл? :) Тем не менее, глядя на эти выводы, и на самом деле мало что объясняется с грустью
Уилл

9

Вы хотите термин «обработка естественного языка», или НЛП. Однако имейте в виду, что формальные методы предназначены для того, чтобы попытаться понять тексты реального мира, тогда как обычно вам нужно только то, что работает для ограниченного подмножества вашего естественного языка.

Обычно вы можете начать с простой грамматики и лексики, а затем написать для нее парсер. Грамматика может быть простой:

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"

Выше представлен вариант формы Бэкуса-Наура, стандартный способ представления грамматик. В любом случае, вы можете использовать генератор синтаксического анализатора для генерации кода для разбора этой грамматики или написать свой собственный довольно легко, если ваш язык имеет достойную обработку строк. (Найдите «парсеры рекурсивного спуска», которые используют одну функцию для каждой строки грамматики.)

После разбора вы можете разобраться, если предложение имеет смысл - «идти на север» может иметь смысл, а «получить зеленый север» - нет. Вы можете решить это двумя способами; сделать грамматику более формальной (например, иметь различные типы глаголов, допустимые только для определенных типов существительных) или сравнить существительные с глаголом впоследствии. Первый способ может помочь вам выдавать более качественные сообщения об ошибках игроку, но вам все равно нужно в какой-то степени повторить второй шаг, так как вам всегда нужно проверять контекст - например. «Взять зеленый ключ» грамматически правильно и синтаксически правильно, но вам все равно нужно проверить, присутствует ли зеленый ключ.

В конце концов ваша программа завершается проверенной командой со всеми проверенными частями; тогда это просто случай вызова правильной функции с аргументами для выполнения действия.


6

Современное состояние для создания текстовых приключений сегодня использует Inform 7 . Источник Inform 7 гласит «как английский» так же, как игры на основе Inform позволяют «писать по-английски». Например, из бронзы Эмили Шорт :

У вещи есть какой-то текст, называемый запахом. Аромат вещи - это обычно «ничто».
Правило обоняния блока не указано ни в одной книге правил.
Выполняйте
    запахи : скажите «От [существительного] вы пахнете [запах существительного]».
Вместо того, чтобы
    чувствовать запах комнаты: если игрок может дотронуться до душистой вещи, скажите: «Вы чувствуете запах [список ароматических вещей, которые может коснуться игроком].»;
    в противном случае сказать: «Это место блаженно не имеет запаха».

Синтаксический анализатор Inform 7 тесно интегрирован с IDE Inform 7, и весь исходный код еще не доступен для изучения:


5

Два лучших современных источника для обучения созданию текстового анализатора приключений - это (как уже упоминалось) сообщество IF и сообщество грязи. Если вы поищите на основных форумах (Intfiction.org/forum, новостная группа rec.arts.int-fiction, Mud Connector, Mudbytes, Mudlab, Top Mud Sites), вы найдете ответы на некоторые вопросы, но если вы просто ищете для статей я бы рекомендовал объяснение Ричарда Бартла парсера в MUD II:

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

И это объяснение на rec.arts.int-fiction:

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

Нет неуважения к другим ответам, но создание грамматики CF или использование BNF не является решением этой проблемы. Нельзя сказать, что это не могло быть решением для другой проблемы, то есть создания более продвинутого синтаксического анализатора естественного языка, но это предмет значительного исследования, а не IMO в рамках текстового приключения.


4

На первом курсе в университете мы создали приключенческую игру на Прологе, и для пользовательского ввода нам пришлось использовать грамматику с определенным предложением или DCG. См. Http://www.amzi.com/manuals/amzi/pro/ref_dcg.htm#DCGCommandLanguage для примера использования его в качестве командного языка. Это казалось принципиальным (в конце концов, это был единый) и гибкий подход в то время.


1

Вам необходимо определить язык, специфичный для предметной области, то есть все предложения, которые являются правильными в вашей игре. Для этого вам нужно определить грамматику для вашего языка (словарный запас и синтаксис). Тип грамматики, который вам нужен, - это контекстно-свободная грамматика, и есть инструменты, которые автоматически генерируют синтаксический анализатор, начиная с синтетического описания грамматики, такого как ANTLR (www.antlr.org). Синтаксический анализатор только проверяет, является ли предложение правильным или нет, и генерирует абстрактное синтаксическое дерево (AST) предложения, которое является навигационным представлением предложения, где каждое слово имеет роль, указанную вами в грамматике. Перемещаясь по AST, вы должны добавить код, который оценивает, какую семантику играет каждое слово при исполнении этой роли по отношению к другим словам в предложении, и проверяете правильность семантики.

Например, предложение «Камень ест человека» является синтаксически правильным, но не обязательно семантически правильным (если только в вашем мире камни, возможно, магические камни не могут съесть людей).

Если семантика верна, вы можете, например, изменить мир в соответствии с ней. Это может изменить контекст, и, таким образом, одно и то же предложение больше не может быть семантически правильным (например, не может быть человека, чтобы есть)


1

Я использовал движок Tads3 (www.tads3.org) для некоторых текстовых приключений, которые я написал. Это больше для программистов, но очень мощный язык. Если вы программист, Tads3 будет намного проще кодировать вещи быстрее, чем Inform7, который я также использовал ранее. Проблема с Inform7 для программистов так же известна, как «угадай глагол», для игроков текстовых приключений в том, что если вы не ОЧЕНЬ напишите свои предложения ОЧЕНЬ тщательно, вы сломаете игру. Если у вас есть терпение, вы можете легко написать синтаксический анализатор на Java с помощью класса Tokenizer. Пример, который я написал с использованием глобального JTextArea и глобального массива String []. Он удаляет ненужные символы, кроме листьев AZ и 0-9, а также вопросительного знака (для ярлыка команды «help»):

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

... Я пропустил определение основного класса и переменную initialize () и т. Д., Поскольку предполагается, что если вы знаете Java, вы уже знаете, как его настроить. Основной класс для этого, вероятно, должен расширять JFrame, и в вашем публичном статическом методе void main () просто создайте его экземпляр. Надеюсь, что часть этого кода поможет.

EDITED - Хорошо, теперь вы должны создать класс Actions и отсканировать действие (например, «получить лампу» или «бросить меч»). Чтобы упростить его, вам понадобится объект или метод RoomScan, чтобы сканировать все видимое в области действия и проверять только те объекты в этом действии. Сам объект обрабатывает обработку действий, и по умолчанию у вас должен быть класс Item, который обрабатывает все известные действия способом по умолчанию, который может быть переопределен. Теперь, если, например, предмет, который вы хотите «получить», удерживается неигровым персонажем, ответ по умолчанию для получения этого предмета, принадлежащего его владельцу, должен выглядеть примерно так: «Не позволит вам его получить». Теперь вам нужно создать тонну стандартных ответов на это в классе Item или Thing. В основном это происходит с точки зрения Tads3 по всему дизайну. Потому что в Tads3 у каждого элемента есть своя собственная процедура обработки действий по умолчанию, которую парсер вызывает, если действие над ним инициализировано. Итак ... Я просто говорю вам, Tads3 уже имеет все это на месте, так что ОЧЕНЬ легко кодировать в текстовом приключении на этом языке. Но если вы хотите сделать это с нуля, как в Java (см. Выше), то лично я бы поступил так же, как и Tads3. Таким образом, вы можете переопределить действия по умолчанию, обрабатывающие процедуры для самих различных объектов, поэтому, например, если вы хотите «получить лампу», а дворецкий ее держит, это может вызвать ответ в методе действия «get» по умолчанию для Item. или возразите и скажите, что «Дворецкий отказывается отдать медную лампу». Я имею в виду ... как только вы станете программистом достаточно долго, как я, тогда это все ОЧЕНЬ легко. Мне более 50 лет, и я занимаюсь этим с семи лет. Мой отец был инструктором Hewlett Packard в 70-х, поэтому я сначала узнал от него ТОННО по программированию. Я также в резерве армии США в качестве администратора сервера. Хм ... да, так что не сдавайся. Это не так сложно, если вы действительно разберетесь с тем, что вы хотите, чтобы ваша программа делала. Иногда метод проб и ошибок - лучший способ подобного рода вещей. Просто проверь это и смотри, и никогда не сдавайся. Ладно? Кодирование - это искусство. Это можно сделать разными способами. Не позволяйте так или иначе, кажется, загнать вас в угол дизайна. м также в резерве армии США в качестве администратора сервера. Хм ... да, так что не сдавайся. Это не так сложно, если вы действительно разберетесь с тем, что вы хотите, чтобы ваша программа делала. Иногда метод проб и ошибок - лучший способ подобного рода вещей. Просто проверь это и смотри, и никогда не сдавайся. Ладно? Кодирование - это искусство. Это можно сделать разными способами. Не позволяйте так или иначе, кажется, загнать вас в угол дизайна. м также в резерве армии США в качестве администратора сервера. Хм ... да, так что не сдавайся. Это не так сложно, если вы действительно разберетесь с тем, что вы хотите, чтобы ваша программа делала. Иногда метод проб и ошибок - лучший способ подобного рода вещей. Просто проверь это и смотри, и никогда не сдавайся. Ладно? Кодирование - это искусство. Это можно сделать разными способами. Не позволяйте так или иначе, кажется, загнать вас в угол дизайна.


К сожалению, это исключает наиболее сложную часть текстового анализатора, а именно: определение глагола, субъекта и объекта пользовательского ввода и сопоставление его с действием.
Филипп

Верно, но как я могу это сделать, это создать класс действий и сохранить набор действий, скажем, в классе словаря, а затем сканировать слова действий. Если действие включает в себя 2-е слово (например, для действия «принять», может быть, «взять лампу»), тогда просканируйте группу объектов (или существительных) на предмет того, где сами эти объекты будут иметь скрипт для обработки действий, выполненных над ними. Все это предполагает, что вы все кодируете прямо в Java, а не пытаетесь сделать реальный внешний файл для чтения, чтобы скомпилировать текстовые приключения.
Уильям Челонис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.