Previous exists logic error
Correct again...

=begin
My second solution.

Most solutions do a tree walk.
Kids will get boring soon,
because it always ask the questions in the same order.
No fun at all...

Here I try to do "ask question in random order".
( ==> Not good to quick find the answer. )

Random select "possible" question.
If we try to count the "weight" of the possible questions,
and select the "heaviest" one, we end up like tree walk order.
Except, if there are equal-heavy, example:
Q1            Q2
/  \   ==>    /  \
Q2   Q2        Q1   Q1

Note: You could change the program to take
the average weigth question instead of
random select.

Since we random ask "possible" questions,
that may help to get more information about
existing knowledge animal, example:
Q1    ==>      Q1
/  \          /    \
Q2   c        Q2     Q3
/  \          /  \   /  \
a    b        a    b  Q2  c
\
d

==> this may happend ask Q2 first, then Q1,
and finally distinct c,d by Q3.

A little explanation about my data structure:
* db_questions: array to store questions. (index 0 no use)
* db_animals: hash; key == animals,
value == array of questions, the absolute value map
to db_questions's index; and positive for 'Yes' answer
and negative for 'No' answer.

=end

require 'yaml'

ANIMALS_FILE = 'animals.yaml'
QUESTIONS_FILE = 'questions.yaml'

# reuse Jim Weirich ConsoleUi class and modified
class ConsoleUi
def ask(prompt)
print prompt + "\n"
answer = gets
answer ? answer.chomp : nil
end

def ask_if(prompt)
answer = ask(prompt + "  (y or n)")
answer =~ /^\s*[Yy]/
end

def say(*msg)
puts msg
end
end

def ui
\$ui ||= ConsoleUi.new
end

def get_possible_questions(animals, asked_questions)
questions = []
animals.each_value do |qs|
qs.each do |q|
q = q.abs;
if !questions.include?(q) &&
!asked_questions.include?(q) &&
!asked_questions.include?(-q)
questions << q
end
end
end
questions
end

def filter_animals(animals, question)
animals.each do |animal, questions|
animals.delete(animal) if questions.include? question
end
end

db_animals = File.exist?(ANIMALS_FILE) ?
YAML.load_file(ANIMALS_FILE) :
{ 'an elephant' => [] }

db_questions = File.exist?(QUESTIONS_FILE) ?
YAML.load_file(QUESTIONS_FILE) :
[ '' ]

loop do
asked_questions = []
animals = db_animals.dup

ui.say "Think of an animal..."

while animals.size > 1
qs = get_possible_questions(animals, asked_questions)
q = qs[rand(qs.size)]
q = -q unless ui.ask_if db_questions[q]
asked_questions << q
filter_animals(animals, -q)
end

animal = animals.keys[0]
if ui.ask_if "Is it #{animal}?"
ui.say "I win!"
# update knowledge, we may have more infomation
# about the animal, since we random asked questions
db_animals[animal] += asked_questions
db_animals[animal].uniq!
else
ui.say "You win. 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 " +
"#{animal} from #{new_animal}."
response = ui.ask_if "For #{new_animal}, " +
"what is the answer to your question?"
ui.say "Thanks."

if db_animals.key?(new_animal)
ui.say "Hey! You are cheating, accroding asked questions," +
"it cannot be #{new_animal}."
# ...
end

q = db_questions.index(question)
if q
if asked_questions.include?(q) || asked_questions.include?(-q)
ui.say "Hey! That question already asked! You try to confuse me."
# ...
end
else
db_questions << question
q = db_questions.size - 1
end
db_animals[animal] << (response ? -q : q)
db_animals[animal].uniq!
db_animals[new_animal] = asked_questions
db_animals[new_animal] << (response ? q : -q)
end

break unless ui.ask_if "Play again?"
ui.say "\n\n"
end

open(ANIMALS_FILE, 'w') { |f| f.puts db_animals.to_yaml }
open(QUESTIONS_FILE, 'w') { |f| f.puts db_questions.to_yaml }
--

David Tran
http://www.doublegifts.com