[SOLUTION] Lisp Game (#49)

Boy, was this fun. My solution, RADS, is at
<http://www.io.com/~jimm/rubyquiz/quiz49/>.

From the engine file:

# This file defines the game mechanics, and is independent of the specifics of
# the game. For a sample game definition including custom verbs, see game.rb.

···

#
# Features:
# - inventory or i
# - look or l, also accepts "look at thing"
# - examine or x prints long description
# - "it": take it, examine it, look at it
# - walk or go
# - short direction names (n, s, e, w, u, d)
# - short direction names are also verbs so you can just type "w" to go west
# - altername names for things ("whiskey bottle", "bottle", "whiskey")
# - decorations, which are objects that can't be taken.
# Try "x wizard" or "look at couch".
# - game-specific verbs are defined in the game file, not here
# - A decoration or thing without a short_desc won't be output as part of
# the room description
# - Any object with no names array defined in the initialization proc will
# have one created for it containing the short_desc
# - Containment (things within other things) is implemented and contents
# of containers like the bucket will be printed, but "put in"/"take out"
# is not yet implemented.
#
# To do:
# - Fix the fact that you can't examine the door in the garden
# - Implement "put in"/"take out"
# - Write a "put x in y" verb
# - Understand prepositions. In addition to "splash bucket [on] wizard" and
# "dunk bucket [in] well", I'd like to allow "splash wizard with bucket".

From the game file:

# features:
# - game-specific verbs are defined here, not as part of the rads library
# - dunk, weld, and splash take (but ignore) prepositions. Try
# "splash bucket on wizard" or "splash bucket wizard"

Jim
--
Jim Menard, jim.menard@gmail.com, jimm@io.com
http://www.io.com/~jimm

Boy, was this fun. My solution, RADS, is at
<http://www.io.com/~jimm/rubyquiz/quiz49/&gt;\.

That is beyond awesome! I've been reading the PAWS source thinking of porting it, but you have a very interesting start here.

My solution is below. Again, I took the direct approach of a line for line translation. So, in my code you will see the Lisp, followed by my Ruby translation. It was interesting making lines like:

> walk east

work in irb. It even came out cleaner than the Lisp version of "(walk east)".

Enjoy.

James Edward Gray II

(setf *objects* '(whiskey-bottle bucket frog chain))
### ^ Lisp >>> Ruby v ###
$objects = %w{whiskey_bottle bucket frog chain}

(setf *map* '((living-room (you are in the living-room of a wizards house. there is a wizard snoring loudly on the couch.)
                            (west door garden)
                            (upstairs stairway attic))
               (garden (you are in a beautiful garden. there is a well in front of you.)
                       (east door living-room))
               (attic (you are in the attic of the wizards house. there is a giant welding torch in the corner.)
                      (downstairs stairway living-room))))
### ^ Lisp >>> Ruby v ###
$map = { "living_room" => [ "You are in the living_room of a wizard's house. There is a wizard snoring loudly on the couch.",
                             %w{west door garden},
                             %w{upstairs stairway attic} ],
          "garden" => [ "You are in a beautiful garden. There is a well in front of you.",
                             %w{east door living_room} ],
          "attic" => [ "You are in the attic of the wizard's house. There is a giant welding torch in the corner.",
                             %w{downstairs stairway living_room} ] }

(setf *object-locations* '((whiskey-bottle living-room)
                            (bucket living-room)
                            (chain garden)
                            (frog garden)))
### ^ Lisp >>> Ruby v ###
$object_locations = { "whiskey_bottle" => "living_room",
                       "bucket" => "living_room",
                       "chain" => "garden",
                       "frog" => "garden" }

