Comparing Strings 2

Started by Atelier, Sun 27/05/2012 23:58:30

Previous topic - Next topic

Atelier

Hi guys, I actually posted the same sort of thing here, the code below is actually monkey's, thanks monkey! I have used it up until now but I wanted to look for something a bit more comprehensive.

Code: AGS

  if (Parser.Said("look rol"))
  {
    int lookID = Parser.FindWordID("look"); // get the word ID for "look" and its synonyms
    String rol = command;
    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 = 1;
      Parser.ParseText(rol); // parse the rol to check for character names
      while (i < Max_Mobs) // fixed this! <= will crash
      {
        if (String.IsNullOrEmpty(mob[i].name)) return;
        String buffer = mob[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)) && (mob[i].loc == mob[0].loc)) Print(mob[i].desc);
        i++;
      }
    }
}


The basic idea of doing it like this is that I do not have to rely on the parser to code for every single interaction, but rather a function checks to see what the player most likely means. For example I would have to do

Code: AGS

if (Parser.Said("look monk")) Print(mob[5].desc);
if (Parser.Said("look mushroom")) Print(item[11].desc);
...


for every entity in the game. At the moment, the code only checks NPCs, but I would like it to check other arrays too (namely items and equipment). I could probably work this out myself, but what complicates matters is that if a player/item/equipment has two or more valid terms in their name (for example, 'Feolfest the Porter' - entering "look feolfest" and "look porter" should do the same thing), the code does not work. Even if you make the two terms synonyms or even different words in the parser dictionary, only one will be valid, so typing "look feolfest the porter" does not work.

Basically, my question is this: is there a way of comparing the target of a command with all the names of the entities, without having to enter all their names to the parser dictionary? All of the different words in the dictionary is becoming unmanageable. Ideally, I would like the dictionary to only contain the verbs and game commands (look, talk, inventory) and not all the nouns (monk, mushroom).

So, if a player enters "look" with anything after it, the target word is isolated and the arrays mob, item, and equipment cycle through the names to see if there is a match. If there is and the entity is in the same room as the player (mob[0].loc), the correct entity description is added. This will be intuitive to the player because no entity can be called something if it's not in the full name. Also, the command "look" on its own has a completely different meaning, which prints the current room, so there must be a space between look (or its variant synonyms e.g. x) to trigger the checking cycle. I hope you can help me out, thanks!

Sephiroth

#1
Hi,

I hope this is what you are looking for, it should get the whole parser string as input, check the first word for any 'lookat' synonym, then check if the 2nd parser's word is contained in any of the current room objects description (excluding some common words like "the,at,to"). If it finds a match it will return the targetted object ID.

Code: ags

int GetItemsCount(String the_str,  char separator)
{
  int cnt = 0;
  int i = 0;
  while(i < the_str.Length)
  {
    if(the_str.Chars[i] == separator)
     cnt++;
    i++;
  }
  return cnt;
}

String GetItem(String the_str, int the_id,  char separator)
{
  int i = 0;
  int cnt = 0;
  int last_cnt = 0;
  String result;
  
  while(i<the_str.Length)
  {
    if(the_str.Chars[i] == separator)
    {
      cnt++;
      if(cnt == the_id)
      {
        if(!last_cnt)
          result = the_str.Substring(last_cnt, i-last_cnt);
        else
          result = the_str.Substring(last_cnt+1, i-last_cnt-1);
          
        return result;
      }
      last_cnt = i;
    }
    i++;
  }
  return "";
}


int GetParserTargetID(String input)
{
  String excluded = "the,it,for,to,of,at,";
  String syn_look ="look,lookat,watch,see,examine,";
  
  input = input.AppendChar(' ');
  
  if(GetItemsCount(input, ' ') <2)
    return -1;
    
  String input_item = GetItem(input, 2,  ' ');
  
  int i = 1;
  int j = 0;
  int count = 0;

  count = GetItemsCount(excluded, ',');
  while(i<=count) //if input is an exluded term, return
  {
    if(input_item == GetItem(excluded, i, ','))
      return -2;
    i++;
  }
  
  count = GetItemsCount(syn_look, ',');
  i = 1;
  while(i<=count) //for all look synonyms
  {
    if(input.StartsWith(GetItem(syn_look, i, ',' )))
    {
      j = 0;
      while(j<Room.ObjectCount)  //for all objects in room
      {
       if(object[j].Name.IndexOf(input_item) >=0) //if one of the terms match the object's name return its ID
         return j;
       j++;
      }
    }
    i++;
  }
  
  return -3;
}


