Use a tree structure instead of a list

(Fix for issue #369)
This commit is contained in:
Aldrin Misquitta
2022-01-16 14:21:17 +00:00
committed by GitHub
parent a131b1d864
commit f1c35db0f7

View File

@@ -2,159 +2,244 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Scanner; import java.util.Scanner;
import java.util.stream.Collectors;
/** /**
* ANIMAL * ANIMAL
* <p> * <p>
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm) * 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 class Animal {
public static void main(String[] args) { public static void main(String[] args) {
printIntro(); printIntro();
Scanner scan = new Scanner(System.in); Scanner scan = new Scanner(System.in);
List<Question> questions = new ArrayList<>(); Node root = new QuestionNode("DOES IT SWIM",
questions.add(new Question("DOES IT SWIM", "FISH", "BIRD")); new AnimalNode("FISH"), new AnimalNode("BIRD"));
boolean stopGame = false; boolean stopGame = false;
while (!stopGame) { while (!stopGame) {
String choice = readMainChoice(scan); String choice = readMainChoice(scan);
switch (choice) { switch (choice) {
case "LIST": case "TREE":
printKnownAnimals(questions); printTree(root);
break; break;
case "Q": case "LIST":
case "QUIT": printKnownAnimals(root);
stopGame = true; break;
break; case "Q":
default: case "QUIT":
if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) { stopGame = true;
int k = 0; break;
boolean correctGuess = false; default:
while (questions.size() > k && !correctGuess) { if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
Question question = questions.get(k); Node current = root; //where we are in the question tree
correctGuess = askQuestion(question, scan); Node previous; //keep track of parent of current in order to place new questions later on.
if (correctGuess) {
System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
} else {
k++;
}
}
if (!correctGuess) { while (current instanceof QuestionNode) {
askForInformationAndSave(scan, questions); 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<Question> questions) { /**
//Failed to get it right and ran out of questions * Prompt for information about the animal we got wrong
//Let's ask the user for the new information * @param current The animal that we guessed wrong
System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A "); * @param previous The root of current
String animal = scan.nextLine(); * @param previousToCurrentDecisionChoice Whether it was a Y or N answer that got us here. true = Y, false = N
System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, questions.get( */
questions.size() - 1).falseAnswer); private static void askForInformationAndSave(Scanner scan, AnimalNode current, QuestionNode previous, boolean previousToCurrentDecisionChoice) {
String newQuestion = scan.nextLine(); //Failed to get it right and ran out of questions
System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal); //Let's ask the user for the new information
boolean newAnswer = readYesOrNo(scan); System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ");
//Add it to our list String animal = scan.nextLine();
addNewAnimal(questions, animal, newQuestion, newAnswer); 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<Question> questions, String animal, String newQuestion, boolean newAnswer) { private static void addNewAnimal(Node current,
Question lastQuestion = questions.get(questions.size() - 1); QuestionNode previous,
String lastAnimal = lastQuestion.falseAnswer; String animal,
lastQuestion.falseAnswer = null; //remove the false option to indicate that there is a next question 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 (previous != null) {
if (newAnswer) { if (previousToCurrentDecisionChoice) {
newOption = new Question(newQuestion, animal, lastAnimal); previous.setTrueAnswer(questionNode);
} else { } else {
newOption = new Question(newQuestion, lastAnimal, animal); previous.setFalseAnswer(questionNode);
} }
questions.add(newOption); }
} }
private static boolean askQuestion(Question question, Scanner scanner) { private static boolean askQuestionAndGetReply(QuestionNode questionNode, Scanner scanner) {
System.out.printf("%s ? ", question.question); System.out.printf("%s ? ", questionNode.question);
return readYesOrNo(scanner);
}
boolean chosenAnswer = readYesOrNo(scanner); private static boolean readYesOrNo(Scanner scanner) {
if (chosenAnswer) { boolean validAnswer = false;
if (question.trueAnswer != null) { Boolean choseAnswer = null;
System.out.printf("IS IT A %s ? ", question.trueAnswer); while (!validAnswer) {
return readYesOrNo(scanner); String answer = scanner.nextLine();
} if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
//else go to the next question validAnswer = true;
} else { choseAnswer = true;
if (question.falseAnswer != null) { } else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
System.out.printf("IS IT A %s ? ", question.falseAnswer); validAnswer = true;
return readYesOrNo(scanner); choseAnswer = false;
} }
//else go to the next question }
} return choseAnswer;
return false; }
}
private static boolean readYesOrNo(Scanner scanner) { private static void printKnownAnimals(Node root) {
boolean validAnswer = false; System.out.println("\nANIMALS I ALREADY KNOW ARE:");
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(List<Question> questions) { List<AnimalNode> leafNodes = collectLeafNodes(root);
System.out.println("\nANIMALS I ALREADY KNOW ARE:"); String allAnimalsString = leafNodes.stream().map(AnimalNode::getAnimal).collect(Collectors.joining("\t\t"));
List<String> 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));
}
private static String readMainChoice(Scanner scan) { System.out.println(allAnimalsString);
System.out.print("ARE YOU THINKING OF AN ANIMAL ? "); }
return scan.nextLine();
}
private static void printIntro() { //Traverse the tree and collect all the leaf nodes, which basically have all the animals.
System.out.println(" ANIMAL"); private static List<AnimalNode> collectLeafNodes(Node root) {
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); List<AnimalNode> collectedNodes = new ArrayList<>();
System.out.println("\n\n"); if (root instanceof AnimalNode) {
System.out.println("PLAY 'GUESS THE ANIMAL'"); collectedNodes.add((AnimalNode) root);
System.out.println("\n"); } else {
System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT."); 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; * Base interface for all nodes in our question tree
String trueAnswer; */
String falseAnswer; private interface Node {
}
public Question(String question, String trueAnswer, String falseAnswer) { private static class QuestionNode implements Node {
this.question = question; private final String question;
this.trueAnswer = trueAnswer; private Node trueAnswer;
this.falseAnswer = falseAnswer; 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 + "'}";
}
}
} }