(* Guess the animal. * This program asks the user to think of an animal. * By using yes/no questions, the program makes an educated guess. * The yes/no questions are stored in a binary decision tree and the * leaves contain the educated guesses. * If the program guesses wrong, it asks the user to provide a yes/no question * that can discriminate between the wrong guess and the right answer. * *) -- Helper procedures for asking questions. -- Because COOL lacks free-standing procedures, -- they'll have to be a member of a class. class IOHelper { yesNoQuestion(io : IO, question : String) : Bool { { io.out_string(question); io.out_string(" "); let answer : String <- io.in_string() in if answer = "y" then true else if answer = "yes" then true else if answer = "n" then false else if answer = "no" then false else { io.out_string("Sorry, I don't understand. Please type in yes or no.\n"); yesNoQuestion(io,question); } fi fi fi fi; } }; stringQuestion(io : IO, question : String) : String { { io.out_string(question); io.out_string(" "); io.in_string(); } }; }; class Node { -- interacts with the user, and returns either self or a new node which -- should replace this node in the binary decision tree. -- A node should never update itself. It should instead return a new version -- of itself. interactAndLearn(terminal : IO) : Node { { abort(); self; } }; }; class YesNoBranch inherits Node { myQuestion : String; myYesBranch : Node; myNoBranch : Node; init(question : String, yesBranch : Node, noBranch : Node) : SELF_TYPE { { myQuestion <- question; myYesBranch <- yesBranch; myNoBranch <- noBranch; self; } }; interactAndLearn(terminal : IO) : Node { let answer : Bool <- (new IOHelper).yesNoQuestion(terminal,myQuestion) in if answer = true then -- remember, we should return a new modified copy of ourselves -- rather than modify ourselves. (new YesNoBranch).init(myQuestion, myYesBranch.interactAndLearn(terminal), myNoBranch) else (new YesNoBranch).init(myQuestion, myYesBranch, myNoBranch.interactAndLearn(terminal)) fi }; }; class Guess inherits Node { myGuess : String; init(guess : String) : SELF_TYPE { { myGuess <- guess; self; } }; interactAndLearn(terminal : IO) : Node { let guessedCorrect : Bool <- (new IOHelper).yesNoQuestion(terminal, "I think I know what it is! It is a " .concat(myGuess) .concat("! Am I correct?")) in if guessedCorrect then { terminal.out_string("Yay! I'm so smart!\n"); self; } else -- Okay, here's an opportunity to learn something new! let correctAnswer : String <- (new IOHelper).stringQuestion(terminal, "Okay, I lost. What was the name of the animal you were thinking of?"), -- If we get a yes/no question that can discriminate between -- the two animals, we can replace our wrong guess -- with a YesNoBranch leading to the correct answer. discriminatingQuestion : String <- (new IOHelper).stringQuestion( terminal, "I see. Please type a yes/no question that can discriminate between a " .concat(myGuess) .concat(" and a ") .concat(correctAnswer) .concat(":")), discriminatingAnswer : Bool <- (new IOHelper).yesNoQuestion( terminal, "And what is the answer for a " .concat(correctAnswer) .concat("?")), dummy : Object <- terminal.out_string("Thanks! I'll make a mental note of that.\n") in if discriminatingAnswer = true then (new YesNoBranch).init(discriminatingQuestion, (new Guess).init(correctAnswer), self) else (new YesNoBranch).init(discriminatingQuestion, self, (new Guess).init(correctAnswer)) fi fi }; }; class Main inherits IO { main() : Object { let -- This is the initial decision tree before -- any learning has taken place. It is non-trivial -- to make it a bit more bearable/fun to the user. decisionTree : Node <- (new YesNoBranch).init( "Does it live in water?", (new YesNoBranch).init( "Does it have fins?", (new Guess).init("fish"), (new Guess).init("jellyfish")), (new YesNoBranch).init( "Does it have four legs?", (new Guess).init("dog"), (new Guess).init("human"))), oneMoreRound : Bool <- true in { out_string("Welcome to Guess the Animal!\n"); while oneMoreRound loop { out_string("Please think of an animal. When you are done I will ask you a few yes/no questions and then we'll see if I can make a correct guess! Hit Enter when you are ready!\n"); in_string(); decisionTree <- decisionTree.interactAndLearn(self); oneMoreRound <- (new IOHelper).yesNoQuestion(self,"Ready for another round?"); } pool; out_string("Okay. See you another time then!\n"); self; } }; };