From 13d9435012fff44fd460515844ab73d505ce869a Mon Sep 17 00:00:00 2001 From: Anton Kaiukov Date: Sun, 8 May 2022 12:29:35 -0700 Subject: [PATCH] 03_Animal in Rust --- 03_Animal/rust/Cargo.toml | 8 + 03_Animal/rust/README.md | 3 + 03_Animal/rust/src/main.rs | 291 +++++++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 03_Animal/rust/Cargo.toml create mode 100644 03_Animal/rust/README.md create mode 100644 03_Animal/rust/src/main.rs diff --git a/03_Animal/rust/Cargo.toml b/03_Animal/rust/Cargo.toml new file mode 100644 index 00000000..bf553e32 --- /dev/null +++ b/03_Animal/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "animal" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/03_Animal/rust/README.md b/03_Animal/rust/README.md new file mode 100644 index 00000000..8b8ed57e --- /dev/null +++ b/03_Animal/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/) by [Anton Kaiukov](https://github.com/batk0) diff --git a/03_Animal/rust/src/main.rs b/03_Animal/rust/src/main.rs new file mode 100644 index 00000000..a48c3268 --- /dev/null +++ b/03_Animal/rust/src/main.rs @@ -0,0 +1,291 @@ +/******************************************************************************* + * Animal + * + * From: Basic computer Games(1978) + * + * Unlike other computer games in which the computer + * picks a number or letter and you must guess what it is, + * in this game you think of an animal and the computer asks + * you questions and tries to guess the name of your animal. + * If the computer guesses incorrectly, it will ask you for a + * question that differentiates the animal it guessed + * from the one you were thinking of. In this way the + * computer "learns" new animals. Questions to differentiate + * new animals should be input without a question mark. + * This version of the game does not have a SAVE feature. + * If your sistem allows, you may modify the program to + * save array A$, then reload the array when you want + * to play the game again. This way you can save what the + * computer learns over a series of games. + * At any time if you reply 'LIST' to the question "ARE YOU + * THINKING OF AN ANIMAL", the computer will tell you all the + * animals it knows so far. + * The program starts originally by knowing only FISH and BIRD. + * As you build up a file of animals you should use broad, + * general questions first and then narrow down to more specific + * ones with later animals. For example, If an elephant was to be + * your first animal, the computer would ask for a question to distinguish + * an elephant from a bird. Naturally there are hundreds of possibilities, + * however, if you plan to build a large file of animals a good question + * would be "IS IT A MAMAL". + * This program can be easily modified to deal with categories of + * things other than animals by simply modifying the initial data + * in Line 530 and the dialogue references to animal in Lines 10, + * 40, 50, 130, 230, 240 and 600. In an educational environment, this + * would be a valuable program to teach the distinguishing chacteristics + * of many classes of objects -- rock formations, geography, marine life, + * cell structures, etc. + * Originally developed by Arthur Luehrmann at Dartmouth College, + * Animal was subsequently shortened and modified by Nathan Teichholtz at + * DEC and Steve North at Creative Computing + ******************************************************************************/ +/******************************************************************************* + * Porting notes: + * + * The data structure used for the game is B-Tree where each leaf is an animal + * and non-leaf node is a question. + * + * B-Tree is implemented in non-traditional way. It uses HashMap for string + * nodes data and use determenistic method to calculate keys for left (yes) and + * right (no) nodes. (See comments in the code.) + * + * The logic of the game mostly kept in `main` function with the use of some + * helper functions. + ******************************************************************************/ +use std::collections::HashMap; +use std::io; + +/// Main function that contains all the logic. +fn main() { + println!("{: ^80}", "Animal"); + println!("{: ^80}\n", "Creative Computing Morristown, New Jersey"); + println!("Play ´Guess the Animal´"); + println!("Think of an animal and the computer will try to guess it.\n"); + + // Initial game data + let mut animal = BTree::new( + "Does it swim".to_string(), + "Fish".to_string(), + "Bird".to_string(), + ); + + // Main game loop + while keep_playing() { + animal.restart(); + // Ask questions until player reaches an animal. + while ! animal.is_leaf() { + println!("{}? ", animal.get()); + if yes_no() { + animal.yes(); + } else { + animal.no(); + } + } + // Ask if this is the animal player is thinking of. + println!("Is it a {}? ", animal.get()); + if ! yes_no() { + // Add a new animal if it's not, and distiguish it from the existing + // one with a question. + println!("The animal you were thinking of was a ? "); + let new_animal = read_input(); + let old_animal = animal.get(); + println!("Please type in a question that would distinguish a {} from a {}: ", + new_animal, old_animal ); + let new_question = read_input(); + println!("for a {} the answer would be: ", new_animal); + if yes_no() { + animal.set(new_question, new_animal, old_animal) + } else { + animal.set(new_question, old_animal, new_animal) + } + } + println!("Why not try another animal?"); + } +} + +/// Reads the input line from [`io::stdin`] and returns it as a [`String`]. +fn read_input() -> String { + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + input.trim().parse::().unwrap() +} + +/// Asks the player whether the player wants to continue playing. Returns +/// [`true`] if the answer is yes. +fn keep_playing() -> bool { + println!("Are you thinking of an animal? "); + yes_no() +} + +/// Checks whether given answer is `yes` or `no`, returns [`true`] if yes. +fn yes_no() -> bool { + loop { + let answer = read_input().to_lowercase(); + if answer != "y" && answer != "yes" && answer != "n" && answer != "no" { + println!("Please type `yes` or `no`"); + continue; + } + return answer == "y" || answer == "yes"; + } +} + +/// Binary Tree data structure. Implemented the similar way to Max/Min Heap. +struct BTree { + /// contains all nodes data (questions and animals). + nodes: HashMap, + /// contains the key to current node in the game. + cursor: usize, +} + +impl BTree { + /// Creates new [`BTree`] with one root node (question) and two childs + /// (animals). + fn new(value: String, yes: String, no: String) -> BTree { + let nodes: HashMap = HashMap::from([ + (0, value), + (1, yes), + (2, no), + ]); + BTree { nodes: nodes, cursor: 0 } + } + + /// Returns the key for left node (yes) based on the postition of the + /// [`BTree::cursor`]. + fn get_yes_key(&self) -> usize { + &self.cursor * 2 + 1 + } + + /// Returns the key for right node (no) based on the postition of the + /// [`BTree::cursor`]. + fn get_no_key(&self) -> usize { + &self.cursor * 2 + 2 + } + + /// Check if current node is a leaf (has no children). + fn is_leaf(&self) -> bool { + ! ( self.nodes.contains_key(&self.get_yes_key()) || + self.nodes.contains_key(&self.get_no_key()) ) + } + + /// Moves cursor to `yes` (left node) if the node exists. + fn yes(&mut self) { + if self.nodes.contains_key(&self.get_yes_key()) { + self.cursor = self.get_yes_key(); + } + } + + /// Moves cursor to `no` (right node) if the node exists. + fn no(&mut self) { + if self.nodes.contains_key(&self.get_no_key()) { + self.cursor = self.get_no_key(); + } + } + + /// Sets new value (question) and two children (animals) at current position + /// of the [`BTree::cursor`]. + fn set(&mut self, value: String, yes: String, no: String) { + if let Some(v) = self.nodes.get_mut(&self.cursor) { + *v = value; + } + self.nodes.insert(self.get_yes_key(), yes); + self.nodes.insert(self.get_no_key(), no); + } + + /// Returns the value (question or animal) of the current node. + fn get(&self) -> String { + if let Some(t) = self.nodes.get(&self.cursor) { + return t.to_string(); + } + "".to_string() + } + + /// Reset cursor to 0 (root node). + fn restart(&mut self) { + self.cursor = 0; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + let want = BTree{nodes: HashMap::from([ + (0, "root".to_string()), + (1, "yes".to_string()), + (2, "no".to_string()), + ]), cursor: 0}; + assert_eq!(got.nodes, want.nodes); + assert_eq!(got.cursor, want.cursor); + } + + #[test] + fn test_get() { + let got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + let want = "root".to_string(); + assert_eq!(got.get(), want); + } + + #[test] + fn test_set() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + got.set("Root".to_string(), "Yes".to_string(), "No".to_string()); + let want = BTree{nodes: HashMap::from([ + (0, "Root".to_string()), + (1, "Yes".to_string()), + (2, "No".to_string()), + ]), cursor: 0}; + assert_eq!(got.nodes, want.nodes); + assert_eq!(got.cursor, want.cursor); + } + + #[test] + fn test_yes() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + let want = "yes".to_string(); + let want_cursor = 1; + got.yes(); + assert_eq!(got.get(), want); + assert_eq!(got.cursor, want_cursor); + } + + #[test] + fn test_no() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + let want = "no".to_string(); + let want_cursor = 2; + got.no(); + assert_eq!(got.get(), want); + assert_eq!(got.cursor, want_cursor); + } + + #[test] + fn test_is_leaf() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + assert!(!got.is_leaf(), "should not be leaf"); + got.yes(); + assert!(got.is_leaf(), "should be leaf"); + } + + #[test] + fn test_get_key() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + assert_eq!(got.get_yes_key(), 1); + assert_eq!(got.get_no_key(), 2); + got.yes(); + assert_eq!(got.get_yes_key(), 3); + assert_eq!(got.get_no_key(), 4); + } + + #[test] + fn test_restart() { + let mut got = BTree::new("root".to_string(), "yes".to_string(), "no".to_string()); + assert_eq!(got.cursor, 0); + got.yes(); + assert_eq!(got.cursor, 1); + got.restart(); + assert_eq!(got.cursor, 0); + } +}