use anyhow::bail;
use rand::seq::{IndexedMutRandom, IndexedRandom, SliceRandom};
use rand::{RngExt, rng};
#[derive(Debug, Clone)]
pub enum TypeProblem {
Addition,
Subtraction,
Multiplication,
Division,
}
#[derive(Debug)]
pub struct Domain {
pub from: f32,
pub to: f32,
pub digits_after_num: Option<u32>,
}
impl Default for Domain {
fn default() -> Self {
Self {
from: 0.0,
to: 100.0,
digits_after_num: None,
}
}
}
#[derive(Debug)]
pub struct GameParams {
pub amount: u32,
pub from: f32,
pub to: f32,
pub digits_after_num: u32,
pub type_problems: Vec<TypeProblem>,
}
impl Default for GameParams {
fn default() -> Self {
Self {
amount: 10,
from: 0.0,
to: 50.0,
digits_after_num: 2,
type_problems: vec![TypeProblem::Multiplication, TypeProblem::Addition],
}
}
}
#[derive(Default, Debug)]
pub struct Example {
pub problem: String,
pub answer: f32,
}
pub fn generate_problem(types: &Vec<TypeProblem>, domain: &GameParams) -> Example {
let mut rng = rand::rng();
let abs = 10u32.pow(domain.digits_after_num) as f32;
let epsilon = 10.0_f32.powi(-((domain.digits_after_num + 1) as i32));
let (operator, num1, num2, answer) = loop {
let type_ = if let Some(t) = types.choose(&mut rng) {
t.clone()
} else {
TypeProblem::Addition
};
let n1 = rng.random_range(domain.from as u32..domain.to as u32) as f32;
let n2 = rng.random_range(domain.from as u32..domain.to as u32) as f32;
match type_ {
TypeProblem::Addition => break ('+', n1, n2, n1 + n2),
TypeProblem::Subtraction => break ('-', n1, n2, n1 - n2),
TypeProblem::Multiplication => break ('*', n1, n2, n1 * n2),
TypeProblem::Division => {
if n2 == 0.0 {
continue;
}
let ans = n1 / n2;
let rounded = (ans * abs).round() / abs;
if (ans - rounded).abs() < epsilon {
break ('/', n1, n2, ans);
}
}
}
};
Example {
problem: format!("{} {} {} = ", num1, operator, num2),
answer,
}
}