use crate::data::stars::StarKey;
use crate::data::types::{EarthlyBranch, Mutagen};
use crate::models::star::Star;
use crate::pattern::view::{BRIGHT, KONG_WANG};
use crate::pattern::{ChartView, PatternHit, PatternKey, Rule};
pub static RULES: &[Rule] = &[
Rule {
key: PatternKey::YangTuoJiaMing,
detect: yang_tuo_jia_ming,
},
Rule {
key: PatternKey::MaTouDaiJian,
detect: ma_tou_dai_jian,
},
Rule {
key: PatternKey::ZuoYouTongGong,
detect: zuo_you_tong_gong,
},
Rule {
key: PatternKey::ZuoYouJiaMing,
detect: zuo_you_jia_ming,
},
Rule {
key: PatternKey::FuBiGongZhu,
detect: fu_bi_gong_zhu,
},
Rule {
key: PatternKey::KuiYueJiaMing,
detect: kui_yue_jia_ming,
},
Rule {
key: PatternKey::ZuoGuiXiangGui,
detect: zuo_gui_xiang_gui,
},
Rule {
key: PatternKey::JieKongJiaMing,
detect: jie_kong_jia_ming,
},
Rule {
key: PatternKey::LuFengLiangSha,
detect: lu_feng_liang_sha,
},
Rule {
key: PatternKey::WenGuiWenHua,
detect: wen_gui_wen_hua,
},
Rule {
key: PatternKey::WenXingChaoMing,
detect: wen_xing_chao_ming,
},
Rule {
key: PatternKey::ChangQuJiaMing,
detect: chang_qu_jia_ming,
},
Rule {
key: PatternKey::WenXingAnGong,
detect: wen_xing_an_gong,
},
Rule {
key: PatternKey::QuanLuShengFeng,
detect: quan_lu_sheng_feng,
},
Rule {
key: PatternKey::KeMingAnLu,
detect: ke_ming_an_lu,
},
Rule {
key: PatternKey::KeQuanLuJia,
detect: ke_quan_lu_jia,
},
Rule {
key: PatternKey::JiaDiDengYong,
detect: jia_di_deng_yong,
},
];
type StarPos<'a> = (usize, &'a Star);
fn jia_pair<'a>(
v: &ChartView<'a>,
i: usize,
a: StarKey,
b: StarKey,
) -> Option<(StarPos<'a>, StarPos<'a>)> {
let (pa, pb) = v.jia(i, &[a], &[b])?;
Some(((pa, v.find(pa, a)?), (pb, v.find(pb, b)?)))
}
fn tong_yin<'a>(v: &ChartView<'a>, i: usize) -> Option<(usize, &'a Star, &'a Star)> {
let at = |p: usize| {
Some((
p,
v.find(p, StarKey::TiantongMaj)?,
v.find(p, StarKey::TaiyinMaj)?,
))
};
at(i).or_else(|| {
(v.config().borrow && v.is_empty(i))
.then(|| at(v.opposite(i)))
.flatten()
})
}
pub fn yang_tuo_jia_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let (p, n) = (v.prev(s), v.next(s));
let (Some(tuo), Some(yang)) = (
v.find(p, StarKey::TuoluoMin),
v.find(n, StarKey::QingyangMin),
) else {
return vec![];
};
let mut stars = vec![(p, tuo), (n, yang)];
if let Some(lu) = v.find(s, StarKey::LucunMin) {
stars.insert(0, (s, lu));
}
vec![v.hit(PatternKey::YangTuoJiaMing, s, stars)]
}
pub fn ma_tou_dai_jian(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
if v.branch(s) != EarthlyBranch::Wu {
return vec![];
}
let Some(yang) = v.find(s, StarKey::QingyangMin) else {
return vec![];
};
if let Some((p, tong, yin)) = tong_yin(v, s) {
return vec![v.hit(
PatternKey::MaTouDaiJian,
s,
vec![(s, yang), (p, tong), (p, yin)],
)];
}
match v.find(s, StarKey::TanlangMaj) {
Some(tan) if v.mutagen_of(tan) == Some(Mutagen::Lu) => {
let mut hit = v.hit(PatternKey::MaTouDaiJian, s, vec![(s, yang), (s, tan)]);
hit.variant = Some("tanlang_lu");
vec![hit]
}
_ => vec![],
}
}
pub fn zuo_you_tong_gong(v: &ChartView) -> Vec<PatternHit> {
v.soul_and_body()
.into_iter()
.filter_map(|i| {
let (zuo, you) = (v.find(i, StarKey::ZuofuMin)?, v.find(i, StarKey::YoubiMin)?);
Some(v.hit(PatternKey::ZuoYouTongGong, i, vec![(i, zuo), (i, you)]))
})
.collect()
}
pub fn zuo_you_jia_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match jia_pair(v, s, StarKey::ZuofuMin, StarKey::YoubiMin) {
Some((zuo, you)) => vec![v.hit(PatternKey::ZuoYouJiaMing, s, vec![zuo, you])],
None => vec![],
}
}
pub fn fu_bi_gong_zhu(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let Some(zi) = v.find(s, StarKey::ZiweiMaj) else {
return vec![];
};
let found = match (
v.find_in_surround(s, StarKey::ZuofuMin),
v.find_in_surround(s, StarKey::YoubiMin),
) {
(Some(zuo), Some(you)) => Some(("surround", zuo, you)),
_ => {
jia_pair(v, s, StarKey::ZuofuMin, StarKey::YoubiMin).map(|(zuo, you)| ("jia", zuo, you))
}
};
let Some((variant, zuo, you)) = found else {
return vec![];
};
let mut hit = v.hit(PatternKey::FuBiGongZhu, s, vec![(s, zi), zuo, you]);
hit.variant = Some(variant);
vec![hit]
}
pub fn kui_yue_jia_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match jia_pair(v, s, StarKey::TiankuiMin, StarKey::TianyueMin) {
Some((kui, yue)) => vec![v.hit(PatternKey::KuiYueJiaMing, s, vec![kui, yue])],
None => vec![],
}
}
pub fn zuo_gui_xiang_gui(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let o = v.opposite(s);
for (a, b) in [
(StarKey::TiankuiMin, StarKey::TianyueMin),
(StarKey::TianyueMin, StarKey::TiankuiMin),
] {
if let (Some(x), Some(y)) = (v.find(s, a), v.find(o, b)) {
return vec![v.hit(PatternKey::ZuoGuiXiangGui, s, vec![(s, x), (o, y)])];
}
}
vec![]
}
pub fn jie_kong_jia_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match jia_pair(v, s, StarKey::DijieMin, StarKey::DikongMin) {
Some((jie, kong)) => vec![v.hit(PatternKey::JieKongJiaMing, s, vec![jie, kong])],
None => vec![],
}
}
pub fn lu_feng_liang_sha(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let Some(lu) = v.find(s, StarKey::LucunMin) else {
return vec![];
};
let Some(kong) = KONG_WANG.iter().find_map(|k| v.find(s, *k)) else {
return vec![];
};
let Some(sha) = [StarKey::DikongMin, StarKey::DijieMin]
.into_iter()
.find_map(|k| v.find_in_surround(s, k))
else {
return vec![];
};
vec![v.hit(PatternKey::LuFengLiangSha, s, vec![(s, lu), (s, kong), sha])]
}
pub fn wen_gui_wen_hua(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let mut candidates = v.soul_and_body();
for p in v.surround(s) {
if !candidates.contains(&p) {
candidates.push(p);
}
}
candidates
.into_iter()
.filter_map(|i| {
let (chang, qu) = (
v.find(i, StarKey::WenchangMin)?,
v.find(i, StarKey::WenquMin)?,
);
Some(v.hit(PatternKey::WenGuiWenHua, i, vec![(i, chang), (i, qu)]))
})
.collect()
}
pub fn wen_xing_chao_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let (Some(chang), Some(qu)) = (
v.find_in_surround(s, StarKey::WenchangMin),
v.find_in_surround(s, StarKey::WenquMin),
) else {
return vec![];
};
let mut hit = v.hit(PatternKey::WenXingChaoMing, s, vec![chang, qu]);
hit.broken = !v.no_sha(s);
vec![hit]
}
pub fn chang_qu_jia_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let Some((chang, qu)) = jia_pair(v, s, StarKey::WenchangMin, StarKey::WenquMin) else {
return vec![];
};
let mut hit = v.hit(PatternKey::ChangQuJiaMing, s, vec![chang, qu]);
hit.broken = !v.no_sha(s);
vec![hit]
}
pub fn wen_xing_an_gong(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let mut hits = Vec::new();
let mut push = |variant, stars| {
let mut hit = v.hit(PatternKey::WenXingAnGong, s, stars);
hit.variant = Some(variant);
hits.push(hit);
};
if let Some((chang, qu)) = jia_pair(v, s, StarKey::WenchangMin, StarKey::WenquMin) {
push("jia", vec![chang, qu]);
}
let o = v.opposite(s);
if let (Some(chang), Some(qu)) = (
v.find(o, StarKey::WenchangMin),
v.find(o, StarKey::WenquMin),
) {
push("opposite", vec![(o, chang), (o, qu)]);
}
if let (Some(chang), Some(qu)) = (
v.find_in_surround(s, StarKey::WenchangMin),
v.find_in_surround(s, StarKey::WenquMin),
) {
push("surround", vec![chang, qu]);
}
hits
}
pub fn quan_lu_sheng_feng(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let (Some(quan), Some(lu)) = (
v.mutagen_star(s, Mutagen::Quan),
v.mutagen_star(s, Mutagen::Lu),
) else {
return vec![];
};
if !quan.with_brightness(&BRIGHT) || !lu.with_brightness(&BRIGHT) {
return vec![];
}
vec![v.hit(PatternKey::QuanLuShengFeng, s, vec![(s, quan), (s, lu)])]
}
pub fn ke_ming_an_lu(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let h = v.hidden(s);
let Some(ke) = v.mutagen_star(s, Mutagen::Ke) else {
return vec![];
};
let mut hits = Vec::new();
if let Some(lu) = v.find(h, StarKey::LucunMin) {
hits.push(v.hit(PatternKey::KeMingAnLu, s, vec![(s, ke), (h, lu)]));
}
if let Some(hua) = v.mutagen_star(h, Mutagen::Lu) {
let mut hit = v.hit(PatternKey::KeMingAnLu, s, vec![(s, ke), (h, hua)]);
hit.variant = Some("hua_lu");
hits.push(hit);
}
hits
}
pub fn ke_quan_lu_jia(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let (p, n) = (v.prev(s), v.next(s));
let trio = [Mutagen::Lu, Mutagen::Quan, Mutagen::Ke];
for a in trio {
for b in trio {
if a == b {
continue;
}
if let (Some(x), Some(y)) = (v.mutagen_star(p, a), v.mutagen_star(n, b)) {
return vec![v.hit(PatternKey::KeQuanLuJia, s, vec![(p, x), (n, y)])];
}
}
}
vec![]
}
pub fn jia_di_deng_yong(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let Some(ke) = v.mutagen_star(s, Mutagen::Ke) else {
return vec![];
};
let [t1, t2] = v.trine(s);
let Some(quan) = [v.opposite(s), t1, t2]
.into_iter()
.find_map(|p| v.mutagen_star(p, Mutagen::Quan).map(|star| (p, star)))
else {
return vec![];
};
let lu = v.find_mutagen_in_surround(s, Mutagen::Lu).or_else(|| {
v.surround(s)
.into_iter()
.find_map(|p| v.find(p, StarKey::LucunMin).map(|star| (p, star)))
});
let mut stars = vec![(s, ke), quan];
stars.extend(lu);
let mut hit = v.hit(PatternKey::JiaDiDengYong, s, stars);
if lu.is_some() {
hit.variant = Some("complete");
}
vec![hit]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::astrolabe::Astrolabe;
use crate::pattern::PatternConfig;
use crate::pattern::testutil::find_chart;
fn view(a: &Astrolabe) -> ChartView<'_> {
ChartView::natal(a, &PatternConfig::default())
}
fn evidence(hit: &PatternHit, star: StarKey, palace: usize) -> bool {
hit.stars
.iter()
.any(|s| s.star == star && s.palace == palace)
}
fn quan_lu_pair<'a>(v: &ChartView<'a>) -> Option<(&'a Star, &'a Star)> {
let s = v.soul();
v.mutagen_star(s, Mutagen::Quan)
.zip(v.mutagen_star(s, Mutagen::Lu))
}
fn trio_count(v: &ChartView, i: usize) -> usize {
[Mutagen::Lu, Mutagen::Quan, Mutagen::Ke]
.into_iter()
.filter(|m| v.has_mutagen(i, *m))
.count()
}
#[test]
fn yang_tuo_jia_ming_follows_lucun_in_soul() {
let a = find_chart(|a| {
let v = view(a);
v.has(v.soul(), StarKey::LucunMin)
});
let v = view(&a);
let s = v.soul();
let hits = yang_tuo_jia_ming(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, s);
assert!(evidence(hit, StarKey::LucunMin, s));
assert!(evidence(hit, StarKey::TuoluoMin, v.prev(s)));
assert!(evidence(hit, StarKey::QingyangMin, v.next(s)));
let b = find_chart(|a| {
let v = view(a);
!v.has(v.soul(), StarKey::LucunMin)
});
assert!(yang_tuo_jia_ming(&view(&b)).is_empty());
}
#[test]
fn ma_tou_dai_jian_covers_tong_yin_and_tanlang_lu() {
let wu_yang = |v: &ChartView| {
let s = v.soul();
v.branch(s) == EarthlyBranch::Wu && v.has(s, StarKey::QingyangMin)
};
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
wu_yang(&v) && v.has(s, StarKey::TiantongMaj) && v.has(s, StarKey::TaiyinMaj)
});
let v = view(&a);
let hits = ma_tou_dai_jian(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
assert_eq!(hit.variant, None);
assert!(evidence(hit, StarKey::QingyangMin, v.soul()));
assert!(evidence(hit, StarKey::TiantongMaj, v.soul()));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
wu_yang(&v)
&& v.find(s, StarKey::TanlangMaj)
.is_some_and(|t| v.mutagen_of(t) == Some(Mutagen::Lu))
});
let v = view(&b);
let hits = ma_tou_dai_jian(&v);
assert_eq!(hits.first().expect("hit").variant, Some("tanlang_lu"));
let c = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.branch(s) == EarthlyBranch::Wu
&& v.has(s, StarKey::TiantongMaj)
&& v.has(s, StarKey::TaiyinMaj)
&& !v.has(s, StarKey::QingyangMin)
});
assert!(ma_tou_dai_jian(&view(&c)).is_empty());
}
#[test]
fn zuo_you_tong_gong_hits_soul_and_body() {
let a = find_chart(|a| {
let v = view(a);
v.has(v.soul(), StarKey::ZuofuMin) && v.has(v.soul(), StarKey::YoubiMin)
});
let v = view(&a);
let hits = zuo_you_tong_gong(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
assert!(evidence(hit, StarKey::ZuofuMin, v.soul()));
assert!(evidence(hit, StarKey::YoubiMin, v.soul()));
let b = find_chart(|a| {
let v = view(a);
matches!(v.body(), Some(b) if b != v.soul()
&& v.has(b, StarKey::ZuofuMin) && v.has(b, StarKey::YoubiMin))
});
let v = view(&b);
let hits = zuo_you_tong_gong(&v);
assert_eq!(hits.first().expect("hit").palace, v.body().unwrap());
let c = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::ZuofuMin) && !v.has(s, StarKey::YoubiMin)
});
assert!(zuo_you_tong_gong(&view(&c)).is_empty());
}
#[test]
fn zuo_you_jia_ming_needs_both_neighbours() {
let a = find_chart(|a| {
let v = view(a);
v.jia(v.soul(), &[StarKey::ZuofuMin], &[StarKey::YoubiMin])
.is_some()
});
let v = view(&a);
let s = v.soul();
let hits = zuo_you_jia_ming(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, s);
let palaces: Vec<_> = hit.stars.iter().map(|x| x.palace).collect();
assert!(palaces.contains(&v.prev(s)) && palaces.contains(&v.next(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(v.prev(s), StarKey::ZuofuMin)
&& v.jia(s, &[StarKey::ZuofuMin], &[StarKey::YoubiMin])
.is_none()
});
assert!(zuo_you_jia_ming(&view(&b)).is_empty());
}
#[test]
fn fu_bi_gong_zhu_reports_surround_and_jia() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::ZiweiMaj)
&& v.in_surround(s, StarKey::ZuofuMin)
&& v.in_surround(s, StarKey::YoubiMin)
});
let v = view(&a);
let hits = fu_bi_gong_zhu(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.variant, Some("surround"));
assert!(evidence(hit, StarKey::ZiweiMaj, v.soul()));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::ZiweiMaj)
&& v.jia(s, &[StarKey::ZuofuMin], &[StarKey::YoubiMin])
.is_some()
});
let v = view(&b);
assert_eq!(
fu_bi_gong_zhu(&v).first().expect("hit").variant,
Some("jia")
);
let c = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::ZiweiMaj)
&& v.in_surround(s, StarKey::ZuofuMin)
&& !v.in_surround(s, StarKey::YoubiMin)
});
assert!(fu_bi_gong_zhu(&view(&c)).is_empty());
}
#[test]
fn kui_yue_jia_ming_needs_adjacent_pair() {
let a = find_chart(|a| {
let v = view(a);
v.jia(v.soul(), &[StarKey::TiankuiMin], &[StarKey::TianyueMin])
.is_some()
});
let v = view(&a);
let s = v.soul();
let hits = kui_yue_jia_ming(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, s);
let palaces: Vec<_> = hit.stars.iter().map(|x| x.palace).collect();
assert!(palaces.contains(&v.prev(s)) && palaces.contains(&v.next(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(v.prev(s), StarKey::TiankuiMin) && !v.has(v.next(s), StarKey::TianyueMin)
});
assert!(kui_yue_jia_ming(&view(&b)).is_empty());
}
#[test]
fn zuo_gui_xiang_gui_needs_soul_surface_axis() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::TiankuiMin) && v.has(v.opposite(s), StarKey::TianyueMin)
});
let v = view(&a);
let s = v.soul();
let hit = zuo_gui_xiang_gui(&v).pop().expect("hit");
assert!(evidence(&hit, StarKey::TiankuiMin, s));
assert!(evidence(&hit, StarKey::TianyueMin, v.opposite(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::TiankuiMin) && !v.has(v.opposite(s), StarKey::TianyueMin)
});
assert!(zuo_gui_xiang_gui(&view(&b)).is_empty());
}
#[test]
fn jie_kong_jia_ming_needs_both_neighbours() {
let a = find_chart(|a| {
let v = view(a);
v.jia(v.soul(), &[StarKey::DijieMin], &[StarKey::DikongMin])
.is_some()
});
let v = view(&a);
let s = v.soul();
let hit = jie_kong_jia_ming(&v).pop().expect("hit");
assert_eq!(hit.palace, s);
let palaces: Vec<_> = hit.stars.iter().map(|x| x.palace).collect();
assert!(palaces.contains(&v.prev(s)) && palaces.contains(&v.next(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has_any(v.prev(s), &[StarKey::DikongMin, StarKey::DijieMin])
&& v.jia(s, &[StarKey::DijieMin], &[StarKey::DikongMin])
.is_none()
});
assert!(jie_kong_jia_ming(&view(&b)).is_empty());
}
#[test]
fn lu_feng_liang_sha_needs_kongwang_and_jiekong() {
let has_kongwang = |v: &ChartView, i: usize| v.has_any(i, &KONG_WANG);
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::LucunMin)
&& has_kongwang(&v, s)
&& v.surround_has_any(s, &[StarKey::DikongMin, StarKey::DijieMin])
});
let v = view(&a);
let s = v.soul();
let hit = lu_feng_liang_sha(&v).pop().expect("hit");
assert_eq!(hit.palace, s);
assert!(evidence(&hit, StarKey::LucunMin, s));
assert_eq!(hit.stars.len(), 3);
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::LucunMin)
&& has_kongwang(&v, s)
&& !v.surround_has_any(s, &[StarKey::DikongMin, StarKey::DijieMin])
});
assert!(lu_feng_liang_sha(&view(&b)).is_empty());
}
#[test]
fn wen_gui_wen_hua_covers_soul_body_and_surround() {
let together = |v: &ChartView| {
(0..12).find(|i| v.has(*i, StarKey::WenchangMin) && v.has(*i, StarKey::WenquMin))
};
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
together(&v)
.is_some_and(|i| v.surround(s).contains(&i) || v.soul_and_body().contains(&i))
});
let v = view(&a);
let hit = wen_gui_wen_hua(&v).pop().expect("hit");
assert_eq!(Some(hit.palace), together(&v));
assert!(evidence(&hit, StarKey::WenchangMin, hit.palace));
assert!(evidence(&hit, StarKey::WenquMin, hit.palace));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
together(&v)
.is_some_and(|i| !v.surround(s).contains(&i) && !v.soul_and_body().contains(&i))
});
assert!(wen_gui_wen_hua(&view(&b)).is_empty());
}
#[test]
fn wen_xing_chao_ming_needs_both_in_surround() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.in_surround(s, StarKey::WenchangMin) && v.in_surround(s, StarKey::WenquMin)
});
let v = view(&a);
let s = v.soul();
let hit = wen_xing_chao_ming(&v).pop().expect("hit");
assert_eq!(hit.palace, s);
assert_eq!(hit.broken, !v.no_sha(s));
assert_eq!(hit.stars.len(), 2);
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.in_surround(s, StarKey::WenchangMin) && !v.in_surround(s, StarKey::WenquMin)
});
assert!(wen_xing_chao_ming(&view(&b)).is_empty());
}
#[test]
fn chang_qu_jia_ming_needs_both_neighbours() {
let a = find_chart(|a| {
let v = view(a);
v.jia(v.soul(), &[StarKey::WenchangMin], &[StarKey::WenquMin])
.is_some()
});
let v = view(&a);
let s = v.soul();
let hit = chang_qu_jia_ming(&v).pop().expect("hit");
assert_eq!(hit.palace, s);
assert_eq!(hit.broken, !v.no_sha(s));
assert!(
wen_xing_an_gong(&v)
.iter()
.any(|h| h.variant == Some("jia"))
);
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(v.prev(s), StarKey::WenchangMin)
&& v.jia(s, &[StarKey::WenchangMin], &[StarKey::WenquMin])
.is_none()
});
assert!(chang_qu_jia_ming(&view(&b)).is_empty());
}
#[test]
fn wen_xing_an_gong_reports_jia_opposite_and_surround() {
let a = find_chart(|a| {
let v = view(a);
let o = v.opposite(v.soul());
v.has(o, StarKey::WenchangMin) && v.has(o, StarKey::WenquMin)
});
let v = view(&a);
let o = v.opposite(v.soul());
let hits = wen_xing_an_gong(&v);
let opp = hits
.iter()
.find(|h| h.variant == Some("opposite"))
.expect("opposite");
assert_eq!(opp.palace, v.soul());
assert!(opp.stars.iter().all(|x| x.palace == o));
assert!(hits.iter().any(|h| h.variant == Some("surround")));
let b = find_chart(|a| {
let v = view(a);
let [t1, t2] = v.trine(v.soul());
(v.has(t1, StarKey::WenchangMin) && v.has(t2, StarKey::WenquMin))
|| (v.has(t1, StarKey::WenquMin) && v.has(t2, StarKey::WenchangMin))
});
let v = view(&b);
let [t1, t2] = v.trine(v.soul());
let hits = wen_xing_an_gong(&v);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].variant, Some("surround"));
let palaces: Vec<_> = hits[0].stars.iter().map(|x| x.palace).collect();
assert!(palaces.contains(&t1) && palaces.contains(&t2));
let c = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has(s, StarKey::WenchangMin) && v.has(s, StarKey::WenquMin)
});
let v = view(&c);
let hits = wen_xing_an_gong(&v);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].variant, Some("surround"));
assert!(hits[0].stars.iter().all(|x| x.palace == v.soul()));
let d = find_chart(|a| {
let v = view(a);
v.jia(v.soul(), &[StarKey::WenchangMin], &[StarKey::WenquMin])
.is_some()
});
let hits = wen_xing_an_gong(&view(&d));
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].variant, Some("jia"));
}
#[test]
fn quan_lu_sheng_feng_requires_both_bright() {
let a = find_chart(|a| {
let v = view(a);
quan_lu_pair(&v)
.is_some_and(|(q, l)| q.with_brightness(&BRIGHT) && l.with_brightness(&BRIGHT))
});
let v = view(&a);
let hit = quan_lu_sheng_feng(&v).pop().expect("hit");
assert_eq!(hit.palace, v.soul());
assert_eq!(hit.stars.len(), 2);
let b = find_chart(|a| {
let v = view(a);
quan_lu_pair(&v)
.is_some_and(|(q, l)| !(q.with_brightness(&BRIGHT) && l.with_brightness(&BRIGHT)))
});
assert!(quan_lu_sheng_feng(&view(&b)).is_empty());
}
#[test]
fn ke_ming_an_lu_uses_hidden_palace_lucun() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has_mutagen(s, Mutagen::Ke) && v.has(v.hidden(s), StarKey::LucunMin)
});
let v = view(&a);
let s = v.soul();
let hit = ke_ming_an_lu(&v)
.into_iter()
.find(|h| h.variant.is_none())
.expect("hit");
assert_eq!(hit.palace, s);
assert!(evidence(&hit, StarKey::LucunMin, v.hidden(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has_mutagen(s, Mutagen::Ke)
&& !v.has(v.hidden(s), StarKey::LucunMin)
&& !v.has_mutagen(v.hidden(s), Mutagen::Lu)
});
assert!(ke_ming_an_lu(&view(&b)).is_empty());
}
#[test]
fn ke_ming_an_lu_hua_lu_variant() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
v.has_mutagen(s, Mutagen::Ke)
&& !v.has(v.hidden(s), StarKey::LucunMin)
&& v.has_mutagen(v.hidden(s), Mutagen::Lu)
&& !v.has(s, StarKey::LucunMin)
&& !v.has_mutagen(s, Mutagen::Lu)
});
let v = view(&a);
let s = v.soul();
let hits = ke_ming_an_lu(&v);
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].variant, Some("hua_lu"));
assert_eq!(hits[0].stars[1].palace, v.hidden(s));
assert_eq!(hits[0].stars[1].mutagen, Some(Mutagen::Lu));
assert!(!a.patterns().iter().any(|h| h.key == PatternKey::MingLuAnLu));
}
#[test]
fn ke_quan_lu_jia_needs_two_sides() {
let a = find_chart(|a| {
let v = view(a);
let s = v.soul();
trio_count(&v, v.prev(s)) > 0 && trio_count(&v, v.next(s)) > 0
});
let v = view(&a);
let s = v.soul();
let hit = ke_quan_lu_jia(&v).pop().expect("hit");
assert_eq!(hit.palace, s);
let palaces: Vec<_> = hit.stars.iter().map(|x| x.palace).collect();
assert!(palaces.contains(&v.prev(s)) && palaces.contains(&v.next(s)));
let b = find_chart(|a| {
let v = view(a);
let s = v.soul();
trio_count(&v, v.prev(s)) >= 2 && trio_count(&v, v.next(s)) == 0
});
assert!(ke_quan_lu_jia(&view(&b)).is_empty());
}
#[test]
fn jia_di_deng_yong_marks_complete_when_lu_joins() {
let quan_chao = |v: &ChartView| {
let s = v.soul();
let [t1, t2] = v.trine(s);
v.has_mutagen(s, Mutagen::Ke)
&& [v.opposite(s), t1, t2]
.iter()
.any(|p| v.has_mutagen(*p, Mutagen::Quan))
};
let with_lu = |v: &ChartView| {
let s = v.soul();
v.find_mutagen_in_surround(s, Mutagen::Lu).is_some()
|| v.in_surround(s, StarKey::LucunMin)
};
let a = find_chart(|a| {
let v = view(a);
quan_chao(&v) && !with_lu(&v)
});
let v = view(&a);
let hit = jia_di_deng_yong(&v).pop().expect("hit");
assert_eq!(hit.palace, v.soul());
assert_eq!(hit.variant, None);
assert_eq!(hit.stars.len(), 2);
let b = find_chart(|a| {
let v = view(a);
quan_chao(&v) && with_lu(&v)
});
let hit = jia_di_deng_yong(&view(&b)).pop().expect("hit");
assert_eq!(hit.variant, Some("complete"));
assert_eq!(hit.stars.len(), 3);
let c = find_chart(|a| {
let v = view(a);
let s = v.soul();
let [t1, t2] = v.trine(s);
v.has_mutagen(s, Mutagen::Ke)
&& v.has_mutagen(s, Mutagen::Quan)
&& ![v.opposite(s), t1, t2]
.iter()
.any(|p| v.has_mutagen(*p, Mutagen::Quan))
});
assert!(jia_di_deng_yong(&view(&c)).is_empty());
}
}