221 lines
5.4 KiB
Rust
221 lines
5.4 KiB
Rust
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<QuestionStats> = 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::<i32>() {
|
|
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
|
|
//
|