

# Variation 1: nested classes

class H
 class E
  class L
   class L
    class O_
     class W
      class O
       class R
        class L
         class D
          puts name.tr(':','').downcase.split(/_/).map {|w| w.capitalize}.join(", ")+"!"
         end
        end
       end
      end
     end
    end 
   end
  end
 end
end


# Variation 2: class definitions are expressions!?

puts(
class H
 class E
  class L
   class L
    class O_
     class W
      class O
       class R
        class L
         class D
          self
         end
        end
       end
      end
     end
    end 
   end
  end
 end
end.name.tr(':','').downcase.split(/_/).map {|w| w.capitalize}.join(", ")+"!"
)


# Variation 3: Senic drive through ObjectSpace

x = "Hello, World!"
ObjectSpace.each_object {|o| puts o if o.object_id == x.object_id}


# Variation 4: Ambulatory greeting

module MazeTypes
  M_START = "^"
  M_END = "*"
  M_VISITED = "."
  M_WALL_TYPES = %w(| - + #)
  X, Y = 0, 1
end

class Maze
  include MazeTypes

  def initialize(maze)
    @rows = maze.split(/\n/)
  end
  
  def [](pos)
    x,y = *pos
    return nil if y >= @rows.length
    @rows[y][x..x]
  end

  def []=(pos, type)
    x,y = *pos
    @rows[y][x..x] = type
  end  

  def find_start
    @rows.each_with_index do |row, y|
      if x = row.index(M_START)
        return [x,y]
      end
    end
    raise "start glyph not found"
  end
  
  def occupy(pos)
    type = self[pos]
    self[pos] = M_VISITED
    type
  end

  def valid_move?(pos, reject_visited)
    return false if pos[X] < 0 or pos[Y] < 0
    type = self[pos]
    return false if reject_visited and type == M_VISITED
    !(M_WALL_TYPES.include? type)
  end
end

class Solver
  include MazeTypes

  def initialize(maze)
    @maze = Maze.new(maze)
  end

  def solve
    @pos = @maze.find_start
    while (type = @maze.occupy(@pos)) != M_END
      moves = valid_uncharted_moves
      if moves.empty?
        moves = valid_moves
      end
      raise("stuck at #{@pos.inspect}!") if moves.empty?

      ennunciate(type)      

      @pos = moves[rand(moves.length)]
    end
  end

  def ennunciate(type)
    print type.tr("^A-Za-z,!_","").tr("_"," ")
  end

  def valid_uncharted_moves 
    reject_visited = true
    valid_moves(reject_visited)
  end
    
  def valid_moves(reject_visited=false)
    potential_moves.select {|pos| @maze.valid_move?(pos, reject_visited)}
  end
  
  def potential_moves
    [north(@pos), east(@pos), south(@pos), west(@pos)]
  end

  def north(pos) [pos[X], pos[Y]-1] end
  def south(pos) [pos[X], pos[Y]+1] end
  def east(pos) [pos[X]+1, pos[Y]] end
  def west(pos) [pos[X]-1, pos[Y]] end
end

m = <<MAZE
###+---+---+
^ H|  l|  o|
#|  e|  l| |
#+---+-+-+ |
#| d !*| , |
#| +--++ -+#
#|l   | _ |#
#+--+ +-+ |#
####|r  |W|#
##+-+- -+ |#
##|   o   |#
##+--+----+#
MAZE

# solve the maze and print the treasures found along the way
Solver.new(m).solve
puts


# Variation 5: Gladitorial Arena greeting

def log(msg)
  # we log messages to stderr, so as not to interfere with the main program output
  # $stderr.puts msg
end

class Gladiator
  attr_reader :name, :score
  attr_accessor :health
  
  def initialize(name, health)
    @name, @health = name, health
    @score = 0
  end

  def dead?
    @health < 1
  end
    
  def attack(other)
    hit = rand(3)
    if hit.zero?
      log("#{self.name} swings at #{other.name} and misses!")
    else
      dmg = rand(15) + 1
      other.health -= dmg
      @score += dmg
      log("#{self.name} hits #{other.name} for #{dmg} damage!")
    end
  end
end

class Arena
  attr_reader :gladiators
  
  def initialize
    @gladiators = []
  end
  
  def add(gladiator)
    @gladiators << gladiator
  end
  
  def fight!
    combatents = @gladiators.dup
    while combatents.length > 1
      combatents.each_with_index do |g, i|
        opponents = combatents.reject {|o| o == g}
        o = opponents[rand(opponents.length)]
        g.attack(o)
      end
      combatents = combatents.reject {|g| g.dead?}
    end
  end
end

gladiator_names = %w(ld! Wor o,\  ll He)

arena = Arena.new

gladiator_names.each_with_index {|n,i| arena.add(Gladiator.new(n, 2**((i*3)+6)))}

srand(2)  # force a consistent outcome (seems to work on both 1.8 and 1.9 ruby)
arena.fight!

# print the names of the gladiators, from highest-scoring to lowest
puts arena.gladiators.sort_by {|g| -(g.score)}.map {|g| g.name}.join



