2

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

Textmode
  • 509
  • 3
  • 18
Ian Cordle
  • 206
  • 4
  • 13

6 Answers6

12

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • My goal is to write a text adventure in order to learn more about C#. Thank you for your opinion, but it didn't really answer my question, however several others did. – Ian Cordle Jan 22 '11 at 15:12
  • +1, Good answer. I tried to explain it more in my answer, but, oh well :) – Moo-Juice Jan 22 '11 at 15:13
  • 12
    @IanVal: your question is "how do I do it wrong?" and the answer is *you're doing it wrong*. Believe me, you'll learn *far more* about programming by building a proper lexer and parser than by hacking together a thousand-line "if" statement that tries to handle every case. – Eric Lippert Jan 22 '11 at 15:17
2

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.

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
1

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.

Earlz
  • 62,085
  • 98
  • 303
  • 499
  • That's great! Are you sure thers is no way for switch statements to work? It would save me a ton of time... – Ian Cordle Jan 22 '11 at 15:00
  • 1
    @Ian yes, sadly there is no way without some complex hackery. Why would a switch statement save you time? The only annoying thing about this code to me is that `input.Contains` is repeated so many times – Earlz Jan 22 '11 at 15:01
  • It's fine. I just don't like lengthy if-else statements. Most likely there will be 5-10 actions per room, and who knows how much data per action, so it could get confusing. – Ian Cordle Jan 22 '11 at 15:06
  • 1
    Right, because a lengthy switch is much better than a lengthy if-else block. In the end you're going to find that neither of these will do. You will likely find a lexical analyzer to be the better tool. – Tergiver Jan 22 '11 at 15:08
  • 1
    @IanVal, This answer is sufficient giving your level of expertise in coding, so good acceptance. See my answer for a way of achieving what you want in an extensible manner rather than trying to look for strings in the input. – Moo-Juice Jan 22 '11 at 15:08
1

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.

Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • Basically equivalent to mine, but a bit more clean because of the temporary `case` variables. – Earlz Jan 22 '11 at 15:04
  • 3
    And what if the user typed "look at the sleepy boxer" ? – Eric Lippert Jan 22 '11 at 15:10
  • that is a good point. I could just add spaces on either side of the data I am checking for. – Ian Cordle Jan 22 '11 at 15:16
  • @Eric: Right, but we can hardly build a robust text adventure engine within the scope of an SO post. My idea was to help a newbie with some basic branching logic, that's where the OP seems to be. I agree with the insights in your answer except that the ideas may be too advanced for the OP. – Paul Sasik Jan 22 '11 at 15:16
  • This is the best way of handling it that I've seen so far. Thanks! – Ian Cordle Jan 22 '11 at 15:17
  • @IanVal: If the ideas in Eric's comment are a consideration you want to make at this point then I would suggest using RegEx to parse your input rather than basic C# string APIs. Of course your learning curve would have to include RegEx as well. – Paul Sasik Jan 22 '11 at 15:17
  • @Paul I don't know what RegEx does or is. If it is a better way of handling it that is basic enough for me to understand, then I don't mind learning it. – Ian Cordle Jan 22 '11 at 15:19
  • RegEx isn't really basic. It's a very terse and complex way of expressing the types of things you need to do. Looking for a good sample now... – Paul Sasik Jan 22 '11 at 15:22
  • 3
    @IanVal: a *language* is a set of strings. A *regular language* is a language that can be successfully pattern-matched against a *regular expression*. A *regular expression* is a concise way of specifying patterns like "four or more letters followed by one or more spaces". Many people attempt to use regular expressions to build lexical analyzers; I recommend against it for beginners. Build your lexer from scratch, you'll learn more that way and then when you learn how to use regexps you'll understand better what they are doing. – Eric Lippert Jan 22 '11 at 15:24
  • 1
    If you're interested in the basics of the theory underlying language pattern recognition my series of articles on the subject is a reasonable introduction: http://blogs.msdn.com/b/ericlippert/archive/tags/regular+expressions/ – Eric Lippert Jan 22 '11 at 15:25
  • @IanVal: The wiki article on lexical analysis is quite good. Check it out: http://en.wikipedia.org/wiki/Lexical_analysis Read it and I think the points of view and tech opinions in this post will make more sense and perhaps open new understandings. – Paul Sasik Jan 22 '11 at 15:28
0

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.

st4rdog
  • 93
  • 1
  • 6
0

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;
}
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • Interesting... Seems like the if-else-if method is more straight-forward, however. – Ian Cordle Jan 22 '11 at 15:05
  • @IanVal: It’s more straight-forward if you don’t think very far ahead, yes. But think about how you are going to extend it if you want your text adventure available in multiple languages (e.g. German, French, etc.)? Or if you want to re-use the same engine to create a new adventure game? If you separate logic properly, each of these things can be made hundreds of times easier in the long run. – Timwi Jan 22 '11 at 19:31