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

Хорошо, у меня есть цель прямо сейчас сделать простое текстовое приключение. Однако для этого мне понадобится/хочу иметь оператор switch, который может делать следующее:

  • Проверьте, есть ли внутри строки слово ГДЕ-НИБУДЬ.
  • Проверьте, содержит ли строка два слова в любой комбинации где-то внутри.

Как бы я это сделал? Не могли бы вы показать мне код для этого конкретного примера:

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

Пожалуйста, объясните все подробно. Я только начал кодить.

РЕДАКТИРОВАТЬ: Спасибо за все ответы. Я понимаю, что есть лучшие, более сложные и более полезные способы справиться с этим, но это пока не мой уровень.


person Ian Cordle    schedule 22.01.2011    source источник


Ответы (6)


Вот еще идея:

    string input = "look at the sleep box";
    bool caseA = input.Contains("sleep");
    bool caseB = input.Contains("look") && input.Contains("box");

    int scenarioId;
    if (caseA && caseB)
        scenarioId = 1;
    else if (caseA)
        scenarioId = 2;
    else if (caseB)
        scenarioId = 3;
    // more logic?
    else
        scenarioId = 0;

    switch (scenarioId)
    {
        case 1:
            Console.WriteLine("Do scenario 1");
            break;
        case 2:
            Console.WriteLine("Do scenario 2");
            break;
        case 3:
            Console.WriteLine("Do scenario 3");
            break;
        // more cases
        default:
            Console.WriteLine("???");
            break;
    }

Он использует if/then/else для оценки конкретного сценария, включая возможные комбинации, такие как ввод типа "look at the sleep box", а затем использует оператор switch для соответствующего выполнения.

person Paul Sasik    schedule 22.01.2011
comment
В основном эквивалентен моему, но немного чище из-за временных переменных case. - person Earlz; 22.01.2011
comment
А что, если бы пользователь набрал посмотреть на сонного боксера? - person Eric Lippert; 22.01.2011
comment
это хороший момент. Я мог бы просто добавить пробелы по обе стороны от данных, которые я проверяю. - person Ian Cordle; 22.01.2011
comment
@Eric: Верно, но мы вряд ли сможем создать надежный движок текстовых приключений в рамках поста SO. Моя идея состояла в том, чтобы помочь новичку с некоторой базовой логикой ветвления, вот где, кажется, ОП. Я согласен с идеями в вашем ответе, за исключением того, что идеи могут быть слишком продвинутыми для ОП. - person Paul Sasik; 22.01.2011
comment
Это лучший способ справиться с этим, который я видел до сих пор. Спасибо! - person Ian Cordle; 22.01.2011
comment
@IanVal: Если вы хотите рассмотреть идеи в комментарии Эрика на этом этапе, я бы предложил использовать RegEx для анализа вашего ввода, а не базовые строковые API C #. Конечно, ваша кривая обучения также должна включать RegEx. - person Paul Sasik; 22.01.2011
comment
@Paul, я не знаю, что делает или чем является RegEx. Если это лучший способ справиться с этим, который достаточно прост для меня, чтобы понять, то я не против изучить его. - person Ian Cordle; 22.01.2011
comment
RegEx на самом деле не является основным. Это очень краткий и сложный способ выражения того, что вам нужно делать. Сейчас ищу хороший образец... - person Paul Sasik; 22.01.2011
comment
@IanVal: language — это набор строк. Регулярный язык — это язык, который можно успешно сопоставить с шаблоном регулярного выражения. Регулярное выражение — это краткий способ указания шаблонов, таких как четыре или более букв, за которыми следует один или несколько пробелов. Многие люди пытаются использовать регулярные выражения для создания лексических анализаторов; Я рекомендую против этого для начинающих. Создайте свой лексер с нуля, так вы узнаете больше, а затем, когда вы научитесь использовать регулярные выражения, вы лучше поймете, что они делают. - person Eric Lippert; 22.01.2011
comment
Если вас интересуют основы теории, лежащей в основе распознавания языковых образов, моя серия статей на эту тему является разумным введением: blogs.msdn.com/b/ericlippert/archive/tags/regular+expressions - person Eric Lippert; 22.01.2011
comment
@IanVal: Вики-статья о лексическом анализе довольно хороша. Проверьте это: en.wikipedia.org/wiki/Lexical_analysis Прочитайте это, и я думаю, что точки взгляды и технические мнения в этом посте будут иметь больше смысла и, возможно, откроют новое понимание. - person Paul Sasik; 22.01.2011

