> by Jim Weirich
> 
> Here's a program I've had a lot of fun with and might make a good Ruby
> Quiz entry.  The program is a animal quiz program.

The first thing I thought was "Knowledge Representation" (expert systems
and the like). But then, for new animals you will have unanswered older
questions, and for old animals you will most definitely not have answers
to new questions.

So you could ask answers to the player for those as well, but that's
getting boring rather soon. If you had the info, you could start with the
question that best splits the collection of animals in half; rinse and
repeat. Hopefully, you wouldn't have to ask all questions, then.

But I don't know how to handle unknown answers for this. Anyone?

Put my solution on my webpages (very primitive for now).
   http://chmeee.dyndns.org/~kero/ruby/quiz/index.html
Solution attached at the bottom if you can't read it from there.
Funny stuff is in the querying; I know Animal#to_s is rather
incomplete. Pointers welcome.

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

Animal = Struct.new(:name, :answers)
TreeNode = Struct.new(:question, :yes, :no)  # left/right has no meaning
tree = Animal.new("cat", {})

class Animal
  def to_s()
    use_an = ["a", "e", "i", "o"].include? name[0,1]
    "#{use_an ? "an" : "a"} #{name}"
  end
end

def query(str)
  STDOUT.write "#{str}? "; STDOUT.flush
  gets
end

def boolean_query(str)
  begin
    STDOUT.write "#{str}?  (y/n) "; STDOUT.flush
    case gets
    when /^y/i; true
    when /^n/i; false
    else raise "ugh"  # an exception feels over the top...
    end
  rescue
    puts "please answer with 'y' or 'n'."
    retry  # ...but the keyword "retry" feels very appropriate.
  end
end

loop {
  puts "You think of an animal..."
  prev, branch = nil, tree
  answers = {}
  while branch.kind_of? TreeNode
    ans = boolean_query branch.question
    answers[branch.question] = ans
    prev = branch
    branch = ans ? branch.yes : branch.no
  end
  if boolean_query "Is it #{branch}"
    puts "I win! Ain't I smart? :P"
  else
    puts "I give up. You win!"
    target = query "What animal were you thinking of"
    target = Animal.new(target.chomp, answers)
    puts "I want to learn from my mistake. Please give me"
    question = query "a question that distinguishes #{target} from #{branch}"
    question.chomp!
    question.capitalize!
    question.slice!(-1)  if question[-1,1] == "?"
    answer = boolean_query "What is the answer to '#{question}?' for #{target}"
    target.answers[question] = answer
    pair = (answer ? [target, branch] : [branch, target])
    new_node = TreeNode.new(question, *pair)
    if prev
      if prev.yes == branch
	prev.yes = new_node
      else
	prev.no = new_node
      end
    else
      tree = new_node
    end
  end

  ans = boolean_query "Do you want to play again"
  break  if not ans
}

puts "Thanks for playing!"