use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Age {
Young,
Old,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Polarity {
Yang,
Yin,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Line {
pub age: Age,
pub polarity: Polarity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reading {
pub lines: [Line; 6], pub question: Option<String>,
}
impl Line {
pub fn new(age: Age, polarity: Polarity) -> Self {
Self { age, polarity }
}
pub fn traditional_number(&self) -> u8 {
match (self.age, self.polarity) {
(Age::Old, Polarity::Yin) => 6,
(Age::Young, Polarity::Yang) => 7,
(Age::Young, Polarity::Yin) => 8,
(Age::Old, Polarity::Yang) => 9,
}
}
pub fn from_traditional_number(num: u8) -> Result<Self, anyhow::Error> {
match num {
6 => Ok(Line::new(Age::Old, Polarity::Yin)),
7 => Ok(Line::new(Age::Young, Polarity::Yang)),
8 => Ok(Line::new(Age::Young, Polarity::Yin)),
9 => Ok(Line::new(Age::Old, Polarity::Yang)),
_ => Err(anyhow::anyhow!(
"Invalid line number: {}. Must be 6, 7, 8, or 9",
num
)),
}
}
pub fn transform(&self) -> Self {
match self.age {
Age::Old => Line::new(
Age::Young,
match self.polarity {
Polarity::Yang => Polarity::Yin,
Polarity::Yin => Polarity::Yang,
},
),
Age::Young => *self,
}
}
pub fn to_symbol(&self) -> &'static str {
match (self.age, self.polarity) {
(Age::Young, Polarity::Yang) => "━━━━━━", (Age::Young, Polarity::Yin) => "━━ ━━", (Age::Old, Polarity::Yang) => "━━━━━━ ○", (Age::Old, Polarity::Yin) => "━━ ━━ ×", }
}
}
impl Reading {
pub fn new(lines: [Line; 6], question: Option<String>) -> Self {
Self { lines, question }
}
pub fn primary_hexagram(&self) -> u8 {
self.lines.iter().enumerate().fold(0u8, |acc, (i, line)| {
acc + match line.polarity {
Polarity::Yang => 2_u8.pow(i as u32),
Polarity::Yin => 0,
}
}) + 1
}
pub fn upper_trigram(&self) -> [Polarity; 3] {
[
self.lines[3].polarity,
self.lines[4].polarity,
self.lines[5].polarity,
]
}
pub fn lower_trigram(&self) -> [Polarity; 3] {
[
self.lines[0].polarity,
self.lines[1].polarity,
self.lines[2].polarity,
]
}
pub fn has_changing_lines(&self) -> bool {
self.lines.iter().any(|line| line.age == Age::Old)
}
pub fn changing_line_positions(&self) -> Vec<u8> {
self.lines
.iter()
.enumerate()
.filter(|(_, line)| line.age == Age::Old)
.map(|(i, _)| (i + 1) as u8)
.collect()
}
pub fn transformed_hexagram(&self) -> Option<Reading> {
if !self.has_changing_lines() {
return None;
}
let transformed_lines = self.lines.map(|line| line.transform());
Some(Reading::new(transformed_lines, self.question.clone()))
}
pub fn traditional_numbers(&self) -> [u8; 6] {
self.lines.map(|line| line.traditional_number())
}
pub fn display(&self) -> String {
let mut result = String::new();
if let Some(ref question) = self.question {
result.push_str(&format!("Question: {}\n\n", question));
}
result.push_str(&format!("Hexagram {}\n", self.primary_hexagram()));
for (i, line) in self.lines.iter().enumerate().rev() {
result.push_str(&format!("{}: {}\n", i + 1, line.to_symbol()));
}
if self.has_changing_lines() {
result.push_str(&format!(
"\nChanging lines: {:?}\n",
self.changing_line_positions()
));
if let Some(transformed) = self.transformed_hexagram() {
result.push_str(&format!(
"Transforms to hexagram {}\n",
transformed.primary_hexagram()
));
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_traditional_numbers() {
assert_eq!(Line::new(Age::Old, Polarity::Yin).traditional_number(), 6);
assert_eq!(
Line::new(Age::Young, Polarity::Yang).traditional_number(),
7
);
assert_eq!(Line::new(Age::Young, Polarity::Yin).traditional_number(), 8);
assert_eq!(Line::new(Age::Old, Polarity::Yang).traditional_number(), 9);
}
#[test]
fn test_line_from_traditional_number() {
assert_eq!(
Line::from_traditional_number(6).unwrap(),
Line::new(Age::Old, Polarity::Yin)
);
assert_eq!(
Line::from_traditional_number(7).unwrap(),
Line::new(Age::Young, Polarity::Yang)
);
assert_eq!(
Line::from_traditional_number(8).unwrap(),
Line::new(Age::Young, Polarity::Yin)
);
assert_eq!(
Line::from_traditional_number(9).unwrap(),
Line::new(Age::Old, Polarity::Yang)
);
assert!(Line::from_traditional_number(5).is_err());
assert!(Line::from_traditional_number(10).is_err());
}
#[test]
fn test_line_transform() {
assert_eq!(
Line::new(Age::Old, Polarity::Yang).transform(),
Line::new(Age::Young, Polarity::Yin)
);
assert_eq!(
Line::new(Age::Old, Polarity::Yin).transform(),
Line::new(Age::Young, Polarity::Yang)
);
assert_eq!(
Line::new(Age::Young, Polarity::Yang).transform(),
Line::new(Age::Young, Polarity::Yang)
);
assert_eq!(
Line::new(Age::Young, Polarity::Yin).transform(),
Line::new(Age::Young, Polarity::Yin)
);
}
#[test]
fn test_hexagram_calculation() {
let all_yang = [Line::new(Age::Young, Polarity::Yang); 6];
let reading = Reading::new(all_yang, None);
assert_eq!(reading.primary_hexagram(), 64); }
#[test]
fn test_changing_lines() {
let lines = [
Line::new(Age::Young, Polarity::Yang),
Line::new(Age::Old, Polarity::Yang), Line::new(Age::Young, Polarity::Yin),
Line::new(Age::Old, Polarity::Yin), Line::new(Age::Young, Polarity::Yang),
Line::new(Age::Young, Polarity::Yin),
];
let reading = Reading::new(lines, None);
assert!(reading.has_changing_lines());
assert_eq!(reading.changing_line_positions(), vec![2, 4]);
let transformed = reading.transformed_hexagram().unwrap();
assert_eq!(transformed.lines[1], Line::new(Age::Young, Polarity::Yin));
assert_eq!(transformed.lines[3], Line::new(Age::Young, Polarity::Yang));
}
}