Hey Peter,

How does this look? https://gist.github.com/cookrn/4755773

By making the Room a separate object, the scoping might be more as you had
hoped e.g. the door method

Regarding the yield vs. instance_eval -- this is a matter of opinion. If
you wanted to be very explicit, you could yield the adventure and room
objects to the blocks given to those methods. This makes your DSL seem a
bit less magic, but maybe that's ok.

I gave a talk on DSLs at Boulder Ruby a while back that you might find
interesting: https://speakerdeck.com/cookrn/dsls-as-teaching-tools

Ryan


On Mon, Feb 11, 2013 at 8:18 AM, Peter Hickman <
peterhickman386 / googlemail.com> wrote:

> I am playing around with writing some DSLs to help me get more familier
> with how they work and I have a couple of questions. First here's an
> example of one I am playing with (based on the classic dungeon adventure
> problem)
>
> class Adventure
>   def initialize
>     @rooms = Hash.new
>     @current_room = nil
>   end
>
>   def room(reference, &block)
>     if @rooms.has_key?(reference)
>       puts "Error: Room #{reference} has already been defined"
>     else
>       @rooms[reference] = {:short => '', :long => '', :exits => Hash.new}
>     end
>
>     @current_room = reference
>
>     yield block
>   end
>
>   def short(text)
>     @rooms[@current_room][:short] = text
>   end
>
>   def long(text)
>     @rooms[@current_room][:long] = text
>   end
>
>   def door(direction, destination, &block)
>     if @rooms[@current_room][:exits].has_key?(direction)
>       puts "Error: Room #{@current_room} already has an exit defined in
> #{direction}"
>     else
>       @rooms[@current_room][:exits][direction] = {:where => destination,
> :condition => block_given? ? block : true}
>     end
>   end
> end
>
> class Object
>   def adventure(&block)
>     a = Adventure.new
>
>     a.instance_eval(&block)
>   end
> end
>
> adventure do
>   room :cave do
>     short "You are in a large cave"
>     long "You are in a very large cave"
>     door :west, :another_cave
>     door :east, :exit do
>       if player.has_item(:key)
>         true
>       else
>         false
>       end
>     end
>   end
>
>   room :another_cave do
>     short "A cave"
>     long "Oh great ... another cave"
>     door :east, :cave
>   end
>
>   room :exit do
>     short "Freedom"
>     long "Freeeeeeeeeeedom!!!!!!"
>     door :west, :cave
>   end
>
>   require 'pp'
>   pp @rooms
> end
>
> Now this works just fine but a couple of things just don't seem right.
> Notice the @current_room variable that is set in the room method and
> referenced in short, long and door. This seems clunky - it is basically a
> global variable to help tie things together. Is there some better way of
> doing this? Perhaps by passing it as a parameter somehow.
>
> Also is the yield at the bottom of the room method the right way to go or
> should I be calling instance_eval or similar?
>
> Also how do I stop, for example, the door method being called outside of
> the room method? I have a solution where I set @current_room to nil after
> the yield and then each method checks to see if it is set and if not it
> errors. But again this seems to be quite a pain.
>
>


-- 
Ryan Cook
720.319.7660