开发者

Using Switch-blocks to Parse input for a Text Adventure?

Okay, I have a goal right now to make a basic text adventure. To do this, however, I would need/want to have a switch statement t开发者_开发问答hat can do the following:

  • Check to see if a string has a word SOMEWHERE inside.
  • Check to see if a string has two words in any combination somewhere inside.

How would I accomplish this? Could you show me coding for this specific example:

A user is prompted for data. A switch statement checks for "look box" as one case, and "sleep" as another. The program doesn't care what order any words are in, but does care about the order of the letters.

Please explain everything in detail. I just started coding.

EDIT: Thank you for all the answers. I understand there are better, more complicated, and more useful ways of handling this, but it just isn't at my level yet.


People sometimes ask me why I don't built a boat. I'm pretty handy, I like building things, and I sail. I always tell them that people who like to sail shouldn't build boats, because you end up spending three years in your garage building a boat before you can go sailing. If your goal is to sail, buy a boat. If your goal is to build a boat, build a boat.

If your goal is to learn C# by writing a text adventure, great, you'll learn a lot. If your goal is to write a text adventure, then don't use C#, use Inform7. It is easy to learn, specifically designed to write text adventures, and is probably the most high level language in the world. It is an amazing programming language and I highly recommend it.

To answer your specific question: that is not a good way to go about it. The way text adventure processors actually work is first you write a program that breaks the sentence typed by the user up into tokens. You have to search through the string character by character looking for boundaries between words, like spaces, commas, periods, and so on. Once you find the boundaries then you extract the substrings between the boundaries and try to recognize every word by comparing it to words in a dictionary.

Once you have a sequence of tokens you then try to match the sequence against a grammar. That is, you see if the sequence of tokens can be classified as a one-word command like {"look"} or a verb-noun phrase like {"look", "at", "the", "red", "button"}. You want to break that down - "look" is the verb, "at" is the preposition, "the red button" is the object of the verb, "the" is the article, "red" is the adjective and "button" is the noun.

It sounds like you're a beginner, so concentrate first on the lexical analysis; walking through the string one character at a time, identifying word boundaries, and building up a List<string> of tokens. Techniques for grammatical analysis can be quite complicated; get the simple stuff done and solid first.


Considering you're starting at, we can look at this in the simple case first, but you can't use a switch statement to achieve this.

Let's assume, for the purposes of simplicity, that your commands are limited to 1 or 2 words and that the first word is a verb and the second would, if present is a noun. This gives us quite a few possibilities:

North
South
Examine
Take
Drop

etc...

Given that we have an input string of strInput:

string strInput = "examine hat";

We want to first split this up. We can do this using String.Split:

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

Which will give us a string array:

arguments [0] is examine

arguments [1] is hat

Note, we don't always have a the 2nd one, if the user typed:

`North`

then:

arguments [0] is North

We'll need to check for this! Now, the horrible (but simple) way to check for this is:

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

Unfortunately, this code is going to get long, complex and unusable. How do you know what you can and can't do at any stage? How do you add new command? So let's use the wonderful delegate feature of C#, and also introduce the Dictionary. A dictionary allows us to map one type (a key) to another (in this case, a delegate). Using this method, we can create delegates to handle different kinds of commands.

public delegate void HandleCommand(string[] _input);

Here, we delegated a delegate. Don't worry about it yet, but let's introduce some functions that will work with commands:

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.
    }
}

And so on. Now we need to create a dictionary to map the commands to these functions:

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

Here, we declare a dictionary that maps strings to our delegate type HandleCommand. Now we need to populate it!

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

Now, given our earlier example, let's split the string up as before, and call the right handler!

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

Now we have an extensible system. It is easy to add new commands and handlers! It gets more complicated, but in essence this is a good way to do what you want.

EDIT: I am aware that your question said that it should not care about the order of the words. If you're writing a text adventure game, you'd do well to form some grammer of Verb/Noun or some such rather than allowing things to be typed randomly.


Here's another idea:

    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;
    }

It uses if/then/else to assess a particular scenario including potential combinations such as input like "look at the sleep box" and then uses a switch statement to execute accordingly.


You can't do this using switch, you'll have to use the if-else-if type of structure.

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
}

With switch each case must be some static, unique value. So you can't use .Contains as a case.


You can’t use switch for this directly, but then again, I think you shouldn’t. You should probably have the logic that finds the words in a different place than the logic that contains the switch.

Consider using an enum to contain all the possible actions:

public enum AdventureAction
{
    LookBox,
    Sleep
}

Consider writing a method that does the “parsing”:

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

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

And then you can use a simple switch statement to perform the action:

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

    case AdventureAction.Sleep:
        // go to sleep
        break;
}


I'm currently writing my own text adventure engine due to Inform/Adrift/Quest all tending to have a fatal flaw that bugs me — Inform's nightmarish, obfuscated syntax (and this is coming from a UX designer who likes things to be as easy as possible for beginners), Adrift's ugly reader, and Adrift/Quests lack of real class/object support.

It may not be the best way to do it, but it's working fine so far. I looked into Regex, but decided to do it this way instead.

The first thing to do is split the player's input/command string into a List. Luckily, in these games, the first element of this list is almost always a verb.

  • look
  • at
  • blue
  • book

You will want verb/object/etc data classes that can be accessed by key, containing all the values that can match to it, such as "look, examine, ex".

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;
}

You will also need a bunch of "Action" subclasses that will be matched from the player's input. Here you "build" your sentence structure that needs to be matched by the player's input.

  • Action (base class)
    • look
    • look {at} [object]
    • look {at} book
    • Jump
    • Jump {on/onto} [object]

.

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();
}

The base Action class has a sentence builder using SentenceElements. It can be a List that describes the sentence, piece by piece.

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 {}

You can now search through your "Action" classes, compare the first SentenceElement to the players first input verb, and keep a list of the ones that match as "potentialActions". "string.Contains" will work.

Now you have to find the closest matching Action by stepping through your players' input command, and stepping through every SentenceElement comparing them. Keep an index of where you are in each one (playerInputIndex, potentialActionsIndex, sentenceElementIndex).

If you find a match, increment the playerInputIndex until it doesn't match the SentenceElement, check your settings (isOptional, etc), then move to the next sentenceElementIndex and run the compare all over again. Eventually you'll reach the end of either the player's input, or the SentenceElements.

Complexity is added when you have SentenceElements that are "isOptional", so it will have to be checked for, or Actions that have SentenceElements of type ObjectAny that aren't supposed to match an existing one, but display a "Which object did you want to eat?" message. Also, objects have extra matching parameters such as prefixes/adjectives/nouns to factor in, unlike verbs.

This system will also require a new class for every action you could ever want to do. Each action would have custom "Restrictions" it must also pass before it will run, such as "Is the referenced character alive?", etc.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