A Text Adventure DSL in Groovy

Thursday, May 21st, 2009

Reading Peter Bell’s write-up on Guillaume LaForge’s talk on creating DSLs with Groovy has inspired me to chime in with my own experience with Groovy DSLs. The following is a transcription of sorts of the first part of a talk I’ve given a few times titled “Building DSLs with Grooy: A Real-World Case Study” (slides available here). This section of the talk introduces the idea of DSLs, and how Groovy is well-suited to creating them, by building a small adventure game interpreter. Enjoy.

If you’re like me, you have intense nostalgia for the text adventure games of the early 80s. Games like Zork, or the Sierra line of adventure games like King’s Quest were popular in part because of their natural language interface.

As an homage to these types of games, I thought we could write our own simple one. We’ll use Java as a starting point, since that’s what I know best, and will just be purely text-based.  In fact our only interface will be a nice big text area where the player can type in commands, and get responses from the game.

And you know, I don’t really want to go through the trouble of writing a natural language parser, so I think we’ll just have the player enter straight Java code into the text area and we’ll have our game compile it, run it, and then return the result.

Here’s some example output right here:

game.go("north");
> You go North.
game.look();
> You see a dagger on the ground.
game.take("dagger");
> You take the dagger.

Note that the player needs to know that they’re interacting with a “game” object, but that’s OK, we’ll be sure to put that in the Javadoc — or the player’s manual. As long as they preface every command with “game.” they’ll be OK.

We’re immediately, however, faced with the problem of Java’s static compilation. I suppose we could grab each command from the user, wrap it in a Java class file, invoke javac on it, and run it, but clearly that’s no good.

No worries though — this is a Groovy talk, so let’s use Groovy! In fact, we can just rename all our Java files to Groovy files, and we’re where we were with the Java version except now we are able to dynamically execute code via the GroovyShell. Great! Now we can actually implement this contrived example.

Right away too here, let’s just squirrel away the call to “game.” since we know every command needs to operate on this object. This will save the user a bit of hassle.

new GroovyShell().evaluate("game.${playerInput}")

And too, this doesn’t sit quite right as being a very “groovy” thing to do, but make a note of this, and we’ll come back to it later. So now the player can just enter commands without the “game.” preface, like so:

go('north');
look();
take('dagger');

Well, but since we made the switch to Groovy, we might as well take advantage of the syntactic sugar. In Groovy semicolons are completely optional, and parentheses are optional provided the method that you’re calling has at least one argument. So now we have this.

go 'north'
look()
take 'dagger'

And here’s our Game class where just every command is mapped to a method with whatever arguments they take. The player just has to remember to enclose any arguments in quotes so it will be treated as a String.

class Game {
  void go(dir) {
    println "You go $dir"
  }
  void look() {
    println "You look around and see nothing"
  }
  void take(it) {
    println "You take the $it"
  }
  ...
}

So here’s where things will start to get interesting. Groovy is a dynamic language, and because of that, it allows us to intercept things like method calls and property access. If we leave off the quotes on a String argument, it will assume that we’re referring to a property (or field) by that name and it will try to call get “whatever” on it. Of course things like “north” and “dagger” don’t exist as properties, so a special method called “propertyMissing” will be called to give us a chance to do something before the exceptions start flying.

If we override this method for the Game class then, we can just assume that any missing property was meant to be an argument of that name, so we’ll just return the name, which is a way of faking that the property actually exists.

class Game {
  ...
  def propertyMissing(String name) {
    name
  }
}

Which enables the player to now leave off the parens on command arguments:

go north
look()
take dagger

Good. So that gets around the quote problem. Now the only wart on the player’s experience is having to remember to use parenthesis on any command that doesn’t take arguments, such as “look”.
So let’s fix that now. Remember, if an identifier is standing off by itself like this, then Groovy will assume that it’s a property — so we’ll just go back to our friend “propertyMissing” and add in this little chunk of code.

def propertyMissing(String name) {
  if (metaClass.respondsTo(this, name)) {
    this."$name"()
  }
  name
}

This says that if you are missing a property by this name, first see if you have a method by this name, and if so call it, otherwise, just return the property like before. So now we have this:

go north
look
take dagger

Hey, this is basically a Zork level of understanding! But we can do better still…

Remember the contrived “game.” preface?

new GroovyShell().evaluate("game.${playerInput}")

That’s fine for a serial one command after another type thing, but let’s say that we want to open this up for players to write bots against so they can write automated scripts and “level grind” without the hassle of actually playing.

So let’s employ the Groovy keyword “with”, which takes a closure as an argument and basically sets up a little mini-context for us.

new GroovyShell().evaluate("game.with{${playerInput}}")

So what we’re saying here is “use this game object as the context for the following statements”. That is, this closure is within the scope of the game object first and foremost. Now that opens us up to scripting some of our code thusly:

3.times {
  go north
}
look
take dagger

I can now use regular Groovy syntax, such as closures, and say “go north three times”.

Depending on how I construct my command interpreter, one could imagine constructing arbitrarily powerful scripts like “keep wandering until you see a monster, and attack it as long as it’s 3 or more levels below me and I’m above half health.” Great, so now we can level grind while at work, and reap the benefits when we log on in the evening without having to resort to Gold Farming.

