use std::io::{self, BufRead, Write}; use std::time::{Duration, Instant, SystemTime}; struct Rng { state: u64, } impl Rng { fn new() -> Self { let seed = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .subsec_nanos() as u64; // Allegedly // The magic number is XOR'd with the seed (`subsec_nanos()`) as a mixing step. // "the golden ratio" has a property where it distributes bits very evenly across // the range, so even a low-entropy seed gets spread across all 64 bits // before the first LCG iteration runs Self { state: seed ^ 0x9e3779b97f4a7c15, } } fn next_u64(&mut self) -> u64 { // Allegedly // Knuth MMIX LCG constants: // multiplier chosen for spectral quality (for uniform distribution) // addend must be odd for full 2^64 period self.state = self .state .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); self.state } fn range(&mut self, min: i32, max: i32) -> i32 { let range = (max - min + 1) as u64; (self.next_u64() % range) as i32 + min } } #[derive(Clone, Copy)] enum Op { Add, Sub, Mul, Div, } impl Op { fn symbol(self) -> char { match self { Op::Add => '+', Op::Sub => '-', Op::Mul => 'x', Op::Div => '/', } } fn apply(self, a: i32, b: i32) -> i32 { match self { Op::Add => a + b, Op::Sub => a - b, Op::Mul => a * b, Op::Div => a / b, } } } struct Question { a: i32, b: i32, op: Op, answer: i32, } impl Question { fn generate(rng: &mut Rng) -> Self { let op = match rng.range(0, 3) { 0 => Op::Add, 1 => Op::Sub, 2 => Op::Mul, _ => Op::Div, }; let (a, b) = match op { Op::Add => (rng.range(1, 99), rng.range(1, 99)), Op::Sub => { let x = rng.range(1, 99); let y = rng.range(1, x); (x, y) } Op::Mul => (rng.range(2, 12), rng.range(2, 12)), Op::Div => { let b = rng.range(2, 12); let quotient = rng.range(2, 12); (b * quotient, b) } }; let answer = op.apply(a, b); Question { a, b, op, answer } } fn prompt(&self) -> String { format!("{} {} {} = ", self.a, self.op.symbol(), self.b) } } struct QuestionStats { a: i32, b: i32, op: Op, answer: i32, time_taken: Duration, mistakes: u32, } fn main() { const ROUNDS: usize = 10; println!("\n=== Fast Math ==="); println!("Complete {ROUNDS} questions to finish a round.\n"); let stdin = io::stdin(); let mut stdin = stdin.lock(); // why lock? let mut rng = Rng::new(); let mut stats: Vec = Vec::with_capacity(ROUNDS); for i in 0..ROUNDS { let q = Question::generate(&mut rng); println!("Question {}/{}:", i + 1, ROUNDS); let mut mistakes = 0u32; let start = Instant::now(); loop { print!(" {}", q.prompt()); io::stdout().flush().unwrap(); let mut line = String::new(); stdin.read_line(&mut line).unwrap(); match line.trim().parse::() { Ok(n) if n == q.answer => { println!(" Correct!\n"); break; } Ok(_) => { mistakes += 1; println!(" Wrong, try again."); } Err(_) => { println!(" Please enter a valid integer."); } } } stats.push(QuestionStats { a: q.a, b: q.b, op: q.op, answer: q.answer, time_taken: start.elapsed(), mistakes, }); } print_summary(&stats); } fn print_summary(stats: &[QuestionStats]) { // voodoo let total_time: Duration = stats.iter().map(|s| s.time_taken).sum(); let total_mistakes: u32 = stats.iter().map(|s| s.mistakes).sum(); let perfect = stats.iter().filter(|s| s.mistakes == 0).count(); println!("=== Round Complete ===\n"); println!( "{:<5} {:<12} {:<8} {:>10} {:>8}", "#", "Question", "Answer", "Time (s)", "Mistakes" ); println!("{}", "-".repeat(50)); for (i, s) in stats.iter().enumerate() { let q = format!("{} {} {}", s.a, s.op.symbol(), s.b); println!( "{:<5} {:<12} {:<8} {:>10.2} {:>8}", i + 1, q, s.answer, s.time_taken.as_secs_f64(), s.mistakes, ); } println!("{}", "-".repeat(50)); println!( "{:<5} {:<12} {:<8} {:>10.2} {:>8}", "Tot", "", "", total_time.as_secs_f64(), total_mistakes ); println!("\nScore: {}/{}", perfect, stats.len()); } // tests // * valid integer // * some basic correctness of correct answers // * some basic correctness of incorrect answers // // * invalid integer // * float // * different representation: hex, binary, octect // * unicode // * some specific attacks // * multiple integers //