From 48522ba27d7fa7ba2a55684126743af66feb1883 Mon Sep 17 00:00:00 2001 From: John Long Date: Sun, 2 Jan 2022 16:26:25 -0800 Subject: [PATCH] Add Kotlin version of Animal Animal fascinated me as a kid. I wrote a version in Modula-2 for a Junior High School project. Didn't realize at the time I was studying binary trees. --- 03_Animal/kotlin/Animal.kt | 119 +++++++++++++++++++++++++++++++++++++ 03_Animal/kotlin/README.md | 3 + 2 files changed, 122 insertions(+) create mode 100644 03_Animal/kotlin/Animal.kt create mode 100644 03_Animal/kotlin/README.md diff --git a/03_Animal/kotlin/Animal.kt b/03_Animal/kotlin/Animal.kt new file mode 100644 index 00000000..bac9af08 --- /dev/null +++ b/03_Animal/kotlin/Animal.kt @@ -0,0 +1,119 @@ +/** + * ANIMAL + * + * + * Converted from BASIC to Kotlin by John Long (@patimen) + * + * Animal is basically a perfect example of a binary tree. Implement it + * as such, with the QuestionNode either having an answer if it is a terminal node + * or a Question + */ + +fun main() { + printIntro() + val rootQuestionNode = + QuestionOrAnswer(question = Question("DOES IT SWIM", QuestionOrAnswer("FISH"), QuestionOrAnswer("BIRD"))) + while (true) { + val choice = ask("ARE YOU THINKING OF AN ANIMAL") + when { + choice == "LIST" -> printKnownAnimals(rootQuestionNode) + choice.startsWith("Q") -> return + choice.startsWith("Y") -> { + // A wrong answer means it's a new animal! + val wrongAnswer = rootQuestionNode.getWrongAnswer() + if (wrongAnswer == null) { + // The computer got the right answer! + println("WHY NOT TRY ANOTHER ANIMAL?") + } else { + // Get a new question to ask next time + wrongAnswer.askForInformationAndSave() + } + } + } + } +} + +// Takes care of asking a question (on the same line) and getting +// an answer or a blank string +fun ask(question: String): String { + print("$question? ") + return readLine()?.uppercase() ?: "" +} + +// Special case for a "yes or no" question, returns true of yes +fun askYesOrNo(question: String): Boolean { + return generateSequence { + print("$question? ") + readLine() + }.firstNotNullOf { yesOrNo(it) } +} + +// If neither Y (true) or N (false), return null, so the above sequence +// will just keep executing until it gets the answer +private fun yesOrNo(string: String): Boolean? = + when (string.uppercase().firstOrNull()) { + 'Y' -> true + 'N' -> false + else -> null + } + +private fun printKnownAnimals(question: QuestionOrAnswer) { + println("\nANIMALS I ALREADY KNOW ARE:") + val animals = question.getAnswers().chunked(4) + animals.forEach { line -> + // The '*' in front of line.toTypedArray() "spreads" the array as a list of parameters instead + System.out.printf("%-15s".repeat(line.size), *line.toTypedArray()) + println() + } +} + +private fun printIntro() { + println(" ANIMAL") + println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println("\n\n") + println("PLAY 'GUESS THE ANIMAL'") + println("\n") + println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.") +} + +class QuestionOrAnswer(private var answer: String? = null, var question: Question? = null) { + fun getAnswers(): List = answer?.let { listOf(it) } ?: question!!.getAnswers() + fun getWrongAnswer(): QuestionOrAnswer? { + if (answer != null) { + // "takeUnless" will return null if the answer is "yes". In this case + // we will return the "wrong answer", aka the terminal answer that was incorrect + return this.takeUnless { askYesOrNo("IS IT A $answer") } + } + return question?.getWrongAnswer() + } + + fun askForInformationAndSave() { + //Failed to get it right and ran out of questions + //Let's ask the user for the new information + val newAnimal = ask("THE ANIMAL YOU WERE THINKING OF WAS A") + val newQuestion = ask("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A \n$newAnimal FROM A $answer\n") + val newAnswer = askYesOrNo("FOR A $newAnimal THE ANSWER WOULD BE") + + val trueAnswer = if (newAnswer) newAnimal else answer + val falseAnswer = if (newAnswer) answer else newAnimal + // Replace our answer with null and set the question with the data we just got + // This makes it a question instead of an answer + this.answer = null + this.question = Question(newQuestion, QuestionOrAnswer(trueAnswer), QuestionOrAnswer(falseAnswer)) + } +} + +class Question( + private val question: String, + private val trueAnswer: QuestionOrAnswer, + private val falseAnswer: QuestionOrAnswer +) { + fun getAnswers(): List = trueAnswer.getAnswers() + falseAnswer.getAnswers() + + fun getWrongAnswer(): QuestionOrAnswer? = + if (askYesOrNo(question)) { + trueAnswer.getWrongAnswer() + } else { + falseAnswer.getWrongAnswer() + } +} diff --git a/03_Animal/kotlin/README.md b/03_Animal/kotlin/README.md new file mode 100644 index 00000000..f43a5b70 --- /dev/null +++ b/03_Animal/kotlin/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Kotlin](https://kotlinlang.org/)