So let’s look at one more problem that plagues any natural language interface, and especially text adventure games of this era — the Synonym Problem (or as my 5 year old says, “Cinnamons” — so adorable). This problem can be best illustrated via an example.

Here the game only understands “take”, but the player (quite reasonably) doesn’t know this and is having difficulty finding the correct command:

look
> You see a dagger on the ground
get dagger
> I don’t understand that
grab dagger
> I don’t understand that
Just let me have the %$!#@ dagger!
> I don’t understand that

In this example “get”, “grab” and “take” all seem like very reasonable commands that a game should understand. Since we’ve implemented each command as a method, it stands to reason that we could simply try to anticipate all of the terms that a player might come up with and implement each one as a method that calls the main “take” method. But that’s not very Groovy.

In Groovy, if a method is called that doesn’t exist, “methodMissing” is called to give the class a chance to do something about it.

def methodMissing(String name, args) {
  if (['grab', 'hold', 'yoink'].contains(name)) {
    this.take(args[0])
  }
}

Here we override methodMissing and attempt to match the unknown command with a list of known synonyms. For illustrative purposes it’s implemented as a static list, but one could just as easily imagine dynamically looking the command up in a thesaurus – this is happening at runtime after all. This little change makes for a happier player:

look
> You see a dagger on the ground
grab dagger
> You take the dagger
You’re darn right I do!
> I don’t understand that

Better still, if we do find a match, we can graft the synonym onto the Game class as a new dynamic method since it stands to reason that if a player uses “grab” instead of “take” once, they will probably continue to do so, so we might as well make it a method. Incidentally this is how GORM works with finder methods, or so I’ve been told.

def methodMissing(String name, args) {
  def funcName = ThesaurusLookup.contains(name)
  if (funcName) {
    Game.metaClass."$name" = { item ->
      this."$funcName"(item)
    }
    this."$name"(args[0])
  }
}

Here all the new found method does is forward to whatever the program-sanctioned method was supposed to be. We can do this through the power of Groovy’s ExpandoMetaClass, which every Groovy class has.

So if we take a step back here, we can see how far some of Groovy’s language features have been able to take us. Here’s the original Java version that we started with:

game.go("north");
game.look();
game.take("dagger");
game.take("fish");

…and here’s the Groovy version that we ended with:

go north
look
take dagger
grab fish

I don’t advocate writing an actual game this way (for example, this way only supports “verb noun” commands), but as an experiment, it’s interesting to see how far we can go without writing any sort of lexer or parser.

Tags: ,

10 Responses to “A Text Adventure DSL in Groovy”

  1. Shawn Hartsock wrote:
    May 22nd, 2009 at 7:12 am |

    Nice clean example! I really like this write up.

  2. Hamlet D'Arcy wrote:
    May 22nd, 2009 at 8:04 am |

    This is the best walk-through of Groovy metaprogramming I’ve ever seen. Really great. You’re in Chicago right? Any desire to present at GroovyCon in Minneapolis this Fall? http://groups.google.com/group/groovycon/web/conference-details contact me hamletdrc@gmail.com

  3. Jeff Alexander wrote:
    May 26th, 2009 at 12:28 pm |

    Hey Sten, interesting stuff. Maybe you could call out to a web service that gives you synonyms to find the right command :-) Then it makes the creation of the dynamic method more important since it avoids a slow WS call.

  4. sanderson wrote:
    May 27th, 2009 at 8:51 am |

    Thanks for the comments.

    @Jeff — I don’t see any JavaOne sessions for you this year. Maybe I’ll see you there…

  5. Jeff Alexander wrote:
    May 28th, 2009 at 8:08 am |

    Sadly, we aren’t making the trek out this year. Have a great time!

  6. Jonas Bandi wrote:
    July 3rd, 2009 at 6:54 am |

    Cool tutorial.

    One little detail, which did cost me some hours to figure out:

    At the end in ‘methodMissing’, you are adding a new method to the meta-class of ‘Game’ and you are immediately calling this newly created method on an instance that already existed before manipulating the meta-class.

    In my case this resulted in a stack-overflow, because the existing instance did not realize that there is this new method.

    One possible solution was to introduce a static initializer block on Game like this:

    static {
    Game.metaClass.allowChangesAfterInit = true
    }

    Now the existing Game-instance got aware of the newly created method in its ‘methodMissing’.

    Do you know a better solution?

  7. sanderson wrote:
    July 6th, 2009 at 11:55 am |

    @Jonas — thanks for bug catch (and fix). I think setting “allowChangesAfterInit” to “true” on the Game Metaclass seems like the way to go.

  8. Matt wrote:
    August 6th, 2009 at 5:02 pm |

    Hey

    I had the same idea at almost the same time!

    I.m writing a mud and this seemed a good way to go with the mobs that inhabit it.

    Check it out,the post is on my blog http://mbrainspace.blogspot.com

    I wrote my own parser before i’d decided to go this way but I do a similar thing.

    Way to go tho!

    M

  9. sanderson wrote:
    August 6th, 2009 at 5:23 pm |

    @Matt — Cool. Yeah, groovymud looks ambitious. I just talk about it — you actually did it :) Nice job.

  10. Michael wrote:
    October 29th, 2009 at 12:01 am |

    Really nice tutorial, very helpful. Thanx!

Leave a Reply