1use serde::Serialize;
38
39use super::bazi::{bazi, nayin, BaziOptions, LunarDate, LIFE_STAGES};
40use super::calendar::ChineseCalendar;
41use super::{cycle_index, year_cycle_index, BRANCHES, STEMS};
42use crate::error::EngineError;
43use crate::instant::UtcInstant;
44
45const PALACES: [&str; 12] = [
47 "life", "siblings", "spouse", "children", "wealth", "health", "travel", "friends", "career",
48 "property", "fortune", "parents",
49];
50
51const ZI_WEI: &str = "zi_wei";
52const TIAN_JI: &str = "tian_ji";
53const TAI_YANG: &str = "tai_yang";
54const WU_QU: &str = "wu_qu";
55const TIAN_TONG: &str = "tian_tong";
56const LIAN_ZHEN: &str = "lian_zhen";
57const TIAN_FU: &str = "tian_fu";
58const TAI_YIN: &str = "tai_yin";
59const TAN_LANG: &str = "tan_lang";
60const JU_MEN: &str = "ju_men";
61const TIAN_XIANG: &str = "tian_xiang";
62const TIAN_LIANG: &str = "tian_liang";
63const QI_SHA: &str = "qi_sha";
64const PO_JUN: &str = "po_jun";
65const ZUO_FU: &str = "zuo_fu";
66const YOU_BI: &str = "you_bi";
67const WEN_CHANG: &str = "wen_chang";
68const WEN_QU: &str = "wen_qu";
69const TIAN_KUI: &str = "tian_kui";
70const TIAN_YUE: &str = "tian_yue";
71const LU_CUN: &str = "lu_cun";
72const TIAN_MA: &str = "tian_ma";
73const QING_YANG: &str = "qing_yang";
74const TUO_LUO: &str = "tuo_luo";
75const HUO_XING: &str = "huo_xing";
76const LING_XING: &str = "ling_xing";
77const DI_KONG: &str = "di_kong";
78const DI_JIE: &str = "di_jie";
79
80const ZI_WEI_GROUP: [(&str, usize); 6] = [
82 (ZI_WEI, 0),
83 (TIAN_JI, 1),
84 (TAI_YANG, 3),
85 (WU_QU, 4),
86 (TIAN_TONG, 5),
87 (LIAN_ZHEN, 8),
88];
89
90const TIAN_FU_GROUP: [(&str, usize); 8] = [
92 (TIAN_FU, 0),
93 (TAI_YIN, 1),
94 (TAN_LANG, 2),
95 (JU_MEN, 3),
96 (TIAN_XIANG, 4),
97 (TIAN_LIANG, 5),
98 (QI_SHA, 6),
99 (PO_JUN, 10),
100];
101
102const TRANSFORMATIONS: [[&str; 4]; 10] = [
104 [LIAN_ZHEN, PO_JUN, WU_QU, TAI_YANG], [TIAN_JI, TIAN_LIANG, ZI_WEI, TAI_YIN], [TIAN_TONG, TIAN_JI, WEN_CHANG, LIAN_ZHEN], [TAI_YIN, TIAN_TONG, TIAN_JI, JU_MEN], [TAN_LANG, TAI_YIN, YOU_BI, TIAN_JI], [WU_QU, TAN_LANG, TIAN_LIANG, WEN_QU], [TAI_YANG, WU_QU, TAI_YIN, TIAN_TONG], [JU_MEN, TAI_YANG, WEN_QU, WEN_CHANG], [TIAN_LIANG, ZI_WEI, ZUO_FU, WU_QU], [PO_JUN, JU_MEN, TAI_YIN, TAN_LANG], ];
115
116const TRANSFORMATION_KINDS: [&str; 4] = ["lu", "quan", "ke", "ji"];
117
118const MIAO: Option<&str> = Some("miao");
119const WANG: Option<&str> = Some("wang");
120const DE: Option<&str> = Some("de");
121const LI: Option<&str> = Some("li");
122const PING: Option<&str> = Some("ping");
123const BU: Option<&str> = Some("bu");
124const XIAN: Option<&str> = Some("xian");
125
126const BRIGHTNESS: [(&str, [Option<&str>; 12]); 20] = [
128 (
129 ZI_WEI,
130 [
131 WANG, WANG, DE, WANG, MIAO, MIAO, WANG, WANG, DE, WANG, PING, MIAO,
132 ],
133 ),
134 (
135 TIAN_JI,
136 [
137 DE, WANG, LI, PING, MIAO, XIAN, DE, WANG, LI, PING, MIAO, XIAN,
138 ],
139 ),
140 (
141 TAI_YANG,
142 [
143 WANG, MIAO, WANG, WANG, WANG, DE, DE, PING, BU, XIAN, XIAN, BU,
144 ],
145 ),
146 (
147 WU_QU,
148 [
149 DE, LI, MIAO, PING, WANG, MIAO, DE, LI, MIAO, PING, WANG, MIAO,
150 ],
151 ),
152 (
153 TIAN_TONG,
154 [
155 LI, PING, PING, MIAO, XIAN, BU, WANG, PING, PING, MIAO, WANG, BU,
156 ],
157 ),
158 (
159 LIAN_ZHEN,
160 [
161 MIAO, PING, LI, XIAN, PING, LI, MIAO, PING, LI, XIAN, PING, LI,
162 ],
163 ),
164 (
165 TIAN_FU,
166 [
167 MIAO, DE, MIAO, DE, WANG, MIAO, DE, WANG, MIAO, DE, MIAO, MIAO,
168 ],
169 ),
170 (
171 TAI_YIN,
172 [
173 WANG, XIAN, XIAN, XIAN, BU, BU, LI, WANG, WANG, MIAO, MIAO, MIAO,
174 ],
175 ),
176 (
177 TAN_LANG,
178 [
179 PING, LI, MIAO, XIAN, WANG, MIAO, PING, LI, MIAO, XIAN, WANG, MIAO,
180 ],
181 ),
182 (
183 JU_MEN,
184 [
185 MIAO, MIAO, XIAN, WANG, WANG, BU, MIAO, MIAO, XIAN, WANG, WANG, BU,
186 ],
187 ),
188 (
189 TIAN_XIANG,
190 [MIAO, XIAN, DE, DE, MIAO, DE, MIAO, XIAN, DE, DE, MIAO, MIAO],
191 ),
192 (
193 TIAN_LIANG,
194 [
195 MIAO, MIAO, MIAO, XIAN, MIAO, WANG, XIAN, DE, MIAO, XIAN, MIAO, WANG,
196 ],
197 ),
198 (
199 QI_SHA,
200 [
201 MIAO, WANG, MIAO, PING, WANG, MIAO, MIAO, WANG, MIAO, PING, WANG, MIAO,
202 ],
203 ),
204 (
205 PO_JUN,
206 [
207 DE, XIAN, WANG, PING, MIAO, WANG, DE, XIAN, WANG, PING, MIAO, WANG,
208 ],
209 ),
210 (
211 WEN_CHANG,
212 [XIAN, LI, DE, MIAO, XIAN, LI, DE, MIAO, XIAN, LI, DE, MIAO],
213 ),
214 (
215 WEN_QU,
216 [
217 PING, WANG, DE, MIAO, XIAN, WANG, DE, MIAO, XIAN, WANG, DE, MIAO,
218 ],
219 ),
220 (
221 HUO_XING,
222 [MIAO, LI, XIAN, DE, MIAO, LI, XIAN, DE, MIAO, LI, XIAN, DE],
223 ),
224 (
225 LING_XING,
226 [MIAO, LI, XIAN, DE, MIAO, LI, XIAN, DE, MIAO, LI, XIAN, DE],
227 ),
228 (
229 QING_YANG,
230 [
231 None, XIAN, MIAO, None, XIAN, MIAO, None, XIAN, MIAO, None, XIAN, MIAO,
232 ],
233 ),
234 (
235 TUO_LUO,
236 [
237 XIAN, None, MIAO, XIAN, None, MIAO, XIAN, None, MIAO, XIAN, None, MIAO,
238 ],
239 ),
240];
241
242const LIFE_MASTERS: [&str; 12] = [
244 TAN_LANG, JU_MEN, LU_CUN, WEN_QU, LIAN_ZHEN, WU_QU, PO_JUN, WU_QU, LIAN_ZHEN, WEN_QU, LU_CUN,
245 JU_MEN,
246];
247
248const BODY_MASTERS: [&str; 12] = [
250 HUO_XING, TIAN_XIANG, TIAN_LIANG, TIAN_TONG, WEN_CHANG, TIAN_JI, HUO_XING, TIAN_XIANG,
251 TIAN_LIANG, TIAN_TONG, WEN_CHANG, TIAN_JI,
252];
253
254const BOSHI: [&str; 12] = [
256 "bo_shi",
257 "li_shi",
258 "qing_long",
259 "xiao_hao",
260 "jiang_jun",
261 "zou_shu",
262 "fei_lian",
263 "xi_shen",
264 "bing_fu",
265 "da_hao",
266 "fu_bing",
267 "guan_fu",
268];
269
270const SUIQIAN: [&str; 12] = [
272 "sui_jian", "hui_qi", "sang_men", "guan_suo", "guan_fu", "xiao_hao", "da_hao", "long_de",
273 "bai_hu", "tian_de", "diao_ke", "bing_fu",
274];
275
276const JIANGQIAN: [&str; 12] = [
278 "jiang_xing",
279 "pan_an",
280 "sui_yi",
281 "xi_shen",
282 "hua_gai",
283 "jie_sha",
284 "zai_sha",
285 "tian_sha",
286 "zhi_bei",
287 "xian_chi",
288 "yue_sha",
289 "wang_shen",
290];
291
292const ZI: usize = 0;
294const CHOU: usize = 1;
295const YIN: usize = 2;
296const MAO: usize = 3;
297const CHEN: usize = 4;
298const SI: usize = 5;
299const WU: usize = 6;
300const WEI: usize = 7;
301const SHEN: usize = 8;
302const YOU: usize = 9;
303const XU: usize = 10;
304const HAI: usize = 11;
305
306#[derive(Debug, Clone)]
308pub struct ZiWeiOptions<'a> {
309 pub solar_time: bool,
312 pub zi_hour: &'a str,
315 pub sex: Option<&'a str>,
318 pub leap_month: &'a str,
321 pub year: Option<i32>,
323}
324
325impl Default for ZiWeiOptions<'_> {
326 fn default() -> Self {
327 ZiWeiOptions {
328 solar_time: true,
329 zi_hour: "next_day",
330 sex: None,
331 leap_month: "split",
332 year: None,
333 }
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Serialize)]
339pub struct ZiWeiStar {
340 pub star: &'static str,
342 pub kind: &'static str,
344 pub brightness: Option<&'static str>,
347 pub transformation: Option<&'static str>,
349}
350
351#[derive(Debug, Clone, PartialEq, Serialize)]
353pub struct AgeRange {
354 pub start: u8,
356 pub end: u8,
358}
359
360#[derive(Debug, Clone, PartialEq, Serialize)]
362pub struct ZiWeiPalace {
363 pub name: &'static str,
365 pub branch: &'static str,
367 pub stem: &'static str,
369 pub is_body: bool,
371 pub stars: Vec<ZiWeiStar>,
373 pub decade: Option<AgeRange>,
375 pub small_limit_ages: Vec<u8>,
377 pub minor_stars: Vec<&'static str>,
379 pub gods: ZiWeiGods,
381 pub flying: Vec<ZiWeiTransformation>,
384}
385
386#[derive(Debug, Clone, PartialEq, Serialize)]
388pub struct ZiWeiGods {
389 pub life_stage: Option<&'static str>,
392 pub boshi: Option<&'static str>,
394 pub suiqian: &'static str,
396 pub jiangqian: &'static str,
398}
399
400#[derive(Debug, Clone, PartialEq, Serialize)]
402pub struct ZiWeiFlowStar {
403 pub star: &'static str,
406 pub branch: &'static str,
408}
409
410#[derive(Debug, Clone, PartialEq, Serialize)]
412pub struct ZiWeiGod {
413 pub god: &'static str,
415 pub branch: &'static str,
417}
418
419#[derive(Debug, Clone, PartialEq, Serialize)]
421pub struct ZiWeiPeriod {
422 pub branch: &'static str,
424 pub stem: &'static str,
426 pub palaces: Vec<ZiWeiGod>,
428 pub transformations: Vec<ZiWeiTransformation>,
430 pub stars: Vec<ZiWeiFlowStar>,
432}
433
434#[derive(Debug, Clone, PartialEq, Serialize)]
436pub struct ZiWeiHoroscope {
437 pub year: i32,
439 pub nominal_age: i32,
441 pub decade: Option<ZiWeiPeriod>,
444 pub childhood: bool,
446 pub small_limit: Option<&'static str>,
448 pub yearly: ZiWeiPeriod,
450 pub suiqian: Vec<ZiWeiGod>,
452 pub jiangqian: Vec<ZiWeiGod>,
454}
455
456#[derive(Debug, Clone, PartialEq, Serialize)]
458pub struct ZiWeiTransformation {
459 pub kind: &'static str,
461 pub star: &'static str,
463 pub palace: &'static str,
465}
466
467#[derive(Debug, Clone, PartialEq, Serialize)]
469pub struct Bureau {
470 pub element: &'static str,
472 pub number: u8,
474}
475
476#[derive(Debug, Clone, PartialEq, Serialize)]
478pub struct ZiWei {
479 pub time_basis: &'static str,
481 pub zi_hour: &'static str,
483 pub lunar_date: LunarDate,
485 pub month: u8,
487 pub hour_branch: &'static str,
489 pub life_palace: &'static str,
491 pub body_palace: &'static str,
493 pub bureau: Bureau,
495 pub life_master: &'static str,
497 pub body_master: &'static str,
499 pub palaces: Vec<ZiWeiPalace>,
501 pub transformations: Vec<ZiWeiTransformation>,
503 pub decade_direction: Option<&'static str>,
506 pub horoscope: Option<ZiWeiHoroscope>,
508}
509
510fn invalid(message: &str) -> EngineError {
511 EngineError::InvalidInput(message.to_string())
512}
513
514fn wrap(index: i64) -> usize {
515 index.rem_euclid(12) as usize
516}
517
518pub(crate) fn zi_wei_branch(bureau: u8, day: u8) -> usize {
520 let (bureau, day) = (i64::from(bureau), i64::from(day));
521 let quotient = (day + bureau - 1) / bureau;
522 let added = quotient * bureau - day;
523 let base = YIN as i64 + quotient - 1;
524 wrap(if added % 2 == 1 {
525 base - added
526 } else {
527 base + added
528 })
529}
530
531fn bureau_of(stem: usize, branch: usize) -> Bureau {
533 let nayin = nayin(cycle_index(stem, branch) / 2);
534 let (element, number) = [
535 ("water", 2),
536 ("wood", 3),
537 ("metal", 4),
538 ("earth", 5),
539 ("fire", 6),
540 ]
541 .into_iter()
542 .find(|(element, _)| nayin.ends_with(element))
543 .unwrap_or(("fire", 6));
544 Bureau { element, number }
545}
546
547fn brightness(star: &str, branch: usize) -> Option<&'static str> {
548 BRIGHTNESS
549 .iter()
550 .find(|(s, _)| *s == star)
551 .and_then(|(_, table)| table[wrap(branch as i64 - YIN as i64)])
552}
553
554fn kui_yue(stem: usize) -> (usize, usize) {
556 match stem {
557 0 | 4 | 6 => (CHOU, WEI),
558 1 | 5 => (ZI, SHEN),
559 2 | 3 => (HAI, YOU),
560 7 => (WU, YIN),
561 _ => (MAO, SI),
562 }
563}
564
565fn lu_cun_of(stem: usize) -> usize {
567 [YIN, MAO, SI, WU, SI, WU, SHEN, YOU, HAI, ZI][stem]
568}
569
570fn trine_of(branch: usize) -> usize {
572 branch % 4
573}
574
575fn horse_of(branch: usize) -> usize {
577 [YIN, HAI, SHEN, SI][trine_of(branch)]
578}
579
580fn luan_xi(branch: usize) -> (usize, usize) {
582 let luan = wrap(MAO as i64 - branch as i64);
583 (luan, wrap(luan as i64 + 6))
584}
585
586fn flow_chang_qu(stem: usize) -> (usize, usize) {
588 match stem {
589 0 => (SI, YOU),
590 1 => (WU, SHEN),
591 2 | 4 => (SHEN, WU),
592 3 | 5 => (YOU, SI),
593 6 => (HAI, MAO),
594 7 => (ZI, YIN),
595 8 => (YIN, ZI),
596 _ => (MAO, HAI),
597 }
598}
599
600fn flow_stars(stem: usize, branch: usize) -> Vec<ZiWeiFlowStar> {
602 let (kui, yue) = kui_yue(stem);
603 let (chang, qu) = flow_chang_qu(stem);
604 let lu = lu_cun_of(stem) as i64;
605 let (luan, xi) = luan_xi(branch);
606 [
607 (TIAN_KUI, kui),
608 (TIAN_YUE, yue),
609 (WEN_CHANG, chang),
610 (WEN_QU, qu),
611 (LU_CUN, wrap(lu)),
612 (QING_YANG, wrap(lu + 1)),
613 (TUO_LUO, wrap(lu - 1)),
614 (TIAN_MA, horse_of(branch)),
615 ("hong_luan", luan),
616 ("tian_xi", xi),
617 ]
618 .into_iter()
619 .map(|(star, b)| ZiWeiFlowStar {
620 star,
621 branch: BRANCHES[b],
622 })
623 .collect()
624}
625
626fn cycle(gods: &[&'static str; 12], start: usize, forward: bool) -> Vec<ZiWeiGod> {
628 (0..12)
629 .map(|i| ZiWeiGod {
630 god: gods[i],
631 branch: BRANCHES[wrap(start as i64 + if forward { i as i64 } else { -(i as i64) })],
632 })
633 .collect()
634}
635
636fn yearly_gods(branch: usize) -> (Vec<ZiWeiGod>, Vec<ZiWeiGod>) {
638 let general = [ZI, YOU, WU, MAO][trine_of(branch)];
639 (
640 cycle(&SUIQIAN, branch, true),
641 cycle(&JIANGQIAN, general, true),
642 )
643}
644
645#[allow(clippy::too_many_arguments)]
647fn minor_stars(
648 year_stem: usize,
649 year_branch: usize,
650 month: i64,
651 day: i64,
652 hour: i64,
653 life: usize,
654 body: usize,
655 anchors: [usize; 4],
656) -> Vec<(usize, &'static str)> {
657 let (s, b, m) = (year_stem, year_branch as i64, month as usize);
658 let [zuo, you, chang, qu] = anchors.map(|a| a as i64);
659 let d = day - 1;
660 let (luan, xi) = luan_xi(year_branch);
661 let (hua_gai, xian_chi) =
662 [(CHEN, YOU), (CHOU, WU), (XU, MAO), (WEI, ZI)][trine_of(year_branch)];
663 let (gu_chen, gua_su) =
664 [(SI, CHOU), (SHEN, CHEN), (HAI, WEI), (YIN, XU)][(year_branch + 10) % 12 / 3];
665 let mut xun_kong = wrap(b + 10 - s as i64);
666 if xun_kong % 2 != year_branch % 2 {
667 xun_kong = wrap(xun_kong as i64 + 1);
668 }
669 let palace_at = |offset: i64| wrap(life as i64 - offset);
671 vec![
672 (luan, "hong_luan"),
673 (xi, "tian_xi"),
674 (wrap(CHOU as i64 + month), "tian_yao"),
675 (xian_chi, "xian_chi"),
676 ([SHEN, XU, ZI, YIN, CHEN, WU][m / 2], "jie_shen"),
677 (wrap(zuo + d), "san_tai"),
678 (wrap(you - d), "ba_zuo"),
679 (wrap(chang + d - 1), "en_guang"),
680 (wrap(qu + d - 1), "tian_gui"),
681 (wrap(CHEN as i64 + b), "long_chi"),
682 (wrap(XU as i64 - b), "feng_ge"),
683 (wrap(life as i64 + b), "tian_cai"),
684 (wrap(body as i64 + b), "tian_shou"),
685 (wrap(WU as i64 + hour), "tai_fu"),
686 (wrap(YIN as i64 + hour), "feng_gao"),
687 ([SI, SHEN, YIN, HAI][m % 4], "tian_wu"),
688 (hua_gai, "hua_gai"),
689 (
690 [WEI, CHEN, SI, YIN, MAO, YOU, HAI, YOU, XU, WU][s],
691 "tian_guan",
692 ),
693 (
694 [YOU, SHEN, ZI, HAI, MAO, YIN, WU, SI, WU, SI][s],
695 "tian_fu_blessing",
696 ),
697 ([SI, WU, ZI, SI, WU, SHEN, YIN, WU, YOU, HAI][s], "tian_chu"),
698 (
699 [XU, SI, CHEN, YIN, WEI, MAO, HAI, WEI, YIN, WU, XU, YIN][m],
700 "tian_yue_moon",
701 ),
702 (wrap(YOU as i64 + b), "tian_de"),
703 (wrap(SI as i64 + b), "yue_de"),
704 (wrap(b + 1), "tian_kong"),
705 (xun_kong, "xun_kong"),
706 ([SHEN, WU, CHEN, YIN, ZI][s % 5], "jie_lu"),
707 ([YOU, WEI, SI, MAO, CHOU][s % 5], "kong_wang"),
708 (gu_chen, "gu_chen"),
709 (gua_su, "gua_su"),
710 (
711 [SHEN, YOU, XU, SI, WU, WEI, YIN, MAO, CHEN, HAI, ZI, CHOU][year_branch],
712 "fei_lian",
713 ),
714 ([SI, CHOU, YOU][year_branch % 3], "po_sui"),
715 (wrap(YOU as i64 + month), "tian_xing"),
716 ([YIN, ZI, XU, SHEN, WU, CHEN][m % 6], "yin_sha"),
717 (wrap(WU as i64 - b), "tian_ku"),
718 (wrap(WU as i64 + b), "tian_xu"),
719 (palace_at(5), "tian_shi"),
720 (palace_at(7), "tian_shang"),
721 (wrap(XU as i64 - b), "nian_jie"),
722 ]
723}
724
725pub fn zi_wei(
728 calendar: &ChineseCalendar,
729 birth: UtcInstant,
730 longitude: f64,
731 utc_offset_minutes: f64,
732 options: &ZiWeiOptions,
733) -> Result<ZiWei, EngineError> {
734 let split_leap = match options.leap_month {
735 "" | "split" => true,
736 "same" => false,
737 _ => return Err(invalid("leap_month must be \"split\" or \"same\"")),
738 };
739 let male = match options.sex {
740 None | Some("") => None,
741 Some("male") => Some(true),
742 Some("female") => Some(false),
743 Some(_) => return Err(invalid("sex must be \"male\" or \"female\"")),
744 };
745 let pillars = bazi(
746 calendar,
747 birth,
748 longitude,
749 utc_offset_minutes,
750 &BaziOptions {
751 solar_time: options.solar_time,
752 zi_hour: options.zi_hour,
753 sex: None,
754 luck_pillars: 0,
755 year: None,
756 },
757 )?;
758 let lunar = pillars
759 .lunar_date
760 .clone()
761 .ok_or_else(|| invalid("the calendar does not cover the birth date"))?;
762 let hour = BRANCHES
763 .iter()
764 .position(|b| *b == pillars.hour.branch)
765 .unwrap_or(ZI);
766 let month = if lunar.leap && split_leap && lunar.day > 15 {
767 lunar.month % 12 + 1
768 } else {
769 lunar.month
770 };
771 let year = year_cycle_index(lunar.year);
772 let (year_stem, year_branch) = (year % 10, year % 12);
773
774 let month_branch = YIN as i64 + i64::from(month) - 1;
776 let life = wrap(month_branch - hour as i64);
777 let body = wrap(month_branch + hour as i64);
778 let yin_stem = year_stem % 5 * 2 + 2;
779 let stem_of = |branch: usize| (yin_stem + wrap(branch as i64 - YIN as i64)) % 10;
780 let bureau = bureau_of(stem_of(life), life);
781
782 let mut placed: Vec<(usize, &'static str, &'static str)> = Vec::with_capacity(28);
784 let zi_wei_at = zi_wei_branch(bureau.number, lunar.day) as i64;
785 for (star, offset) in ZI_WEI_GROUP {
786 placed.push((wrap(zi_wei_at - offset as i64), star, "major"));
787 }
788 let tian_fu_at = 4 - zi_wei_at;
789 for (star, offset) in TIAN_FU_GROUP {
790 placed.push((wrap(tian_fu_at + offset as i64), star, "major"));
791 }
792 let (m, h) = (i64::from(month) - 1, hour as i64);
793 let (kui, yue) = kui_yue(year_stem);
794 let lu_cun = lu_cun_of(year_stem) as i64;
795 let horse = horse_of(year_branch);
796 let (fire, bell, small_start) = match trine_of(year_branch) {
797 0 => (YIN, XU, XU), 1 => (MAO, XU, WEI), 2 => (CHOU, MAO, CHEN), _ => (YOU, XU, CHOU), };
802 let anchors = [
803 wrap(CHEN as i64 + m),
804 wrap(XU as i64 - m),
805 wrap(XU as i64 - h),
806 wrap(CHEN as i64 + h),
807 ];
808 let [zuo, you, chang, qu] = anchors;
809 for (branch, star, kind) in [
810 (zuo, ZUO_FU, "auxiliary"),
811 (you, YOU_BI, "auxiliary"),
812 (chang, WEN_CHANG, "auxiliary"),
813 (qu, WEN_QU, "auxiliary"),
814 (kui, TIAN_KUI, "auxiliary"),
815 (yue, TIAN_YUE, "auxiliary"),
816 (wrap(lu_cun), LU_CUN, "auxiliary"),
817 (horse, TIAN_MA, "auxiliary"),
818 (wrap(lu_cun + 1), QING_YANG, "malefic"),
819 (wrap(lu_cun - 1), TUO_LUO, "malefic"),
820 (wrap(fire as i64 + h), HUO_XING, "malefic"),
821 (wrap(bell as i64 + h), LING_XING, "malefic"),
822 (wrap(HAI as i64 - h), DI_KONG, "malefic"),
823 (wrap(HAI as i64 + h), DI_JIE, "malefic"),
824 ] {
825 placed.push((branch, star, kind));
826 }
827
828 let transformed = TRANSFORMATIONS[year_stem];
829 let transformation_of = |star: &str| {
830 transformed
831 .iter()
832 .position(|s| *s == star)
833 .map(|k| TRANSFORMATION_KINDS[k])
834 };
835
836 let minor = minor_stars(
837 year_stem,
838 year_branch,
839 m,
840 i64::from(lunar.day),
841 h,
842 life,
843 body,
844 anchors,
845 );
846
847 let forward = male.map(|male| male == (year_stem % 2 == 0));
849 let palace_name = |branch: usize| PALACES[wrap(life as i64 - branch as i64)];
850 let star_branch = |star: &str| placed.iter().find(|(_, s, _)| *s == star).map(|p| p.0);
851 let transformations_of = |stem: usize| -> Vec<ZiWeiTransformation> {
852 TRANSFORMATIONS[stem]
853 .iter()
854 .zip(TRANSFORMATION_KINDS)
855 .filter_map(|(&star, kind)| {
856 Some(ZiWeiTransformation {
857 kind,
858 star,
859 palace: palace_name(star_branch(star)?),
860 })
861 })
862 .collect()
863 };
864 let life_stages = forward.map(|forward| {
865 let start = match bureau.number {
866 2 | 5 => SHEN,
867 3 => HAI,
868 4 => SI,
869 _ => YIN,
870 };
871 cycle(&LIFE_STAGES, start, forward)
872 });
873 let boshi = forward.map(|forward| cycle(&BOSHI, lu_cun as usize, forward));
874 let (suiqian, jiangqian) = yearly_gods(year_branch);
875 let god_at = |gods: &[ZiWeiGod], branch: usize| {
876 gods.iter()
877 .find(|g| g.branch == BRANCHES[branch])
878 .map(|g| g.god)
879 .unwrap_or_default()
880 };
881
882 let palaces: Vec<ZiWeiPalace> = (0..12)
883 .map(|i| {
884 let branch = wrap(life as i64 - i);
885 let stars = placed
886 .iter()
887 .filter(|(b, ..)| *b == branch)
888 .map(|&(_, star, kind)| ZiWeiStar {
889 star,
890 kind,
891 brightness: brightness(star, branch),
892 transformation: transformation_of(star),
893 })
894 .collect();
895 let decade = forward.map(|forward| {
896 let steps = if forward {
897 wrap(branch as i64 - life as i64)
898 } else {
899 wrap(life as i64 - branch as i64)
900 } as u8;
901 let start = bureau.number + 10 * steps;
902 AgeRange {
903 start,
904 end: start + 9,
905 }
906 });
907 let small_limit_ages = male
908 .map(|male| {
909 let steps = if male {
910 wrap(branch as i64 - small_start as i64)
911 } else {
912 wrap(small_start as i64 - branch as i64)
913 } as u8;
914 (0..10).map(|j| steps + 1 + 12 * j).collect()
915 })
916 .unwrap_or_default();
917 ZiWeiPalace {
918 name: PALACES[i as usize],
919 branch: BRANCHES[branch],
920 stem: STEMS[stem_of(branch)],
921 is_body: branch == body,
922 stars,
923 decade,
924 small_limit_ages,
925 minor_stars: minor
926 .iter()
927 .filter(|(b, _)| *b == branch)
928 .map(|&(_, star)| star)
929 .collect(),
930 gods: ZiWeiGods {
931 life_stage: life_stages.as_ref().map(|g| god_at(g, branch)),
932 boshi: boshi.as_ref().map(|g| god_at(g, branch)),
933 suiqian: god_at(&suiqian, branch),
934 jiangqian: god_at(&jiangqian, branch),
935 },
936 flying: transformations_of(stem_of(branch)),
937 }
938 })
939 .collect();
940
941 let transformations = transformations_of(year_stem);
942
943 let horoscope = options.year.map(|target| {
945 let age = target - lunar.year + 1;
946 let period = |branch: usize, stem: usize, flow_branch: usize| ZiWeiPeriod {
947 branch: BRANCHES[branch],
948 stem: STEMS[stem],
949 palaces: (0..12)
950 .map(|k| ZiWeiGod {
951 god: PALACES[k],
952 branch: BRANCHES[wrap(branch as i64 - k as i64)],
953 })
954 .collect(),
955 transformations: transformations_of(stem),
956 stars: flow_stars(stem, flow_branch),
957 };
958 let decade_branch = palaces.iter().find_map(|p: &ZiWeiPalace| {
959 let d = p.decade.as_ref()?;
960 (i32::from(d.start) <= age && age <= i32::from(d.end)).then_some(p.branch)
961 });
962 let childhood = forward.is_some()
963 && decade_branch.is_none()
964 && (1..i32::from(bureau.number)).contains(&age)
965 && age <= 6;
966 let decade_at = decade_branch
967 .and_then(|b| BRANCHES.iter().position(|x| *x == b))
968 .or_else(|| {
969 childhood.then(|| {
970 let name = ["life", "wealth", "health", "spouse", "fortune", "career"]
971 [(age - 1) as usize];
972 wrap(life as i64 - PALACES.iter().position(|p| *p == name).unwrap_or(0) as i64)
973 })
974 });
975 let small_limit = palaces
976 .iter()
977 .find(|p: &&ZiWeiPalace| {
978 u8::try_from(age).is_ok_and(|a| p.small_limit_ages.contains(&a))
979 })
980 .map(|p| p.branch);
981 let cycle_index = year_cycle_index(target);
982 let (y_stem, y_branch) = (cycle_index % 10, cycle_index % 12);
983 let (suiqian, jiangqian) = yearly_gods(y_branch);
984 ZiWeiHoroscope {
985 year: target,
986 nominal_age: age,
987 decade: decade_at.map(|b| period(b, stem_of(b), b)),
988 childhood: childhood && decade_branch.is_none(),
989 small_limit,
990 yearly: period(y_branch, y_stem, y_branch),
991 suiqian,
992 jiangqian,
993 }
994 });
995
996 Ok(ZiWei {
997 time_basis: pillars.time_basis,
998 zi_hour: pillars.zi_hour,
999 lunar_date: lunar,
1000 month,
1001 hour_branch: BRANCHES[hour],
1002 life_palace: BRANCHES[life],
1003 body_palace: BRANCHES[body],
1004 bureau,
1005 life_master: LIFE_MASTERS[life],
1006 body_master: BODY_MASTERS[year_branch],
1007 palaces,
1008 transformations,
1009 decade_direction: forward.map(|f| if f { "forward" } else { "backward" }),
1010 horoscope,
1011 })
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016 use super::*;
1017
1018 #[test]
1019 fn zi_wei_from_the_bureau_and_the_day() {
1020 assert_eq!(BRANCHES[zi_wei_branch(3, 27)], "xu");
1023 assert_eq!(BRANCHES[zi_wei_branch(6, 13)], "hai");
1024 assert_eq!(BRANCHES[zi_wei_branch(5, 6)], "wei");
1025 assert_eq!(BRANCHES[zi_wei_branch(2, 1)], "chou");
1026 assert_eq!(BRANCHES[zi_wei_branch(2, 2)], "yin");
1027 assert_eq!(BRANCHES[zi_wei_branch(6, 1)], "you");
1028 }
1029
1030 #[test]
1031 fn the_bureau_is_the_nayin_of_the_life_palace() {
1032 assert_eq!(
1034 bureau_of(9, 7),
1035 Bureau {
1036 element: "wood",
1037 number: 3
1038 }
1039 );
1040 assert_eq!(bureau_of(0, 0).number, 4);
1042 assert_eq!(bureau_of(2, 0).number, 2);
1044 }
1045}