Comparing Strings

Started by Atelier, Wed 20/07/2011 14:36:43

Previous topic - Next topic

Atelier

Hi, what's the best way of comparing two similar, but not necessarily identical, strings?
What I'm looking for is a 'best guess' at which character the player wants to look at.

Code: ags


//string command is what the player types in

if (Parser.Said("look rol"))
{
     int c = 0;
     String s = command.Substring(5, command.Length-5);  // Take off the 'look' and space
     
     while (c <= Game.CharacterCount) {
          if ((s.IndexOf(character[c].Name) != -1) && (character[c].Room == player.Room)) Add(info[character[c].ID].description);
          c++;
     }
}



For example, if the player types "look monk", when the actual script name of the character is "a pious monk", the description is not added.
Everything works if you type in the full name: "look a pious monk" but that's inconvenient.

So how can I make it recognise similar strings, or at least pick out a defining word from the character's name?

ddq

First of all, just because the player said "look at something" doesn't mean they entered "look at something". They could have said "observe the somediddle" and Parser.Said could recognize it as "look at something" due to synonyms. You can set "monk", "pious monk", "holy man", "dude of the cloth", etc. to all be synonyms in the Parser panel. Further, many short words and articles such as "a", "an", and "the" are ignored by default, but you can customize those as well.

First thing you potentially have to worry about is trimming your string. You could have any number of synonyms for "look" that could be more or less than 4 characters plus a space, in addition to ignored words in between "look" and the character name you're trying to parse. If you can robustly trim the string to just the rol, which I'm pretty sure you can, then you can use the parser again, which will take care of spaces, ignored words, and most importantly, synonyms for you.

Code: ags

int c = 0;
String s = command.Substring(4, command.Length-4); 
// Probably a better way, but can't think of it off the top of my head.
// definitely won't work if player puts ignore words before look

Parser.ParseText(s); // Parse the substring
while (c <= Game.CharacterCount) 
{
      if ( Parser.Said(character[c].Name) && (character[c].Room == player.Room)) 
          Add(info[character[c].ID].description);
      c++;
}
'
As long as you have the actual character name set as a synonym for what you want the player to enter, this should work, but it's untested.

monkey0506

#2
The Parser.FindWordID function would actually be pretty useful I imagine in determining the length of the actual said word in place of the length of the checked synonym.

Code: ags
if (Parser.Said("look rol"))
{
  int lookID = Parser.FindWordID("look"); // get the word ID for "look" and its synonyms
  String rol = txtParser.Text;
  int index = rol.IndexOf(" ");
  while (index != -1)
  {
    String word = rol.Substring(0, index);
    int wordID = Parser.FindWordID(word);
    if ((!wordID) || (wordID == lookID))
    {
      // word was an ignored word or "look" synonym
      if (index == (rol.Length - 1))
      {
        index = -1; // no more words left!
        rol = ""; // there is no rol
      }
      else
      {
        // strip out the remaining word(s), and update the index
        rol = rol.Substring(index + 1, rol.Length - (index + 1));
        index = rol.IndexOf(" ");
        if (index == -1) index = (rol.Length - 1);
      }
    }
    else index = -1; // if the word wasn't an ignored word or "look" synonym, simply break the loop (we found the rol!)
  }
  if (rol != "")
  {
    i = 0;
    Parser.Parse(rol); // parse the rol to check for character names
    while (i < Game.CharacterCount) // fixed this! <= will crash
    {
      if ((Parser.Said(character[i].Name)) && (character[i].Room == player.Room)) Add(info[i].description);
      i++;
    }
  }
}


Please note that I haven't actually tested this out, but it should work fine. Oh, and just so you know, Game.CharacterCount is not a valid index to the character array (valid indices are from 0 to (Game.CharacterCount - 1)), and character[c].ID will always be the same as the value of c. :P

Edit: By the way, you shouldn't necessarily need to add synonyms for every character name if your parser word list is set up properly. For example you said the monk's full name is "a pious monk". The word "a" is already listed as an ignored word. "pious" is descriptive, so unless you specifically need to be able to distinguish between various types of monks for some reason, you could add it as an ignored word too. Do that and then "a pious monk" will match "monk" when passed through the parser (you would of course have to have "monk" in the word list or it would be thrown as an "unknown" word ;)).

Also, I fixed the code where it was ignoring single-word rols with no trailing space. In case you've read this while I've been editing it, please make sure you get the code that is present after I've made this edit! ;)

Atelier

Thanks for the help, sorry it took so long to get back.

Monkey your code is promising and almost works! I get an error:

Code: ags

if ((Parser.Said(character[i].Name)) && (character[i].Room == player.Room)) Add(info[i].description);


"Supplied word 'a' is not in dictionary or is an ignored word" (even if I didn't type in a) I suspect just because it's the top word on the ignore list. monk3h to mai rescue? D:

monkey0506

Can you not check Parser.Said with ignored words? That would seem rather silly to me, but I can't think of any other reason why it would be throwing an error there. Of course it's 9 in the morning and I haven't slept...so I'll take another look after I've had some sleep. Worst case scenario I would have to simply parse the entire character name via a buffer string and strip out any ignored words. So, should be perfectly feasible, even in the worst case.

monkey0506

#5
Bah, Parser.Said doesn't strip out ignored words itself. As I said, seems silly to me, but it can be worked around.

Code: ags
  if (Parser.Said("look rol"))
  {
    int lookID = Parser.FindWordID("look"); // get the word ID for "look" and its synonyms
    String rol = text;
    int index = rol.IndexOf(" ");
    while (index != -1)
    {
      String word = rol.Substring(0, index);
      int wordID = Parser.FindWordID(word);
      if ((!wordID) || (wordID == lookID))
      {
        // word was an ignored word or "look" synonym
        // strip out the remaining word(s), and update the index
        rol = rol.Substring(index + 1, rol.Length - (index + 1));
        index = rol.IndexOf(" ");
      }
      else index = -1; // if the word wasn't an ignored word or "look" synonym, simply break the loop (we found the rol!)
    }
    if (rol != "")
    {
      int i = 0;
      Parser.ParseText(rol); // parse the rol to check for character names
      while (i < Game.CharacterCount) // fixed this! <= will crash
      {
        String buffer = character[i].Name;
        String name = "";
        index = buffer.IndexOf(" ");
        while (index != -1)
        {
          String word = buffer.Substring(0, index);
          if (index < buffer.Length) buffer = buffer.Substring(index + 1, buffer.Length - (index + 1));
          else buffer = "";
          index = buffer.IndexOf(" ");
          if ((buffer != "") && (index == -1)) index = buffer.Length;
          if (Parser.FindWordID(word) > 0) name = name.Append(word.AppendChar(' ' * (index != -1)));
        }
        if ((name != "") && (Parser.Said(name)) && (character[i].Room == player.Room)) Add(info[i].description);
        i++;
      }
    }
  }


Haven't tested any of this still, but replace the if (rol != "") bit above with this.

Edit: Tested and fixed a few things. The full code here should work.

SMF spam blocked by CleanTalk