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 + "'}"; + } + } }