views:

62

answers:

4

This is fairly difficult to explain. I am working on a text adventure at the moment and I am parsing input from a player.

When a player enters input, there can be two possible subjects (let's call them subject1 and subject2). For example, in the following: "hit door with stick", subject1 is "door" and subject2 is "stick".

In this case, both door and stick are of the type "Item". There can also be subjects of the type "Character".

The problem is that if I parse items, then characters, the item parse will find the item as subject1 even if it's actually the second subject. The code I am using looks like this:

public static void ParseForSubjects(string Input, Player CurrentPlayer, ref object Subject1, ref object Subject2)
{
    // This method doesn't really work properly, as it looks up Inventory items, Environment items and then Characters in order
    // when it may well be that a character is subject1 and an item is subject2, but they will be reversed because of the parsing order
    Input = Input.ToLower();

    // Parse items in Player inventory
    foreach (Item InventoryItem in CurrentPlayer.Inventory)
    {
        if (Input.Contains(InventoryItem.Name.ToLower()))
        {
            if (Subject1 == null)
            {
                Subject1 = InventoryItem;
            }
            else
            {
                Subject2 = InventoryItem;
            }
        }
    }

    // Parse items in environment
    foreach (Item EnvironmentalItem in CurrentPlayer.CurrentArea.Items)
    {
        if (Input.Contains(EnvironmentalItem.Name.ToLower()))
        {
            if (Subject1 == null)
            {
                Subject1 = EnvironmentalItem;
            }
            else
            {
                Subject2 = EnvironmentalItem;
            }
        }
    }

    // Parse present characters
    foreach (Character PresentCharacter in CurrentPlayer.CurrentArea.Characters)
    {
        if (Input.Contains(PresentCharacter.Name.ToLower()))
        {
            if (Subject1 == null)
            {
                Subject1 = PresentCharacter;
            }
            else
            {
                Subject2 = PresentCharacter;
            }
        }
    }
}

I'm sure if this is really clear enough. Basically regardless of the type, I need subject1 to be the first subject in the Input string and subject2 to be the second subject in the Input string.

Feel free to ask questions, this probably isn't 100% clear.

A: 

Could you do something incredibly simple like save the starting position of each subject you find, and then at the end of your function, use the saved positions to determine which subject came first?

Jeremy Goodell
Potentially. You mean once I find a subject, save the index in the string at which the subject starts and then if they are mismatched, swap them later on?
Liggi
But you shouldn't swap them... if the player types "Hit stick with door" You shouldn't assume they meant hit the door with the stick... you'd want to tell them "You can't hit stick with door". or "Door is not in your inventory."
JohnForDummies
@JohnForDummies: He's trying to put them in the order they occurred in the sentence. The problem is he searches the whole sentence for items, and then the whole list for characters (or whatever), so if a character was the first noun and an item was the second, he'll have recorded them in the wrong order
Tim Goodman
But they could say "Use key in lock" or "Open lock with key" so there's some cases where they could easily be reversed. Just something to keep in mind.
md5sum
Oooh. That's a good point. I didn't think of that. Oh bugger.
Liggi
+2  A: 

Just check Input.IndexOf(Subject1.Name.ToLower()) < Input.IndexOf(Subject2.Name.ToLower()) and if false, then switch them.

md5sum
Not quite. As I'm saving the subject into an object, the object no longer has the Name property.
Liggi
You have a `Name` property on all of the Item types. Consider using an interface so that you can cast the object to that interface type and retrieve the name.
md5sum
Spot on. I'll give this a go.
Liggi
You are my saviour, sir. This worked a treat!
Liggi
A: 

If I understand you correctly, what you're actually saying is that "stick" becomes Subject1 if it is found first in the inventory collection, even though it is actually Subject2 in the input line. If so, why not change the Subject variables into an array, and then populate that array based on the position of the noun in the input?

Thus, Subject[] would be populated as: Subject[1]="door" and Subject[2]="stick". You then iterate through the array, processing the subjects in ascending order.

This requires a little more support code to determine which of the 'n' nouns in the input you are currently parsing, but if you restructure the parse loop so that you're parsing the input rather than the inventory, this becomes trivial.

This article may also help refine the thought-process on this: Natural Language Processing

Jonners
+1  A: 

I'd suggest turning your logic upside down. Rather than checking every inventory item to see if it's mentioned in the string, just parse the string and check the inventory for the item. For example:

string[] parsedInput = Input.Split([' ']);

Now you have an array that contains the user's input, word-by-word. If you always know that subject1 is the second word and subject2 is the fourth, then:

string subject1 = parsedInput[1];
// check to see if subject1 is in the Inventory or the Environment collections

Do the same thing with subject2 (i.e. parsedInput[3])

If you can't guarantee where the subjects are in the array, then you'll have to iterate over the array to determine which of the items are subjects. The key is to use the user's input to check the inventory/environment/character collections rather than the other way around. So rather than saying:

for each item in inventory/environment/characters
  did user's input mention this?

You say instead:

for each item that the user mentioned
  is it in the inventory/environment/characters collections?
Jim Mischel
This is exactly what I was going to suggest.
Erik Forbes
It's not necessarily always set in stone though. Someone may say "knock down the door with the axe", "knock down door with axe" etc. etc.
Liggi