views:

314

answers:

5

I'm playing with writing a MUD/text adventure (please don't laugh) in Ruby. Can anyone give me any pointers towards an elegant, oop-based solution to parsing input text?

We're talking about nothing more complex than "put wand on table", here. But everything needs to be soft; I want to extend the command set painlessly, later.

My current thoughts, slightly simplified:

  1. Each item class (box, table, room, player) knows how to recognise a command that 'belongs' to it.

  2. The game class understands a sort of a domain-specific language involving actions such as "move object X inside object Y", "show description of object X", etc.

  3. The game class asks each item in the room if it recognises the input command. First to say yes wins.

  4. It then passes control to a method in the item class that handles the command. This method rephrases the command in the DSL, passes it back to the game object to make it happen.

There must be well-worn, elegant ways of doing this stuff. Can't seem to google anything, though.

+3  A: 

The Interpreter design pattern is the most object-oriented approach to parsing that I'm aware of, but I'm sure compiler experts will point out algorithms that are more powerful.

Mark Seemann
I'm rubbish at design patterns, and my googling had failed to see this one. I will certainly have a look at this, ta.
Shadowfirebird
Indeed, the interpreter pattern looks like a expression tree in a compiling process ! nice. It's seems like a straighforward implementation for a compiler. ( after the parsing of the string token )
Antoine Claval
I'm still struggling to apply this pattern to my project, but it does rather look as if it was the idea I was groping towards. What it leaves unsaid, however, is how the command string is parsed.
Shadowfirebird
A: 

Split it into tokens as the format will always be:
[command] [object1] ([refering] [object2])

You could call method [command] on the [object1] in your room and pass it the [object2] if applicable.

dbemerlin
That is so very tempting. But, "turn light on". Or, for that matter, it would be really nice to be able to accept "turn on light".
Shadowfirebird
You could do that by simply deleting what is usually called "stop words" from the string. Words like "the", "an", "on". In that case, all that's left from the sentence "now turn on that light there" would be "turn light". Of course, that might be *too* much, but in this specific case the *absence* of the word "off" tells you everything you need to know.
Jörg W Mittag
@Jorg: Actually that's pretty much what my current approach does, except it detects "start words" instead. So (to simplify somewhat) the light class looks for /light.*(on|off)/. Maybe you have it the right way around, and I don't, though.
Shadowfirebird
The concept of "stop words" comes from search engines, where these are words that simply don't contribute anything useful. So, they are generally filtered out both from the index and from the search query. It will definitely have to be tweaked here. You might take a look at the natural language date parser from the Git version control system. It *looks* like it can parse very complicated expressions like "about an hour ago", but what it really does is simply throw away anything it doesn't understand, so it just ends up with "hour" which is simply defined to be `3600` and that's it. Works well.
Jörg W Mittag
+1  A: 

For command interpreters, I'm rather fond of this simple, not all that elegant pattern. Patterns in dynamic languages tend to involve fewer boxes and lines than GOF patterns.

class Thing

  # Handle a command by calling the method "cmd_" + command.
  # Raise BadCommand exception if there is no method for that command.

  def handle_command(command, args)
    method_name = "cmd_#{command}"
    raise BadCommand, command unless respond_to?(method_name)
    send(method_name, args)
  end

  def cmd_quit(args)
    # the code for command "quit"
  end

  def cmd_list(args)
    # the code for command "list"
  end

  ...

end

In this way, adding a new command is just adding a new method. No tables or case statements need to be adjusted.

Wayne Conrad
This looks at first squint like a freaky three-way between Visitor, Strategy and Command, but like you said: in an expressive language (I don't buy the "dynamic" part *that* much, it would probably look equally elegant in Scala, F# or Haskell) it's just not that big a deal. Remember: a Pattern is nothing but a Program that you wish to write, but your language doesn't let you. Well, Ruby *does* let you.
Jörg W Mittag
@Jorg, Well said. I dig that definition of a pattern. Is it yours?
Wayne Conrad
I don't think so. That precise *wording* might be mine, but the idea is general. In fact, if you flip open your copy of the GoF to page 4 and read the last paragraph of section 1.1, it's actually spelled out there. The classic rant on this is http://blog.plover.com/prog/design-patterns.html with this reply http://www.cincomsmalltalk.com/userblogs/ralph/blogView?entry=3335803396 and then this http://blog.plover.com/prog/johnson.html . And of course the famous Norvig presentation: http://norvig.com/design-patterns/
Jörg W Mittag
@Jog, That's a cornucopia of great links. Thanks!
Wayne Conrad
A: 

Ok. So you need a semantic ? (turn is an action, light an object, on an argument... (I relate to your comment to dbemerlin)).

Why not defining a grammar ? humm... I guess lex and yacc are not a option ? (since it's not OOP at all, but what you want to do is to "compile" user input to produce something - executing some code who change the data of the room, and output a result)

You can have an OOP design for your object and its action (like, all objects have a .describeMe() method ..) , and beside that, an input parser+compiler.

Am I completely off the subject?

Edit : after looking to the interpreter pattern pointed out by Marc Seeman, it seems to be the way to go in you want it in OOP. (but you will somewhat re-invent the wheel)

Antoine Claval
I did look at lex and yacc. But they make my head hurt, and this is supposed to be a fun project. Plus, if I understand correctly, they tie me to a far stricter grammar than the english language actually has. I suppose my golden target is for the user to be able to type "on table put wand" an be understood... or at the very least have the game try to put the table on the wand ;)
Shadowfirebird
+2  A: 

Sounds like you need a parser.

Split the input string into tokens (words). Then feed the tokens, one at a time, to a state machine. I find that the push-down automata is rather intuitive and powerful way to write such an stm.

troelskn
Any suggestions as to how I start studying the concept of state machines? The Wikipedia entry is a given, of course.
Shadowfirebird
I take the wikipedia comment back. The page on pushdown automata looks fascinating, but it's written in high mathematics.
Shadowfirebird
@Shadow: http://stackoverflow.com/questions/1669/learning-to-write-a-compiler is the canonical page for compiler resources on SO. But you need is specialized and may not be well treated. This answer is technically correct, but not very helpful because natural language processing is notoriously difficult and has its own culture and language.
dmckee