Rust for the Novice Programmer/Basic Maths Testing Program/Source Code and Extensions
Full Source Code
edit/// bind fmt and io to std::fmt and std::io to shorten later use std::fmt; use std::io; /// Entry point of our program fn main() { //generate and print a random maths question let question = MathsQuestion::new_random(); question.print(); let val = wait_for_input(); let result = question.calc_result(); if val == result { println!("You got it correct! Well done!"); } else { println!("Incorrect! The correct answer was {}", result); } }
/// Gets input from stdin (standard input) /// Will ask continuously until a valid i32 number is inputted fn wait_for_input() -> i32 { //create a new string buffer let mut buffer = String::new(); //reads a line from stdin into the string buffer io::stdin().read_line(&mut buffer).unwrap(); //remove newline characters from the string buffer = buffer.replace("\r", "").replace("\n", "");
//attempts to convert the string into a base 10 i32 number let mut result = i32::from_str_radix(&buffer, 10);
//as long as converting the string to a number fails, keep asking for new input from the user //the underscore indicates we are ignoring the specific error since we don't require it for anything while let Err(_) = result { println!("Invalid number, please re-enter:"); //clear the string buffer so it doesn't contain last line's result buffer.clear(); //reads a line from stdin again io::stdin().read_line(&mut buffer).unwrap(); //remove newline characters again buffer = buffer.replace("\r", "").replace("\n", ""); //try to convert the string into an i32 number again result = i32::from_str_radix(&buffer, 10); } //result must be an Ok() value here, so this unwrap is safe let num = result.unwrap(); //return the number num }
/// An enum representing an operator in a maths equation enum Operator { Addition, Subtraction, }
/// Implement the Display trait on the Operator type, allowing it to be displayed using {} in format strings impl fmt::Display for Operator { //the required fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Operator::Addition => write!(f, "+"), Operator::Subtraction => write!(f, "-"), } } }
/// A struct representing a maths question struct MathsQuestion { left: i32, right: i32, operator: Operator, }
impl MathsQuestion { /// Construct a new MathsQuestion from parts fn new(left: i32, right: i32, operator: Operator) -> MathsQuestion { MathsQuestion { left, right, operator, } } /// Creates a new MathsQuestion from random values fn new_random() -> MathsQuestion { //random values between -50 and +50 (inclusive) let left = fastrand::i32(-50..=50); let right = fastrand::i32(-50..=50); //operator randomly addition or subtraction (50/50) let operator = if fastrand::bool() { Operator::Addition } else { Operator::Subtraction }; MathsQuestion::new(left, right, operator) } /// Prints out the Maths question fn print(&self) { println!("What is {} {} {}?", self.left, self.operator, self.right); } /// Calculates the expected result of the maths question fn calc_result(&self) -> i32 { match self.operator { Operator::Addition => self.left + self.right, Operator::Subtraction => self.left - self.right, } } }
Possible extensions you could try to implement, in order from easy to hard with some hints:
- Ask multiple questions in one run of the program(maybe use a for loop to run the code inside the main function a certain number of times)
- When asking multiple questions, add a counter that tracks how many of the questions the user got correct and print out what percentage they got correct
- Add multiplication and division to the operators(note you will need to change all the spots the operator is used) also you will need to change the number type for division if you want to handle that, although this may have more problems since what do you do about 1 divided by 3 since the resulting decimal will be 0.3333333333 which the user can't be expected to put in. Also make sure you handle division by 0 since that would cause problems. There are a lot of things to consider so this may be quite difficult if you want 'proper' division but integer division is an easier thing to implement.
- Track the time it takes the user to answer each question. It is suggested you use the standard library Instant struct to track the time. You can then print out the mean, median or other statistical measures. It is also suggested to use a Vec to store the times.
- Create more complicated maths questions with random numbers of operators in a single question. This is quite complicated for reasons that may be quite obscure with the Rust knowledge you have now but will be elaborated later. You may also want to put brackets between formulas
Please don't feel obligated to do these, only do them if you think it's interesting and you're having fun. You may easily run into issues that you don't know how to solve because of limited knowledge that will be covered later. The best advice is to read and try to understand compiler errors, maybe searching them if you're not sure for more context. Move on when you've had enough.
Next: