use crate::core::reading::{Age, Line, Polarity, Reading};
use rand::Rng;
pub struct Diviner {
rng: rand::rngs::ThreadRng,
}
impl Diviner {
pub fn new() -> Self {
Self {
rng: rand::rng(),
}
}
pub fn cast_reading(&mut self, question: Option<String>) -> Reading {
let lines = [
self.cast_line(),
self.cast_line(),
self.cast_line(),
self.cast_line(),
self.cast_line(),
self.cast_line(),
];
Reading::new(lines, question)
}
fn number_to_line(number: u8) -> Line {
match number {
6 => Line::new(Age::Old, Polarity::Yin), 7 => Line::new(Age::Young, Polarity::Yang), 8 => Line::new(Age::Young, Polarity::Yin), 9 => Line::new(Age::Old, Polarity::Yang), _ => panic!("Invalid line number: {}. Must be 6, 7, 8, or 9", number),
}
}
fn cast_line(&mut self) -> Line {
let coin_sum: u8 = (0..3)
.map(|_| if self.rng.random_bool(0.5) { 3 } else { 2 })
.sum();
Self::number_to_line(coin_sum)
}
pub fn cast_reading_from_numbers(
&self,
numbers: [u8; 6],
question: Option<String>,
) -> Result<Reading, anyhow::Error> {
let mut lines = [Line::new(Age::Young, Polarity::Yang); 6];
for (i, &num) in numbers.iter().enumerate() {
lines[i] = Line::from_traditional_number(num)?;
}
Ok(Reading::new(lines, question))
}
#[cfg(test)]
pub fn cast_reading_with_numbers(
&mut self,
numbers: [u8; 6],
question: Option<String>,
) -> Reading {
let lines = numbers.map(Self::number_to_line);
Reading::new(lines, question)
}
}
impl Default for Diviner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_number_interpretation() {
let mut diviner = Diviner::new();
let numbers = [6, 7, 8, 9, 6, 8];
let reading = diviner.cast_reading_with_numbers(numbers, Some("Test question".to_string()));
assert_eq!(reading.traditional_numbers(), [6, 7, 8, 9, 6, 8]);
assert!(reading.has_changing_lines());
assert_eq!(reading.changing_line_positions(), vec![1, 4, 5]); }
#[test]
fn test_cast_from_numbers() {
let diviner = Diviner::new();
let numbers = [7, 8, 9, 6, 7, 8];
let reading = diviner
.cast_reading_from_numbers(numbers, Some("Test from numbers".to_string()))
.unwrap();
assert_eq!(reading.traditional_numbers(), numbers);
assert!(reading.has_changing_lines());
assert_eq!(reading.changing_line_positions(), vec![3, 4]); }
#[test]
fn test_invalid_numbers() {
let diviner = Diviner::new();
let invalid_numbers = [7, 8, 5, 6, 7, 8];
let result = diviner.cast_reading_from_numbers(invalid_numbers, None);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid line number: 5"));
}
#[test]
fn test_random_casting() {
let mut diviner = Diviner::new();
for _ in 0..10 {
let reading = diviner.cast_reading(Some("Random test".to_string()));
for &num in &reading.traditional_numbers() {
assert!([6, 7, 8, 9].contains(&num));
}
let hexagram = reading.primary_hexagram();
assert!(hexagram >= 1 && hexagram <= 64);
}
}
}