«

»

Sep 05

A “grue”some look at Statemachine and Treetop

In this blog entry, dear reader, we examine the statemachine and treetop gems via an old standby, a Zork imitation.  And, despite the title, you won’t find a grue.

Statemachine is used for creating, as you no doubt have guessed, statemachines.  Treetop is a parser generator.  A zork-like game is a fairly useful simple example of how to use both.

Statemachine

Let’s start with a state diagramme:

Basically we have three states: Begin, Main, and Done. We can transition from one state to another in a variety of ways, as shown by the arrows. The text with the transition shows the events which can trigger the transition.  Statemachine uses two basic types of statements to describe these state changes (what follows is simplified from the actual code):

trans is used to move from one state to another upon the receipt of an event. Thus,
trans :start, :begin, :main, :enter can be interpreted as “From the start state, transition to the main state when you receive a begin event. In so doing, invoke the enter method.”
Likewise, the block starting with state :main contains code which handles events, as well as invoking a method (check_if_transition_needed) on entrance of the state. event :move, :main, :move may be read as “Within the context of the main state, when a move event is received, transition to main, invoking the move method.”

The check_if_transition_needed is something of a hack — without it setting the state within a state transition wasn’t working properly (so we couldn’t go to the “done” state and exit).

The invoked methods are performed by the object which is declared to be the context — namely an instance of AdventureContext.  The last line, @machine.context.statemachine = @machine, allows our context to interract with the statemachine.

Grammar

I’ve chosen to use treetop for my grammar.  Let’s take a look at the grammar:

All grammars start with the keyword grammar and the word which follows is the name used for the parser’s class. So, grammar Adventure creates an AdventureParser class. Within the grammar block exist a series of rules. Personally, I think that they’re easier to understand than BNF.

One interesting piece that caused me some grief — the rules are order dependant. If you have a rule that can match “in” or “inside”, “inside” has to come before “in” — otherwise it will error when you try to parse “inside”. However, in debugging parsers, irb is very helpful:

There are other methods to use for finding failures, but failure_reason is very helpful most of the time. However, if there’s an issue with grammar, such as “in” coming before “inside” it has issues.

Locations and Items

I have chosen to set up my map within a yaml file.  It’s split into two pieces — the first defines the locations and the second the items and their properties.  In order to keep my yaml file as dry as possible, I’m only including things that are “special” about a location or an item — things which differ from the defaults.

Locations have the following properties:

Property Default Description
name (inferred) “” Location’s name
description “” Description of the location
paths [] Paths away from the current location
items [] Items which are contained in this location
transition nil Any transitions which might occur from moving to this location — these include victory or failure conditions.

So a location might look like this in the yaml file:

Items have the following properties:

Property Default Description
name (inferred) “” The name of the item
description “”, The description
take true, Can the item be taken?
container false, Can the item hold other items?
droppable true Can the item be dropped?

So an item might look like this:

The Source

Finally we have our source code. It’s pretty straightforward, so I’ll not talk about it here, but if anyone has questions, please comment.

Running the Code

First off, be sure you’ve installed the treetop and statemachine gems. You can do so via:

From there you can download the files and unarchive them into a directory.  adventure1.tgz

Then run it via

Where to go from here?

Well, there’s a few things to add…. Save and restore for one. Scoring for another. Combat, if you wanted as well. But as a demonstration of statemachine and treetop, I think this does a pretty good job.

EDIT: Fixed an error in adventure.rb

4 comments

Skip to comment form

  1. Matt Smith

    Thanks! This is one of the best tutorials on Treetop that I have seen. It cleared some things up for me.

  2. Matt Williams

    Most welcome. Glad it was useful for you. Truth be told, I learned a good bit from it too. It took a bit to get the grammar working for me.

  3. jarodzz

    can you share any other debugging info here?

    I couldn’t find any useful info on the project page.

    it’s a little bit miss-leading there.

    my @parser.failure_reason always output nil for me.

    it took me 4 days, still i couldn’t make it work .sigh~

  4. Matt Williams

    Yes, there’s not a lot of debugging info. One thing that I’ve found is that if your grammar isn’t constructed “properly”, it won’t complain and you won’t get proper output from the @parser.failure_reason. I’d suggest starting with a small subset of your parser, make sure that works, and build from there.

    Good luck, and please feel free to ask more questions

Leave a Reply

%d bloggers like this: