[SOLUTION] Ducksay (#52)

This was quite fun. I decided not to go into ASCII art creation and
"cheated" by getting some wacky animals from http://www.ascii-art.de,
including several ducks. As noted in the source I developed this on a
Windows machine which does not have fortune (at least without getting
Cygwin, etc.), so I found a CGI script online which has fortune, and I
use open-uri to access it.

I'm sure my "bubble maker" could be golfed down a bit, but I preferred
clarity over terseness.

All the animals in the "zoo" are embedded in the DATA section of the
script, with a special format to delimit them. It is fairly easy to
add new animals should one want to.

I also used a different style of command-line options, since it fit
this problem. Here is an example run:

C:\_Ryan\ruby\quiz\52_ducksay>ruby ducksay.rb gecko.say "Ruby Rocks!"

···

_____________
< Ruby Rocks! >
-------------
         \
          \
              ___
       )/_ ,@ /
       >(_,' _@/
       > /
  \)/ / (_)/
  ((_/ ,----~
   \ (_)/
   / ,-----~
  ((' _,-.
   \\=//

The text after my name will be the code for my solution.

Ryan

require 'open-uri'
require 'singleton'

# Here we have a bubble machine
class Bubble
  def self.make(text)
    return '' if text.nil? or text == ''
    result = ''
    lines = text.split("\n")
    max_length = lines.max{|a,b| a.length <=> b.length}.length
    hf = proc {|s| " #{s * (max_length+2)} \n"}
    result << hf['_']
    case lines.length
    when 1: result << "< #{lines[0]} >\n"
    else
      pad_it = proc {|s| s.ljust(max_length)}
      result << "/ #{pad_it[lines.shift]} \\\n"
      0.upto(lines.length-2) do |i|
        result << "| #{pad_it[lines[i]]} |\n"
      end
      result << "\\ #{pad_it[lines[-1]]} /\n"
    end
    result << hf['-']
  end
end

# Our lovely Zoo, full of many wacky animals
class Zoo
  include Singleton
  attr_reader :animals
  def initialize
    @animals = {}
    current_key = nil
    DATA.each do |line|
      if line.chomp =~ /^'(\w*)':$/
        current_key = $1
        @animals[current_key] = []
      elsif current_key
        @animals[current_key] << line
      end
    end
  end

  # A random animal has escaped!
  def escape
    choices = @animals.keys
    @animals[choices[rand(choices.length)]]
  end
end

# Since I wrote this on Windows I don't have fortune...but the
# internet does!
def get_fortune
  open('http://www.coe.neu.edu/cgi-bin/fortune') do |page|
    page.read.scan(/<pre>(.*)<\/pre>/m)[0][0].gsub("\t",' ')
  end
end

if $0 == __FILE__
  animal = nil
  message = nil
  case ARGV[0]
  when 'list':
    puts "Your zoo contains the following animals:"
    puts Zoo.instance.animals.keys.sort.join("\n")
    exit(0)
  when /\A(\w*)\.say/:
    animal = Zoo.instance.animals[$1]
    unless animal
      puts "Unknown animal #$1! Try 'list' to list your animals."
      exit(1)
    end
    if ARGV.length > 1
      message = ARGV[1..-1].join(" ")
    end
  when nil:
    animal = Zoo.instance.escape
  else
    puts "Usage: #$0 <command>",
    "Where command is",
    "\tlist: to list your animals.",
    "\t<animal>.say \"<message>\": to have that animal say that",
    "\t\tmessage, where the message is optional.",
    "\nWith no command the default is a random animal, with a",
    "message from fortune."
    exit(1)
  end
  unless message
    message = get_fortune
  end
  puts Bubble.make(message)
  puts animal
end

# The ASCII animals below are courtesy of http://www.ascii-art.de
__END__
'crazyduck':
    \ _____
     \ \\_-~~ ~~-_
      \ /~ ~\
       \ _| _ |
        \ ___) ~~) ~~~–_ |
         \ _-~ ~-_ ___ \ |/
          \ / _-~ ~-_ /
           \ | / \ |
            \ | O) | | |
                   > > > >
                    > > (O | |
                     \ | | |\
                     (~-_ _-\ / _–_ / \
                      \__~~~ ~-_ _-~ / ~\
                      / ~—~~-_ ~~~ _-~ /| |
                   _-~ / \ ~~–~ | | |
                _-~ | / |
   ,-~~-_ __–~~ |-~ /
   > \ | _-~
    \ |–~~
     \ | |
      ~-_ _ | |
         ~-_ ~~—__ _–~~\ |
            ~~–__ / |
                  ~~—___ __–~~| |
                          ~~~~~ | |
                                     > >
'lazyduck':
   \
    \ .-"""-. _.—…-;
      :.) ;"" \/
__…–'\ ;-"""-. ;._
`-.___.^.___.'-.____J__/-._J
'duck':
        \
         \ _.-.
         __.-' , \
        '–'-'._ \
                '. \
                 )-- \ __.–._
                / .' '–.
               . _/-._
               : ____._/ _-'
                '._ _.'-'
                   '-._ _.'
                       \_|/
                      __|||
                      >__/'
'chunkybacon':
      \
       \ __ _…_
       .-'__`-._.'.–.'.__.,
      /–' '-._.' '-._./
     /__.–._.–._.'``-.__/
     '._.-'-._.-._.-''-…'
'raccoon':
   \
    \ __ .-.
     \ .-"` .`'. /\\|
       _(\-/)_" , . ,\ /\\\/
      {(#b^d#)} . ./, |/\\\/
      `-.(Y).-` , | , |\.-`
           /~/,_/~~~\,__.-`
          ////~ // ~\\
        ==`==` ==` ==`
'bat':
     \
      \
    , \ ,
  ./( )\.
  ) \/\_/\/ (
  `) (^Y^) (`
   `),-(~)-,(`
       '"'
'buffalo':
    \
     \ _.-````'-,_
      _,.,_ ,-'` `'-.,_
    /) (\ '``-.
   (( ) ) `\
    \) (_/ )\
     > /) ' ,' / \
     `\ ^' ' ( / ))
       > _/\ , / ,`\ ( "`
        \Y, | \ \ | ````| / \_ \
          `)_/ \ \ ) ( > ( >
                   \( \( |/ |/
                  /_(/_( /_( /_(
'gecko':
         \
          \
              ___
       )/_ ,@ /
       >(_,' _@/
       > /
  \)/ / (_)/
  ((_/ ,----~
   \ (_)/
   / ,-----~
  ((' _,-.
   \\=//
'gorilla':
       \
        \
         \ ."`".
        .-./ _=_ \.-.
       { (,(oYo),) }}
       {{ | " |} }
       { { \(—)/ }}
       {{ }'-=-'{ } }
       { { }._:_.{ }}
       {{ } -:- { } }
       {_{ }`===`{ _}
      ((((\) (/))))
'gryphon':
    \ ______
     \ ______,—'__,—'
      \ _,-'—_---__,—'
           /_ (, —____',
          / ', `, ,-'
         ;/) ,',_/,'
         > /\ ,.'//\
         `-` \ ,' `.
              `', ,-- `.
              '/ / | `, _
              //'',.\_ .\\ ,{==>-
           __// __;_`- \ `;.__,;'
         ((,–,) (((,------; `–'
         ``` ' ```
'wombat':
  \ ,.–""""–…_
   \ ." .' `-.
    \ ; ; ;
     \ ' ; )
      \ / ' . ;
         / ; `. `;
       ,.' : . : )
       ;|\' : `./|) \ ;/
       ;| \" -,- "-./ |; ).;
       /\/ \/ );
      : \ ;
      : _ _ ; )
      `. \;\ /;/ ; /
        ! : : ,/ ;
         (`. : _ : ,/"" ;
          \\\`"^" ` : ;
                   ( )
                   ////
'seaturtle':
        /
       / _,.—.---.—.–…_
      / _.-' `–.`—.`—'-. _,`–…_
     / /`–._ .'. `. `,`-.`-._\
          >> \ `.`—.__`__…-`. ,'`-._/
     _ ,`\ `-._\ \ `. `_.-`-._,``-.
  ,` `-_ \/ `-.`–.\ _\_.-'\__.-`-.`-._`.
(_.o> ,–. `._/'–.-`,–` \_.-' \`-._ \
  `—' `._ `—._/__,----` `-. `-\
            /_, , _…-' `-._\
            \_, \/ ._(
             \_, \/ ._\
              `._,\/ ._\
                `._// ./`-._
                  `-._-_-_.-'
'lemur':
    \ ,
     \ ==
      \ ==
       \ ==
        \ ==
                == ==
              == == ==
             == ==
      , , ==
      >\/| ,-…-,
  ,d__(…)\_/ \
  ;-,_`o/ |
      '-| \_,' /^| /
        ( // / \ \
        >> \ < \ )
       _\| \ ) _\\
        ~` _\| ~`
             ~`
'koala':
      \
       \ ) ( |
        \ ) ( / .-
        _ ,—. _ ( / /
      (~-| . . |-~) V /
       \._ 0 _,/ /
        / `-^-'`-._ /
       ' `-. (
      : )E
      : ,—' (
       . )E (
        '._____,—' (
               ) (
               ) (
               ) (
               ) (
'armadillo':
    \
     \ __-----.,
             .-:::,\\\\\::::,
        \|\;`:::`` \\\\\\\'':\
        /`'\\:: |||||| '\
       / e (\` |||||| , '.
     .` _.-`~\_____/`````_X__/'-__~===-
    '-~` ~~ ~~