Forum: Ruby Wizard quiz

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
leslie (Guest)
on 2005-11-28 22:13
(Received via mailing list)
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
James G. (Guest)
on 2005-11-29 01:35
(Received via mailing list)
On Nov 28, 2005, at 2:10 PM, Leslie V. 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 M.
also built a RADS library, for example...

James Edward G. II
leslie (Guest)
on 2005-11-29 09:15
(Received via mailing list)
James Edward G. II wrote:

> On Nov 28, 2005, at 2:10 PM, Leslie V. 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 M.
> 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
martindemello (Guest)
on 2005-11-29 16:04
(Received via mailing list)
Leslie V. <removed_email_address@domain.invalid> 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.

martin
James G. (Guest)
on 2005-11-29 16:08
(Received via mailing list)
On Nov 29, 2005, at 1:12 AM, Leslie V. 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 G. II
dbalmain.ml (Guest)
on 2005-11-29 16:16
(Received via mailing list)
On 11/29/05, James Edward G. II <removed_email_address@domain.invalid> wrote:
> James Edward G. 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.

Dave
James G. (Guest)
on 2005-11-29 16:24
(Received via mailing list)
On Nov 29, 2005, at 8:15 AM, David B. wrote:

>>
>> James Edward G. 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.

James Edward G. II
leslie (Guest)
on 2005-11-29 20:19
(Received via mailing list)
Martin DeMello wrote:

>
>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
leslie (Guest)
on 2005-11-29 20:43
(Received via mailing list)
David B. wrote:

>>>
>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
gregory.t.brown (Guest)
on 2005-11-29 20:52
(Received via mailing list)
On 11/29/05, David B. <removed_email_address@domain.invalid> 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.


Wow!  Excellent!... this could have great use to implement DSLs  and
of course for the obvious gaming fun :)
leslie (Guest)
on 2005-11-30 01:51
(Received via mailing list)
James Edward G. II wrote:

>
> James Edward G. 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
leslie (Guest)
on 2005-11-30 02:15
(Received via mailing list)
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
niyazi.toytok (Guest)
on 2005-11-30 03:08
(Received via mailing list)
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
James G. (Guest)
on 2005-11-30 03:36
(Received via mailing list)
On Nov 29, 2005, at 7:04 PM, 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?

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 G. II
dblack (Guest)
on 2005-11-30 04:24
(Received via mailing list)
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
dooby (Guest)
on 2005-11-30 04:28
(Received via mailing list)
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
lyndon.samson (Guest)
on 2005-11-30 04:44
(Received via mailing list)
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" }
niyazi.toytok (Guest)
on 2005-11-30 05:05
(Received via mailing list)
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" <removed_email_address@domain.invalid>
To: "ruby-talk ML" <removed_email_address@domain.invalid>
Sent: Wednesday, November 30, 2005 4:21 AM
Subject: Re: blocks
dblack (Guest)
on 2005-11-30 05:17
(Received via mailing list)
Hi --

On Wed, 30 Nov 2005, Niyazi Toytok wrote:

> 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

You can pass any number of them as normal params:

   def block_test(b1,b2)
     b1.call
     b2.call
   end

   b1 = lambda { puts "one" }
   b2 = lambda { puts "two" }

   block_test(b1,b2)

   =>  one
       two


David
niyazi.toytok (Guest)
on 2005-11-30 05:25
(Received via mailing list)
I was putting & sign before parameters. that's why I thought it does not
work. thank you
----- Original Message -----
From: "David A. Black" <removed_email_address@domain.invalid>
To: "ruby-talk ML" <removed_email_address@domain.invalid>
Sent: Wednesday, November 30, 2005 5:14 AM
Subject: Re: blocks
dbalmain.ml (Guest)
on 2005-11-30 06:33
(Received via mailing list)
On 11/29/05, James Edward G. II <removed_email_address@domain.invalid> wrote:
> >> 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 G. 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.

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
James G. (Guest)
on 2005-11-30 06:58
(Received via mailing list)
On Nov 29, 2005, at 10:32 PM, David B. wrote:

>>>> process
>>
>> I would LOVE to see a pure Ruby library for something like this.  I
>> can't imagine it wouldn't get used.
>
> 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.

It still sounds very interesting.

James Edward G. II
jqshenker (Guest)
on 2005-11-30 08:39
(Received via mailing list)
I have countless uses for such a parser, I'm sure many other do too!
Very interesting project, keep us informed.

Jacob
leslie (Guest)
on 2005-11-30 16:12
(Received via mailing list)
Leslie V. wrote:

>> of solving it myself, to prove it's not too much work.  My free time
>
> 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 W.:
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
This topic is locked and can not be replied to.