(setf *location* 'living-room)
### ^ Lisp >>> Ruby v ###
$location = "living_room"

(defun describe-location (location map)
   (second (assoc location map)))
### ^ Lisp >>> Ruby v ###
def describe_location( location, map )
   map[location].first
end

(describe-location 'living-room *map*)
### ^ Lisp >>> Ruby v ###
describe_location "living_room", $map

(defun describe-path (path)
   `(there is a ,(second path) going ,(first path) from here.))
### ^ Lisp >>> Ruby v ###
def describe_path( path )
   "There is a #{path[1]} going #{path[0]} from here."
end

(describe-path '(west door garden))
### ^ Lisp >>> Ruby v ###
describe_path %w{west door garden}

(defun describe-paths (location map)
   (apply #'append (mapcar #'describe-path (cddr (assoc location map)))))
### ^ Lisp >>> Ruby v ###
def describe_paths( location, map )
   map[location][1..-1].map { |p| describe_path(p) }.join(" ")
end

(describe-paths 'living-room *map*)
### ^ Lisp >>> Ruby v ###
describe_paths "living_room", $map

(defun is-at (obj loc obj-loc)
   (eq (second (assoc obj obj-loc)) loc))
### ^ Lisp >>> Ruby v ###
def is_at?( object, location, object_locations )
   location == object_locations[object]
end

(is-at 'whiskey-bottle 'living-room *object-locations*)
### ^ Lisp >>> Ruby v ###
is_at? "whiskey_bottle", "living_room", $object_locations

(defun describe-floor (loc objs obj-loc)
   (apply #'append (mapcar (lambda (x)
                             `(you see a ,x on the floor.))
                           (remove-if-not (lambda (x)
                                            (is-at x loc obj-loc))
                                          objs))))
### ^ Lisp >>> Ruby v ###
def describe_floor( location, object_locations )
   object_locations.select { |obj, loc| loc == location }.map do |obj, loc|
     "You see a #{obj} on the floor."
   end.join(" ")
end

