Wizard quiz

I have written a few text adventures in TADS, the Texts
Adventure Development System. The system is excellent
and I highly recommend it, but since learning Ruby I've wanted
something similar to TADS in Ruby. The only reference to such
a project I could find was the Quiz 49: http://www.rubyquiz.com/quiz49.html

Here's my late solution, if anyone is interested - but with the intention
of building a library that allows near-TADS in Ruby.

I've just put in the 'invisible' water object so you can "throw water at wizard".
The parser is still very simple but I want to develop it to TADS' level.
(TADS: http://www.tads.org/)

Comments very welcome!

-------------------------- rads.rb
module Util
  Map_syns = [%w(n north), %w(s south), %w(e east), %w(w west),
              %w(ne northeast), %w(se southeast), %w(sw southwest), %w(nw northwest), %w(u up), %w(d down)]

  def Util.map_short(long)
    Map_syns.each do |syn|
      return syn[0] if syn[1] == long.to_s
      return long if syn[0] == long.to_s #If long is actually a short, return it
    end
    raise "map_short can't find short name for #{long}"
  end

  def Util.map_long(short)
    Map_syns.each do |syn|
      return syn[1] if syn[0] == short.to_s
      return short if syn[1] == short.to_s #If short is actually a long, return it
    end
    raise "map_long can't find long name for #{short}"
  end

  def Util.is_direction?(word)
    Map_syns.each do |syn|
      return true if word == syn[0] || word == syn[1]
    end
    return false
  end
end

class ThingError < Exception
  attr_reader :noun
   def initialize(noun)
    @noun = noun
    super
  end
end

class DirectionError < Exception
  attr_reader :dir

  def initialize(dir)
    @dir = dir
    super
  end
end

class Thing
  attr_accessor :name, :sdesc, :ldesc, :adesc,
                :location, :immobile, :invisible

  def initialize(name, sdesc, ldesc, adesc, location)
    @name = name
    @sdesc = sdesc
    @ldesc = ldesc
    @adesc = adesc
    @location = location

    @immobile = false
    @invisible = false
  end
end

class KickableThing < Thing
  def do_kick
    "You kick the #{name} like you mean it"
  end
end

class ImmobileThing < Thing
  def initialize(name, sdesc, ldesc, adesc, location)
    super
    @immobile = true
  end
end

class InvisibleThing < Thing
  def initialize(name, sdesc, ldesc, adesc, location)
    super
    @invisible = true
  end
end

class Map
  attr_reader :start, :player_room

  def initialize(rooms, start)
    @start = start
    @rooms = rooms
     #Add a player_room if there wasn't one got_player = false
    @rooms.each do |room|
      got_player = true if room.name == "player"
    end
    if !got_player
      @rooms.push(Room.new("player", ""))
    end

    @player_room = self["player"]
  end

  def find(name)
    @rooms.each do |room|
      return room if room.name == name
    end
    return nil
  end
  alias [] find
end

class Room
  attr_accessor :name, :sdesc, :exits

  def initialize(name, sdesc)
    @name = name
    @sdesc = sdesc
  end

  def go(dir)
    return @exits[dir] if @exits[dir]
    raise DirectionError.new(dir)
  end

  def fmt_exits
    list = []
    @exits.keys.each do |short_exit|
      list << Util.map_long(short_exit)
    end
    return list.join(", ")
  end

  def fmt_desc
    sdesc + "\nexits are: " + fmt_exits
  end
end

class World
  attr_accessor :map, :things

  def initialize(map, things)
    @map = map
    @things = things end

  def find(thing_name)
    @things.each do |thing|
      return thing if thing.name == thing_name
    end
    return nil
  end
  alias [] find

  def noun_to_thing(noun)
    @things.each do |thing|
      return thing if thing.name == noun
    end
    raise ThingError.new(noun)
  end

  def fmt_list(list) #list of strings -> this, that and last
    return "" if list.empty?

    n = list.length
    if n == 1
      return list.first
    end
       "#{list[0..n-2].join(", ")} and #{list.last}"
  end

  def things_present(location)
    list = []
    @things.each do |thing|
      list << thing if thing.location == location
    end
    list
  end

  def things_visible(location)
    list = []
    @things.each do |thing|
      list << thing if thing.location == location && !thing.invisible
    end
    list
  end

  def fmt_things(location)
    list = things_visible(location)
    return "" if list.empty?

    alist = list.collect do |thing|
      thing.adesc
    end

    "You see #{fmt_list(alist)} here"
  end
end

#TODO: The player object must be carefully spliced to include only
# standard things - so it can be nicely overridden for any game

class Player
  attr_reader :world
  attr_accessor :location

  def initialize(world)
    @world = world
    @location = @world.map.start
    @player_room = @world.map.player_room
  end

  def inventory
    @world.things_present(@player_room)
  end

  def inventory_visible
    @world.things_visible(@player_room)
  end

  def can_reach?(thing)
    thing.location == @player_room || thing.location == @location
  end
   #com_xxx methods implement verbs without objects

  def com_quit
    exit
  end
  alias com_q com_quit

  def com_look
    @location.fmt_desc + "\n" + @world.fmt_things(@location)
  end
  alias com_l com_look

  def com_inventory
    list = inventory_visible
    return "You have nothing" if list.empty?

    names = []
    list.each do |item|
      names << item.adesc
    end
    "You have #{world.fmt_list(names)}"
  end
  alias com_i com_inventory

  #do_xxx methods implement verbs with single objects
  def do_get(thing)
    return "No matter how hard you try, you cannot move the #{thing.name}" if thing.immobile

    thing.location = @player_room
    "You take the #{thing.name}"
  end
  alias do_take do_get

  def do_drop(thing)
    thing.location = @location
    "Dropped"
  end

  def do_kick(thing)
    if thing.respond_to?("do_kick")
      thing.do_kick
    else "That's not kickable!"
    end
  end

  def do_examine(thing)
    thing.ldesc
  end
  alias do_x do_examine
  alias do_look do_examine
  alias do_l do_examine

  #io_xxx methods implement verbs with two objects
  #def io_weld(dobj, iobj)
  #end
end

class Parser
  attr_reader :player

  def initialize(player)
    @player = player
  end

  def parse(line)

    words = line.downcase.split(" ")
    %w(the to with in on at).each do |w|
      words.delete(w)
    end

    return if words.empty?

    verb = words.first

    ### direction
    if Util.is_direction?(verb)
      dir = Util.map_short(verb) #Convert any long direction names to short ones
        begin
        @player.location = @player.location.go(dir.to_sym)
      rescue DirectionError => err
        return "Sorry, there's nothing in that direction"
      else
        return @player.com_look
      end
    end

    ### verb
    if words.length == 1
      method = "com_"+verb
      if @player.respond_to?(method)
        return @player.send(method.to_sym)
      else
        return "Sorry, I don't know how to #{verb}"
      end
    end

    ### verb + direct object
    if words.length == 2
      method = "do_" + verb
       begin
        dobj = player.world.noun_to_thing(words[1])
      rescue ThingError => err
        return "The #{words[1]} is not here"
      end

      return "The #{words[1]} is not here" if !@player.can_reach?(dobj)

      if @player.respond_to?(method)
        return @player.send(method.to_sym, dobj)
      else
        return "Sorry, I don't know how to #{verb} #{dobj.adesc}"
      end
    end

    ### verb + direct obj + indirect obj
    if words.length == 3
      method = "io_" + verb
         begin
        dobj = @player.world.noun_to_thing(words[1])
        iobj = @player.world.noun_to_thing(words[2])
      rescue ThingError => err
        return "The #{err.noun} is not here"
      end

      return "The #{words[1]} is not here" if !@player.can_reach?(dobj)
      return "The #{words[2]} is not here" if !@player.can_reach?(iobj)

      if @player.respond_to?(method)
        return @player.send(method.to_sym, dobj, iobj)
      else
        return "Sorry, I don't know how to do that"
      end
    end

    "Sorry, I'm not sure what you mean, try being less wordy"
  end
end

-------------------------- wizard.rb
require 'rads'

########################################### Class overrides
class MyPlayer < Player

  def initialize(world)
    super
    @welded = false
    @water_filled = false
  end

  def io_weld(dobj, iobj)
    return "There's nothing here to weld with" if @location != @world.map["attic"]

    if [dobj.name, iobj.name].sort == ["bucket", "chain"]
      @welded = true
      "You weld the #{dobj.sdesc} to the #{iobj.sdesc}"
    else
      "Welding only really works on metal"
    end
  end

  def do_get(thing)
    return "He's too heavy" if thing.name == "wizard"
    super
  end

  def io_dunk(dobj, iobj) return "You can't dunk those in this game" if [dobj.name, iobj.name].sort != ["bucket", "well"]
    return "The water is too deep to reach" if !@welded

    @world["bucket"].ldesc = "The bucket is full of water"
    @water_filled = true
    @world["water"].location = @player_room
    "You dunk the bucket in the well and fill it with water"
  end
  alias io_dip io_dunk

  def io_splash(dobj, iobj)
    if [dobj.name, iobj.name].sort != ["bucket", "wizard"] && [dobj.name, iobj.name].sort != ["water", "wizard"]
      return "You can't splash those in this game"
    end

    return "The bucket is empty" if !@water_filled

    if @world["frog"].location == @world.map["player"]
      "The wizard awakens but when he sees that you have his pet frog he banishes you to the wild woods!"
    else
      "You splash the wizard and he wakes from his slumber! He greets you warmly and gives you a magic wand." +
      "\nYou win!"
    end
  end
  alias io_pour io_splash
  alias io_throw io_splash

end

########################################### The game - rooms
player = Room.new("player", "")
garden = Room.new("garden", "The garden is a little overgrown but still lush with plants.")
living = Room.new("living", "You are in the living-room of the Wizard's house. There is a wizard snoring loudly on the couch.")
attic = Room.new("attic", "You are in the attic of the abandoned house. There is a giant welding torch in the corner.")

garden.exits = {:e => living}
living.exits = {:w => garden, :u => attic}
attic.exits = {:d => living}

rooms = [player, garden, living, attic]
start = living

########################################### The game - things

chain = Thing.new("chain", "chain", "The chain looks quite strong", "a chain", garden)
frog = Thing.new("frog", "slimy frog", "The frog is completely unfazed", "a frog", garden)
wiz = Thing.new("wizard", "sleeping wizard", "The wizard is dead to the world", "a wizard", living)

bucket = KickableThing.new("bucket", "old bucket", "The rusty old bucket looks really old", "a bucket", living)
well = ImmobileThing.new("well", "well", "The well is old and the water deep", "a well", garden)
water = InvisibleThing.new("water", "water", "The water sloshes as you go", "water", nil)

things = [bucket, chain, well, frog, wiz, water]

···

#################################################################

map = Map.new(rooms, start)
world = World.new(map, things)
player = MyPlayer.new(world)
parser = Parser.new(player)

puts
puts parser.player.com_look

loop do
  print "\n> "
  line = gets
  puts parser.parse(line)
end

I haven't had a chance to dig through your code yet, but I just wanted to stress that you look through all the solutions to that quiz if you haven't already. A lot of good ideas in there. Jim Menard also built a RADS library, for example...

James Edward Gray II

···

On Nov 28, 2005, at 2:10 PM, Leslie Viljoen wrote:

Comments very welcome!

Offtopic, but I was wondering what made you prefer TADS to Inform.

martin

···

Leslie Viljoen <leslie@camary.co.za> wrote:

I have written a few text adventures in TADS, the Texts
Adventure Development System. The system is excellent
and I highly recommend it, but since learning Ruby I've wanted
something similar to TADS in Ruby. The only reference to such
a project I could find was the Quiz 49: Ruby Quiz - Lisp Game (#49)

James Edward Gray II wrote:

Comments very welcome!

I haven't had a chance to dig through your code yet, but I just wanted to stress that you look through all the solutions to that quiz if you haven't already. A lot of good ideas in there. Jim Menard also built a RADS library, for example...

I saw that, and I looked at many of the solutions. AFAIR Jim's parser allowed running arbitrary methods
in Object - so I wanted to take that out, and also bend a solution to work a bit more like TADS.
In my program the "kick" verb consults the object (bucket) to see if it's "kickable". This is very similar to
how TADS works with objects.

The real challenge will be the parser - in fact, that might be a good quiz too. TADS has thousands of lines
dedicated to tokenising and parsing english - breaking sentences into phrases and phrases into
adverb + (noun phrase) where (noun phrase is adverb + noun-phrase). Almost a regex pattern matcher
for word types. It has a ranking system where it tries to figure out the most likely thing you are trying
to say instead of just taking the first possible meaning. I'm still going through the TADS3 parsing code
trying to understand it.

Les

···

On Nov 28, 2005, at 2:10 PM, Leslie Viljoen wrote:

Martin DeMello wrote:

···

Leslie Viljoen <leslie@camary.co.za> wrote:

I have written a few text adventures in TADS, the Texts
Adventure Development System. The system is excellent
and I highly recommend it, but since learning Ruby I've wanted
something similar to TADS in Ruby. The only reference to such
a project I could find was the Quiz 49: http://www.rubyquiz.com/quiz49.html
   
Offtopic, but I was wondering what made you prefer TADS to Inform.

I can't actually remember why. Way back when I started with IF I made
an effort to compare the two and TADS came out ahead. I do remember
making use of the HTML features of TADS.. since I'm always getting
lost, my game had an automatic graphical map builder which grew as you
went along, a lot like the Magnetic Scrolls games' map window.

L

I've been considering this for a quiz. I'm currently in the process of solving it myself, to prove it's not too much work. My free time is scarce currently though, so if you beat me to a solution mail it to me, along with a count of how many hours it took to build...

James Edward Gray II

···

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

The real challenge will be the parser - in fact, that might be a good quiz too.

Parsing English is what I wrote my thesis on at university. I wonder
if a general purpose English tokenizing and parsing library would be
of any use to anyone. I'm thinking of taking it up again.

Dave

···

On 11/29/05, James Edward Gray II <james@grayproductions.net> wrote:

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

> The real challenge will be the parser - in fact, that might be a
> good quiz too.

I've been considering this for a quiz. I'm currently in the process
of solving it myself, to prove it's not too much work. My free time
is scarce currently though, so if you beat me to a solution mail it
to me, along with a count of how many hours it took to build...

James Edward Gray II

James Edward Gray II wrote:

The real challenge will be the parser - in fact, that might be a good quiz too.

I've been considering this for a quiz. I'm currently in the process of solving it myself, to prove it's not too much work. My free time is scarce currently though, so if you beat me to a solution mail it to me, along with a count of how many hours it took to build...

James Edward Gray II

For a quiz, perhaps you could provide a selection of say 10 or so sentences that a parser would
need to "understand" and convert to a range of method calls such as get(obj), light(dobj, iobj),
feed(dobj, iobj) etc.

TO would need to be understood to get the direct-object/indirect-object right:
feed bird to lion vs. feed bird lion.

I'll start on such a solution and let you know how it goes...

Les

···

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

BTW, for simple english parsing I found this to be a great resource:
http://www.mud.co.uk/richard/cgtaghtw.htm

Particularly this BNF is interesting:

command ::= basic-command [adverb]
basic-command ::= predicate object |
predicate indirect-object object |
predicate object preposition indirect-object
predicate :: = verb | verb short-adverb
indirect-object ::= object
object ::= {adjective} noun | object AND {adjective} noun

I would LOVE to see a pure Ruby library for something like this. I can't imagine it wouldn't get used.

James Edward Gray II

···

On Nov 29, 2005, at 8:15 AM, David Balmain wrote:

On 11/29/05, James Edward Gray II <james@grayproductions.net> wrote:

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

The real challenge will be the parser - in fact, that might be a
good quiz too.

I've been considering this for a quiz. I'm currently in the process
of solving it myself, to prove it's not too much work. My free time
is scarce currently though, so if you beat me to a solution mail it
to me, along with a count of how many hours it took to build...

James Edward Gray II

Parsing English is what I wrote my thesis on at university. I wonder
if a general purpose English tokenizing and parsing library would be
of any use to anyone. I'm thinking of taking it up again.

David Balmain wrote:

The real challenge will be the parser - in fact, that might be a
good quiz too.
     

I've been considering this for a quiz. I'm currently in the process
of solving it myself, to prove it's not too much work. My free time
is scarce currently though, so if you beat me to a solution mail it
to me, along with a count of how many hours it took to build...

James Edward Gray II
   
Parsing English is what I wrote my thesis on at university. I wonder
if a general purpose English tokenizing and parsing library would be
of any use to anyone. I'm thinking of taking it up again.

Take it up! You could use something like this in all sorts of games,
text-to-speech systems, perhaps even voice recognition. Even
if you don't take it up, some ideas and algorithms would be
greatly appreciated.

Make a topic on my wiki!
http://mobeus.homelinux.org/eclectica

BTW: My wiki has two Ruby script sections where I dump useful
"nuggets" (tiny bits of useful code) and longer scripts. I'd be grateful
to anyone who contributes.

Les

···

On 11/29/05, James Edward Gray II <james@grayproductions.net> wrote:

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

Wow! Excellent!... this could have great use to implement DSLs and
of course for the obvious gaming fun :slight_smile:

···

On 11/29/05, David Balmain <dbalmain.ml@gmail.com> wrote:

Parsing English is what I wrote my thesis on at university. I wonder
if a general purpose English tokenizing and parsing library would be
of any use to anyone. I'm thinking of taking it up again.

Hello all,
I'm learning ruby recently and one thing makes me think. is it possible to pass more than one block to methods? also is it necessary or is there need for that?

niyo

Leslie Viljoen wrote:

James Edward Gray II wrote:

The real challenge will be the parser - in fact, that might be a good quiz too.

I've been considering this for a quiz. I'm currently in the process of solving it myself, to prove it's not too much work. My free time is scarce currently though, so if you beat me to a solution mail it to me, along with a count of how many hours it took to build...

James Edward Gray II

For a quiz, perhaps you could provide a selection of say 10 or so sentences that a parser would
need to "understand" and convert to a range of method calls such as get(obj), light(dobj, iobj),
feed(dobj, iobj) etc.

TO would need to be understood to get the direct-object/indirect-object right:
feed bird to lion vs. feed bird lion.

I'll start on such a solution and let you know how it goes...

In my research I happened on a part-of-speech tagger by Mark Watson:
http://www.markwatson.com/opensource/

That would certainly make Mark a bit of an expert in this area! He posted
here in the forum about 10 days ago but I see his site says he is on holiday.
Anyway the tagger uses a 92000 line lexicon and some rules to transform
the results of the lexicon lookup. It's very interesting indeed.

I studied some linguistics at university but not enough to produce
something like this. It seems there may be a standard way to accomplish
such things if one does study the field further.

Les

PS: I got this using Mark's program:

feed:VBN the:DT lion:NN to:TO the:DT bird:NN
feed:VBN the:DT lion:NN the:DT bird:NN
feed:VBN the:DT lion:NN bird:NN
put:VB the:DT red:JJ card:NN in:IN the:DT card:NN reader:NN

···

On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:

Hello all,
I'm learning ruby recently and one thing makes me think. is it possible to pass more than one block to methods?

It's not what I would call easy. You can make lambdas and pass those in. Same effect, but no syntactic sugar.

also is it necessary or is there need for that?

I've actually found myself wanting this twice in the last week or two. I can think of at least some uses.

James Edward Gray II

···

On Nov 29, 2005, at 7:04 PM, Niyazi Toytok wrote:

Not very nice but...

def a(&block)
  puts "A"

  return block
end

def b(b2)
  puts "B"
  b2.call
  yield
end

b(a { puts "HELLO 1" }) { puts "HELLO 2" }

Hi --

···

On Wed, 30 Nov 2005, Niyazi Toytok wrote:

Hello all,
I'm learning ruby recently and one thing makes me think. is it possible to pass more than one block to methods? also is it necessary or is there need for that?

You can pass lambda objects around. For every method call, there's
only one "the" block, though. It's a syntactic construct, analogous
to the argument list.

David

--
David A. Black
dblack@wobblini.net

Niyazi Toytok wrote:

Hello all,
I'm learning ruby recently and one thing makes me think. is it possible to
pass more than one block to methods? also is it necessary or is there need
for that?

Not in the sense of xyz {|a| ...} {|b| ...} but I've had the
occasional need to yield more than once from the same method.

Like:

def xyz
  val = 'build :a'
  yield [1, val]
  val = 'build :b'
  yield [2, val]
  nil
end

xyz do |i, arg|
  case i
   when 1
     # stuff #
     puts arg
   when 2
     puts "do something with #{arg}"
     # stuff #
  end
end

#-> build :a
#-> do something with build :b

Flexible enough ?

daz

Unfortunately I think I'd have to do it in C. My linguistic skills
aren't good enough to take a purely grammatical approach. I'd need to
take a learning approach. The parser I wrote at university took 72
hours to process the input corpus which it learned all the rules from.
I'd hate to think how long this would take in pure ruby. Writing this
as a ruby extension would be the way to go. For me at least anyway.

Dave

···

On 11/29/05, James Edward Gray II <james@grayproductions.net> wrote:

On Nov 29, 2005, at 8:15 AM, David Balmain wrote:

> On 11/29/05, James Edward Gray II <james@grayproductions.net> wrote:
>> On Nov 29, 2005, at 1:12 AM, Leslie Viljoen wrote:
>>
>>> The real challenge will be the parser - in fact, that might be a
>>> good quiz too.
>>
>> I've been considering this for a quiz. I'm currently in the process
>> of solving it myself, to prove it's not too much work. My free time
>> is scarce currently though, so if you beat me to a solution mail it
>> to me, along with a count of how many hours it took to build...
>>
>> James Edward Gray II
>
> Parsing English is what I wrote my thesis on at university. I wonder
> if a general purpose English tokenizing and parsing library would be
> of any use to anyone. I'm thinking of taking it up again.

I would LOVE to see a pure Ruby library for something like this. I
can't imagine it wouldn't get used.

I've checked lambdas and I think it's not possible also to pass more than one proc as parameter to a method also? I used hash instead

p = lambda { |name|
    puts "Hello1" + " " + name
  }

z = lambda { |name2|
    puts "Hello2" + " " + name2
  }

class Test
  def blockTest(name,procHash)

    procHash['test1'].call(name)
    procHash['test2'].call(name)

  end

end

test = Test.new
procs = { 'test1' => p, 'test2' => z }
test.blockTest("niyo",procs)

niyo

···

----- Original Message ----- From: "David A. Black" <dblack@wobblini.net>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Wednesday, November 30, 2005 4:21 AM
Subject: Re: blocks

Hi --

On Wed, 30 Nov 2005, Niyazi Toytok wrote:

Hello all,
I'm learning ruby recently and one thing makes me think. is it possible to pass more than one block to methods? also is it necessary or is there need for that?

You can pass lambda objects around. For every method call, there's
only one "the" block, though. It's a syntactic construct, analogous
to the argument list.

David

--
David A. Black
dblack@wobblini.net

--
No virus found in this incoming message.
Checked by AVG Free Edition.
Version: 7.1.362 / Virus Database: 267.13.9/185 - Release Date: 28.11.2005