Люди иногда спрашивают меня, почему я не строю лодку. Я очень ловкий, люблю строить и хожу под парусом. Я всегда говорю им, что люди, которые любят плавать, не должны строить лодки, потому что в конечном итоге вы проведете три года в своем гараже, строя лодку, прежде чем вы сможете отправиться в плавание. Если ваша цель — плавать, купите лодку. Если ваша цель — построить лодку, постройте лодку.

Если ваша цель — изучить C#, написав текстовое приключение, отлично, вы многому научитесь. Если ваша цель — написать текстовое приключение, не используйте C#, используйте Inform7. Он прост в освоении, специально разработан для написания текстовых приключений и, вероятно, является языком самого высокого уровня в мире. Это удивительный язык программирования, и я очень рекомендую его.

Чтобы ответить на ваш конкретный вопрос: это не лучший способ сделать это. На самом деле процессоры текстовых приключений работают следующим образом: сначала вы пишете программу, которая разбивает введенное пользователем предложение на токены. Вы должны искать в строке символ за символом, ища границы между словами, такими как пробелы, запятые, точки и так далее. Как только вы найдете границы, вы извлекаете подстроки между границами и пытаетесь распознать каждое слово, сравнивая его со словами в словаре.

Получив последовательность токенов, вы пытаетесь сопоставить эту последовательность с грамматикой. То есть вы видите, может ли последовательность токенов быть классифицирована как команда из одного слова, такая как {"look"}, или фраза глагол-существительное, такая как {"look", "at", "the", "red", " кнопка"}. Вы хотите разбить это на части: «смотреть» — это глагол, «на» — предлог, «красная кнопка» — объект глагола, «the» — артикль, «красный» — прилагательное и «кнопка». это существительное.

Похоже, вы новичок, поэтому сначала сконцентрируйтесь на лексическом анализе; обход строки по одному символу за раз, определение границ слов и создание List<string> маркеров. Методы грамматического анализа могут быть довольно сложными; сделайте простые вещи и прочные в первую очередь.

person Eric Lippert    schedule 22.01.2011
comment
Моя цель — написать текстовое приключение, чтобы узнать больше о C#. Спасибо за ваше мнение, но оно на самом деле не ответило на мой вопрос, однако ответили несколько других. - person Ian Cordle; 22.01.2011
comment
+1, хороший ответ. Я попытался объяснить это подробнее в своем ответе, но да ладно :) - person Moo-Juice; 22.01.2011
comment
@IanVal: твой вопрос в том, как мне сделать это неправильно? и ответ вы делаете это неправильно. Поверьте мне, вы узнаете гораздо больше о программировании, создав правильный лексер и синтаксический анализатор, чем собирая воедино оператор if из тысячи строк, который пытается обработать каждый случай. - person Eric Lippert; 22.01.2011

Учитывая, что вы начинаете с, мы можем сначала рассмотреть это в простом случае, но вы не можете использовать оператор switch для достижения этого.

Давайте предположим, для простоты, что ваши команды ограничены 1 или 2 словами и что первое слово является глаголом, а второе, если присутствует, является существительным. Это дает нам довольно много возможностей:

North
South
Examine
Take
Drop

так далее...

Учитывая, что у нас есть входная строка strInput:

string strInput = "examine hat";

Мы хотим сначала разделить это. Мы можем сделать это, используя String.Split:

string[] arguments = strInput.Split(' ');

Что даст нам массив строк:

arguments [0] проверить

аргументы [1] — это шапка

Обратите внимание, что у нас не всегда есть второй, если пользователь набрал:

`North`

тогда:

arguments [0] — Север

Нам нужно это проверить! Теперь ужасный (но простой) способ проверить это:

if(arguments[0] == "North")
{
    // code to go North
}
else if(arguments[0] == "Take")
{
    // code to work with Take.  We'd check on arguments[1] here!
}
// etc...

К сожалению, этот код станет длинным, сложным и непригодным для использования. Откуда вы знаете, что вы можете и не можете делать на каком-либо этапе? Как добавить новую команду? Итак, давайте воспользуемся замечательной функцией делегата C#, а также представим класс Dictionary. Словарь позволяет нам сопоставлять один тип (ключ) с другим (в данном случае с делегатом). Используя этот метод, мы можем создавать делегатов для обработки различных типов команд.

