diff --git a/03_Animal/java/Animal.java b/03_Animal/java/Animal.java
index 11e9e889..681425c1 100644
--- a/03_Animal/java/Animal.java
+++ b/03_Animal/java/Animal.java
@@ -2,159 +2,244 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
+import java.util.stream.Collectors;
/**
* ANIMAL
*
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
+ * The original BASIC program uses an array to maintain the questions and answers and to decide which question to
+ * ask next. Updated this Java implementation to use a tree instead of the earlier faulty one based on a list (thanks @patimen).
*/
public class Animal {
- public static void main(String[] args) {
- printIntro();
- Scanner scan = new Scanner(System.in);
+ public static void main(String[] args) {
+ printIntro();
+ Scanner scan = new Scanner(System.in);
- List questions = new ArrayList<>();
- questions.add(new Question("DOES IT SWIM", "FISH", "BIRD"));
+ Node root = new QuestionNode("DOES IT SWIM",
+ new AnimalNode("FISH"), new AnimalNode("BIRD"));
- boolean stopGame = false;
- while (!stopGame) {
- String choice = readMainChoice(scan);
- switch (choice) {
- case "LIST":
- printKnownAnimals(questions);
- break;
- case "Q":
- case "QUIT":
- stopGame = true;
- break;
- default:
- if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
- int k = 0;
- boolean correctGuess = false;
- while (questions.size() > k && !correctGuess) {
- Question question = questions.get(k);
- correctGuess = askQuestion(question, scan);
- if (correctGuess) {
- System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
- } else {
- k++;
- }
- }
+ boolean stopGame = false;
+ while (!stopGame) {
+ String choice = readMainChoice(scan);
+ switch (choice) {
+ case "TREE":
+ printTree(root);
+ break;
+ case "LIST":
+ printKnownAnimals(root);
+ break;
+ case "Q":
+ case "QUIT":
+ stopGame = true;
+ break;
+ default:
+ if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
+ Node current = root; //where we are in the question tree
+ Node previous; //keep track of parent of current in order to place new questions later on.
- if (!correctGuess) {
- askForInformationAndSave(scan, questions);
- }
- }
- }
- }
+ while (current instanceof QuestionNode) {
+ var currentQuestion = (QuestionNode) current;
+ var reply = askQuestionAndGetReply(currentQuestion, scan);
- }
+ previous = current;
+ current = reply ? currentQuestion.getTrueAnswer() : currentQuestion.getFalseAnswer();
+ if (current instanceof AnimalNode) {
+ //We have reached a animal node, so offer it as the guess
+ var currentAnimal = (AnimalNode) current;
+ System.out.printf("IS IT A %s ? ", currentAnimal.getAnimal());
+ var animalGuessResponse = readYesOrNo(scan);
+ if (animalGuessResponse) {
+ //we guessed right! end this round
+ System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
+ } else {
+ //we guessed wrong :(, ask for feedback
+ //cast previous to QuestionNode since we know at this point that it is not a leaf node
+ askForInformationAndSave(scan, currentAnimal, (QuestionNode) previous, reply);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
- private static void askForInformationAndSave(Scanner scan, List questions) {
- //Failed to get it right and ran out of questions
- //Let's ask the user for the new information
- System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ");
- String animal = scan.nextLine();
- System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, questions.get(
- questions.size() - 1).falseAnswer);
- String newQuestion = scan.nextLine();
- System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal);
- boolean newAnswer = readYesOrNo(scan);
- //Add it to our list
- addNewAnimal(questions, animal, newQuestion, newAnswer);
- }
+ /**
+ * Prompt for information about the animal we got wrong
+ * @param current The animal that we guessed wrong
+ * @param previous The root of current
+ * @param previousToCurrentDecisionChoice Whether it was a Y or N answer that got us here. true = Y, false = N
+ */
+ private static void askForInformationAndSave(Scanner scan, AnimalNode current, QuestionNode previous, boolean previousToCurrentDecisionChoice) {
+ //Failed to get it right and ran out of questions
+ //Let's ask the user for the new information
+ System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ");
+ String animal = scan.nextLine();
+ System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, current.getAnimal());
+ String newQuestion = scan.nextLine();
+ System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal);
+ boolean newAnswer = readYesOrNo(scan);
+ //Add it to our question store
+ addNewAnimal(current, previous, animal, newQuestion, newAnswer, previousToCurrentDecisionChoice);
+ }
- private static void addNewAnimal(List questions, String animal, String newQuestion, boolean newAnswer) {
- Question lastQuestion = questions.get(questions.size() - 1);
- String lastAnimal = lastQuestion.falseAnswer;
- lastQuestion.falseAnswer = null; //remove the false option to indicate that there is a next question
+ private static void addNewAnimal(Node current,
+ QuestionNode previous,
+ String animal,
+ String newQuestion,
+ boolean newAnswer,
+ boolean previousToCurrentDecisionChoice) {
+ var animalNode = new AnimalNode(animal);
+ var questionNode = new QuestionNode(newQuestion,
+ newAnswer ? animalNode : current,
+ !newAnswer ? animalNode : current);
- Question newOption;
- if (newAnswer) {
- newOption = new Question(newQuestion, animal, lastAnimal);
- } else {
- newOption = new Question(newQuestion, lastAnimal, animal);
- }
- questions.add(newOption);
- }
+ if (previous != null) {
+ if (previousToCurrentDecisionChoice) {
+ previous.setTrueAnswer(questionNode);
+ } else {
+ previous.setFalseAnswer(questionNode);
+ }
+ }
+ }
- private static boolean askQuestion(Question question, Scanner scanner) {
- System.out.printf("%s ? ", question.question);
+ private static boolean askQuestionAndGetReply(QuestionNode questionNode, Scanner scanner) {
+ System.out.printf("%s ? ", questionNode.question);
+ return readYesOrNo(scanner);
+ }
- boolean chosenAnswer = readYesOrNo(scanner);
- if (chosenAnswer) {
- if (question.trueAnswer != null) {
- System.out.printf("IS IT A %s ? ", question.trueAnswer);
- return readYesOrNo(scanner);
- }
- //else go to the next question
- } else {
- if (question.falseAnswer != null) {
- System.out.printf("IS IT A %s ? ", question.falseAnswer);
- return readYesOrNo(scanner);
- }
- //else go to the next question
- }
- return false;
- }
+ private static boolean readYesOrNo(Scanner scanner) {
+ boolean validAnswer = false;
+ Boolean choseAnswer = null;
+ while (!validAnswer) {
+ String answer = scanner.nextLine();
+ if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
+ validAnswer = true;
+ choseAnswer = true;
+ } else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
+ validAnswer = true;
+ choseAnswer = false;
+ }
+ }
+ return choseAnswer;
+ }
- private static boolean readYesOrNo(Scanner scanner) {
- boolean validAnswer = false;
- Boolean choseAnswer = null;
- while (!validAnswer) {
- String answer = scanner.nextLine();
- if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
- validAnswer = true;
- choseAnswer = true;
- } else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
- validAnswer = true;
- choseAnswer = false;
- }
- }
- return choseAnswer;
- }
+ private static void printKnownAnimals(Node root) {
+ System.out.println("\nANIMALS I ALREADY KNOW ARE:");
- private static void printKnownAnimals(List questions) {
- System.out.println("\nANIMALS I ALREADY KNOW ARE:");
- List animals = new ArrayList<>();
- questions.forEach(q -> {
- if (q.trueAnswer != null) {
- animals.add(q.trueAnswer);
- }
- if (q.falseAnswer != null) {
- animals.add(q.falseAnswer);
- }
- });
- System.out.println(String.join("\t\t", animals));
- }
+ List leafNodes = collectLeafNodes(root);
+ String allAnimalsString = leafNodes.stream().map(AnimalNode::getAnimal).collect(Collectors.joining("\t\t"));
- private static String readMainChoice(Scanner scan) {
- System.out.print("ARE YOU THINKING OF AN ANIMAL ? ");
- return scan.nextLine();
- }
+ System.out.println(allAnimalsString);
+ }
- private static void printIntro() {
- System.out.println(" ANIMAL");
- System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
- System.out.println("\n\n");
- System.out.println("PLAY 'GUESS THE ANIMAL'");
- System.out.println("\n");
- System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
- }
+ //Traverse the tree and collect all the leaf nodes, which basically have all the animals.
+ private static List collectLeafNodes(Node root) {
+ List collectedNodes = new ArrayList<>();
+ if (root instanceof AnimalNode) {
+ collectedNodes.add((AnimalNode) root);
+ } else {
+ var q = (QuestionNode) root;
+ collectedNodes.addAll(collectLeafNodes(q.getTrueAnswer()));
+ collectedNodes.addAll(collectLeafNodes(q.getFalseAnswer()));
+ }
+ return collectedNodes;
+ }
+
+ private static String readMainChoice(Scanner scan) {
+ System.out.print("ARE YOU THINKING OF AN ANIMAL ? ");
+ return scan.nextLine();
+ }
+
+ private static void printIntro() {
+ System.out.println(" ANIMAL");
+ System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ System.out.println("\n\n");
+ System.out.println("PLAY 'GUESS THE ANIMAL'");
+ System.out.println("\n");
+ System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
+ }
+
+ //Based on https://stackoverflow.com/a/8948691/74057
+ private static void printTree(Node root) {
+ StringBuilder buffer = new StringBuilder(50);
+ print(root, buffer, "", "");
+ System.out.println(buffer);
+ }
+
+ private static void print(Node root, StringBuilder buffer, String prefix, String childrenPrefix) {
+ buffer.append(prefix);
+ buffer.append(root.toString());
+ buffer.append('\n');
+
+ if (root instanceof QuestionNode) {
+ var questionNode = (QuestionNode) root;
+ print(questionNode.getTrueAnswer(), buffer, childrenPrefix + "├─Y─ ", childrenPrefix + "│ ");
+ print(questionNode.getFalseAnswer(), buffer, childrenPrefix + "└─N─ ", childrenPrefix + " ");
+ }
+ }
- public static class Question {
- String question;
- String trueAnswer;
- String falseAnswer;
+ /**
+ * Base interface for all nodes in our question tree
+ */
+ private interface Node {
+ }
- public Question(String question, String trueAnswer, String falseAnswer) {
- this.question = question;
- this.trueAnswer = trueAnswer;
- this.falseAnswer = falseAnswer;
- }
- }
+ private static class QuestionNode implements Node {
+ private final String question;
+ private Node trueAnswer;
+ private Node falseAnswer;
+
+ public QuestionNode(String question, Node trueAnswer, Node falseAnswer) {
+ this.question = question;
+ this.trueAnswer = trueAnswer;
+ this.falseAnswer = falseAnswer;
+ }
+
+ public String getQuestion() {
+ return question;
+ }
+
+ public Node getTrueAnswer() {
+ return trueAnswer;
+ }
+
+ public void setTrueAnswer(Node trueAnswer) {
+ this.trueAnswer = trueAnswer;
+ }
+
+ public Node getFalseAnswer() {
+ return falseAnswer;
+ }
+
+ public void setFalseAnswer(Node falseAnswer) {
+ this.falseAnswer = falseAnswer;
+ }
+
+ @Override
+ public String toString() {
+ return "Question{'" + question + "'}";
+ }
+ }
+
+ private static class AnimalNode implements Node {
+ private final String animal;
+
+ public AnimalNode(String animal) {
+ this.animal = animal;
+ }
+
+ public String getAnimal() {
+ return animal;
+ }
+
+ @Override
+ public String toString() {
+ return "Animal{'" + animal + "'}";
+ }
+ }
}