room, a text adventure framework

I used to hog my grandmother's phone line to play the text-based MMO Federation, so I have a soft spot for that kind of verb-noun interface.

look
examine desk
get laptop
type blog post
buy warehouse permit
declare bankruptcy
quit

A little while back I wondered what it would be like to walk around a Ruby object space like that. method argument. The return value is your new location.

Time
now
+ 3

Interesting, but pretty limiting. But! Maybe if we seeded Ruby with just the right classes. Maybe… maybe RUBY IS THE DSL.

class WestOfHouse < Room
  def look
    "West of House" |
    "You are standing in an open field west of a white" |
    "house, with a boarded front door." |
    "There is a small mailbox here."
  end

  def examine_mailbox
    "The small mailbox is #{@mailbox_open ? "open" : "closed"}."
  end

  def open_mailbox
    if @mailbox_open
      "The mailbox is already open." |
      ("There is a leaflet inside." unless have? :leaflet)
    else
      @mailbox_open = true
      "Opening the small mailbox reveals a leaflet."
    end
  end

  def get_leaflet
    if have? :leaflet
      huh?
    else
      take :leaflet
      "Taken."
    end
  end
end

My room gem lets you write doohickeys like these. I wrote a Twitter client. Just gem install room.

workybook:~ tom$ room for tweets


You pull into the crowded parking lot, shove a
cinderblock under your car tire, and look up.
The metallic blue neon sign above the door reads:

              DICK'S BAR
      "we all like the same things"

You sure hope so.

You attempt to open the door, but it won't budge.
Instead, you see flickers of movement behind two
holes in the door about eyes' width apart. You squat
to look into them and see someone rummaging...

"Here. Use this." A long piece of paper slides beneath the door.

Type 'look' to look around, but eventually you're going to have to
pick up that piece of paper.
> look

You're standing in front of Dick's Bar and
you really want to pick up the piece of paper on the ground.
> get the piece of paper

You pick up the piece of paper and stuff it in your pocket!
But aren't you curious what's on it?
> read the piece of paper

The paper contains the following message:

   http://twitter.com//oauth/authorize?oauth_token=L2AGuEDrzZDl9JzOpcrtmTCKr1eDDFbtphyzbgnUHik
   SAY YOUR PIN

And that's all.
> 7968852

You speak your pin and the eyes disappear for a moment.

And then... the door swings open and the doorman ushers
you inside. "Come on, come on." The door shuts tight behind you.


The bar.


Randall Bohn says, "Mayan long count via SMS 801/430-9783."

Lima Sky says, "Candy, Chicks, and Doodle Jump! Perfect iOS game for this Easter - Doodle Jump: HOP The Movie - FREE on the App Store: http://t.co/syo6wN0"
> say oh, what a world, what a world.

You proclaim to everyone in the room, "oh, what a world, what a world."

> 

I've actually been using this as my main Twitter client for weeks. >.> I keep wanting to extend it, make it so you can send private messages by pulling people aside, allowing you get a change of clothes from your car to change accounts, defecate, maybe make a metagame on top using hash tags… perhaps one day.

If you check out the examples in the rooms/ directory, I bet you can figure out how to make your own rooms.

How does it work?

Nothing too tricky going on here. The room program loads your script with Kernel#load and the Room class waits for subclasses by overriding Class#inherited(subclass) and Module#method_added(method_name). (I found this to be more reliable than doing something like (room.methods - Room.new.methods), and I wanted to know the order they were defined anyway.)

room generates regular expressions for each method defined to match the same string with spaces in place of the underscores. It replaces "XXX" with "(.+)" so you can create actions like:

def give_XXX_to_XXX item, whom
  if have? item
    lose item
    "You give #{item} to #{whom}."
  else
    "You don't have #{item}!"
  end
end

room creates one instance of each sub-class you define. It marshalls those rooms to disk (preserving instance variables!) as well as the global $state map where you can stash bits of global information (like OpenID credentials!).

I overrode String#| to conveniently concatenate strings. Bwa ha ha.

So there you go. What do you want to make?

Did I level up with this post?


Comments

Click here to view the comments on this post, or just send me an e-mail.