int GetParserTargetID(String parser_string);
Currently only checks the second word in the parser string, but you can alter it to check for others, or even use GetItem() independantly. Returns negative value if no match, object ID (can be 0) if found.

Atelier

Thank you Sephiroth! I have a confession - I started working on a solution before I saw your reply :-[ In any case, your code looks much more concise than mine. I appreciate it a lot! For posterity:

Code: AGS

if (Parser.Said("look rol") || Parser.Said("take rol") || Parser.Said("fight rol")) // Can add more but these are the main global commands
{
               String word[49];
               int verb_type, index, w;
               
	       // Set the action the player wants to perform, to be used much later on
               if (Parser.Said("look rol")) verb_type = 1;
               else if (Parser.Said("take rol")) verb_type = 2;
               else if (Parser.Said("fight rol")) verb_type = 3;
               
               if (command.IndexOf(" ") != -1) { // Player has typed more than two words so has a verb AND a target
                                                       
                    // Separate the verb...
                    String verb = command.Substring(0, command.IndexOf(" "));
                    // from the target.
                    String target = command.Substring(command.IndexOf(" ")+1, command.Length-command.IndexOf(" "));
                    
                    if (target.IndexOf(" ") == -1) word[0] = target; // It is a one-word target, so we can skip below!
                    
                    else {
                    
                    // Now remove all additional spaces from the target so we get separate words to store in an array
                                        
                         while (target.IndexOf(" ") != -1) {

                              // Get the first word in the target string
                              index = target.IndexOf(" ");
                              String first_word = target.Substring(0, index);
                                                            
                              // Put the first word into an array. Do not if it is an ignored word in parser dictionary, or another space.
                              if (Parser.FindWordID(first_word) != 0 && first_word.Length != 1) {
                                   word[w] = first_word; w++; }
                              
                              // Now, make the original string the remainder of the text
                              target = target.Substring(index+1, target.Length-index);
                              
                              // If the remainder doesn't contain any more spaces, it is the final word so we must store it!
                              if (target.IndexOf(" ") == -1) word[w] = target;
                              
                         }
                    }                       
 
                    // Now we have found the words to check, we can cycle through all the named entities in the game:
                                       
                    struct Occurence { int occurence[99], match, max, a; };
                    Occurence entity[3]; // entity 1 for mobs, 2 for items, 3 for equipment
                    int x, m = 1, i, e; // m = 1 because do not check mob 0 (player)
                   
                    while (x < 49) // Cycle through all the words stored
                    {
                         if (!String.IsNullOrEmpty(word[x])) {
                              
                              while (m < Max_Mobs) 
                              {
			           // To be a valid target, m must be in the same room and contain the word(s) the player entered
                                   if (!String.IsNullOrEmpty(mob[m].name) && mob[m].loc == mob[0].loc && mob[m].name.IndexOf(word[x]) != -1) entity[0].occurence[m]++; // It does, so it matches at least one word
                                   m++;
                              }
                              
                              while (i < Max_Items) 
                              {
                                   // Must be in the player's inventory and have a name match
                                   if (!String.IsNullOrEmpty(item[i].name) && (item[i].loc == mob[0].loc || item[i].quan) && item[i].name.IndexOf(word[x]) != -1) entity[1].occurence[i]++;
                                   i++;
                              }
                              
                              while (e < Max_Equipment) 
                              {
                                   // Equipment must be available to wear/wield
                                   if (!String.IsNullOrEmpty(equ[e].name) && equ[e].unlocked && equ[e].name.IndexOf(word[x]) != -1) entity[2].occurence[e]++;
                                   e++;
                              }
                         }
                        
                         x++; m = 1; i = 0; e = 0;

                     }


                     // Now we have looked at all the words, we can find the entity with the most matched words (occurence) - ie, the most likely target
                     
                     while (m < Max_Mobs)
                     {
                         if (!String.IsNullOrEmpty(mob[m].name)) {
                         if (entity[0].max < entity[0].occurence[m]) { entity[0].max = entity[0].occurence[m]; entity[0].match = m; } // If it has more matches than the current max, it is potentially the target                       

                         // Print(String.Format("%s - %d matches", mob[m].name, entity[0].occurence[m])); // Just shows a list of mobs and how many word matches they had
                                        
                         }
                                        
                         m++;
                                        
                         if (m == Max_Mobs) { // The while loop has ended
                              m = 1;
                              while (m < Max_Mobs) // Cycle through again to check whether more than one word has the max value
                              {
                                   if (entity[0].max && entity[0].occurence[m] == entity[0].max) entity[0].a++; // a counts how many times the maximum value appears
                                   m++;
                              }
                         }
                     }

                     while (i < Max_Items) // Comments as above
                     {
                         if (!String.IsNullOrEmpty(item[i].name)) {
                         if (entity[1].max < entity[1].occurence[i]) { entity[1].max = entity[1].occurence[i]; entity[1].match = i; }                         

                         // Print(String.Format("%s - %d matches", item[i].name, entity[1].occurence[i]));
                                        
                         }
                                        
                         i++;
                                        
                         if (i == Max_Items) {
                              i = 0;
                              while (i < Max_Items)
                              {
                                   if (entity[1].max && entity[1].occurence[i] == entity[1].max) entity[1].a++;
                                   i++;
                              }
                         }
                     }

                     while (e < Max_Equipment)
                     {
                         if (!String.IsNullOrEmpty(equ[e].name)) {
                         if (entity[2].max < entity[2].occurence[e]) { entity[2].max = entity[2].occurence[e]; entity[2].match = e; }                         

                         // Print(String.Format("%s - %d matches", equ[e].name, entity[2].occurence[e]));
                                        
                         }
                                        
                         e++;
                                        
                         if (e == Max_Equipment) {
                              e = 0;
                              while (e < Max_Equipment)
                              {
                                   if (entity[2].max && entity[2].occurence[e] == entity[2].max) entity[2].a++;
                                   e++;
                              }
                         }
                     }

	       // Now we can find the actual target:

               if (!entity[0].a && !entity[1].a && !entity[2].a) { Print("There is nothing here by that name."); return; } // There were no matches at all :(
               else if (1 < entity[0].a || 1 < entity[1].a || 1 < entity[2].a) { Print("Please be more specific."); return; } // i.e, max occurence appears at least twice, so we can never find the desired target

                     int target_type;

                     if (entity[1].max < entity[0].max && entity[2].max < entity[0].max) target_type = 1;        // If there are more matches for a mob than there are for items or equipment, target is a mob
                     else if (entity[0].max < entity[1].max && entity[2].max < entity[1].max) target_type = 2;   // ...
                     else if (entity[0].max < entity[2].max && entity[1].max < entity[2].max) target_type = 3;   
                     else { Print("Please be more specific."); return; } // No entity type has a majority so again the target cannot be found

	             // Finally, give the response:

                     if (verb_type == 1) // LOOK
                     {
                         if (target_type == 1) Print(mob[entity[0].match].desc);        // A mob had the most matches, and the specific mob was the match
                         else if (target_type == 2) Print(item[entity[1].match].desc);  // ...
                         else if (target_type == 3) Print(equ[entity[2].match].desc);
                     }
                     
                     if (verb_type == 2) // TAKE
                     {
                         if (target_type == 1) // ...
                         if (target_type == 2) // ...
                     }
                     
                     if (verb_type == 3)
                     {
                          // ...
                     }

                 }
               
                    else { // Only a single word was entered
                         if (verb_type == 1) Build(mob[0].loc);
                         // ...
                    }
}


In an indirect way it checks how likely the player wants one thing taking into account all the other entities in a room. "look c" would show the description for a chest if it's the only thing in the room, but not if there was also a candle present. If you entered "look ch" then it would describe the chest. I'm not sure how foolproof it is but so far it seems to be working...

Sephiroth

#3
Quote from: Atelier on Wed 30/05/2012 21:10:52
I have a confession - I started working on a solution before I saw your reply :-[
Sorry I've seen your topic a bit late.

Anyways if you ever need a more comprehensive and extended functionality, here is a little modification of the code I gave. It can detect any fight/look/talk action with any of their (predifined) synonyms, if the target specified is an excluded word or is contained in more than one object/character's description then it will return negative. Otherwise you'll get 3 global variables filled : ParserCommand - ParserTargetType - ParserTargetID. (you need to create them as strings in the global var pane and the last one as INT)

Your final code w/o all the function declarations would look like this:
Code: ags

    if(GetParserAction(parser_string) > 0)
    {

      if(ParserCommand == "look")
      {
        if(ParserTargetType == "character")
        {
          //Display(character[ParserTargetID].Name);
        }
        else if(ParserTargetType == "object")
        {
          //Display(object[ParserTargetID].Name);
        }
      }
      else if(ParserCommand == "talk")
      {
        if(ParserTargetType == "character")
        {
          //talk to: character[ParserTargetID]
        }
        else if(ParserTargetType == "object")
        {
          //talk to: object[ParserTargetID]
        }        
      }
      else if(ParserCommand == "fight")
      {
        if(ParserTargetType == "character")
        {
          //fight: character[ParserTargetID]
        }
        else if(ParserTargetType == "object")
        {
          //fight: object[ParserTargetID]
        }        
      }
      
    }


If you have a character named Sephiroth and he's in the same room as the player, then these examples should yield the same result:
Code: ags

GetParserAction("fight sep");
GetParserAction("hit sephi");
GetParserAction("strike sephiroth");

//if no other character share the same name it will return 1 and fill Globals with appropriate values

ParserCommand -> "fight"
ParserTargetType -> "character"
ParserTargetID -> char_id


And here are the modified functions:
Code: ags

    int GetItemsCount(String the_str,  char separator)
    {
      int cnt = 0;
      int i = 0;
      while(i < the_str.Length)
      {
        if(the_str.Chars[i] == separator)
         cnt++;
        i++;
      }
      return cnt;
    }
     
    String GetItem(String the_str, int the_id,  char separator)
    {
      int i = 0;
      int cnt = 0;
      int last_cnt = 0;
      String result;
     
      while(i<the_str.Length)
      {
        if(the_str.Chars[i] == separator)
        {
          cnt++;
          if(cnt == the_id)
          {
            if(!last_cnt)
              result = the_str.Substring(last_cnt, i-last_cnt);
            else
              result = the_str.Substring(last_cnt+1, i-last_cnt-1);
             
            return result;
          }
          last_cnt = i;
        }
        i++;
      }
      return "";
    }
     
     
     
int GetParserAction(String input)
{
  String excluded = "the,it,for,to,of,at,";
  
  String syn_look ="look,lookat,watch,see,examine,";
  String syn_talk ="talk,speak,";
  String syn_fight ="fight,attack,hit,strike,";
  
  ParserCommand = "none";
  ParserTargetType = "none";
  ParserTargetID = -1;
  int match_id = -1;
     
  input = input.AppendChar(' ');
     
  if(GetItemsCount(input, ' ') <2)
    return -1;
    
  String command = GetItem(input, 1, ' ');     
  String target = GetItem(input, 2,  ' ');
  
  
  //Figure out which command the player typed
  int i = 1;
  int j = 0;
  int count = 0;
   
  count = GetItemsCount(syn_look, ',');
  i = 1;
  while(i<=count) 
  {
    if(command == GetItem(syn_look, i, ',' ))
      ParserCommand = "look";
    i++;
  }
  
  if(ParserCommand == "none")
  {
    count = GetItemsCount(syn_talk, ',');
    i = 1;
    while(i<=count) 
    {
      if(command == GetItem(syn_talk, i, ',' ))
        ParserCommand = "talk";
      i++;
    }
  }
  
  if(ParserCommand == "none")
  {     
    count = GetItemsCount(syn_fight, ',');
    i = 1;
    while(i<=count) 
    {
      if(command == GetItem(syn_fight, i, ',' ))
        ParserCommand = "fight";   
      i++;
    }
  }
  
  if(ParserCommand == "none")
    return -2;
    
    
  //Make sure the target isn't an excluded term
  i = 1;
  count = GetItemsCount(excluded, ',');
  while(i<=count) 
  {
    if(target == GetItem(excluded, i, ','))
      return -3;
    i++;
  }    
  
  
  //Cycle through room OBJECT names
  i = 0;
  count = 0;
  while(i<Room.ObjectCount)
  {
   if(object[i].Name.IndexOf(target) >= 0)
   {
     ParserTargetType = "object";
     ParserTargetID = i;
     count++;
   }
   i++;
  } 
  if(count>1)
    return -4;
  else if(count == 1)
    return 1;
    
    
  //Cycle through CHARACTER names
  i = 0;
  count = 0;
  while(i<Game.CharacterCount)
  {
   if(character[i].Room == player.Room && character[i].Name.IndexOf(target) >= 0)
   {
     ParserTargetType = "character";
     ParserTargetID = i;
     count++;
   }
   i++;
  } 
  if(count>1)
    return -4;
  else if(count == 1)
    return 1;
    
     
  return -5;
  
}


I hope it helps.

SMF spam blocked by CleanTalk