public delegate void HandleCommand(string[] _input);

Здесь мы делегировали делегата. Пока не беспокойтесь об этом, но давайте представим некоторые функции, которые будут работать с командами:

public void Handle_North(string[] _input)
{
    // code to go North.  This function could just as easily be something that handles
    // *all* directions and checks _input[0] to see where to go!
}

public void Handle_Take(string[] _input)
{
    if(_input.Length > 1) // Did the user specify an object to take?
    {
        // code to handle Take.
    }
}

И так далее. Теперь нам нужно создать словарь для сопоставления команд с этими функциями:

Dictionary<String, HandleCommand> map = new Dictionary<String, HandleCommand>();

Здесь мы объявляем словарь, который сопоставляет строки с нашим типом делегата HandleCommand. Теперь нам нужно заполнить его!

map["north"] = Handle_North;
map["take"]  = Handle_Take;
// and the rest of our commands

Теперь, учитывая наш предыдущий пример, давайте разделим строку, как и раньше, и вызовем правильный обработчик!

string[] arguments = strInput.Split(' ');
if(arguments.Length > 0 && map.ContainsKey(arguments[0]))
    map[arguments[0]](arguments);  // calls our function!

Теперь у нас есть расширяемая система. Легко добавлять новые команды и обработчики! Это становится сложнее, но по сути это хороший способ делать то, что вы хотите.

РЕДАКТИРОВАТЬ: я знаю, что ваш вопрос сказал, что он не должен заботиться о порядке слов. Если вы пишете текстовую приключенческую игру, вам лучше сформировать грамматику глагола/существительного или что-то в этом роде, а не позволять печатать слова случайным образом.

person Moo-Juice    schedule 22.01.2011

Вы не можете сделать это, используя switch, вам придется использовать структуру типа if-else-if.

string input=...
if(input.Contains("sleep")){ //contains sleep? 
  //do stuff for some word
}else if(input.Contains("look") && input.Contains("box")){ //contains look and box
  //do stuff for the combination thing
}

С switch каждое case должно быть некоторым статическим уникальным значением. Таким образом, вы не можете использовать .Contains в качестве случая.

person Earlz    schedule 22.01.2011
comment
Замечательно! Вы уверены, что операторы switch не работают? Это сэкономило бы мне кучу времени... - person Ian Cordle; 22.01.2011
comment
@ Ян, да, к сожалению, без сложного хакерства не обойтись. Почему оператор switch сэкономит ваше время? Единственное, что меня раздражает в этом коде, это то, что input.Contains повторяется очень много раз. - person Earlz; 22.01.2011
comment
Все хорошо. Я просто не люблю длинные операторы if-else. Скорее всего будет 5-10 действий на комнату, и кто знает сколько данных на действие, так что может запутаться. - person Ian Cordle; 22.01.2011
comment
Правильно, потому что длинный переключатель намного лучше, чем длинный блок if-else. В конце концов вы обнаружите, что ни один из них не подойдет. Скорее всего, вы найдете лексический анализатор лучшим инструментом. - person Tergiver; 22.01.2011
comment
@IanVal, этого ответа достаточно, чтобы дать ваш уровень знаний в области кодирования, так что это хорошее признание. См. мой ответ, чтобы узнать, как добиться того, чего вы хотите, расширяемым образом, а не пытаться искать строки во входных данных. - person Moo-Juice; 22.01.2011

Вы не можете использовать switch для этого напрямую, но опять же, я думаю, что вы не должны. Вероятно, у вас должна быть логика, которая находит слова в другом месте, чем логика, содержащая switch.

Рассмотрите возможность использования перечисления, содержащего все возможные действия:

public enum AdventureAction
{
    LookBox,
    Sleep
}

Рассмотрите возможность написания метода, выполняющего «анализ»:

public static AdventureAction Parse(string text)
{
    if (text.Contains("look") && text.Contains("box"))
        return AdventureAction.LookBox;

    if (text.Contains("sleep"))
        return AdventureAction.Sleep;
}

И затем вы можете использовать простой оператор switch для выполнения действия:

