Here is my solution, which is based on Dwemthy's Array.
<code>
#! /usr/bin/ruby
# Author: Morton Goldberg
#
# Date: 2006-09-23
#
# Based on the Dwemthy's Array example in Chapter 6 of "why's (poignant)
# guide to ruby".
# This is the story of a rabbit that went on a quest to slay a dragon.
# But before he got to face the dragon, he had to kill a whole bunch
# of other montsters, each much stronger than he. How could a tiny, weak
# rabbit survive against this terrible arary of monsters? Well, he had
# a magick sword, magick lettuce, a few bombs, and -- most
importantly --
# incredible good luck.
# Abstract class providing the basic behavior of all the creatures in
the
# story.
class Creature
# Get a metaclass for this class.
def self.metaclass
class << self; self; end
end
# Advanced metaprogramming code for nice, clean traits.
def self.traits(*arr)
return @traits if arr.empty?
# Set up accessors for each variable
attr_accessor(*arr)
# Add a new class method to for each trait.
arr.each do |a|
metaclass.instance_eval do
define_method(a) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
# For each monster, the `initialize' method should use the
default
# number for each trait.
class_eval do
define_method(:initialize) do
self.class.traits.each do |k,v|
instance_variable_set("@#{k}", v)
end
end
end
end
# Damage assessment after taking a hit during fight.
def hit(damage)
bonus = rand(magick)
if bonus % 9 == 7
@life += bonus
puts "Protective spell adds #{bonus} to #{self} life force."
end
puts "Fighting lowers #{self} life force by #{damage}."
case
when damage < 1
puts "#{self} wasn't touched!"
when damage < 0.25 * life
puts "#{self} suffered a minor wound."
when damage < 0.5 * life
puts "#{self} was wounded."
when damage < 0.75 * life
puts "#{self} was seriously wounded but carries on."
when damage < life
puts "#{self} was gravely wounded but survives."
else
puts "#{self} dies."
end
@life -= damage
end
# One participant's attack during one fight turn.
def attack
puts attack_description
damage = rand(strength * life + weapon_force)
foe.hit(damage)
end
# One fight (attack + counter-attack) in a battle.
def fight
if life <= 0
puts "#{self} is too dead to fight."
else
puts "#{self} [#{life}] and #{foe} [#{foe.life}] fight."
# Attack opponent.
attack if foe.life > 0
# Opponent's counter-attack.
foe.attack if life > 0 && foe.life > 0
end
self
end
def to_s
self.class.name
end
# Description of how an attack was made. Subclasses will often
override
# this.
def attack_description
"#{self} attacks #{foe} with #{weapon}."
end
# Creature default attributes are read-only.
traits :life, :strength, :magick, :challenge, :weapon, :weapon_force
# This trait is dynaminc -- don't give it a default value.
traits :foe
end
# The monsters.
class BogusFox < Creature
life 50
strength 0.6
magick 100
weapon 'axe'
weapon_force 20
challenge "Hail, %s, prepare to die!"
def attack_description
"BogusFox " + ["swings", "strikes with"][rand(2)] + " his axe."
end
end
class Jabberwocky < Creature
life 100
strength 0.8
magick 100
weapon 'teeth and claws'
weapon_force 20
challenge "Ah, a tasty %s!"
end
class DemonAngel < Creature
life 540
strength 0.2
magick 200
weapon 'black sword'
weapon_force 20
challenge "%s, I will eat your soul!"
def attack_description
"DemonAngel thrusts " + ["high", "low"][rand(2)] +
" with her black sword."
end
end
class ViciousGreenFungus < Creature
life 320
strength 0.8
magick 300
weapon 'acid spray'
weapon_force 100
challenge "No %s has ever left my presence alive."
end
class Dragon < Creature
life 1340 # really tough hide
strength 1.0 # big muscles
magick 1066 # studied with Merlin
weapon 'blast of flame' # fiery breath
weapon_force 940
challenge "A brave %s burns just as well as a timid one."
end
MONSTERS = [
BogusFox.new,
Jabberwocky.new,
DemonAngel.new,
ViciousGreenFungus.new,
Dragon.new
].freeze
# The hero.
class Rabbit < Creature
traits :bombs
life 25 # no armor or shield
strength 0.4 # not in great shape
magick 50 # more than you might expect
weapon 'boomerang'
weapon_force 4 # can't handle heavy stuff
challenge "I fear you not, %s!"
bombs 3 # quantity
# Little boomerang. Nearly useless.
def ^
self.weapon = Rabbit.weapon
self.weapon_force = Rabbit.weapon_force
fight
end
# Potent magick sword. Rabbit's Vorpal blade is powered by
opponent's
# life force -- a healthier opponent suffers more damage. The
rabbit's
# only chance? Well, he has some bombs.
def /
self.weapon = 'magick sword'
self.weapon_force = magick + foe.life
fight
end
# Bombs. Powerful, but rabbits don't have very many.
def *
if @bombs.zero?
puts "Rabbit is out of bombs!"
return
end
@bombs -= 1
self.weapon = 'bomb'
self.weapon_force = 1600
fight
end
# Eating magick lettuce casts a spell that improves health.
def %
gain = 5 + rand(magick)
puts "Eating magick lettuce adds #{gain} to Rabbit life force."
@life += gain
end
end
QUOTE = '"'
# The quest.
if $0 == __FILE__
hero = Rabbit.new
MONSTERS.each do |a_foe|
hero.foe = a_foe
a_foe.foe = hero
cry = a_foe.challenge % hero
puts %Q[A #{a_foe} emerges from the gloom and cries out,"#{cry}"]
puts QUOTE + (hero.challenge % a_foe) + QUOTE
# Combat! Hero attacks, foe retaliates. Over and over to the
death,
# but whose death?
while hero.life > 0
if a_foe.life <= 0
# Victory -- hero munches on magic lettuce.
hero.%
break
end
# Strike foe with magick sword.
hero./
# Throw bomb if foe still major treat.
hero.* if a_foe.life > 250
end
break if hero.life <= 0
end
puts "It's over. It's all over."
end
</code>
Regards, Morton