(describe-floor 'living-room *objects* *object-locations*)
### ^ Lisp >>> Ruby v ###
describe_floor "living_room", $object_locations

(defun look ()
   (append (describe-location *location* *map*)
           (describe-paths *location* *map*)
           (describe-floor *location* *objects* *object-locations*)))
### ^ Lisp >>> Ruby v ###
def look
   [ describe_location($location, $map),
     describe_paths($location, $map),
     describe_floor($location, $object_locations) ].join(" ").strip
end

(look)
### ^ Lisp >>> Ruby v ###
look

(defun walk-direction (direction)
   (let ((next (assoc direction (cddr (assoc *location* *map*)))))
     (cond (next (setf *location* (third next)) (look))
           (t '(you cant go that way.)))))
### ^ Lisp >>> Ruby v ###
def walk_direction( direction )
   if to = $map[$location][1..-1].assoc(direction)
     $location = to.last
     look
   else
     "You can't go that way."
   end
end

(walk-direction 'west)
### ^ Lisp >>> Ruby v ###
walk_direction "west"

(defmacro defspel (&rest rest) `(defmacro ,@rest))
### ^ Lisp >>> Ruby v ###
$stringify = %w{west east upstairs downstairs}
def method_missing( method, *args, &block )
   if $stringify.include? method.to_s
     method.to_s
   else
     "I don't know the word '#{method}'."
   end
end

(defspel walk (direction)
   `(walk-direction ',direction)
### ^ Lisp >>> Ruby v ###
alias walk walk_direction

(walk east)
### ^ Lisp >>> Ruby v ###
walk east

(defun pickup-object (object)
   (cond ((is-at object *location* *object-locations*) (push (list object 'body) *object-locations*)
                                                       `(you are now carrying the ,object))
         (t '(you cannot get that.))))
### ^ Lisp >>> Ruby v ###
def pickup_object( object )
   if is_at? object, $location, $object_locations
     $object_locations[object] = "body"
     "You are now carrying #{object}."
   else
     "You cannot get that."
   end
end

(defspel pickup (object)
   `(pickup-object ',object))
### ^ Lisp >>> Ruby v ###
$stringify.push(*$objects)
alias pickup pickup_object

(pickup whiskey-bottle)
### ^ Lisp >>> Ruby v ###
pickup whiskey_bottle

(defun inventory ()
   (remove-if-not (lambda (x)
                    (is-at x 'body *object-locations*))
                  *objects*))
### ^ Lisp >>> Ruby v ###
def inventory
   $objects.select { |obj| $object_locations[obj] == "body" }
end

(defun have (object)
   (member object (inventory)))
### ^ Lisp >>> Ruby v ###
def have?( object )
   inventory.include? object
end

(setf *chain-welded* nil)
### ^ Lisp >>> Ruby v ###
$chain_welded = nil

(defun weld (subject object)
   (cond ((and (eq *location* 'attic)
               (eq subject 'chain)
               (eq object 'bucket)
               (have 'chain)
               (have 'bucket)
               (not *chain-welded*))
          (setf *chain-welded* 't)
          '(the chain is now securely welded to the bucket.))
         (t '(you cannot weld like that.))))
### ^ Lisp >>> Ruby v ###
def weld( *objects )
   if $location == "attic" and not $chain_welded and
      objects.all? { |obj| have? obj } and objects.sort == %w{bucket chain}
     $chain_welded = true
     "The chain is now securely welded to the bucket."
   else
     "You cannot weld like that."
   end
end

(weld 'chain 'bucket)
### ^ Lisp >>> Ruby v ###
weld "chain", "bucket"

(setf *bucket-filled* nil)
### ^ Lisp >>> Ruby v ###
$bucket_filled = nil

(defun dunk (subject object)
   (cond ((and (eq *location* 'garden)
               (eq subject 'bucket)
               (eq object 'well)
               (have 'bucket)
               *chain-welded*)
          (setf *bucket-filled* 't) '(the bucket is now full of water))
         (t '(you cannot dunk like that.))))
### ^ Lisp >>> Ruby v ###
def dunk( *objects )
   if $location == "garden" and $chain_welded and objects.sort == %w{bucket well}
     $bucket_filled = true
     "The bucket is now full of water."
   else
     "You cannot dunk like that."
   end
end

(defspel game-action (command subj obj place &rest rest)
   `(defspel ,command (subject object)
      `(cond ((and (eq *location* ',',place)
                   (eq ',subject ',',subj)
                   (eq ',object ',',obj)
                   (have ',',subj))
              ,@',rest)
             (t '(i cant ,',command like that.)))))
### ^ Lisp >>> Ruby v ###
def game_action( action, subject, object, location, &block )
   $stringify << object unless $stringify.include? object
   self.class.send(:define_method, action) do |sub, obj|
     begin
       if $location == location and
          subject == sub and object == obj and have?(subject)
         block[sub, obj]
       else
         raise
       end
     rescue
       "You can't #{action} like that."
     end
   end
end

(game-action weld chain bucket attic
              (cond ((and (have 'bucket) (setf *chain-welded* 't))
                     '(the chain is now securely welded to the bucket.))
                    (t '(you do not have a bucket.))))
### ^ Lisp >>> Ruby v ###
game_action(*%w{weld chain bucket attic}) do |subject, object|
   have?(object) or raise
   $chain_welded = true
   "The chain is now securely welded to the bucket."
end

(weld chain bucket)
### ^ Lisp >>> Ruby v ###
weld chain, bucket

(game-action dunk bucket well garden
              (cond (*chain-welded* (setf *bucket-filled* 't) '(the bucket is now full of water))
                    (t '(the water level is too low to reach.))))
### ^ Lisp >>> Ruby v ###
game_action(*%w{dunk bucket well garden}) do |subject, object|
   $chain_welded or raise
   $bucket_filled = true
   "The bucket is now full of water."
end

(game-action splash bucket wizard living-room
              (cond ((not *bucket-filled*) '(the bucket has nothing in it.))
                    ((have 'frog) '(the wizard awakens and sees that you stole his frog.
                                    he is so upset he banishes you to the
                                    netherworlds- you lose! the end.))
                    (t '(the wizard awakens from his slumber and greets you warmly.
                         he hands you the magic low-carb donut- you win! the end.))))
### ^ Lisp >>> Ruby v ###
game_action(*%w{splash bucket wizard living_room}) do |subject, object|
   $bucket_filled or raise
   if have? "frog"
     "The wizard awakens and sees that you stole his frog. " +
     "He is so upset he banishes you to the netherworlds--you lose! The end."
   else
     "The wizard awakens from his slumber and greets you warmly. " +
     "He hands you the magic low-carb donut--you win! The end."
   end
end

···

On Oct 2, 2005, at 8:35 AM, Jim Menard wrote:

This was really a very nice quiz. I actually had bookmarked this tutorial, but this quiz finally motivated me to read it.

I did two versions: The first versions is an almost literal translation of the lisp code (see attachment). It uses Hashes instead of Arrays for some stuff and describe_path has a different interface but other than that it's very similar to the original. I also used method_missing to allow the user to write "pickup bucket" instead of "pickup :bucket".

The second versions (see below and attachment) is still near the lisp version but object oriented and more ruby-like.

And thanks to Brian Schröder and his thread "Eval and block problems" (and of course to everybody, who replied): I had a bug in my game_action method, I used "block.call" instead of "instance_eval &block". I wouldn't have found this without the thread because it actually worked because of my method_missing trick...

Here is a walkthrough (spoiler alert ;-):

$ irb --prompt simple -r lispgame.rb

WizardGame.start_irb_game

=> nil

help

=> [describe_floor, describe_location, describe_paths, dunk, get, go, h, help, i, inventory, l, look, pickup, pickup_object, splash, take, w, walk, walk_direction, weld]

l

=> you are in the living-room of a wizard's house. there is a wizard snoring loudly on the couch.
there is a door going west from here. there is a stairway going upstairs from here.
you see a whiskey_bottle on the floor. you see a bucket on the floor.

take bucket

=> you are now carrying the bucket

go west

=> you are in a beautiful garden. there is a well in front of you.
there is a door going east from here.
you see a frog on the floor. you see a chain on the floor.

dunk bucket, well

=> the water level is too low to reach.

pickup chain

=> you are now carrying the chain

w east

=> you are in the living-room of a wizard's house. there is a wizard snoring loudly on the couch.
there is a door going west from here. there is a stairway going upstairs from here.
you see a whiskey_bottle on the floor.

i

=> [bucket, chain]

w upstairs

=> you are in the attic of the abandoned house. there is a giant welding torch in the corner.
there is a stairway going downstairs from here.

weld chain, bucket

=> the chain is now securely welded to the bucket.

w downstairs

=> you are in the living-room of a wizard's house. there is a wizard snoring loudly on the couch.
there is a door going west from here. there is a stairway going upstairs from here.
you see a whiskey_bottle on the floor.

w west

=> you are in a beautiful garden. there is a well in front of you.
there is a door going east from here.
you see a frog on the floor.

dunk bucket, well

=> the bucket is now full of water.

w east

=> you are in the living-room of a wizard's house. there is a wizard snoring loudly on the couch.
there is a door going west from here. there is a stairway going upstairs from here.
you see a whiskey_bottle on the floor.

splash bucket, wizard

=> the wizard awakens from his slumber and greets you warmly. he hands you the magic low-carb donut- you win! the end.

Dominik

The code of the second version:

class TextAdventureEngine

   Location = Struct.new(:description, :paths)
   Path = Struct.new(:description, :destination)

   def describe_location
     @map[@location].description
   end

   def describe_paths
     paths = @map[@location].paths
     paths.keys.map { |dir|
       "there is a #{paths[dir].description} going #{dir} from here."
     }.join(" ")
   end

   def is_at?(object, location)
     @object_locations[object] == location
   end

   def describe_floor
     @objects.find_all { |obj| is_at?(obj, @location) }.map { |obj|
       "you see a #{obj} on the floor."
     }.join(" ")
   end

   def look
     [describe_location, describe_paths, describe_floor].join("\n")
   end

   alias :l :look

   def walk_direction(direction)
     next_loc = @map[@location].paths[direction]
     if next_loc
       @location = next_loc.destination
       look
     else
       "you can't go that way."
     end
   end

   alias :walk :walk_direction
   alias :go :walk_direction
   alias :w :walk_direction

   def pickup_object(object)
     if is_at?(object, @location)
       @object_locations[object] = :body
       "you are now carrying the #{object}"
     else
       "you cannot get that."
     end
   end

   alias :pickup :pickup_object
   alias :take :pickup_object
   alias :get :pickup_object

   def inventory
     @objects.find_all { |obj|
       is_at?(obj, :body)
     }.map { |o| o.to_s }
   end

   alias :i :inventory

   def have?(object)
     is_at?(object, :body)
   end

   def help
     (methods - Object.instance_methods).reject { |m| m=~/\?$/ }.sort
   end

   alias :h :help

   def self.game_action(command, subj, obj, place, &block)
     define_method(command) { |subject, object|
       if @location == place && subject == subj && object == obj && have?(subj)
         instance_eval &block
       else
         "i can't #{command} like that."
       end
     }
   end

   def self.start_irb_game
     $irb_ta = self.new
     Object.class_eval {
       # send missing methods to $irb_ta or return their name as symbol
       def method_missing(m, *a, &b)
         if $irb_ta.respond_to? m
           $irb_ta.send(m, *a, &b)
         elsif (a.empty? && !b)
           m
         else
           super
         end
       end
     }
     String.class_eval {
       # nicer output
       def inspect
         self
       end
     }
   end

end

class WizardGame < TextAdventureEngine

   def initialize
     @objects = [:whiskey_bottle, :bucket, :frog, :chain]

     @map = {
       :living_room => Location.new(
       "you are in the living-room of a wizard's house. there is a wizard snoring loudly on the couch.",
       {:west => Path.new("door", :garden), :upstairs => Path.new("stairway", :attic)}
       ),
       :garden => Location.new(
       "you are in a beautiful garden. there is a well in front of you.",
       {:east => Path.new("door", :living_room)}
       ),
       :attic => Location.new(
       "you are in the attic of the abandoned house. there is a giant welding torch in the corner.",
       {:downstairs => Path.new("stairway", :living_room)}
       )
     }

     @object_locations = {
       :whiskey_bottle => :living_room,
       :bucket => :living_room,
       :chain => :garden,
       :frog => :garden
     }

     @location = :living_room
   end

   game_action(:weld, :chain, :bucket, :attic) {
     if have?(:bucket)
       @chain_welded = true
       "the chain is now securely welded to the bucket."
     else
       "you do not have a bucket."
     end
   }

   game_action(:dunk, :bucket, :well, :garden) {
     if @chain_welded
       @bucket_filled = true
       "the bucket is now full of water."
     else
       "the water level is too low to reach."
     end
   }

   game_action(:splash, :bucket, :wizard, :living_room) {
     if not @bucket_filled
       "the bucket has nothing in it."
     elsif have?(:frog)
       "the wizard awakens and sees that you stole his frog. he is so upset he banishes you to the netherworlds- you lose! the end."
     else
       "the wizard awakens from his slumber and greets you warmly. he hands you the magic low-carb donut- you win! the end."
     end
   }

end

lispgame.rb (3.66 KB)

lispgame_literal.rb (3.07 KB)

Good quiz!

Here's my 2-class, 260-loc.

http://www.d10.karoo.net/ruby/quiz/49/lisp_game.rb

Major features:

1) No documentation.

2) Scottish spelling of whisky.

3) '-cheat' command line option so that it plays itself
   and saves you a whole bunch of time/typos.

4) 'weld bucket chain' == 'weld chain bucket' config option.

5) 'weld frog JEG_II' bug fixed (was jumping to "YOU WIN").

Apart from those, it's just as user-hostile as the original.
:wink:

Cheers,

daz

Thanks for the quiz, James - a great one!

I set up a LISPy environment and copied the LISP code as closely as
possible, using Ruby procs, arrays and strings. Closer than James, even - I
use setf and let, although I didn't make "defmacro" :slight_smile: I did, however,
manage to make the Ruby code "splash bucket wizard" equivalent to
"splash('bucket', 'wizard')".

Find it here: http://www.dave.burt.id.au/ruby/lisperati.rb

Cheers,
Dave

D:\Docs\ruby>irb
irb(main):001:0> require 'lisperati'
(YOU ARE IN THE LIVING_ROOM OF A WIZARDS HOUSE. THERE IS A WIZARD SNORING
LOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A
STAIRWAY GOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY_BOTTLE ON THE FLOOR.
YOU SEE A BUCKET ON THE FLOOR.)
=> true
irb(main):002:0> pickup bucket
=> (YOU ARE NOW CARRYING THE BUCKET)
irb(main):003:0> walk west
=> (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU. THERE IS
A DOOR GOING EAST FROM HERE. YOU SEE A FROG ON THE FLOOR. YOU SEE A CHAIN ON
THE FLOOR.)
irb(main):004:0> inventory[]
=> (BUCKET)

Boy, was this fun. My solution, RADS, is at

aye, was fun!

<http://www.io.com/~jimm/rubyquiz/quiz49/&gt;\.

Mine is at http://chmeee.dyndns.org/~kero/ruby/quiz/49-lispgame.rb

I have a game_action(), but I'm definitely going to look how others
solved that.

From the "look mom, no braces!" department, there's

  walk west

I've been refactoring too fast to be able to solve this in irb :slight_smile:

+--- Kero ------------------------- kero@chello@nl ---+

all the meaningless and empty words I spoke |
                      Promises -- The Cranberries |

+--- M38c --- http://members.chello.nl/k.vangelder ---+

Here's my solution.
I started by translating the tutorial line-for line, then I refactored
it into something that made sense for me. I tried to keep the generic
game engine separate from the part that defined this specific game.
I added a few new actions and the ability to type just 'west' instead
of 'walk west'.

To run the game, load the file in IRB. If you run it from the command
line it performs a automatic walkthrough.

This was fun, and I think I learned a few things about method_missing
and define_method.
-Adam

PS: Is there a better way to do the following? Can it be done in a
single line?:

def describe_set set
  s=""
  set.each {|o| s+= "There is a #{o} here. " }
  s
end

SOLUTION:

···

----
#SPELS game ported from lisp
#Author: Adam Shelly
#Run from IRB to play adventure. Run from Command line to see walkthrough.
$Interactive = __FILE__ != $0

# # Game Mechanics # #
# This section is independent of any particular map, object list, rules, etc...

class Room
  attr_reader :name, :description, :exits
  def initialize name, desc, *exits
    @name = name
    @description = desc+"\n"
    @exits = exits
  end
end

class ObjectTracker
  def initialize objs
    @obj_loc = objs
  end
  def objs_in loc
      @obj_loc.reject{|o,l| l!=loc}.keys
  end
  def move_object obj, from, to
    if @obj_loc[obj] == from
      @obj_loc[obj] = to
    end
  end
  def describe_floor loc, pre, post
    s = ""
    objs_in(loc).each{|o| s += pre + o + post}
    s
  end
end

class Map
  def initialize a
    @map = a
  end
  def get_room location
    @map.find {|r| r.name == location}
  end
  def describe_location location
    get_room(location).description
  end
  def paths location
    get_room(location).exits
  end
  def describe_paths location, pre, mid, post
    s=""
    paths(location).each {|p| s+= pre + p[1]+ mid + p[0] + post}
    s
  end
  def room_in_direction dir, loc
     if p = paths(loc).find{|p| p[0]==dir}
       p[2]
     else
       nil
    end
  end
end

# # Game Definition # #
#the Map , Ojbect List, and possible actions are here.
module GameDef
  def init_game
    map = Map.new [ Room.new(living_room, "You are in the living_room
of a wizards house. There is a wizard snoring loudly on the couch.",
[west, door, garden] ,[upstairs, stairway, attic]),
            Room.new(garden, "You are in a beautiful garden. There is a well
in front of you.", [east, door, living_room]),
            Room.new(attic, "You are in the attic of the wizard's house.
There is a giant welding torch in the corner.", [downstairs, stairway,
living_room])]
    objects = ObjectTracker.new({whiskey_bottle => living_room, bucket
=> living_room, frog => garden, chain=>garden})
    location = living_room
    @chain_welded = nil
    @bucket_filled = nil
    @wiz_kisses = -1
    game_action(:weld, bucket, chain, attic, proc { if have? bucket then
@chain_welded = true; "The chain is now securely welded to the bucket"
else "you do not have a bucket" end })
    game_action(:dunk, bucket, well, garden, proc { if @chain_welded
then @bucket_filled = true; "The bucket is now full of water" else
"the water level is too low to reach" end })
    game_action(:splash, bucket, wizzard, living_room, proc {
      if !@bucket_filled then "the bucket has nothing in it"
      elsif have? frog then "the wizzard awakens and sees that you stole
his frog. He is so upset that he banishes you to the netherworlds -
You lose! the end."
      else "the wizzard awakens from his slumber and greets you warmly.
He hands you the magic chunky bacon. You win! the end."
      end})
    game_multi_action(:wake, {wizzard=>[proc{"If it were only that
easy."},living_room],frog=>[proc{"the frog is already awake"},nil]})
    game_multi_action(:kiss, {frog => [proc{"Sorry, no prince"},nil],
                        whiskey_bottle => [proc{"You warmly greet your old friend"},nil],
                        wizzard => [proc{results = ["You timidly kiss the
wizzard's cheek. Nothing happens","You give the wizzard a tiny peck on
the lips. His eylid twitches.","You plant a big wet kiss right on the
wizzard's mouth. His beard feels scratchy."]
                                      results[@wiz_kisses+=1]||"Enough Already!"},living_room]})
    game_directions([north, south, west, east, upstairs, downstairs])
    #any new game def must return these 3 things.
    return map,objects,location
  end
  private :init_game
end

#Here is the game logic. Any additional the output strings are here
(not in the game mechanics objects)
class Game
  include GameDef
  def initialize
    @map, @objects, @location = init_game
  end
  def look
    @map.describe_location(@location)+
    @map.describe_paths(@location, "There is a "," going ", " from here.\n")+
    @objects.describe_floor(@location, "You see an ", " on the floor.\n")
  end
  def walk direction
    if new_room = @map.room_in_direction(direction, @location)
      @location=new_room; look
    elsif direction
      "You Can't Go That Way"
    end
  end
  def pickup object
    if @objects.move_object(object, @location, :body)
      "You are now carrying the #{object}"
    else
      "You cannot get that."
    end
  end
  alias :get :pickup
  def inventory
    s = "You are carrying: "
    @objects.objs_in(:body).each{|o| s += "#{o} "}
    s
  end
  def have? obj
    inventory.scan(obj)!=[]
  end
  def game_action command, subj, obj, place,block
    p = proc {|subject, object|
      if (!have? subj)
        "You don't have a #{subject}"
      elsif (@location == place and subject == subj and object == obj)
        block.call
      else
        "I can't #{command} like that"
      end
    }
    self.class.send(:define_method, command , &p)
  end
  def game_multi_action command, hash
    p = proc {|subject|
      action = hash[subject]
      if ! (@objects.objs_in(@location).include?(subject) ||
have?(subject) || (action && action[1]==@location))
        "You don't see any #{subject} to #{command} here"
      elsif action
        action[0].call
      else
        "You can't #{command} that."
      end
    }
    self.class.send(:define_method, command , &p)
  end
  def game_directions dirs
    dirs.each{|d|
      #p = proc{ walk d}
      self.class.send(:define_method, d, proc{walk d})
      }
  end
  private :game_action, :game_multi_action, :game_directions
  def help
    ($g.methods - Object.methods).join ' '
  end
end

# The $words array is filled with symbols that become valid tokens for the game.
# Only symbols that are referenced _before_ the game object is
created are added.
# This allows us to detect invalid words after the game has started.
$words = []

def method_missing symbol, *args
  #p "MM:#{symbol} [#{args}]"
  
  #if it's a game method, send it.
  if $g and ($g.methods.include? symbol.to_s )
    puts "> #{symbol} #{args.join ' '}" if !$Interactive
    puts s = $g.send(symbol,*args)
  #if the game hasn't started, define it as a valid token.
  elsif !$g
    $words << symbol
    symbol.to_s
  #if the game has started, see if it is a token
  elsif $words.include? symbol
    symbol.to_s
  #otherwise, it is an unknown command.
  else
    puts "> #{symbol} #{args.join ' '}" if !$Interactive
    puts "Sorry, I don't understand #{symbol}"
  end
end

$g = Game.new
look

if __FILE__ == $0
help
north
wake wizzard
get frog
get whiskey_bottle
kiss frog
kiss wizzard
kiss wizzard
kiss wizzard
kiss wizzard
west
kiss frog
pickup chain
inventory
have? frog
welt chain, bucket
weld chain, bucket
walk east
pickup bucket
kiss whiskey_bottle
upstairs
weld bucket, chain
downstairs
splash bucket, wizzard
west
dunk bucket, well
east
inventory
splash bucket, wizzard
end

No quiz administrators where harmed in the making of this solution, I hope.

James Edward Gray II

···

On Oct 4, 2005, at 10:26 AM, daz wrote:

5) 'weld frog JEG_II' bug fixed (was jumping to "YOU WIN").

class Array
   def inspect # (JUST FOR FUN, MAKE ARRAYS LOOK LIKE LISP LISTS)
     '(' + map{|x| x.upcase }.join(" ") + ')'
   end
end

That cracked me up! :wink:

James Edward Gray II

···

On Oct 5, 2005, at 1:51 AM, Dave Burt wrote:

I set up a LISPy environment and copied the LISP code as closely as
possible, using Ruby procs, arrays and strings.

Adam Shelly wrote:

PS: Is there a better way to do the following? Can it be done in a
single line?:

def describe_set set
  s=""
  set.each {|o| s+= "There is a #{o} here. " }
  s
end

Edward Faulkner wrote:
> [...]
> set.inject("") {|s,o| s + "There is a #{o} here. "}

I was thinking

   set.map{|x| "There is a #{x} here."}.join(" ")

James Edward Gray II:

···

On Oct 5, 2005, at 1:51 AM, Dave Burt wrote:

I set up a LISPy environment and copied the LISP code as closely as
possible, using Ruby procs, arrays and strings.

class Array
  def inspect # (JUST FOR FUN, MAKE ARRAYS LOOK LIKE LISP LISTS)
    '(' + map{|x| x.upcase }.join(" ") + ')'
  end
end

That cracked me up! :wink:

["i", "had", "had", "enough", "of", "this"]

and something was lacking in the obvious presentation,

(AND THIS MADE ME FEEL BETTER ABOUT ALL THE RUBY SYNTAX I'D BEEN ABUSING)

Thanks for reading my horrid code, James - too many lists and first-class
functions for my taste :slight_smile:

Dave