var action = Parse(input);
switch (action)
{
    case AdventureAction.LookBox:
        // do something interesting with the box
        break;

    case AdventureAction.Sleep:
        // go to sleep
        break;
}
person Timwi    schedule 22.01.2011
comment
Интересно... Однако похоже, что метод if-else-if более прямолинеен. - person Ian Cordle; 22.01.2011
comment
@IanVal: Это более прямолинейно, если вы не заглядываете далеко вперед, да. Но подумайте, как вы собираетесь его расширить, если хотите, чтобы ваше текстовое приключение было доступно на нескольких языках (например, на немецком, французском и т. д.)? Или если вы хотите повторно использовать тот же движок для создания новой приключенческой игры? Если вы правильно разделите логику, каждую из этих вещей можно сделать в сотни раз проще в долгосрочной перспективе. - person Timwi; 22.01.2011

В настоящее время я пишу свой собственный текстовый движок приключений из-за того, что у Inform/Adrift/Quest есть фатальный недостаток, который меня раздражает — кошмарный, запутанный синтаксис Inform (и это исходит от UX-дизайнера, который любит, чтобы все было так же просто, как возможно для новичков), уродливый читатель Adrift, а в Adrift/Quests отсутствует поддержка реальных классов/объектов.

Возможно, это не лучший способ сделать это, но пока он работает нормально. Я изучил Regex, но вместо этого решил сделать это таким образом.

Первое, что нужно сделать, это разделить строку ввода/команды игрока на список. К счастью, в этих играх первым элементом этого списка почти всегда является глагол.

  • Смотреть
  • at
  • синий
  • книга

Вам понадобятся классы данных глагола/объекта/и т. д., к которым можно получить доступ по ключу, содержащему все значения, которые могут совпадать с ним, такие как «смотреть, исследовать, пример».

class Verb
{
    string uniqueID;
    List<string> values;
}

class Object
{
    public uniqueID; // Because this is shared with Verbs, you could do a better unique ID system, but hey...
    public List<string> articles;
    public List<string> adjectives;
    public List<string> noun;
}

Вам также понадобится куча подклассов «Действие», которые будут сопоставляться с вводом игрока. Здесь вы «строите» свою структуру предложения, которая должна соответствовать вводу игрока.

  • Action (base class)
    • look
    • смотреть {на} [объект]
    • смотреть {на} книгу
    • Прыгать
    • Перейти {на/на} [объект]

.

class Action
{
    string uniqueID;
    List<SentenceElement> sentence;

    void Execute();
    bool RestrictionsCheck();
}

class Look : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

class LookAtObject : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

class LookAtBook : Action
{
    override void Execute();
    override bool RestrictionsCheck();
}

В базовом классе Action есть построитель предложений, использующий SentenceElements. Это может быть список, описывающий предложение по частям.

class SentenceElement
{
    List<string> values;
    bool isOptional;
}

class VerbID : SentenceElement {}
class ObjectID : SentenceElement {}
class ObjectAny : SentenceElement {}
class CharacterID : SentenceElement {}
class CharacterAny : SentenceElement {}
class TextOptional : SentenceElement {}
class TextRequired : SentenceElement {}
class Parameter : SentenceElement {}

Теперь вы можете выполнять поиск по своим классам «Действие», сравнивать первый SentenceElement с первым введенным игроком глаголом и сохранять список тех, которые соответствуют «потенциальным действиям». "string.Contains" будет работать.

Теперь вам нужно найти наиболее подходящее действие, пройдя через команду ввода ваших игроков и пройдясь по каждому SentenceElement, сравнивая их. Сохраняйте индекс того, где вы находитесь в каждом из них (playerInputIndex, PotentialActionsIndex, OfferingElementIndex).

Если вы найдете совпадение, увеличивайте playerInputIndex до тех пор, пока он не будет соответствовать SentenceElement, проверьте свои настройки (isOptional и т. д.), затем перейдите к следующему предложениюElementIndex и снова запустите сравнение. В конце концов вы дойдете до конца либо ввода игрока, либо SentenceElements.

Сложность добавляется, когда у вас есть SentenceElements, которые являются «isOptional», поэтому их нужно будет проверять, или действия, которые имеют SentenceElements типа ObjectAny, которые не должны совпадать с существующим, но отображают «Какой объект вы хотите кушать?" сообщение. Кроме того, объекты имеют дополнительные параметры соответствия, такие как префиксы/прилагательные/существительные, которые необходимо учитывать, в отличие от глаголов.

Эта система также потребует новый класс для каждого действия, которое вы когда-либо захотите сделать. Каждое действие будет иметь настраиваемые «Ограничения», которые оно также должно пройти, прежде чем оно будет запущено, например «Жив ли упомянутый персонаж?» и т. Д.

person st4rdog    schedule 02.09.2015