[QUIZ] Animal Quiz (#15)

R

Ruby Quiz

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 ]
 
G

Glenn Parker

--------------060403060801050107090001
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

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!

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

--------------060403060801050107090001
Content-Type: text/plain;
name="animal.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="animal.rb"

#!/usr/bin/env ruby -w

STDOUT.sync = true

# $qtree is the root of a tree where non-leaf nodes are 3-element arrays.
# The first element of a non-leaf node is a question. The second
# and third elements are nodes. The second element corresponds
# to a "yes" answer for the question, and the third element corresponds
# to a "no" answer for the question. A leaf node is a 1-element array
# that contains an animal name. The initial node for $qtree is a leaf
# node for the animal "human".

$qtree = [ "human" ]

# $current_node, $parent_node, and $parent_branch maintain our current
# position while navigating down the tree. Except when $parent_node
# is nil, $parent_node[$parent_branch] == $current_node.

$current_node = $parent_node = $parent_branch = nil

def main
$log = File.open("animal-log.txt", "a+")
# Replay all previous sessions from the logfile to initialize $qtree.
read_from($log) until $log.eof
# Play interactively.
read_from(STDIN)
$log.close
end

def read_from(i)
$istream = i
$replay = ($istream != STDIN)
loop do
prompt "Would you like to play a game? "
if get_answer.downcase[0] == ?y
prompt "Please think of an animal...\n\n"
play
else
prompt "Good bye.\n"
break
end
end
end

# Print a prompt unless we are in replay mode.
def prompt(str)
print str unless $replay
end

# Get an answer and log it unless we are in replay mode.
def get_answer
input = $istream.gets.chomp
$log.puts(input) unless $replay
input
end

# Play a round of the game
def play
# Reset pointers to top of $qtree.
$parent_node = $parent_branch = nil
$current_node = $qtree
# Keep guessing until we're done.
while guess; end
end

def guess
question = $current_node.length == 1 ?
"Is your animal \"" + $current_node[0] + "\"? " :
$current_node[0]
prompt question
answer = get_answer.downcase[0]

if $current_node.length == 1
if answer == ?y
prompt "I win!\n\n"
else
learn
end
return false
else
$parent_node = $current_node
$parent_branch = (answer == ?y) ? 1 : 2
$current_node = $parent_node[$parent_branch]
return true
end
end

def learn
last_animal = $current_node[0]
prompt "I give up. What is your animal? "
animal = get_answer
prompt "What question distinguishes \"#{last_animal}\" from \"#{animal}\"?\n"
question = get_answer
# Adjust the punctuation at the end of the question.
question.sub!(/\??\s*$/, '? ')
prompt "What is the answer to this question for \"#{animal}\"? "
yes = (get_answer.downcase[0] == ?y)
prompt "Thank you.\n\n"

# Build a new node refering to $current_node,
# then insert it into the location of $current_node.
node = yes ?
[ question, [animal], $current_node ] :
[ question, $current_node, [animal] ]
if $parent_node == nil
$qtree = node
else
$parent_node[$parent_branch] = node
end
end

main

--------------060403060801050107090001--
 
M

Markus Koenig

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

James Edward Gray II

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) }
 
K

Kero

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

Lee Marlow

------=_NextPart_000_01B5_01C4FC09.5579B000
Content-Type: text/plain;
charset="US-ASCII"
Content-Transfer-Encoding: 7bit

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

------=_NextPart_000_01B5_01C4FC09.5579B000
Content-Type: application/octet-stream;
name="quiz15.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="quiz15.rb"

$stdout.sync = true

class Question
attr_accessor :parent
attr_reader :q
attr_accessor :yes
attr_accessor :no
attr_reader :answer

def initialize(question, answer = nil)
@q = question
@answer = answer
end
end

class Quiz
def initialize(quiz_type, first_guess)
@type = quiz_type
@root = Question.new("Is it #{first_guess}?", first_guess)
end

def a_an(word)
/^[^aeiou]/i =~ word ? "a" : "an"
end

def ask(prompt)
puts prompt
gets.chomp
end

def ask?(prompt)
/^y/i =~ ask("#{prompt} (y or n)")
end

def add_question(failed_question)
new_ans = ask("What #{@type} were you thinking of?")
new_ques = ask("Give me a question to distinguish #{new_ans} from #{failed_question.answer}.")
differentiator = Question.new(new_ques)
if ask?("For #{new_ans}, what is the answer to your question?")
differentiator.yes = Question.new("Is it #{new_ans}?", new_ans)
differentiator.no = failed_question
else
differentiator.no = Question.new("Is it #{new_ans}?", new_ans)
differentiator.yes = failed_question
end
parent_question = failed_question.parent
if parent_question
differentiator.parent = parent_question
if parent_question.yes == failed_question
parent_question.yes = differentiator
else
parent_question.no = differentiator
end
else
@root = differentiator
end
differentiator.yes.parent = differentiator.no.parent = differentiator
puts "Thanks"
end

def play
playing = true
while playing
puts "Think of #{a_an(@type)} #{@type}..."
question = @root
win = false
continue = true
while continue
if ask?(question.q)
if question.answer
win = true
continue = false
else
question = question.yes
end
else
if question.no
question = question.no
else
continue = false
end
end
end
if win
puts "I win. Pretty smart, ain't I?"
else
puts "You win. Help me learn from my mistake before you go..."
add_question(question)
end
playing = ask?("Play again?")
end
end
end

quiz = Quiz.new("animal", "an elephant")
#quiz = Quiz.new("food", "Is it an apple?")
quiz.play

------=_NextPart_000_01B5_01C4FC09.5579B000--
 
D

Dick Davies

* Ruby Quiz <[email protected]> [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 :)

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<-----------------------------------
 
J

James Edward Gray II

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 :)

That's priceless! :D

James Edward Gray II
 
J

Jim Weirich

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.
 
J

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. ...

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 -----------------------------------------------------------
 
W

why the lucky stiff

Jim said:
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/>
YAML: <http://go.hobix.com/0.3/hobix-install.yaml>

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

_why
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,228
Members
46,818
Latest member
SapanaCarpetStudio

Latest Threads

Top