1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4pub enum Age {
5 Young,
6 Old,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
10pub enum Polarity {
11 Yang,
12 Yin,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub struct Line {
17 pub age: Age,
18 pub polarity: Polarity,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Reading {
23 pub lines: [Line; 6], pub question: Option<String>,
25}
26
27impl Line {
28 pub fn new(age: Age, polarity: Polarity) -> Self {
29 Self { age, polarity }
30 }
31
32 pub fn traditional_number(&self) -> u8 {
34 match (self.age, self.polarity) {
35 (Age::Old, Polarity::Yin) => 6,
36 (Age::Young, Polarity::Yang) => 7,
37 (Age::Young, Polarity::Yin) => 8,
38 (Age::Old, Polarity::Yang) => 9,
39 }
40 }
41
42 pub fn from_traditional_number(num: u8) -> Result<Self, anyhow::Error> {
44 match num {
45 6 => Ok(Line::new(Age::Old, Polarity::Yin)),
46 7 => Ok(Line::new(Age::Young, Polarity::Yang)),
47 8 => Ok(Line::new(Age::Young, Polarity::Yin)),
48 9 => Ok(Line::new(Age::Old, Polarity::Yang)),
49 _ => Err(anyhow::anyhow!(
50 "Invalid line number: {}. Must be 6, 7, 8, or 9",
51 num
52 )),
53 }
54 }
55
56 pub fn transform(&self) -> Self {
58 match self.age {
59 Age::Old => Line::new(
60 Age::Young,
61 match self.polarity {
62 Polarity::Yang => Polarity::Yin,
63 Polarity::Yin => Polarity::Yang,
64 },
65 ),
66 Age::Young => *self,
67 }
68 }
69
70 pub fn to_symbol(&self) -> &'static str {
72 match (self.age, self.polarity) {
73 (Age::Young, Polarity::Yang) => "━━━━━━", (Age::Young, Polarity::Yin) => "━━ ━━", (Age::Old, Polarity::Yang) => "━━━━━━ ○", (Age::Old, Polarity::Yin) => "━━ ━━ ×", }
78 }
79}
80
81impl Reading {
82 pub fn new(lines: [Line; 6], question: Option<String>) -> Self {
83 Self { lines, question }
84 }
85
86 pub fn primary_hexagram(&self) -> u8 {
88 self.lines.iter().enumerate().fold(0u8, |acc, (i, line)| {
89 acc + match line.polarity {
90 Polarity::Yang => 2_u8.pow(i as u32),
91 Polarity::Yin => 0,
92 }
93 }) + 1
94 }
95
96 pub fn upper_trigram(&self) -> [Polarity; 3] {
98 [
99 self.lines[3].polarity,
100 self.lines[4].polarity,
101 self.lines[5].polarity,
102 ]
103 }
104
105 pub fn lower_trigram(&self) -> [Polarity; 3] {
107 [
108 self.lines[0].polarity,
109 self.lines[1].polarity,
110 self.lines[2].polarity,
111 ]
112 }
113
114 pub fn has_changing_lines(&self) -> bool {
116 self.lines.iter().any(|line| line.age == Age::Old)
117 }
118
119 pub fn changing_line_positions(&self) -> Vec<u8> {
121 self.lines
122 .iter()
123 .enumerate()
124 .filter(|(_, line)| line.age == Age::Old)
125 .map(|(i, _)| (i + 1) as u8)
126 .collect()
127 }
128
129 pub fn transformed_hexagram(&self) -> Option<Reading> {
131 if !self.has_changing_lines() {
132 return None;
133 }
134
135 let transformed_lines = self.lines.map(|line| line.transform());
136 Some(Reading::new(transformed_lines, self.question.clone()))
137 }
138
139 pub fn traditional_numbers(&self) -> [u8; 6] {
141 self.lines.map(|line| line.traditional_number())
142 }
143
144 pub fn display(&self) -> String {
146 let mut result = String::new();
147
148 if let Some(ref question) = self.question {
149 result.push_str(&format!("Question: {}\n\n", question));
150 }
151
152 result.push_str(&format!("Hexagram {}\n", self.primary_hexagram()));
153
154 for (i, line) in self.lines.iter().enumerate().rev() {
156 result.push_str(&format!("{}: {}\n", i + 1, line.to_symbol()));
157 }
158
159 if self.has_changing_lines() {
160 result.push_str(&format!(
161 "\nChanging lines: {:?}\n",
162 self.changing_line_positions()
163 ));
164
165 if let Some(transformed) = self.transformed_hexagram() {
166 result.push_str(&format!(
167 "Transforms to hexagram {}\n",
168 transformed.primary_hexagram()
169 ));
170 }
171 }
172
173 result
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_line_traditional_numbers() {
183 assert_eq!(Line::new(Age::Old, Polarity::Yin).traditional_number(), 6);
184 assert_eq!(
185 Line::new(Age::Young, Polarity::Yang).traditional_number(),
186 7
187 );
188 assert_eq!(Line::new(Age::Young, Polarity::Yin).traditional_number(), 8);
189 assert_eq!(Line::new(Age::Old, Polarity::Yang).traditional_number(), 9);
190 }
191
192 #[test]
193 fn test_line_from_traditional_number() {
194 assert_eq!(
195 Line::from_traditional_number(6).unwrap(),
196 Line::new(Age::Old, Polarity::Yin)
197 );
198 assert_eq!(
199 Line::from_traditional_number(7).unwrap(),
200 Line::new(Age::Young, Polarity::Yang)
201 );
202 assert_eq!(
203 Line::from_traditional_number(8).unwrap(),
204 Line::new(Age::Young, Polarity::Yin)
205 );
206 assert_eq!(
207 Line::from_traditional_number(9).unwrap(),
208 Line::new(Age::Old, Polarity::Yang)
209 );
210
211 assert!(Line::from_traditional_number(5).is_err());
212 assert!(Line::from_traditional_number(10).is_err());
213 }
214
215 #[test]
216 fn test_line_transform() {
217 assert_eq!(
219 Line::new(Age::Old, Polarity::Yang).transform(),
220 Line::new(Age::Young, Polarity::Yin)
221 );
222 assert_eq!(
223 Line::new(Age::Old, Polarity::Yin).transform(),
224 Line::new(Age::Young, Polarity::Yang)
225 );
226
227 assert_eq!(
229 Line::new(Age::Young, Polarity::Yang).transform(),
230 Line::new(Age::Young, Polarity::Yang)
231 );
232 assert_eq!(
233 Line::new(Age::Young, Polarity::Yin).transform(),
234 Line::new(Age::Young, Polarity::Yin)
235 );
236 }
237
238 #[test]
239 fn test_hexagram_calculation() {
240 let all_yang = [Line::new(Age::Young, Polarity::Yang); 6];
246 let reading = Reading::new(all_yang, None);
247
248 assert_eq!(reading.primary_hexagram(), 64); }
253
254 #[test]
255 fn test_changing_lines() {
256 let lines = [
257 Line::new(Age::Young, Polarity::Yang),
258 Line::new(Age::Old, Polarity::Yang), Line::new(Age::Young, Polarity::Yin),
260 Line::new(Age::Old, Polarity::Yin), Line::new(Age::Young, Polarity::Yang),
262 Line::new(Age::Young, Polarity::Yin),
263 ];
264
265 let reading = Reading::new(lines, None);
266 assert!(reading.has_changing_lines());
267 assert_eq!(reading.changing_line_positions(), vec![2, 4]);
268
269 let transformed = reading.transformed_hexagram().unwrap();
270 assert_eq!(transformed.lines[1], Line::new(Age::Young, Polarity::Yin));
271 assert_eq!(transformed.lines[3], Line::new(Age::Young, Polarity::Yang));
272 }
273}