use serde::{Deserialize, Serialize};
use crate::data::heavenly_stems::get_heavenly_stem_info;
use crate::data::stars::StarKey;
use crate::data::types::*;
use crate::models::star::Star;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Decadal {
pub range: (u32, u32),
pub heavenly_stem: HeavenlyStem,
pub earthly_branch: EarthlyBranch,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PalaceData {
pub index: usize,
pub name: Palace,
pub is_body_palace: bool,
pub is_original_palace: bool,
pub heavenly_stem: HeavenlyStem,
pub earthly_branch: EarthlyBranch,
pub major_stars: Vec<Star>,
pub minor_stars: Vec<Star>,
pub adjective_stars: Vec<Star>,
pub changsheng12: StarKey,
pub boshi12: StarKey,
pub jiangqian12: StarKey,
pub suiqian12: StarKey,
pub decadal: Decadal,
pub ages: Vec<u32>,
#[serde(skip)]
pub overrides: Option<std::sync::Arc<TableOverrides>>,
}
impl PalaceData {
fn all_star_keys(&self) -> Vec<StarKey> {
let mut keys = Vec::new();
for s in &self.major_stars {
keys.push(s.key);
}
for s in &self.minor_stars {
keys.push(s.key);
}
for s in &self.adjective_stars {
keys.push(s.key);
}
keys
}
pub fn has(&self, stars: &[StarKey]) -> bool {
let all_keys = self.all_star_keys();
stars.iter().all(|s| all_keys.contains(s))
}
pub fn not_have(&self, stars: &[StarKey]) -> bool {
let all_keys = self.all_star_keys();
stars.iter().all(|s| !all_keys.contains(s))
}
pub fn has_one_of(&self, stars: &[StarKey]) -> bool {
let all_keys = self.all_star_keys();
stars.iter().any(|s| all_keys.contains(s))
}
pub fn has_mutagen(&self, mutagen: Mutagen) -> bool {
self.major_stars
.iter()
.chain(self.minor_stars.iter())
.any(|s| s.mutagen == Some(mutagen))
}
pub fn not_have_mutagen(&self, mutagen: Mutagen) -> bool {
!self.has_mutagen(mutagen)
}
pub fn is_empty(&self) -> bool {
self.is_empty_excluding(&[])
}
pub fn is_empty_excluding(&self, exclude_stars: &[StarKey]) -> bool {
if self
.major_stars
.iter()
.any(|s| s.star_type == StarType::Major)
{
return false;
}
if !exclude_stars.is_empty() && self.has_one_of(exclude_stars) {
return false;
}
true
}
pub fn mutagen_stars(&self, mutagens: &[Mutagen]) -> Vec<StarKey> {
let table = self
.overrides
.as_ref()
.and_then(|t| t.mutagens_of(self.heavenly_stem))
.copied()
.unwrap_or_else(|| get_heavenly_stem_info(self.heavenly_stem).mutagen);
mutagens
.iter()
.map(|m| {
table[match m {
Mutagen::Lu => 0,
Mutagen::Quan => 1,
Mutagen::Ke => 2,
Mutagen::Ji => 3,
}]
})
.collect()
}
pub fn flies_to(&self, target_palace: &PalaceData, mutagens: &[Mutagen]) -> bool {
let stars = self.mutagen_stars(mutagens);
if stars.is_empty() {
return false;
}
target_palace.has(&stars)
}
pub fn flies_one_of_to(&self, target_palace: &PalaceData, mutagens: &[Mutagen]) -> bool {
let stars = self.mutagen_stars(mutagens);
if stars.is_empty() {
return true;
}
target_palace.has_one_of(&stars)
}
pub fn not_fly_to(&self, target_palace: &PalaceData, mutagens: &[Mutagen]) -> bool {
let stars = self.mutagen_stars(mutagens);
if stars.is_empty() {
return true;
}
target_palace.not_have(&stars)
}
pub fn self_mutaged(&self, mutagens: &[Mutagen]) -> bool {
self.has(&self.mutagen_stars(mutagens))
}
pub fn self_mutaged_one_of(&self, mutagens: &[Mutagen]) -> bool {
self.has_one_of(&self.mutagen_stars(Self::or_all(mutagens)))
}
pub fn not_self_mutaged(&self, mutagens: &[Mutagen]) -> bool {
self.not_have(&self.mutagen_stars(Self::or_all(mutagens)))
}
fn or_all(mutagens: &[Mutagen]) -> &[Mutagen] {
const ALL: [Mutagen; 4] = [Mutagen::Lu, Mutagen::Quan, Mutagen::Ke, Mutagen::Ji];
if mutagens.is_empty() { &ALL } else { mutagens }
}
pub fn mutaged_places(&self, all_palaces: &[PalaceData]) -> Vec<Option<usize>> {
self.mutagen_stars(Self::or_all(&[]))
.iter()
.map(|star_key| {
all_palaces.iter().find_map(|p| {
if p.has(&[*star_key]) {
Some(p.index)
} else {
None
}
})
})
.collect()
}
}
#[cfg(test)]
mod tests {
use crate::astro::builder::by_solar;
use crate::data::stars::StarKey;
use crate::data::types::*;
fn make_astrolabe() -> crate::models::astrolabe::Astrolabe {
by_solar(
"2000-8-16",
2,
Gender::Female,
true,
Language::ZhCN,
Config::default(),
)
.unwrap()
}
#[test]
fn test_palace_has() {
let a = make_astrolabe();
let found = a
.palaces
.iter()
.find(|p| p.major_stars.iter().any(|s| s.key == StarKey::ZiweiMaj));
assert!(found.is_some());
let p = found.unwrap();
assert!(p.has(&[StarKey::ZiweiMaj]));
}
#[test]
fn test_palace_not_have() {
let a = make_astrolabe();
let p = a
.palaces
.iter()
.find(|p| p.has(&[StarKey::ZiweiMaj]))
.unwrap();
assert!(p.not_have(&[StarKey::PojunMaj]) || p.has(&[StarKey::PojunMaj]));
let not_here: Vec<StarKey> = vec![
StarKey::ZiweiMaj,
StarKey::TianjiMaj,
StarKey::TaiyangMaj,
StarKey::WuquMaj,
StarKey::TiantongMaj,
StarKey::LianzhenMaj,
StarKey::TianfuMaj,
StarKey::TaiyinMaj,
StarKey::TanlangMaj,
StarKey::JumenMaj,
StarKey::TianxiangMaj,
StarKey::TianliangMaj,
StarKey::QishaMaj,
StarKey::PojunMaj,
]
.into_iter()
.filter(|k| !p.has(&[*k]))
.collect();
if !not_here.is_empty() {
assert!(p.not_have(¬_here));
}
}
#[test]
fn test_palace_has_one_of() {
let a = make_astrolabe();
let _p = &a.palaces[0];
let all_majors = [
StarKey::ZiweiMaj,
StarKey::TianjiMaj,
StarKey::TaiyangMaj,
StarKey::WuquMaj,
StarKey::TiantongMaj,
StarKey::LianzhenMaj,
StarKey::TianfuMaj,
StarKey::TaiyinMaj,
StarKey::TanlangMaj,
StarKey::JumenMaj,
StarKey::TianxiangMaj,
StarKey::TianliangMaj,
StarKey::QishaMaj,
StarKey::PojunMaj,
];
let any_has = a.palaces.iter().any(|p| p.has_one_of(&all_majors));
assert!(any_has);
}
#[test]
fn test_palace_has_mutagen() {
let a = make_astrolabe();
let has_lu = a.palaces.iter().any(|p| p.has_mutagen(Mutagen::Lu));
assert!(has_lu);
let has_ji = a.palaces.iter().any(|p| p.has_mutagen(Mutagen::Ji));
assert!(has_ji);
}
#[test]
fn test_palace_is_empty() {
let a = make_astrolabe();
let non_empty = a.palaces.iter().filter(|p| !p.is_empty()).count();
assert!(non_empty > 0);
let empty_count = a.palaces.iter().filter(|p| p.is_empty()).count();
assert_eq!(non_empty + empty_count, 12);
}
#[test]
fn test_palace_flies_to() {
let a = make_astrolabe();
let source = &a.palaces[0];
let mut found_target = false;
for target in &a.palaces {
if source.flies_to(target, &[Mutagen::Lu]) {
found_target = true;
break;
}
}
assert!(found_target, "Lu mutagen star should exist in some palace");
}
#[test]
fn test_palace_self_mutaged() {
let a = make_astrolabe();
for p in &a.palaces {
let _ = p.self_mutaged(&[Mutagen::Lu]);
let _ = p.self_mutaged_one_of(&[]);
let _ = p.not_self_mutaged(&[]);
}
}
#[test]
fn test_palace_mutaged_places() {
let a = make_astrolabe();
let places = a.palaces[0].mutaged_places(&a.palaces);
assert_eq!(places.len(), 4);
for place in &places {
assert!(
place.is_some(),
"Each mutagen star should be found in some palace"
);
}
}
}