[QUIZ] Animal Quiz (#15)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.grayproductions.net/ruby_quiz/

3. Enjoy!

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

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.

It works like this. The program starts by telling the user to think
of an animal. It then begins asking a series of yes/no questions
about that animal: does it swim, does it have hair, etc. Eventually,
it will narrow down the possibilities to a single animal and guess
that (Is it a mouse?).

If the program has guessed correctly, the game is over and may be
restarted with a new animal. If the program has guess incorrectly, it
asks the user for the kind of animal they were thinking of and then
asks for the user to provide a question that can distinguish between
its incorrect guess and the correct answer. It then adds the new
question and animal to its "database" and will guess that animal in
the future (if appropriate).

[ Editor's Note: Here's a sample run of my solution, by way of example:

  Think of an animal...
  Is it an elephant? (y or n)
  n
  You win. Help me learn from my mistake before you go...
  What animal were you thinking of?
  a rabbit
  Give me a question to distinguish a rabbit from an elephant.
  Is it a small animal?
  For a rabbit, what is the answer to your question? (y or n)
  y
  Thanks.
  Play again? (y or n)
  y
  Think of an animal...
  Is it a small animal? (y or n)
  y
  Is it a rabbit? (y or n)
  n
  You win. Help me learn from my mistake before you go...
  What animal were you thinking of?
  a Shih Tzu
  Give me a question to distinguish a Shih Tzu from a rabbit.
  Is it a kind of dog?
  For a Shih Tzu, what is the answer to your question? (y or n)
  y
  Thanks.
  Play again? (y or n)
  y
  Think of an animal...
  Is it a small animal? (y or n)
  y
  Is it a kind of dog? (y or n)
  y
  Is it a Shih Tzu? (y or n)
  y
  I win. Pretty smart, aren't I?
  Play again? (y or n)
  n

-JEG2 ]

Not to toot my own horn, but this was an easy one. I spent more time polishing and commenting than I did on actual coding. Viva la Ruby!

animal.rb (2.85 KB)

···

--
Glenn Parker | glenn.parker-AT-comcast.net | <http://www.tetrafoil.com/>

Hello!

Here is my solution. It holds its whole database in a tree of arrays. A
question node is a three-element array: [question, yes_tree, no_tree].
A leaf node is an array containing a single string.

It saves its data into ~/.animal-quiz using Array#inspect and reads it
using eval (to be simplistic).

It is located at <http://www.stber-koenig.de/ruby-quiz/>, along with
some other solutions for which I hadn't got time to submit.

Have a nice day!

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!"

* Ruby Quiz <james@grayproductions.net> [0123 14:23]:

Thanks, had fun with that. Sounded easy at first read but had
me stumped several times last night.

Got a slightly hacky version, but hopefully shortish version.
Largely based on Exception abuse :slight_smile:

Tricky bit was figuring out the tree updating - knocking together
a quick tree and getting the traversal right helped a lot there.
The updating bit wrote itself after that.

I'm sure there's a more rubyish way of switching the node from
'animal mode' to 'question mode', but it seems to avoid a lot of
book-keeping and linked-list-esque linking and unlinking of references,
so it'll do...

It's fairly short so here you go:

--------------------------------8<-----------------------------------
#!/usr/bin/env ruby

class TreeNode

  attr_accessor :yes, :no, :question, :animal

  def initialize(animal=nil)
    @animal = animal
    @question = @no = @yes = nil
  end

  def walk
    begin
      return (prompt(question) ? yes.walk : no.walk)
    rescue NoMethodError
      # yes, no or question was nil. Make a guess.
      if ( prompt "I think I am a #{animal}. Am I?")
        puts "Yay! Let's start again."
      else
        update_tree
      end
    end
  end

  def update_tree
    puts "OK, I give up. What am i?"
    new_animal = gets.chomp.intern
    puts "Give me a question which is true for #{new_animal} and false for #{animal}"
    new_question = gets.chomp
    # become a decision branch and connect our forks

    @no = TreeNode.new(animal)
    @yes = TreeNode.new(new_animal)
    @animal = nil
    @question = new_question

    puts "Duly noted. Let's try again:"
  end

  def prompt(str)
    # no question to ask, so punt
    raise NoMethodError unless str
    puts "#{str} ( q/Q to quit) :"
    response = gets

    exit if response =~ /q.*/i
    return true if response =~ /y.*/i
    false
  end

end

top = TreeNode.new(:elephant)
loop { top.walk }
--------------------------------8<-----------------------------------

···

--
'Good news, everyone! I've taught the toaster to feel love!'
    -- Prof. Farnsworth
Rasputin :: Jack of All Trades - Master of Nuns

Thought for a while how to express 2 types of
nodes - questions and animals. Decided that
I would have a simple array, with each entry
being another array. If the entry was an "animal"
i.e. end node, it would just have a single
entry [ 'pig' ], if it was a question, it would
have the question, then indexes to the nodes for
'true' or 'false' [ 'does your animal oink', 3, 15]

Both my kids love playing it, although the first time
it asked my 13 y.o. "what is your animal", she said
she didn't want to tell ... it was a secret :slight_smile:

Added sections to save and reload the state to
preserve the acquired 'knowledge'.

V

···

---
#! /usr/bin/env ruby

def getans(q)
  begin
     print "#{q} \?(y/n)"
     linein = gets
  end until linein =~ /^y/i || linein =~ /^n/i
  return linein =~ /^y/i
end
    
qs = []
savndx = 0
savans = false

fname = ARGV.shift if ARGV
if fname
  begin
    ifile = File.open(fname)
    qs = Marshal.load(ifile.read)
  rescue
  end
end

ndx = qs.length > 0 ? 1 : 0
qs[0] = [ 'elephant', nil, nil]

# walk q/a path till getting an animal q
while ndx >= 0
  ndx = (qs.length - 1) if ndx >= qs.length
  if (qs[ndx][1] != nil || qs[ndx][2] != nil) # classification q
    savndx = ndx
    resp = getans(qs[ndx][0])
    savans = resp
    ndx = resp ? qs[ndx][1] : qs[ndx][2]
    next
  else # animal question
    animal = qs[ndx][0]
    qsstr = 'Is your animal a'
    qsstr += (qs[ndx][0] =~ /^[aeiou]/) ? 'n ' : ' '
    qsstr += qs[ndx][0]
    resp = getans(qsstr)
    if resp # got it
       puts "I win"
    else # add question and animal
       print 'What is your animal ?'
       newanimal = gets
       newanimal.chomp!
       print "What questions distinguishes a #{newanimal} from a
#{animal} ? "
       question = gets
       question.chomp!
       ans = getans "For a #{newanimal} how would you answer this"
       na_ndx = qs.length + 1
       if ans
         qs << [question, na_ndx, ndx]
       else
         qs << [question, ndx, na_ndx]
      end
      qs[savndx][savans ? 1 : 2] = qs.length - 1 if savndx > 0
      qs << [ newanimal, nil, nil]
    end
  end
  puts
  ndx = getans('Play again') ? 1 : -1
end

if fname
  ofile = File.open(fname, "w+")
  Marshal.dump(qs, ofile)
end

For a very impressive version of this game, see http://20q.com.

···

On Friday 14 January 2005 09:23 am, Ruby Quiz wrote:

It works like this. The program starts by telling the user to think
of an animal. It then begins asking a series of yes/no questions
about that animal: does it swim, does it have hair, etc. Eventually,
it will narrow down the possibilities to a single animal and guess
that (Is it a mouse?).

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

Here's my solution ...

#-- animals.rb ----------------------------------------------
#!/usr/bin/env ruby

require 'yaml'
require 'ui'

def ui
  $ui ||= ConsoleUi.new
end

class Question
  def initialize(question, yes, no)
    @question = question
    @yes = yes
    @no = no
    @question << "?" unless @question =~ /\?$/
    @question.sub!(/^([a-z])/) { $1.upcase }
  end

  def walk
    if ui.ask_if @question
      @yes = @yes.walk
    else
      @no = @no.walk
    end
    self
  end
end

class Animal
  attr_reader :name
  def initialize(name)
    @name = name
  end

  def walk
    if ui.ask_if "Is it #{an name}?"
      ui.say "Yea! I win!\n\n"
      self
    else
      ui.say "Rats, I lose"
      ui.say "Help me play better next time."
      new_animal = ui.ask "What animal were you thinking of?"
      question = ui.ask "Give me a question " +
  "to distinguish a #{an name} from #{an new_animal}."
      response = ui.ask_if "For #{an new_animal}, the answer to your question
would be?"
      ui.say "Thank you\n\n"
      if response
  Question.new(question, Animal.new(new_animal), self)
      else
  Question.new(question, self, Animal.new(new_animal))
      end
    end
  end

  def an(animal)
    ((animal =~ /^[aeiouy]/) ? "an " : "a ") + animal
  end
end

if File.exist? "animals.yaml"
  current = open("animals.yaml") { |f| YAML.load(f.read) }
else
  current = Animal.new("mouse")
end

loop do
  current = current.walk
  break unless ui.ask_if "Play again?"
  ui.say "\n\n"
end

open("animals.yaml", "w") do |f| f.puts current.to_yaml end
# END --------------------------------------------------------

The above code depends upon a very simple UI module:

#-- ui.rb ---------------------------------------------------------
#!/usr/bin/env ruby

class ConsoleUi
  def ask(prompt)
    print prompt + " "
    answer = gets
    answer ? answer.chomp : nil
  end

  def ask_if(prompt)
    answer = ask(prompt)
    answer =~ /^\s*[Yy]/
  end

  def say(*msg)
    puts msg
  end
end
# END -----------------------------------------------------------

···

On Friday 14 January 2005 09:23 am, Ruby Quiz wrote:

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.

It works like this. The program starts by telling the user to think
of an animal. It then begins asking a series of yes/no questions
about that animal: does it swim, does it have hair, etc. ...

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

My solution.

James Edward Gray II

#!/usr/bin/env ruby

require "yaml"

class AnimalTree
  def initialize( question, yes = nil, no = nil )
    @question = question
    @yes = yes
    @no = no
  end
  
  attr_reader :yes, :no
  
  def question
    if animal?
      "Is it #{@question}? (y or n)"
    else
      "#{@question} (y or n)"
    end
  end
  
  def learn( question, other, yes_or_no )
    if yes_or_no =~ /^\s*y/i
      @yes = AnimalTree.new(other)
      @no = AnimalTree.new(@question)
    else
      @yes = AnimalTree.new(@question)
      @no = AnimalTree.new(other)
    end
    @question = question
  end
  
  def animal?
    @yes.nil? and @no.nil?
  end
  
  def to_s
    @question
  end
end

### Load Animals ###

if test(?e, "animals.yaml")
  animals = File.open("animals.yaml") { |f| YAML.load(f) }
else
  animals = AnimalTree.new("an elephant")
end

### Interface ###

puts "Think of an animal..."
sleep 3
quiz = animals
loop do
  puts quiz.question
  response = $stdin.gets.chomp
  if quiz.animal?
    if response =~ /^\s*y/i
      puts "I win. Pretty smart, aren't I?"
    else
      puts "You win. Help me learn from my mistake before you go..."
      puts "What animal were you thinking of?"
      other = $stdin.gets.chomp
      puts "Give me a question to distinguish #{other} from #{quiz}."
      question = $stdin.gets.chomp
      puts "For #{other}, what is the answer to your question? (y or n)"
      answer = $stdin.gets.chomp
      puts "Thanks."
      quiz.learn(question, other, answer)
    end
    puts "Play again? (y or n)"
    response = $stdin.gets.chomp
    if response =~ /^\s*y/i
      puts "Think of an animal..."
      sleep 3
      quiz = animals
    else
      break
    end
  else
    if response =~ /^\s*y/i
      quiz = quiz.yes
    else
      quiz = quiz.no
    end
  end
end

### Save Animals ###

File.open("animals.yaml", "w") { |f| YAML.dump(animals, f) }

That's priceless! :smiley:

James Edward Gray II

···

On Jan 17, 2005, at 7:44 PM, Vance A Heron wrote:

Both my kids love playing it, although the first time
it asked my 13 y.o. "what is your animal", she said
she didn't want to tell ... it was a secret :slight_smile:

Jim Weirich wrote:

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.

It works like this. The program starts by telling the user to think
of an animal. It then begins asking a series of yes/no questions
about that animal: does it swim, does it have hair, etc. ...
   
Here's my solution ...

#-- animals.rb ----------------------------------------------
#!/usr/bin/env ruby

require 'yaml'
require 'ui'

These solutions are reminding me a lot of the install script that's used with Hobix. The script basically reads a YAML document that describes the installation flow and contains the Hobix distribution.

Script: <http://go.hobix.com/0.3/&gt;
YAML: <http://go.hobix.com/0.3/hobix-install.yaml&gt;

It's such a great quiz, though, because we could use the exercise of improving our app's interaction with users.

_why

···

On Friday 14 January 2005 09:23 am, Ruby Quiz wrote:

I really like how that one lists all the questions and their answers as it works.

James Edward Gray II

···

On Jan 17, 2005, at 11:19 PM, Jim Weirich wrote:

On Friday 14 January 2005 09:23 am, Ruby Quiz wrote:

It works like this. The program starts by telling the user to think
of an animal. It then begins asking a series of yes/no questions
about that animal: does it swim, does it have hair, etc. Eventually,
it will narrow down the possibilities to a single animal and guess
that (Is it a mouse?).

For a very impressive version of this game, see http://20q.com.

Here's my attempt. It's longer than I would have liked, too many if/else's, but I'm still catching on to Ruby.

-Lee

quiz15.rb (2.32 KB)