[SOLUTION] Lisp Game (#49)

J

Jim Menard

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.r=
b.
#
# 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 wes=
t
# - 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
 
J

James Edward Gray II

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

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
 
A

Adam Shelly

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. =20
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
=09s=3D""
=09set.each {|o| s+=3D "There is a #{o} here. " }
=09s
end


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

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

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

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

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

# # Game Definition # #
#the Map , Ojbect List, and possible actions are here.
module GameDef
=09def init_game
=09=09map =3D 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]),
=09=09=09=09=09=09Room.new(garden, "You are in a beautiful garden. There is=
a well
in front of you.", [east, door, living_room]),
=09=09=09=09=09=09Room.new(attic, "You are in the attic of the wizard's hou=
se.
There is a giant welding torch in the corner.", [downstairs, stairway,
living_room])]
=09=09objects =3D ObjectTracker.new({whiskey_bottle =3D> living_room, bucke=
t
=3D> living_room, frog =3D> garden, chain=3D>garden})
=09=09location =3D living_room
=09=09@chain_welded =3D nil
=09=09@bucket_filled =3D nil
=09=09@wiz_kisses =3D -1
=09=09game_action:)weld, bucket, chain, attic, proc { if have? bucket then
@chain_welded =3D true; "The chain is now securely welded to the bucket"
else "you do not have a bucket" end })
=09=09game_action:)dunk, bucket, well, garden, proc { if @chain_welded
then @bucket_filled =3D true; "The bucket is now full of water" else
"the water level is too low to reach" end })
=09=09game_action:)splash, bucket, wizzard, living_room, proc {
=09=09=09if !@bucket_filled then "the bucket has nothing in it"
=09=09=09elsif 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."
=09=09=09else "the wizzard awakens from his slumber and greets you warmly.=
=20
He hands you the magic chunky bacon. You win! the end."
=09=09=09end})
=09=09game_multi_action:)wake, {wizzard=3D>[proc{"If it were only that
easy."},living_room],frog=3D>[proc{"the frog is already awake"},nil]})
=09=09game_multi_action:)kiss, {frog =3D> [proc{"Sorry, no prince"},nil],
=09=09=09=09=09=09=09=09=09=09=09=09whiskey_bottle =3D> [proc{"You warmly g=
reet your old friend"},nil],
=09=09=09=09=09=09=09=09=09=09=09=09wizzard =3D> [proc{results =3D ["You ti=
midly 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."]
=09=09=09=09=09=09=09=09=09=09=09=09=09=09=09=09=09=09 results[@wiz_kisses=
+=3D1]||"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
=09end
=09private :init_game
end


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


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

$g =3D Game.new
look

if __FILE__ =3D=3D $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
 
R

Ron M

Adam said:
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 said:
[...]
set.inject("") {|s,o| s + "There is a #{o} here. "}

I was thinking

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

daz

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.
;-)

Cheers,

daz
 
J

James Edward Gray II

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

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

James Edward Gray II
 
D

Dave Burt

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" :) 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)
 
J

James Edward Gray II

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! ;)

James Edward Gray II
 
D

Dave Burt

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

That cracked me up! ;)

["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 :)

Dave
 
K

Kero

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

aye, was fun!

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 :)

+--- Kero ------------------------- kero@chello@nl ---+
| all the meaningless and empty words I spoke |
| Promises -- The Cranberries |
+--- M38c --- http://members.chello.nl/k.vangelder ---+
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top