feat: Added more Feng-Shui calculations

This commit is contained in:
minagawah
2022-03-15 07:43:35 +09:00
parent f498cc8a84
commit a724b98c02
47 changed files with 5868 additions and 378 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "mikaboshi"
version = "0.4.1"
version = "0.5.0"
edition = "2018"
[dependencies]
@@ -13,3 +13,6 @@ sowngwala = { git = "https://github.com/minagawah/sowngwala", version = "0.4.0"
[dev-dependencies]
approx_eq = "0.1.8"
js-sys = "0.3.52"
wasm-bindgen = { version = "0.2.70", features = ["serde-serialize"] }
wasm-bindgen-test = "0.3.13"

239
README.md
View File

@@ -1,121 +1,187 @@
# mikaboshi
## About
![screenshot](./screenshot.png)
![screenshot2](./screenshot2.png)
`mikaboshi` provides basic calculations for Chinese astrology, known as _Bazi_.
The name `mikaboshi` derives from a Marvel character,
[Amatsu-mikaboshi](https://marvel.fandom.com/wiki/Amatsu-Mikaboshi_(Earth-616)),
or one of goddesses in Shinto who equates to Lucifer in the west.
It literally means "the shinning star in the sky".
## 1. About
__"mikaboshi"__ is a Rust library for 風水 (风水) (_Feng-Shui_) calculations.
As such, it provides commonly used calculation logic
for basic Chinese astrological concepts such as:
- [八卦 (Ba-Gua)](./docs/bagua.md)
- [干支 (Gan-Zhi)](./docs/ganzhi.md)
- [九星 (Jiu-Xing)](./docs/jiuxing.md)
- [二十四节气 (Er-Shi-Si Jie-Qi)](./docs/solar_terms.md)
- [二十四山向 (Er-Shi-Si Shan-Xiang)](./docs/compass.md)
- [生死衰旺 (Sheng-Si Shuai-Wang)](./docs/shengsi.md)
As you may have noticed that sample codes in
[samples](./docs/examples/index.md)
are that of a WASM (WebAssembly) app,
it does not always have to be a Rust app when using the library.
It is amazing how you could easily link
your codes written in Rust to JS apps
when provided as a WASM app.
The name __"mikaboshi"__ derives from a _Marvel_ character
_["Amatsu-Mikaboshi"](https://marvel.fandom.com/wiki/Amatsu-Mikaboshi_(Earth-616))_
or one of the goddesses in _Shinto_. _"Amatsu-Mikaboshi"_ means
_"The Shinning Star in the Sky"_ when literally translated,
which reminds us of _Lucifer_ in the west.
This library depends on
_[sowngwala](https://github.com/minagawah/sowngwala/)_
for calculating sun's position.
It also use some structs from
_[sowngwala](https://github.com/minagawah/sowngwala/)_,
namely, _`Date`_, _`DateTime`_, _`Time`_, and _`Month`_
with which you need to specify as arguments for some functions.
Also, they are re-exported for public use.
__What Makes The Program Tricky?__
So, the library expects you to have
a Feng-Shui board with 9 boxes drawn on a device screen.
1 empty box in the middle surrounded by 8 boxes.
360 divided by 8, makes it 45 degrees for each.
See how it goes when pointing "N" (north):
(When pointing "N")
1st row ‐‐> "NW", "N", and "NE"
2nd row ‐‐> "W", (middle), and "E"
3rd row ‐‐> "SW", "S", and "SE"
![tricky device rotation](./tricky.png)
However, it gets tricky when device rotates.
Say, the device rotates for 45 degrees clockwise.
It now points to "NE" (north-east):
(When pointing "NE")
1st row ‐‐> "N", "NE", and "E"
2nd row ‐‐> "NW", (middle), and "SE"
3rd row ‐‐> "W", "SW", and "S"
As you can imagine, when the above is expressed in a Rust program,
we need `Vec` and `HashMap`, and that is why we have
many `Vec` and `HashMap` as to map variations
that manifest per compass direction
(usually 8, but sometimes 9 when center is concerned).
This is so, not only for compass directions,
but for Feng-Shui mappings as well.
Whenever we calculate positions for Feng-Shui elements,
the positions are provided in `Vec` or `HashMap`
so that they will have 8 or 9 patterns.
## Important
## 2. Examples
`mikaboshi` depends on
[sowngwala](https://github.com/minagawah/sowngwala/)
for many structs and functions.
When you work with `mikaboshi`, you need some structs from `sowngwala`.
For some functions in `mikaboshi` expects `DateTime` of `sowngwala` as arguments.
As such, `mikaboshi` is re-exporting date & time related structs from `sowngwala`:
[A few examples](./docs/examples/index.md) which may or may not help...
```rust
pub use sowngwala::time::{
Month,
Date,
Time,
DateTime,
};
## 3. Documentation
You may:
```bash
cargo doc
```
Refer to the source codes of `sowngwala` for specifications:
https://github.com/minagawah/sowngwala/blob/main/src/time.rs
however, you will probably get more from documentations bellow:
## Calculation
### [八卦 (Bagua)](./docs/bagua.md)
- Longitude of the sun is that of 0:00 midnight for the given day
- Day changes at 0:00 midnight
- [Bagua](./docs/bagua.md#baguabagua)
- [BaguaRawData](./docs/bagua.md#baguabaguarawdata)
- [BAGUA](./docs/bagua.md#baguabagua)
- [BAGUA_START_NORTH_INDEXES](./docs/bagua.md#baguabagua_start_north_indexes)
- [BAGUA_START_NORTH](./docs/bagua.md#baguabagua_start_north)
- [get_bagua_start_north](./docs/bagua.md#baguaget_bagua_start_north)
## Usage
### [二十四山向 (Er-Shi-Si Shan-Xiang)](./docs/compass.md)
### `ut_from_local()`
- [Direction](./docs/compass.md#compassdirection)
- [TwentyFourType](./docs/compass.md#compasstwentyfourtype)
- [DIRECTIONS](./docs/compass.md#compassdirections)
- [OPPOSITE_DIRECTION](./docs/compass.md#compassopposite_direction)
- [DIRECTION_POSITIONS_IN_CHART](./docs/compass.md#compassdirection_positions_in_chart)
- [TWENTYFOUR_DIRECTIONS_TO_INDEX](./docs/compass.md#compasstwentyfour_directions_to_index)
- [TWENTYFOUR_INDEX_TO_DIRECTIONS](./docs/compass.md#compasstwentyfour_index_to_directions)
- [TWENTYFOUR_ORDER_START_NORTH](./docs/compass.md#compasstwentyfour_order_start_north)
- [TWENTYFOUR_SECTORS](./docs/compass.md#compasstwentyfour_sectors)
- [get_direction_positions_in_chart](./docs/compass.md#compassget_direction_positions_in_chart)
- [get_opposite_direction](./docs/compass.md#compassget_opposite_direction)
- [get_twentyfour_data_from_direction](./docs/compass.md#compassget_twentyfour_data_from_direction)
- [get_twentyfour_data_from_index](./docs/compass.md#compassget_twentyfour_data_from_index)
- [get_twentyfour_direction_from_degrees](./docs/compass.md#compassget_twentyfour_direction_from_degrees)
- [get_twentyfour_direction_from_direction](./docs/compass.md#compassget_twentyfour_direction_from_direction)
- [get_twentyfour_direction_from_index](./docs/compass.md#compassget_twentyfour_direction_from_index)
- [get_twentyfour_index_from_direction](./docs/compass.md#compassget_twentyfour_index_from_direction)
You may convert your local time into _UT_:
### [干支 (Gan-Zhi)](./docs/ganzhi.md)
```rust
use mikaboshi::time::{
Month,
DateTime,
ut_from_local,
};
- [Stem](./docs/ganzhi.md#ganzhistem)
- [Branch](./docs/ganzhi.md#ganzhibranch)
- [StemRawData](./docs/ganzhi.md#ganzhistemrawdata)
- [BranchRawData](./docs/ganzhi.md#ganzhibranchrawdata)
- [GanZhi](./docs/ganzhi.md#ganzhiganzhi)
- [Bazi](./docs/ganzhi.md#ganzhibazi)
- [STEMS](./docs/ganzhi.md#ganzhistems)
- [BRANCHES](./docs/ganzhi.md#ganzhibranches)
- [GANZHI_SEXAGESIMAL](./docs/ganzhi.md#ganzhiganzhi_sexagesimal)
- [HOUR_STEM_TABLE](./docs/ganzhi.md#ganzhihour_stem_table)
- [Bazi::from_local](./docs/ganzhi.md#ganzhibazifrom_local)
let zone: i8 = 9;
### [九星 (Jiu-Xing)](./docs/jiuxing.md)
let local = DateTime {
year: 2021,
month: Month::Jul,
day: 7.0,
hour: 0,
min: 0,
sec: 0.0,
};
- [JiuXing](./docs/jiuxing.md#jiuxingjiuxing)
- [JiuXingRawData](./docs/jiuxing.md#jiuxingjiuxingrawdata)
- [XiaGuaTu](./docs/jiuxing.md#jiuxingxiaguatu)
- [DIRECTION_TO_JIU_XING](./docs/jiuxing.md#jiuxingdirection_to_jiu_xing)
- [JIU_XING](./docs/jiuxing.md#jiuxingjiu_xing)
- [JIU_XING_DI_PAN_POSITIONS](./docs/jiuxing.md#jiuxingjiu_xing_di_pan_positions)
- [get_jiuxing_dipan_positions_from_direction](./docs/jiuxing.md#jiuxingget_jiuxing_dipan_positions_from_direction)
- [get_jiuxing_from_index](./docs/jiuxing.md#jiuxingget_jiuxing_from_index)
- [normalize_jiuxing](./docs/jiuxing.md#jiuxingnormalize_jiuxing)
- [fly_flying_stars](./docs/jiuxing.md#jiuxingfly_flying_stars)
- [get_xiaguatu_from_unpan_index](./docs/jiuxing.md#jiuxingget_xiaguatu_from_unpan_index)
let ut: DateTime = ut_from_local(&local, zone);
println!("ut: {:?}", ut);
### [生死衰旺 (Sheng-Si Shuai-Wang)](./docs/shengsi.md)
// {
// year: 2021,
// month: Jul,
// day: 6.0,
// hour: 14,
// min: 57,
// sec: 17.13778432735566
// }
```
- [ShengSi](./docs/shengsi.md#shengsishengsi)
- [ShengSiYearlyAlloc](./docs/shengsi.md#shengsishengsiyearalloc)
- [SHENG_SI](./docs/shengsi.md#shengsisheng_si)
- [SHENG_SI_ALLOC](./docs/shengsi.md#shengsisheng_si_alloc)
- [get_shengsi_mapping](./docs/shengsi.md#shengsiget_shengsi_mapping)
### `Bazi::from_local()`
### [二十四节气 (Er-Shi-Si Jie-Qi) and 立春 (Li-Chun)](./docs/solar_terms.md)
You can easily calculate for _Bazi_:
- [get_last_term](./docs/solar_terms.md#solar_termsget_last_term)
- [get_lichun](./docs/solar_terms.md#solar_termsget_lichun)
```rust
use mikaboshi::time::{ Month, DateTime };
use mikaboshi::ganzhi::{ Bazi, GanZhi };
### [Planets](./docs/planet.md)
let zone: i8 = 9;
- [Planet](./docs/planet.md#planet)
- [PlanetRawData](./docs/planet.md#planetrawdata)
- [PLANETS](./docs/planet.md#planets)
let lt = DateTime {
year: 2021,
month: Month::Jul,
day: 7.0,
hour: 0,
min: 0,
sec: 0.0,
};
let bazi: Bazi = Bazi::from_local(&lt, zone);
### [Time](./docs/time.md)
let year: GanZhi = bazi.year;
let month: GanZhi = bazi.month;
let day: GanZhi = bazi.day;
let hour: GanZhi = bazi.hour;
- [Date](./docs/time.md#timedate)
- [DateTime](./docs/time.md#timedatetime)
- [Time](./docs/time.md#timetime)
- [ut_from_local](./docs/time.md#timeut_from_local)
println!("年: {} ({})", year.alphabet(), year.alphabet_ja());
println!("月: {} ({})", month.alphabet(), month.alphabet_ja());
println!("日: {} ({})", day.alphabet(), day.alphabet_ja());
println!("時: {} ({})", hour.alphabet(), hour.alphabet_ja());
// 年: 辛丑 (かのと・うし)
// 月: 甲午 (きのえ・うま)
// 日: 乙卯 (きのと・う)
// 時: 癸未 (みずのと・ひつじ)
```
## Test
## 4. Test
```
RUST_BACKTRACE=1 cargo test -vv -- --nocapture
```
## Dislaimer
## 5. Dislaimer
There is absolutely no gurantee about the accuracy of the service,
information, or calculated results provided by the program,
@@ -127,6 +193,7 @@ for which the author of the program shall not be liable.
It shall be your own responsibility to ensure the service,
information, or calculated results meet your specific requirements.
## License
## 6. License
MIT license ([LICENSE](LICENSE))

127
docs/bagua.md Normal file
View File

@@ -0,0 +1,127 @@
# 八卦 (Bagua)
Source: [src/bagua.rs](../src/bagua.rs)
![sample_bagua](./sample_bagua.png)
Although 八卦 (Ba-Gua) is a concept in 易経 (I-Ching),
when used in Feng-Shui, it is often associated with 九星 (Jiu-Xing).
While everthing in this world is (said to be) divided into either 陰 (Yin)
or 陽 (Yang), each could be further divided into lesser Yin and Yang.
For Yang, some may be abundant in Yang. Or, some may slightly lean toward Yin.
This goes for Yin as well. Here, the initial Yin and Yang,
now divided into 4, or becomes 4 patterns. Then, for each,
there are further divisions, and for this time, it now makes it 8 patterns.
Ancient Chinese had a specific term for these 8 patterns,
and it is called, 八卦 (Ba-Gua), or "8 Gua".
[0] 坎 (Kan)
[1] 坤 (Kun)
[2] 震 (Zhen)
[3] 巽 (Xun)
[4] 乾 (Qian)
[5] 兌 (Dui)
[6] 艮 (Gen)
[7] 離 (Li)
## bagua::Bagua
A struct representing 卦 (Gua) and stores its attributes.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bagua {
pub num: u8,
pub name: Language,
pub direction: String,
pub element: u8,
}
```
## bagua::BaguaRawData
A temporary struct for loading JSON data when defining a static const `BAGUA`.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaguaRawData {
pub num: u8,
pub name: LanguageData,
pub direction: String,
pub element: u8,
}
```
## bagua::BAGUA
`Vec<Bagua>`
A static vector with 9 items, each represents 卦 (Gua) of 八卦 (Ba-Gua),
or what known as "Eight Trigrams". Each stores associated attributes
for the 卦 (Gua), and are in the order of Ba-Gua Numbers. For instance,
坎 (Kan) is the first in Ba-Gua, so it comes first in the vector.
However, be careful when you refer to each 卦 (Gua) as the program
refers 卦 (Gua) not by Ba-Gua Numbers, but by vector indexes.
For 坎 (Kan), for instance, while it has the Ba-Gua Number of 1,
it is referred as 0 because that is the index is for 坎 (Kan).
[0] 坎 (1 = Kan)
[1] 坤 (2 = Kun)
[2] 震 (3 = Zhen)
[3] 巽 (4 = Xun)
[4] 中 (5 = Zhong)
[5] 乾 (6 = Qian)
[6] 兌 (7 = Dui)
[7] 艮 (8 = Gen)
[8] 離 (9 = Li)
For attributes details stored in the vector is found in JSON file:
[json/bagua.json](../json/bagua.json)
## bagua::BAGUA_START_NORTH_INDEXES
`Vec<u8>`
Another static vector for 八卦 (Ba-Gua) (and is `Vec<u8>`).
However, this time, it has only 8 items because this is used
for compass rotation. When using a compass, only 8 directions matter,
and the middle does not mean anything. When 八卦 (Ba-Gua) are
placed in 洛書 (Lo-Shu) order, 中 (Zhong) comes in the middle.
So, 中 (Zhong) is missing for this vector. For 八卦 (Ba-Gua),
it begins with 坎 (Kan) which resides in the north
when they are placed in Lo-Shu order. Moving clockwise,
what you see next in the north-east, is 艮 (Gen).
Then, comes 震 (3 = Zhen) which is in the east, and, so on.
[0] 坎 (1 = Kan)
[1] 艮 (8 = Gen)
[2] 震 (3 = Zhen)
[3] 巽 (4 = Xun)
[4] 離 (9 = Li)
[5] 坤 (2 = Kun)
[6] 兌 (7 = Dui)
[7] 乾 (6 = Qian)
## bagua::BAGUA_START_NORTH
`Vec<Bagua>`
While the order is the same as `BAGUA_START_NORTH_INDEXES`,
for this time, it is `Vec<Bagua>` instead of `Vec<u8>`.
## bagua::get_bagua_start_north
A getter for `BAGUA_START_NORTH`.
Example:
```rust
use mikaboshi::bagua::{get_bagua_start_north, Bagua};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(index: usize) -> JsValue {
let bagua: Option<&Bagua> = get_bagua_start_north(index);
JsValue::from_serde(&bagua).unwrap()
}
```

286
docs/compass.md Normal file
View File

@@ -0,0 +1,286 @@
# 二十四山向 (Er-Shi-Si Shan-Xiang)
Source: [src/compass.rs](../src/compass.rs)
![sample_twentyfour](./sample_twentyfour.png)
A module for compass directions. When dividing 360 degrees into 8,
we get 45 degrees. Ancient Chinese further divided them each into 3
(called "sectors"), each having 15 degrees. Meaning, there are
24 sectors as a total. This is called, 二十四山向 (Er-Shi-Si Shan-Xiang).
Not only for 8 directions, but these 24 directions (sectors)
are used in Feng-Shui, and this is the module for these directions.
Reference:
- [二十四山 - Wiki](https://ja.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%9B%9B%E5%B1%B1)
## compass::Direction
A struct representing compass direction.
For each direction, there are 3 sectors.
Example:
```rust
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Direction {
pub direction: String,
pub sector: usize,
}
```
## compass::TwentyFourType
二十四山向 (Er-Shi-Si Shan-Xiang) can be either 卦 (Gua), 干 (Gan), or 支 (Zhi).
```rust
pub enum TwentyFourType<'a> {
Bagua(&'a Bagua),
Stem(&'a Stem),
Branch(&'a Branch),
}
```
## compass::DIRECTIONS
`[&str; 8]`
An array for 8 directions.
```rust
pub const DIRECTIONS: [&str; 8] =
["n", "ne", "e", "se", "s", "sw", "w", "nw"];
```
## compass::OPPOSITE_DIRECTION
`HashMap<&str, &str>`
A hash map for the opposite direction.
## compass::DIRECTION_POSITIONS_IN_CHART
`HashMap<&str, [&str; 9]>`
A hash map with 9 items.
Say, we have 9 boxes displayed on a device screen.
Except for the box in the middle, we have 8 boxes
around the middle to represent 8 compass directions.
When facing "n" (north), for the first row,
we have "nw", "n", and "ne". For the second row,
we have "w", "", and "e" (where "" being the middle box).
For the last, we have "sw", "s", and "se".
[0] nw [1] n [2] ne
[3] w [4] [5] e
[6] sw [7] s [8] se
Now, consider when the device rotates.
Depending on which direction the device is facing,
we have different labels. For all 8 directions,
this HashMap provides a map for the positions.
## compass::TWENTYFOUR_DIRECTIONS_TO_INDEX
`HashMap<String, usize>`
A HashMap mapping direction (combination of "direction" and "sector")
to the corresponding index.
n2: 0
n3: 1
ne1: 2
ne2: 3
...
...
## compass::TWENTYFOUR_INDEX_TO_DIRECTIONS
`Vec<Direction>`
An array with 24 items, for each represents
each in 二十四山向 (Er-Shi-Si Shan-Xiang).
Note, the array begins with "N2"
(and "N1" is stored at the very last, or [23]).
0: Direction { direction: "n", sector: 2 }
1: Direction { direction: "n", sector: 3 }
2: Direction { direction: "ne", sector: 1 }
3: Direction { direction: "ne", sector: 2 }
...
...
## compass::TWENTYFOUR_ORDER_START_NORTH
`[(usize, usize); 24]`
An array with 24 items, each being a tuple. For each tuple,
the first represents the type of "二十四山向" (Er-Shi-Si Shan-Xiang),
and the second is the index of the type.
The type being: [0] BAGUA, [1] STEM, or [2] BRANCH.
(2, 0) --> [0] 子
(1, 9) --> [9] 癸
(2, 1) --> [1] 丑
(0, 7) --> [7] 艮
(2, 2) --> [2] 寅
(1, 0) --> [0] 甲
...
...
## compass::TWENTYFOUR_SECTORS
`[u8; 24]`
An array with 24 items. Imagine having a circlar disc displayed
on a device screen. When dividing 360 by 8 directions, we get
45 degrees for each. When each direction is further divided
into 3, then each is called a "sector", and it has 15 degrees
for each "sector". Sectors are placed in clockwise order
(left to right) for each direction, so that you see
the sector 1 being placed on your very left. Then, you see
the sector 2 in the middle, and the sector 3 on your right.
Imagine the device pointing north. On the circular disc,
what you see at the very top is the sector 2 of "N" (north),
denoted as "N2". On your left, you see "N1".
On your right, "N3".
When we want to express all the 24 sectors, we want
an array with 24 items. For the first item in the array [0],
it is convenient to have "N2". Then, for the second item
in the array [1], we want "N3". For [2], we want "NE1".
For [3], we want "NE2". And, so on. As you can imagine,
"N1" comes to the very last in the array, or [23].
2 --> n
3 --> n
1 --> ne
2 --> ne
3 --> ne
1 --> e
...
...
## compass::get_direction_positions_in_chart
An getter for `DIRECTION_POSITIONS_IN_CHART`.
Example:
```rust
use mikaboshi::compass::get_direction_positions_in_chart;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(direction: &str) -> JsValue {
JsValue::from(
(match get_direction_positions_in_chart(direction) {
Some(positions) => positions.to_vec(),
_ => Vec::new(),
})
.into_iter()
.map(JsValue::from)
.collect::<js_sys::Array>(),
)
}
```
## compass::get_opposite_direction
A getter for `OPPOSITE_DIRECTION`.
Example:
```rust
use mikaboshi::compass::get_opposite_direction;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(direction: &str) -> JsValue {
JsValue::from(get_opposite_direction(direction))
}
```
## compass::get_twentyfour_data_from_direction
From the given direction and sector, returns `TwentyFourType`.
Example:
```rust
use mikaboshi::compass::{get_twentyfour_data_from_direction, TwentyFourType};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(direction: &str, sector: usize) -> JsValue {
let t_type: TwentyFourType = get_twentyfour_data_from_direction(direction, sector);
match t_type {
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
}
}
```
## compass::get_twentyfour_data_from_index
From index, simply returns the corresponding `TwentyFourType`.
Example:
```rust
use mikaboshi::compass::{get_twentyfour_data_from_index, TwentyFourType};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(index: usize) -> JsValue {
let t_type: TwentyFourType = get_twentyfour_data_from_index(index);
match t_type {
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
}
}
```
## compass::get_twentyfour_direction_from_degrees
From the given degrees, returns the corresponding `Direction`.
Example:
```rust
use mikaboshi::compass::{get_twentyfour_direction_from_degrees, Direction};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(degrees: f32) -> JsValue {
let dir: Direction = get_twentyfour_direction_from_degrees(degrees);
JsValue::from_serde(&dir).unwrap()
}
```
## compass::get_twentyfour_direction_from_direction
From the given direction and sector, returns `Direction`.
## compass::get_twentyfour_direction_from_index
Example:
```rust
use mikaboshi::compass::{get_twentyfour_direction_from_index, Direction};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(index: usize) -> JsValue {
let dir: &Direction = get_twentyfour_direction_from_index(index);
JsValue::from_serde(dir).unwrap()
}
```
## compass::get_twentyfour_index_from_direction
From the given direction and sector, finds the corresponding index
in `TWENTYFOUR_DIRECTIONS_TO_INDEX`

32
docs/examples/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = ""
version = "0.1.0"
authors = [""]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "^0.1.6"
js-sys = "0.3.52"
mikaboshi = { git = "https://github.com/minagawah/mikaboshi", version = "0.5.0" }
serde = { version = "1.0.127", features = ["derive"] }
wasm-bindgen = { version = "0.2.70", features = ["serde-serialize"] }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

613
docs/examples/FengShui.js Normal file
View File

@@ -0,0 +1,613 @@
/**
* A context provider to load a WASM app ("voi-feng-shui" in our case).
* Once `FengShuiContext` is set in the parent component, you can use
* `useFengShui` in child components to access the given properties.
* To start with, it requies data in `profile`, however, `profile`
* is managed in `ProfilesProvider`. For two providers cannot
* communicate to each other, we have `useFengShuiSync`
* to serve the purpose. For any components handling `profile`,
* we run `useFengShuiSync` in there, and it will automatically
* @namespace FengShui
*/
import React, {
useContext,
createContext,
useEffect,
useState,
useCallback,
useRef,
} from 'react';
import moment from 'moment';
import init, {
// 八卦 (Ba-Gua)
get_bagua_start_north as wasm_get_bagua_start_north,
// 二十四山向 (Er-Shi Si-Shan Xiang)
get_twentyfour_direction_from_index as wasm_get_twentyfour_direction_from_index,
get_twentyfour_direction_from_degrees as wasm_get_twentyfour_direction_from_degrees,
get_twentyfour_data_from_index as wasm_get_twentyfour_data_from_index,
get_twentyfour_data_from_direction as wasm_get_twentyfour_data_from_direction,
// 干支 (Gan-Zhi)
get_bazi as wasm_get_bazi,
get_lichun as wasm_get_lichun,
// 九星 (Jiu-Xings)
get_jiuxing_from_index as wasm_get_jiuxing_from_index,
get_unpan_xing_index as wasm_get_unpan_xing_index,
get_xiaguatu_from_unpan_index as wasm_get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction as wasm_get_jiuxing_dipan_positions_from_direction,
// 生死衰旺 (Sheng-Si Shuai-Wang)
get_shengsi_mapping as wasm_get_shengsi_mapping,
} from 'voi-feng-shui';
import { DATETIME_FORMAT, BAZI_TYPE_KEYS, GANZHI_TYPE_KEYS } from '@/constants';
import { int, noop, get_utc_offset_in_hours } from '@/lib/utils';
import {
useProfiles,
get_first_valid_profile_from_profiles,
} from '@/contexts/Profiles';
const WASM_PATH =
NODE_ENV === 'production'
? 'wasm/voi-feng-shui/voi-feng-shui_bg.wasm'
: void 0;
// console.log('[useFengShui] WASM_PATH: ', WASM_PATH);
const DATETIME_KEYS = Object.keys(DATETIME_FORMAT);
const DATE_KEYS = DATETIME_KEYS.filter(
key =>
key.indexOf('year') > -1 ||
key.indexOf('month') > -1 ||
key.indexOf('day') > -1
);
/** @public */
export const has_valid_profile = (p = {}) =>
p.localtime && p.direction && p.sector;
// ----------------------------------------------------------------
// FengShuiContext
// ----------------------------------------------------------------
/**
* @typedef FengShui.FengShuiContext
* @property {boolean} ready
* @property {Object} profile
* @property {Object} bazi
* @property {Object} lichun
* @property {Object} unpan_xing
* @property {Object} shan_xing
* @property {Object} xiang_xing
* @property {Function} update
* @property {FengShui.FengShuiContext.get_bagua_start_north}
* @property {FengShui.FengShuiContext.get_direction_positions_in_chart} - NOT IN USE
* @property {FengShui.FengShuiContext.get_opposite_direction} - NOT IN USE
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_index}
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
* @property {FengShui.FengShuiContext.get_bazi} get_bazi
* @property {FengShui.FengShuiContext.get_lichun} get_lichun
* @property {FengShui.FengShuiContext.get_unpan_xing_index}
* @property {FengShui.FengShuiContext.get_jiuxing_from_index}
* @property {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index}
* @property {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
* @property {FengShui.FengShuiContext.get_shengsi_mapping}
*/
const FengShuiContext = createContext({
ready: false,
profile: null, // localtime, direction, sector
bazi: null,
lichun: null,
unpan_xing: null, // 運盤星
shan_xing: null, // 山星
xiang_xing: null, // 向星
update: noop,
// 八卦 (Ba-Gua)
get_bagua_start_north: noop,
// 二十四山向 (Er-Shi Si-Shan Xiang)
get_twentyfour_direction_from_index: noop,
get_twentyfour_direction_from_degrees: noop,
get_twentyfour_data_from_index: noop,
get_twentyfour_data_from_direction: noop,
// 干支 (Gan-Zhi)
get_bazi: noop,
get_lichun: noop,
// 九星 (Jiu-Xings)
unpan_xing_index: noop,
get_xiaguatu_from_unpan_index: noop,
get_jiuxing_dipan_positions_from_direction: noop,
// 生死衰旺 (Sheng-Si Shuai-Wang)
get_shengsi_mapping: noop,
});
// ----------------------------------------------------------------
// FengShuiProvider
// ----------------------------------------------------------------
/**
* @typedef FengShui.FengShuiProvider
* @function
*/
export const FengShuiProvider = props => {
const [ready, setReady] = useState(false);
const [profile, setProfile] = useState(null);
const [bazi, setBazi] = useState(null);
const [lichun, setLiChun] = useState(null);
const [unpan_xing, setUnPanXing] = useState(null);
const [shan_xing, setShanXing] = useState(null);
const [xiang_xing, setXiangXing] = useState(null);
// ================================================================
// 八卦 (Bagua)
// ================================================================
/**
* A simple accessor for values stored in `BAGUA_START_NORTH`.
* @typedef FengShui.FengShuiContext.get_bagua_start_north
* @function
*/
/**
* @type {FengShui.FengShuiContext.get_bagua_start_north}
*/
const get_bagua_start_north = useCallback(
dir => {
return ready && wasm_get_bagua_start_north(dir);
},
[ready]
);
// ================================================================
// 二十四山向 (Er-Shi Si-Shan Xiang)
// ================================================================
/**
* From the index, returns `Direction`.
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_index
* @function
* @param {number} [index]
* @returns {Object} - { direction, sector }
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
*/
const get_twentyfour_direction_from_index = useCallback(
index => {
return ready && wasm_get_twentyfour_direction_from_index(index);
},
[ready]
);
/**
* From `degrees`, returns `Direction` (for 二十四山向; Er-Shi Si-Shan Xiang).
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_degrees
* @function
* @param {number} [degrees=0]
* @returns {Object} - { direction, sector }
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
*/
const get_twentyfour_direction_from_degrees = useCallback(
degrees => {
return ready && wasm_get_twentyfour_direction_from_degrees(degrees);
},
[ready]
);
/**
* From index, returns `TwentyFourType`.
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_index
* @function
* @param {number} [index]
* @returns {Bagua|Stem|Branch} - TwentyFourType
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_index}
*/
const get_twentyfour_data_from_index = useCallback(
index => {
return ready && wasm_get_twentyfour_data_from_index(index);
},
[ready]
);
/**
* From `direction`, returns `TwentyFourType`.
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_direction
* @function
* @param {number} [index]
* @returns {Bagua|Stem|Branch} - TwentyFourType
*/
/**
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
*/
const get_twentyfour_data_from_direction = useCallback(
(dir, sec) => {
return ready && wasm_get_twentyfour_data_from_direction(dir, sec);
},
[ready]
);
// ================================================================
// 干支 (Gan-Zhi)
// ================================================================
/**
* From `t` (localtime), calculates for 八字 (Bazi).
* Internally mapped to: get_bazi
* @typedef FengShui.FengShuiContext.get_bazi
* @function
* @param {Object} [t] - localtime (as a momemt object)
* @returns {Object} - Bazi
*/
/** @type {FengShui.FengShuiContext.get_bazi} */
const get_bazi = useCallback(
t => {
return (
ready &&
normalize_bazi_data(wasm_get_bazi(datetime_params_from_localtime(t)))
);
},
[ready]
);
/**
* From `year`, calculates for 立春 (Li-Chun).
* Internally mapped to: get_lichun
* @typedef FengShui.FengShuiContext.get_lichun
* @function
* @param {number} [y] - year
* @returns {string} - Li-Chun
*/
/** @type {FengShui.FengShuiContext.get_lichun} */
const get_lichun = useCallback(
y => {
return ready && y && wasm_get_lichun(~~y);
},
[ready]
);
// ================================================================
// 九星 (Jiu-Xing)
// ================================================================
/**
* A simple accessor for values stored in `JIU_XING`.
* @typedef FengShui.FengShuiContext.get_jiuxing_from_index
* @function
* @param {Object} [index] - Jiu-Xing index
* @returns {Object} - JiuXing
*/
/** @type {FengShui.FengShuiContext.get_jiuxing_from_index} */
const get_jiuxing_from_index = useCallback(
index => {
return ready && wasm_get_jiuxing_from_index(index);
},
[ready]
);
/**
* From: (1) `current` (current localtime), and
* (2) `lichun` (立春 (Li-Chun) for the year),
* returns 運盤星 (Un-Pan Xing) index.
* @typedef FengShui.FengShuiContext.get_unpan_xing_index
* @function
* @param {Object} [current] - Current localtime
* @param {Object} [lichun] - Li-Chun (for the year)
* @returns {number} - Un-Pan Xing index
*/
/** @type {FengShui.FengShuiContext.get_unpan_xing_index} */
const get_unpan_xing_index = useCallback(
params => {
const { current, lichun } = params;
return (
ready &&
wasm_get_unpan_xing_index(
date_params_from_localtime(current),
date_params_from_localtime(lichun)
)
);
},
[ready]
);
/**
* Takes 運盤星 (Un-Pan Xing) information and 向星 (Xiang-Xing)
* information, and returns 下卦図 (Xia-Gua-Tu).
* For 運盤星 (Un-Pan Xing), we need 2 kinds: 運盤 (Un-Pan)
* which is currently in the center, and 洛書 (Lo-Shu) order
* in its re-arranged form. For 向星 (Xiang-Xing),
* we want `direction` and `sector`.
* @typedef FengShui.FengShuiContext.get_xiaguatu_from_unpan_index
* @function
* @param {Object} arg
* @param {number} arg.unpan_xing_center - 運盤星 (Un-Pan Xing) index
* @param {Array} arg.unpan_xing_order - Jiu-Xing Order
* @param {string} arg.xiang_xing_direction - Direction for 向星 (Xiang-Xing)
* @param {number} arg.xiang_xing_sector - Sector for both 山星 (Shan-Xing) and 向星 (Xiang-Xing)
* @returns {Object}
*/
/** @type {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index} */
const get_xiaguatu_from_unpan_index = useCallback(
params => {
return (
ready &&
wasm_get_xiaguatu_from_unpan_index({
...params,
unpan_xing_order: params.unpan_xing_order || [
5, 0, 7, 6, 4, 2, 1, 8, 3,
],
})
);
},
[ready]
);
/**
* A simple accessor for values stored in `JIU_XING_DI_PAN_POSITIONS`.
* @type {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
*/
const get_jiuxing_dipan_positions_from_direction = useCallback(
dir => {
return ready && wasm_get_jiuxing_dipan_positions_from_direction(dir);
},
[ready]
);
// ================================================================
// 生死衰旺 (Sheng-Si Shuai-Wang)
// ================================================================
/**
* When provided with 運盤 (Un-Pan) index and the current chart,
* returns the corresponding 生死衰旺 (Sheng-Si Shuai-Wang).
* @typedef FengShui.FengShuiContext.get_shengsi_mapping
* @function
* @param {Object} arg
* @param {number} arg.unpan_id - 運盤星 (Un-Pan Xing) index
* @param {Array} arg.unpan_xing_chart - 運盤星 (Un-Pan Xing) index
* @returns {Object}
*/
/** @type {FengShui.FengShuiContext.get_shengsi_mapping} */
const get_shengsi_mapping = useCallback(
params => {
return (
ready &&
wasm_get_shengsi_mapping({
...params,
unpan_xing_chart: params.unpan_xing_chart || [
5, 0, 7, 6, 4, 2, 1, 8, 3,
],
})
);
},
[ready]
);
// ================================================================
// MAIN FOR THE CONTEXT PROVIDER
// ================================================================
/** @private */
const _set = useCallback(
(prof = {}) => {
const { localtime: current, direction, sector } = prof;
if (ready && current && direction) {
const lichun_0 = get_lichun(current.year());
const lichun = moment(lichun_0);
const center = get_unpan_xing_index({ current, lichun });
const xgtu = get_xiaguatu_from_unpan_index({
unpan_xing_center: center,
xiang_xing_direction: direction,
xiang_xing_sector: sector,
});
setBazi(
normalize_bazi_data(
wasm_get_bazi(datetime_params_from_localtime(current))
)
);
setLiChun(lichun);
setUnPanXing(xgtu.unpan_xing);
setShanXing(xgtu.shan_xing);
setXiangXing(xgtu.xiang_xing);
}
},
[ready, profile?.locatltime, profile?.direction]
);
/**
* @typedef FengShui.FengShuiContext.update
* @function
*/
/** @type {FengShui.FengShuiContext.update} */
const update = useCallback(
prof => {
if (ready) {
if (!has_valid_profile(prof)) {
throw new Error(
'[FengShuiContext] Need a valid profile for the argument'
);
}
setProfile(prof);
_set(prof);
}
},
[ready, _set]
);
useEffect(() => {
if (ready !== true) {
init(WASM_PATH)
.then(() => {
setReady(true);
})
.catch(err => {
throw err;
});
}
}, [props?.localtime, props?.direction]);
return (
<FengShuiContext.Provider
value={{
ready,
profile,
bazi,
lichun,
unpan_xing,
shan_xing,
xiang_xing,
update,
get_bagua_start_north,
get_twentyfour_direction_from_index,
get_twentyfour_direction_from_degrees,
get_twentyfour_data_from_index,
get_twentyfour_data_from_direction,
get_bazi,
get_lichun,
get_jiuxing_from_index,
get_unpan_xing_index,
get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction,
get_shengsi_mapping,
}}
{...props}
/>
);
};
// ----------------------------------------------------------------
// useFengShui
// ----------------------------------------------------------------
/**
* @typedef FengShui.useFengShui
* @function
*/
export const useFengShui = () => useContext(FengShuiContext);
// ----------------------------------------------------------------
// useFengShuiSync (hook)
// ----------------------------------------------------------------
/**
* Syncing the latest primary profile to FengShuiContext.profile
* @typedef FengShui.useFengShuiSync
* @function
*/
export const useFengShuiSync = () => {
const { profiles } = useProfiles();
const { ready, update } = useFengShui();
const [hasProfile, setHasProfile] = useState(false);
useEffect(() => {
const p = get_first_valid_profile_from_profiles(profiles);
if (p) {
const t = p.localtime.format('YYYY-MM-DD');
const dir = `${p.direction}${p.sector}`;
// console.log(`[FengShuiContext] (useFengShuiSync) localtime: ${t}`);
// console.log(`[FengShuiContext] (useFengShuiSync) dir: ${dir}`);
setHasProfile(true);
update(p);
} else {
// console.log('[FengShuiContext] No valid profiles...');
setHasProfile(false);
}
}, [ready, profiles]);
return hasProfile;
};
// ----------------------------------------------------------------
// All the others
// ----------------------------------------------------------------
/**
* @private
* @param {Object} t - Moment object in localtime + explicit UTC offset
*/
function date_params_from_localtime(t) {
if (!t) throw new Error("No 'localtime' specified for FengShui");
return {
...DATE_KEYS.reduce((acc, key) => {
acc[key] = int(t.format(DATETIME_FORMAT[key]));
return acc;
}, {}),
};
}
/**
* @private
* @param {Object} t - Moment object in localtime + explicit UTC offset
*/
function datetime_params_from_localtime(t) {
if (!t) throw new Error("No 'localtime' specified for FengShui");
return {
...DATETIME_KEYS.reduce((acc, key) => {
acc[key] = int(t.format(DATETIME_FORMAT[key]));
return acc;
}, {}),
zone: get_utc_offset_in_hours(t),
};
}
/**
* @private
*/
function normalize_bazi_data(orig) {
const hash = { __orig: { ...orig } };
// 'year', 'month', 'hour', 'day'
BAZI_TYPE_KEYS.forEach(bazi_key => {
hash[bazi_key] = {
__obj: orig[bazi_key][bazi_key],
};
// 'stem', 'branch'
GANZHI_TYPE_KEYS.forEach(ganzhi_key => {
hash[bazi_key][ganzhi_key] = {
kanji: orig[bazi_key][ganzhi_key].name.zh_cn.alphabet,
yomi: orig[bazi_key][ganzhi_key].name.ja.alphabet,
};
});
hash[
bazi_key
].kanji = `${hash[bazi_key].stem.kanji}${hash[bazi_key].branch.kanji}`;
hash[
bazi_key
].yomi = `${hash[bazi_key].stem.yomi}${hash[bazi_key].branch.yomi}`;
});
return hash;
}

134
docs/examples/bagua.jsx Normal file
View File

@@ -0,0 +1,134 @@
/**
* SVG Pie Drawing
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*/
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import Snap from 'snapsvg-cjs';
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_BAGUA } from '@/constants';
import { int, rad_to_deg, normalize_angle, get_position } from '@/lib/utils';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
const FILL_COLOR = '#ef4444';
const STROKE_COLOR = '#ffffff';
const OUTER_RATIO = 0.8;
const wrapperStyle = css`
${tw`absolute w-full flex flex-col justify-center items-center`}
z-index: ${Z_INDEX_FENGSHUI_BAGUA};
`;
export const FengShuiBagua = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const snap = useRef(null);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const alpha = deviceOrientation?.alpha;
const draw = useCallback(
({ index }) => {
const s = snap.current && Snap(snap.current);
if (s) {
const beg = get_position(index * RADIAN_45, OUTER_RATIO);
const end = get_position(index * RADIAN_45 + RADIAN_45, OUTER_RATIO);
const large = end.deg - beg.deg > 180 ? 1 : 0;
// Remember, the whole wrapper is rotated later
// by 112.5 (= 90 + 45/2), degrees so that
// the first slice which originally sits
// at the 3 o'clock position will come
// to the 12 o'clock position.
//
// Also, we apply `viewbox="-1 -1 2 2"` for the SVG,
// meaning, the origin of SVG is situated
// at the very center of the wrapper.
// That is to say, we have `x = 0`.
// When we have `x = 1`, it means `x` is reaching
// all the way at the right edge of the wrapper.
//
// For each slice, we use SVG's arc function
// which is defined as:
//
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
//
// At first, we will first move (`M`) to where
// it is close to the right edge of the screen.
// If `OUTER_RATIO = 0.8`, it will probably
// be like `M0.8,0`. Next, for the arc, we will
// probably have `A0.8,0.8 0 0,1 0.56,0.56`.
// So, it is an arc having `0.8` for the radius,
// and it will draw the arc for 45 degrees.
// Lastly, we have `L0,0` to go back to the origin,
// otherwise, it won't fill (with given color)
// the region surrounded by the path.
const path = [
`M${beg.x},${beg.y}`,
`A${OUTER_RATIO},${OUTER_RATIO} 0 ${large},1 ${end.x},${end.y}`,
`L0,0`,
].join(' ');
const fill =
index === 0
? {
fill: FILL_COLOR,
}
: {};
s.path(path).attr({
...fill,
stroke: STROKE_COLOR,
strokeWidth: world?.stroke_size,
});
}
},
[chart_w]
);
const baguaStyle = useMemo(() => {
const rotation = normalize_angle(
alpha - rad_to_deg(Math.PI / 2 + RADIAN_45 / 2)
);
return css`
width: ${chart_w}px;
height: ${chart_h}px;
transform: rotate(${rotation}deg);
`;
}, [chart_w, alpha]);
useEffect(() => {
const s = snap.current && Snap(snap.current);
if (s) {
[...new Array(8)].map((_, i) => {
draw({ index: i });
});
}
}, [chart_w, draw]);
return (
<div id="bagua-wrapper" css={wrapperStyle}>
<svg id="bagua" ref={snap} viewBox="-1 -1 2 2" css={baguaStyle}></svg>
</div>
);
};
// const pos = [];
// pos[0] = get_position(index * RADIAN_45, OUTER_RATIO);
// pos[1] = get_position(index * RADIAN_45 + RADIAN_45, OUTER_RATIO);
// pos[2] = get_position(index * RADIAN_45 + RADIAN_45, INNER_RATIO);
// pos[3] = get_position(index * RADIAN_45, INNER_RATIO);
//
// const path = [
// `M${pos[0].x},${pos[0].y}`,
// `L${pos[1].x},${pos[1].y}`,
// `L${pos[2].x},${pos[2].y}`,
// `L${pos[3].x},${pos[3].y}`,
// ].join(' ');

View File

@@ -0,0 +1,115 @@
/**
* SVG Pie Drawing
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*/
import React, {
useRef,
useState,
useMemo,
useEffect,
useCallback,
} from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_BAGUA_INFO } from '@/constants';
import {
fixed,
deg_to_rad,
get_position_clock,
gen_code_12,
} from '@/lib/utils';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
import { useFengShui } from '@/contexts/FengShui';
import { BaguaIcon } from '@/components/icons/bagua';
const RADIUS_RATIO = 0.66;
const ICON_RATIO = 0.094;
const RADIAN_45_HALF = RADIAN_45 / 2;
const fix2 = fixed(2);
const fix3 = fixed(3);
export const FengShuiBaguaInfo = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const { ready, get_bagua_start_north } = useFengShui();
const [boxes, setBoxes] = useState([]);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const alpha = deviceOrientation?.alpha || 0;
const wrapperStyle = useMemo(
() => css`
${tw`absolute text-white`}
width: ${chart_w}px;
height: ${chart_h}px;
`,
[chart_w]
);
const update = useCallback(
index => {
const chart_w_half = chart_w / 2;
const chart_h_half = chart_h / 2;
const icon_size = fix3(chart_w_half * ICON_RATIO);
const angle = index * RADIAN_45 + deg_to_rad(alpha);
const radius = chart_w_half * RADIUS_RATIO;
const pos = get_position_clock(angle, radius, {
x: chart_w_half,
y: chart_h_half,
});
const z_index = Z_INDEX_FENGSHUI_BAGUA_INFO + index * 1;
const style = css`
${tw`absolute flex flex-col justify-center items-center`}
top: ${fix2(pos.y)}px;
left: ${fix2(pos.x)}px;
z-index: ${z_index};
font-size: ${world?.text_lg}px;
line-height: 1em;
transform: translate(-50%, -50%);
`;
return { icon_size, style };
},
[chart_w, alpha]
);
useEffect(() => {
if (ready) {
setBoxes(
[...new Array(8)].map((_, index) => {
const bagua = get_bagua_start_north(index);
const name = bagua?.name;
return name
? {
key: gen_code_12(),
en: name.en,
ch: name.zh_cn?.alphabet,
}
: {};
})
);
}
}, [ready]);
useEffect(() => {
if (ready && chart_w > 0) {
setBoxes(prev => prev.map((p, i) => ({ ...p, ...update(i) })));
}
}, [ready, chart_w, alpha, update]);
return (
<div id="bagua-info-wrapper" css={wrapperStyle}>
{boxes.map((box, i) => (
<div id={`bagua-info-box-${i}`} key={box.key} css={box.style}>
<BaguaIcon name={box.en} styles={{ size: box.icon_size }} />
<div tw="mt-1">{box.ch}</div>
</div>
))}
</div>
);
};

50
docs/examples/chart.jsx Normal file
View File

@@ -0,0 +1,50 @@
import React, { useMemo } from 'react';
import tw, { css } from 'twin.macro';
import { gen_code_12 } from '@/lib/utils';
import { useErrors } from '@/contexts/Errors';
import { useWorld } from '@/contexts/World';
import { useFengShuiSync } from '@/contexts/FengShui';
import { FengShuiTwentyFour } from '@/components/fengshui/twentyfour';
import { FengShuiTwentyFourInfo } from '@/components/fengshui/twentyfour_info';
import { FengShuiCircle } from '@/components/fengshui/circle';
import { FengShuiNorth } from '@/components/fengshui/north';
import { FengShuiJiuXing } from '@/components/fengshui/jiuxing';
const wrapperStyleBase = tw`flex-none relative overflow-hidden w-full flex flex-col justify-center items-center`;
export const ChartChart = () => {
const { errors } = useErrors();
const { worldInfo: world } = useWorld();
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const wrapperStyle = useMemo(
() => css`
width: ${chart_w || 0}px;
height: ${chart_h || 0}px;
`,
[chart_w]
);
// Sync the latest primary profile to FengShuiContext.profile
useFengShuiSync();
return errors.length ? (
<div id="chart-main-wrapper" tw="text-white text-xs">
{errors.map(err => (
<div key={err.key}>{err.error}</div>
))}
</div>
) : (
<div id="chart-main-wrapper" css={[wrapperStyleBase, wrapperStyle]}>
<FengShuiTwentyFour key={gen_code_12()} />
<FengShuiTwentyFourInfo key={gen_code_12()} />
<FengShuiCircle key={gen_code_12()} ratio={0.33} />
<FengShuiNorth key={gen_code_12()} />
<FengShuiJiuXing key={gen_code_12()} />
</div>
);
};

298
docs/examples/index.md Normal file
View File

@@ -0,0 +1,298 @@
# Examples
[1. Example Rust + React Codes](#1-example-rust-react-codes)
[2. Setups for WASM Apps in Webpack](#2-setups-for-wasm-apps-in-webpack)
[3. Using WASM Apps from React](#3-using-wasm-apps-from-react)
[4. Feng-Shui Examples](#4-feng-shui-examples)
&nbsp; &nbsp; [4-1. `src/contexts/FengShui.js`](#4-1-srccontextsfengshuijs)
&nbsp; &nbsp; [4-2. `src/components/fengshui/jiuxing.jsx`](#4-2-srccomponentsfengshuijiuxingjsx)
## 1. Example Rust + React Codes
I picked up some files from one of the real-world projects of mine.
In this project, I have Rust codes in `src_for_wasm` and React codes in `src`.
Rust
- [src_for_wasm/Cargo.toml](./Cargo.toml)
- [src_for_wasm/lib.rs](./lib.rs)
Acts like a proxy to all the public functions provided by _mikaboshi_.
React
- [src/contexts/FengShui.js](FengShui.js)
A context provider set to the React app top, and delegates functions provided by `src_for_wasm/lib.rs`.
- [src/components/chart/chart.jsx](chart.jsx)
Provides a layout for Feng-Shui chart which contains a several child components such as `src/components/fengshui/jiuxing.jsx`, `src/components/twentyfour.jsx`, and so forth.
- [src/components/fengshui/bagua_info.jsx](bagua_info.jsx)
- [src/components/fengshui/bagua.jsx](bagua.jsx)
- [src/components/fengshui/jiuxing.jsx](jiuxing.jsx)
- [src/components/fengshui/north.jsx](north.jsx)
- [src/components/fengshui/twentyfour_info.jsx](twentyfour_info.jsx)
- [src/components/fengshui/twentyfour.jsx](twentyfour.jsx)
- [src/lib/utils.js](utils.js)
## 2. Setups for WASM Apps in Webpack
For how you can serve a WASM app, you may want to check out
one of my other projects,
_[perlin-experiment](https://github.com/minagawah/perlin-experiment)_.
Setups are about the same.
## 3. Using WASM Apps from React
When using a WASM app from React, you need to first
asynchronously wait for the module to be ready.
```js
import React, { useContext, createContext, useEffect, useState } from 'react';
import init, { order_pizza } from 'wasm-pizza';
const WASM_PATH =
NODE_ENV === 'production'
? 'wasm/wasm-pizza/wasm-pizza_bg.wasm'
: void 0;
const PizzaContext = createContext({
ready: false,
orderPizza: () => {},
});
export const PizzaProvider = () => {
const [ready, setReady] = useState(false);
useEffect(() => {
if (ready !== true) {
init(WASM_PATH)
.then(() => {
setReady(true);
})
.catch(err => {
throw err;
});
}
}, []);
const orderPizza = params => {
return order_pizza(params);
};
return (
<PizzaContext.Provider
value={{
ready,
orderPizza,
}}
/>
};
export const usePizza = () => useContext(PizzaContext);
```
Notice that we import `init` from `wasm-pizza` which is a compiled WASM app
provided as a NPM module. As mentioned in the previous section,
[take a look at one of my projects](https://github.com/minagawah/perlin-experiment)
for it explains how.
Now, you may use the provider:
`src/App.jsx`
```js
ReactDOM.render(
<PizzaProvider>
<App />
</PizzaProvider>,
document.getElementById('root')
);
```
From one of your components, you call the method:
```js
import { usePizza } from '@/contexts/Pizza';
export const Order = () => {
const { ready, orderPizza } = usePizza();
const [pizza, setPizza] = useState(null);
useEffect(() => {
setPizza(
orderPizza()
);
}, [ready])
return <div>{pizza}</div>;
};
```
## 4. Feng-Shui Examples
For example files provided at the beginning,
I will explain key features found in the codes.
Although it is the usage from React,
I believe you will at least get ideas
when using _mikaboshi_ for your projects.
### 4-1. `src/contexts/FengShui.js`
Source: [src/contexts/FengShui.js](FengShui.js)
`src/contexts/FengShui.js` is a React context provider,
and it provides `FengShuiContext`.
For resources provided by `FengShuiContext`
will be accessible for any child components
when using `useFengShui()`.
```js
const FengShuiContext = createContext({
ready: false,
profile: null, // localtime, direction, sector
bazi: null,
lichun: null,
unpan_xing: null, // 運盤星
shan_xing: null, // 山星
xiang_xing: null, // 向星
update: noop,
...
...
...
});
```
`src/contexts/FengShui.js` loads a WASM app (`src_for_wasm/lib/rs`).
Since it loads the WASM app asynchronously,
components must wait for `ready` to become `true`
for all the features to become available.
For `profile` is not actually peculiar to `src/contexts/FengShui.js`,
but is something managed in another provider `src/contexts/Profiles.js`.
Since there is no way for 2 providers to communicate,
we run `useFengShuiSync()` somewhere in one of the components
to sync the contents of `profile`.
`_set()` runs when the content of `profile` changes:
```js
const _set = useCallback(
(prof = {}) => {
const { localtime: current, direction, sector } = prof;
if (ready && current && direction) {
const lichun_0 = get_lichun(current.year());
const lichun = moment(lichun_0);
const center = get_unpan_xing_index({ current, lichun });
const xgtu = get_xiaguatu_from_unpan_index({
unpan_xing_center: center,
xiang_xing_direction: direction,
xiang_xing_sector: sector,
});
setBazi(
normalize_bazi_data(
wasm_get_bazi(datetime_params_from_localtime(current))
)
);
setLiChun(lichun);
setUnPanXing(xgtu.unpan_xing);
setShanXing(xgtu.shan_xing);
setXiangXing(xgtu.xiang_xing);
}
},
[ready, profile?.locatltime, profile?.direction]
);
```
In the above, using _mikaboshi_'s `get_xiaguatu_from_unpan_index()`
to obtain 下卦図 (Xia-Gua-Tu). For 3 arguments required,
we already have `xiang_xing_direction` and `xiang_xing_sector`
in `profile`, but for `unpan_xing_center` needs a preparation.
For `unpan_xing_center`, we run `get_unpan_xing_index()`.
And, again, it requires another preparation for `lichun`,
and we run `get_lichun()` for `lichun`.
### 4-2. `src/components/fengshui/jiuxing.jsx`
Source: [src/components/fengshui/jiuxing.jsx](jiuxing.jsx)
`jiuxing.jsx` is one of the child components wrapped in `FengShuiContext` provider.
Here is how it starts:
```js
export const FengShuiJiuXing = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const {
ready,
unpan_xing,
shan_xing,
xiang_xing,
get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction,
get_twentyfour_direction_from_degrees,
get_shengsi_mapping,
} = useFengShui();
```
As mentioned, using `useFengShui()`, will get you an access
for resources provided by `FengShuiContext`.
Let's continue.
```js
const { direction: curr_dir } = get_twentyfour_direction_from_degrees(
360 - alpha
);
```
Now, we are using `get_twentyfour_direction_from_degrees()`.
For a given angle (in degrees),
`get_twentyfour_direction_from_degrees()`
returns `direction` and `sector`.
`direction` is a compass direction represented
in a lower case string (e.g. `n`, `ne`, `e`, `se`, etc.).
`sector` is special concept unique to
[二十四山向 (Er-Shi-Si Shan-Xiang)](../compass.md).
For each compass direction is further divided into 3 sectors,
represented in a number `1`, `2`, or `3`.
Yet, for the above example, this time,
we are getting `direction` only,
and naming it: `curr_dir`
```js
const u_id = unpan_xing.center;
// When calculating for 下卦図 (Xia-Gua-Tu), not only
// the current 運盤星 (Un-Pan Xing), but we also want
// all 九星 (Jiu-Xing) in the 洛書 (Lo-Shu) order.
// Although we have `JIU_XING_DI_PAN_POSITIONS`
// which defines 洛書 (Lo-Shu) order, we want it
// in re-arranged order for the current device rotation,
// and that is what we pass for the second argument
// of `get_xiaguatu_from_unpan_index()`.
const u_order = get_jiuxing_dipan_positions_from_direction(curr_dir);
// Now, calculate for 下卦図 (Xia-Gua-Tu).
const xiagua = get_xiaguatu_from_unpan_index({
unpan_xing_center: u_id,
unpan_xing_order: u_order,
xiang_xing_direction: xiang_xing.direction,
xiang_xing_sector: xiang_xing.sector,
});
```
As mentioned before, `unpan_xing` (運盤星; Un-Pan Xing)
is managed in `src/contexts/FengShui.js`,
and we simply want to refer to it.
The same goes for `shan_xing` (山星; Shan-Xing),
and `xiang_xing` (向星; Xiang-Xing),
and we expect that we already have the values
stored in `src/contexts/FengShui.js`.

258
docs/examples/jiuxing.jsx Normal file
View File

@@ -0,0 +1,258 @@
/**
* This component renders 下卦図 (Xia-Gua-Tu)
* (also known as 飞星図; Fei-Xing-Tu; "Flying Star Chart").
* Like any other Feng-Shui charts, it consists of 9 boxes.
* Each box holds 3 different kinds of 九星 (Jiu-Xing),
* namely,
* 運盤星 (Un-Pan Xing) at the BOTTOM CENTER,
* 山星 (Shan-Xing) at the TOP LEFT, and
* 向星 (Xiang-Xing) at the TOP RIGHT.
* It also displays (in each box) the star's
* 生死衰旺 (Sheng-Si Shuai-Wang) status.
*/
import React, {
useRef,
useState,
useMemo,
useEffect,
useCallback,
} from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import { Z_INDEX_FENGSHUI_JIU_XING_BOXES } from '@/constants';
import { fixed, gen_code_12 } from '@/lib/utils';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
import { useFengShui } from '@/contexts/FengShui';
const BOX_RATIO = 0.55;
const SHENG_SI_COLOR = {
si: tw`bg-tomato-dark text-gray-100`,
shuai: tw`bg-abura-dark text-gray-100`,
};
const fix2 = fixed(2);
// This is the inner most wrapper which holds 9 boxes.
// As you can see, we have 3 rows and 3 columns.
const wrapperStyle = css`
width: calc(100% - 2px);
height: calc(100% - 2px);
display: grid;
grid-gap: 1px;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
`;
// This is the wrapper for each box.
// As you can see, it has 2 rows and 2 columns.
// However, the bottom row will fill up
// using all 2 columns, and 運盤星 (Un-Pan Xing)
// is placed in the middle.
const boxStyle = css`
${tw`text-gray-900 text-sm`}
display: grid;
grid-gap: 1px;
grid-template-columns: repeat(2, 0.5fr);
grid-template-rows: 0.333fr 0.666fr;
`;
const xingStyle = tw`
flex flex-col justify-center items-center text-sm font-medium
`;
export const FengShuiJiuXing = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const {
ready,
unpan_xing,
shan_xing,
xiang_xing,
get_xiaguatu_from_unpan_index,
get_jiuxing_dipan_positions_from_direction,
get_twentyfour_direction_from_degrees,
get_shengsi_mapping,
} = useFengShui();
const [flyingStarChart, setFlyingStarChart] = useState([]);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const body_h = world?.body?.height;
// Passing current degrees for the device, and obtaining
// the compass direction: "n", "ne", "e", "se", etc.
const alpha = deviceOrientation?.alpha || 0;
// `alpha` is a value that you get from Web API,
// but what you get for `alpha` is counterintuitive...
// When you have 10 degrees, it means that your device
// is not pointing toward the compass NE, but NW.
// When the device is pointing NW, you will see
// the red arrow (which usually points the magnetic N)
// will come to the top right. There, getting 10 degrees
// means that your device is rotating counter-clockwise,
// but we want the opposite. When it gives us 10 degrees,
// we want it to mean it is rotating clockwise,
// and the device to be pointing NE (instead of NW).
// That is why we are passing the complementary angle
// as a function argument (by subtracting the degree
// from 360).
const { direction: curr_dir } = get_twentyfour_direction_from_degrees(
360 - alpha
);
// This is the outer most wrapper, and is positioned `absolute`.
const wrapperWrapperWrapperStyle = useMemo(
() => css`
${tw`absolute w-full flex flex-col justify-center items-center`}
height: ${fix2(body_h)}px;
z-index: ${Z_INDEX_FENGSHUI_JIU_XING_BOXES};
`,
[body_h]
);
// The 2nd wrapper which basically defines width and height,
// and place the inner wrapper vertically and horizontally
// in the center.
const wrapperWrapperStyle = useMemo(() => {
const width = chart_w * BOX_RATIO;
return css`
${tw`flex flex-col justify-center items-center bg-gray-900`}
width: ${fix2(width)}px;
height: ${fix2(width)}px;
`;
}, [chart_w]);
// As long as you have information for the current
// 運盤星 (Un-Pan Xing) and 向星 (Xiang-Xing),
// you can have `get_xiaguatu_from_unpan_index()`
// calculates 下卦図 (Xia-Gua-Tu) for you.
// Once you obtain 下卦図 (Xia-Gua-Tu)
// (expressed as `xiagua` in the program),
// you will get charts for all 3 stars.
// For 生死衰旺 (Sheng-Si Shuai-Wang) is
// calculated from 運盤星 (Un-Pan Xing) only.
useEffect(() => {
if (ready && unpan_xing && shan_xing && xiang_xing) {
// console.log('[fengshui/jiuxing] curr_dir: ', curr_dir);
// The current 運盤星 (Un-Pan Xing) holds the key to everything!
const u_id = unpan_xing.center;
// When calculating for 下卦図 (Xia-Gua-Tu), not only
// the current 運盤星 (Un-Pan Xing), but we also want
// all 九星 (Jiu-Xing) in the 洛書 (Lo-Shu) order.
// Although we have `JIU_XING_DI_PAN_POSITIONS`
// which defines 洛書 (Lo-Shu) order, we want it
// in re-arranged order for the current device rotation,
// and that is what we pass for the second argument
// of `get_xiaguatu_from_unpan_index()`.
const u_order = get_jiuxing_dipan_positions_from_direction(curr_dir);
// Now, calculate for 下卦図 (Xia-Gua-Tu).
const xiagua = get_xiaguatu_from_unpan_index({
unpan_xing_center: u_id,
unpan_xing_order: u_order,
xiang_xing_direction: xiang_xing.direction,
xiang_xing_sector: xiang_xing.sector,
});
const u_chart = xiagua?.unpan_xing?.chart || [];
const s_chart = xiagua?.shan_xing?.chart || [];
const x_chart = xiagua?.xiang_xing?.chart || [];
if (u_chart.length > 0 && s_chart.length > 0 && x_chart.length > 0) {
// 生死衰旺 (Sheng-Si Shuai-Wang) for the current 運盤星 (Un-Pan Xing).
const shengsi = get_shengsi_mapping({
unpan_id: u_id,
unpan_xing_chart: u_chart,
});
let arr = [];
// Iterating for 9 boxes, and for each box, we have 3 stars.
// For each star, we are simply adding 1 to the star's "index"
// to get the star's "number" which is to be displayed.
for (let i = 0; i < 9; i++) {
const kanji = shengsi[i]?.kanji;
arr.push({
key: gen_code_12(),
style: SHENG_SI_COLOR[kanji] || tw`bg-cream`,
unpan_xing: { num: u_chart[i] + 1, kanji },
shan_xing: { num: s_chart[i] + 1 },
xiang_xing: { num: x_chart[i] + 1 },
});
}
if (arr.length > 0) {
setFlyingStarChart(arr);
}
}
}
}, [
ready,
unpan_xing?.center,
shan_xing?.center,
xiang_xing?.center,
curr_dir,
]);
return (
<div id="jiuxing-wrapper-wrapper-wrapper" css={wrapperWrapperWrapperStyle}>
<div id="jiuxing-wrapper-wrapper" css={wrapperWrapperStyle}>
<div id="jiuxing-wrapper" css={wrapperStyle}>
{flyingStarChart.map((box, i) => (
<div key={box.key} id={`box-${i}`} css={[boxStyle, box.style]}>
<div
id={`shan-xing-${i}`}
css={[
xingStyle,
css`
grid-column: 1 / 2;
grid-row: 1 / 2;
`,
]}
>
{box.shan_xing.num}
</div>
<div
id={`xiang-xing-${i}`}
css={[
xingStyle,
css`
grid-column: 2 / -1;
grid-row: 1 / 2;
`,
]}
>
{box.xiang_xing.num}
</div>
<div
id={`un-pan-${i}`}
css={[
xingStyle,
css`
grid-column: 1 / -1;
grid-row: 2 / -1;
justify-content: flex-start;
`,
]}
>
<div>{box.unpan_xing.num}</div>
<div tw="text-xs font-bold">{box.unpan_xing.kanji}</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};

201
docs/examples/lib.rs Normal file
View File

@@ -0,0 +1,201 @@
use mikaboshi::bagua::{get_bagua_start_north as _bagua_start_north, Bagua};
use mikaboshi::compass::{
get_direction_positions_in_chart as _direction_positions_in_chart,
get_opposite_direction as _opposite_direction,
get_twentyfour_data_from_direction as _twentyfour_data_from_direction,
get_twentyfour_data_from_index as _twentyfour_data_from_index,
get_twentyfour_direction_from_degrees as _twentyfour_direction_from_degrees,
get_twentyfour_direction_from_index as _twentyfour_direction_from_index, Direction,
TwentyFourType,
};
use mikaboshi::ganzhi::Bazi;
use mikaboshi::jiuxing::{
get_jiuxing_dipan_positions_from_direction as _jiuxing_dipan_positions_from_direction,
get_jiuxing_from_index as _jiuxing_from_index,
get_xiaguatu_from_unpan_index as _xiaguatu_from_unpan_index,
unpan_xing_index as _unpan_xing_index, JiuXing, XiaGuaTu,
};
use mikaboshi::shengsi::{get_shengsi_mapping as _get_shengsi_mapping, ShengSi};
use mikaboshi::solar_terms::get_lichun as _get_lichun;
use mikaboshi::time::{Date, DateTime};
use std::collections::HashMap;
use std::convert::{From, TryInto};
use wasm_bindgen::prelude::*;
// use log::info;
// use log::Level;
pub mod structs;
use crate::structs::{DateParams, DateTimeParams, ShengSiParams, XiaGuaTuParams};
/// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen(start)]
pub fn main() {
console_error_panic_hook::set_once();
}
// ================================================================
// 八卦 (Bagua)
// ================================================================
#[wasm_bindgen]
pub fn get_bagua_start_north(index: usize) -> JsValue {
let bagua: Option<&Bagua> = _bagua_start_north(index);
JsValue::from_serde(&bagua).unwrap()
}
// ================================================================
// 二十四山向 (Er-Shi Si-Shan Xiang)
// ================================================================
#[wasm_bindgen]
pub fn get_twentyfour_direction_from_index(index: usize) -> JsValue {
let dir: &Direction = _twentyfour_direction_from_index(index);
JsValue::from_serde(dir).unwrap()
}
#[wasm_bindgen]
pub fn get_twentyfour_data_from_index(index: usize) -> JsValue {
let t_type: TwentyFourType = _twentyfour_data_from_index(index);
match t_type {
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
}
}
#[wasm_bindgen]
pub fn get_twentyfour_direction_from_degrees(degrees: f32) -> JsValue {
let dir: Direction = _twentyfour_direction_from_degrees(degrees);
// log(&format!("[wasm] degrees: {}", degrees));
// log(&format!("[wasm] dir: {:?}", dir));
JsValue::from_serde(&dir).unwrap()
}
#[wasm_bindgen]
pub fn get_twentyfour_data_from_direction(direction: &str, sector: usize) -> JsValue {
let t_type: TwentyFourType = _twentyfour_data_from_direction(direction, sector);
match t_type {
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
}
}
// ================================================================
// 干支 (Gan-Zhi)
// ================================================================
#[wasm_bindgen]
pub fn get_bazi(params: &JsValue) -> JsValue {
let params: DateTimeParams = params.into_serde().unwrap();
let localtime = DateTime::from(&params);
let zone = params.zone;
JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
}
#[wasm_bindgen]
pub fn get_lichun(year: i16) -> JsValue {
// log(&format!("{:?}", year));
let lichun = _get_lichun(year);
JsValue::from_str(&format!(
"{:04}-{:02}-{:02}",
lichun.year as u16, lichun.month as u8, lichun.day as u8
))
}
// ================================================================
// 九星 (Jiu-Xing)
// ================================================================
#[wasm_bindgen]
pub fn get_jiuxing_from_index(index: usize) -> JsValue {
let dir: &JiuXing = _jiuxing_from_index(index);
JsValue::from_serde(dir).unwrap()
}
#[wasm_bindgen]
pub fn get_unpan_xing_index(current: &JsValue, lichun: &JsValue) -> JsValue {
let params_1: DateParams = current.into_serde().unwrap();
let params_2: DateParams = lichun.into_serde().unwrap();
// log(&format!("params_1: {:?}", params_1));
// log(&format!("params_2: {:?}", params_2));
let current = Date::from(&params_1);
let lichun = Date::from(&params_2);
let index: usize = _unpan_xing_index(&current, &lichun);
JsValue::from_f64(index as f64)
}
#[wasm_bindgen]
pub fn get_xiaguatu_from_unpan_index(params: &JsValue) -> JsValue {
let params: XiaGuaTuParams = params.into_serde().unwrap();
// log("[wasm] get_xiaguatu_from_unpan_index()");
// log(&format!("[wasm] params: {:?}", params));
let unpan_xing_order: [usize; 9] =
params
.unpan_xing_order
.try_into()
.unwrap_or_else(|v: Vec<usize>| {
panic!("Expected a Vec of length 9 but it was {}", v.len())
});
let xia_gua_tu: HashMap<&str, XiaGuaTu> = _xiaguatu_from_unpan_index(
params.unpan_xing_center,
&unpan_xing_order,
params.xiang_xing_direction.as_str(),
params.xiang_xing_sector,
);
JsValue::from_serde(&xia_gua_tu).unwrap()
}
// A simple accessor for getting values in JIU_XING_DI_PAN_POSITIONS.
#[wasm_bindgen]
pub fn get_jiuxing_dipan_positions_from_direction(direction: &str) -> JsValue {
JsValue::from(
(match _jiuxing_dipan_positions_from_direction(direction) {
Some(positions) => positions.to_vec(),
_ => Vec::new(),
})
.into_iter()
.map(|index| JsValue::from(index as u32))
.collect::<js_sys::Array>(),
)
}
// ================================================================
// 生死衰旺 (Sheng-Si Shuai-Wang)
// ================================================================
#[wasm_bindgen]
pub fn get_shengsi_mapping(params: &JsValue) -> JsValue {
let params: ShengSiParams = params.into_serde().unwrap();
let unpan_id: usize = params.unpan_id;
let chart: [usize; 9] = params
.unpan_xing_chart
.try_into()
.unwrap_or_else(|v: Vec<usize>| {
panic!("Expected a Vec of length 9 but it was {}", v.len())
});
let mapping: Vec<Option<&ShengSi>> = _get_shengsi_mapping(unpan_id, &chart);
// log(&format!("[wasm] mapping: {:?}", mapping));
JsValue::from_serde(&mapping).unwrap()
}

94
docs/examples/north.jsx Normal file
View File

@@ -0,0 +1,94 @@
/**
* SVG Pie Drawing
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*/
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import Snap from 'snapsvg-cjs';
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_NORTH } from '@/constants';
import { int, rad_to_deg, normalize_angle, get_position } from '@/lib/utils';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
const FILL_COLOR = '#ef4444';
const OUTER_RATIO = 0.8;
const INNER_RATIO = 0.7;
const ARROW_SIZE = (OUTER_RATIO - INNER_RATIO) * 0.5;
const wrapperStyle = css`
${tw`absolute w-full flex flex-col justify-center items-center`}
z-index: ${Z_INDEX_FENGSHUI_NORTH};
`;
export const FengShuiNorth = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const snap = useRef(null);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const alpha = deviceOrientation?.alpha;
const northStyle = useMemo(() => {
const rotation = normalize_angle(alpha - rad_to_deg(Math.PI / 2));
return css`
width: ${chart_w}px;
height: ${chart_h}px;
transform: rotate(${rotation}deg);
`;
}, [chart_w, alpha]);
// A: rx,ry x-axis-rotation large-flag,sweep-flag end-x,end-y
const draw = useCallback(
({ index }) => {
const s = snap.current && Snap(snap.current);
if (s) {
}
},
[chart_w, chart_h]
);
useEffect(() => {
const s = snap.current && Snap(snap.current);
if (s) {
const g = s.path('');
g.remove();
const pos = [];
pos[0] = {
x: INNER_RATIO,
y: -ARROW_SIZE,
};
pos[1] = {
x: OUTER_RATIO,
y: 0,
};
pos[2] = {
x: INNER_RATIO,
y: ARROW_SIZE,
};
const path = [
`M${pos[0].x},${pos[0].y}`,
`L${pos[1].x},${pos[1].y}`,
`L${pos[2].x},${pos[2].y}`,
`L${pos[0].x},${pos[0].y}`,
].join(' ');
s.path(path).attr({
fill: FILL_COLOR,
});
}
}, [chart_w, chart_h]);
return (
<div id="north-wrapper" css={wrapperStyle}>
<svg id="north" ref={snap} viewBox="-1 -1 2 2" css={northStyle}></svg>
</div>
);
};

View File

@@ -0,0 +1,147 @@
/**
* SVG Pie Drawing
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*/
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import Snap from 'snapsvg-cjs';
import { RADIAN_15, Z_INDEX_FENGSHUI_ER_SHI_SI } from '@/constants';
import {
rad_to_deg,
deg_to_rad,
normalize_angle,
get_position,
} from '@/lib/utils';
import { TW_CUSTOM_COLORS } from '@/styles/shared';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
import { useFengShui } from '@/contexts/FengShui';
const FILL_COLOR = '#303030';
const FILL_COLOR_DARK = '#000000';
const STROKE_COLOR = '#ffffff';
const wrapperStyle = css`
${tw`absolute w-full flex flex-col justify-center items-center`}
z-index: ${Z_INDEX_FENGSHUI_ER_SHI_SI};
`;
export const FengShuiTwentyFour = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const {
ready,
xiang_xing,
get_twentyfour_direction_from_index,
get_twentyfour_data_from_index,
} = useFengShui();
const snap = useRef(null);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const alpha = deviceOrientation?.alpha || 0;
const svgStyle = useMemo(() => {
const rotation = normalize_angle(
alpha - rad_to_deg(Math.PI / 2 + RADIAN_15 / 2)
);
return css`
transform: rotate(${rotation}deg);
width: ${chart_w}px;
height: ${chart_h}px;
`;
}, [chart_w, alpha]);
const draw = useCallback(
({ index }) => {
const s = snap.current && Snap(snap.current);
if (s && ready) {
const beg = get_position(index * RADIAN_15);
const end = get_position(index * RADIAN_15 + RADIAN_15);
const large = end.deg - beg.deg > 180 ? 1 : 0;
// Remember, the whole wrapper is rotated later
// by 262.5 (= 90 + 15/2), degrees so that
// the first slice which originally sits
// at the 3 o'clock position will come
// to the 12 o'clock position.
//
// Also, we apply `viewbox="-1 -1 2 2"` for the SVG,
// meaning, the origin of SVG is situated
// at the very center of the wrapper.
// That is to say, we have `x = 0`.
// When we have `x = 1`, it means `x` is reaching
// all the way at the right edge of the wrapper.
//
// For each slice, we use SVG's arc function
// which is defined as:
//
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
//
// A1,1 0 0,1 0.965,0.258
//
// At first, we will first move (`M`) to where
// it is close to the right edge of the screen.
// Since we have no ratio specified, and the SVG
// will fill up the whole space for the wrapper,
// we will have it being `M1,0`.
// Next, for the arc, we will probably have
// `A1,1 0 0,1 0.965,0.258`.
// So, it is an arc having `1` for the radius,
// and it will draw the arc for 15 degrees.
// Lastly, we have `L0,0` to go back to the origin,
// otherwise, it won't fill (with given color)
// the region surrounded by the path.
const path = [
`M${beg.x},${beg.y}`,
`A1,1 0 ${large},1 ${end.x},${end.y}`,
`L0,0`,
].join(' ');
const er_shi_si = get_twentyfour_data_from_index(index);
const { direction, sector } =
get_twentyfour_direction_from_index(index);
let fill = er_shi_si[0] === 2 ? FILL_COLOR : FILL_COLOR_DARK;
if (
xiang_xing &&
direction === xiang_xing.direction &&
sector === xiang_xing.sector
) {
fill = TW_CUSTOM_COLORS['abura-lightest'];
}
s.path(path).attr({
fill,
stroke: STROKE_COLOR,
strokeWidth: world?.stroke_size,
});
}
},
[ready, chart_w, xiang_xing]
);
useEffect(() => {
const s = snap.current && Snap(snap.current);
if (s && ready) {
[...new Array(24)].map((_, i) => {
draw({ index: i });
});
}
}, [ready, chart_w, xiang_xing, draw]);
return (
<div id="er-shi-si-wrapper" css={wrapperStyle}>
<svg id="er-shi-si" ref={snap} viewBox="-1 -1 2 2" css={svgStyle}></svg>
</div>
);
};

View File

@@ -0,0 +1,147 @@
/**
* SVG Pie Drawing
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*/
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { compose, tap } from 'ramda';
import tw, { css } from 'twin.macro';
import { RADIAN_15, Z_INDEX_FENGSHUI_ER_SHI_SI_INFO } from '@/constants';
import {
int,
fixed,
deg_to_rad,
get_position_clock,
gen_code_12,
} from '@/lib/utils';
import { useWorld } from '@/contexts/World';
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
import { useFengShui } from '@/contexts/FengShui';
const RATIO = 0.9;
const RADIAN_15_HALF = RADIAN_15 / 2;
const fix2 = fixed(2);
const boxBaseStyle = css`
${tw`absolute flex flex-col justify-center items-center`}
line-height: 1em;
transform: translate(-50%, -50%);
`;
const sectorStyle = css`
font-size: 0.94em;
`;
export const FengShuiTwentyFourInfo = () => {
const { worldInfo: world } = useWorld();
const deviceOrientation = useDeviceOrientation();
const {
ready,
xiang_xing,
get_twentyfour_direction_from_index,
get_twentyfour_direction_from_degrees,
get_twentyfour_data_from_index,
} = useFengShui();
const [boxes, setBoxes] = useState([]);
const chart_w = world?.chart?.width;
const chart_h = world?.chart?.height;
const alpha = deviceOrientation?.alpha || 0;
const wrapperStyle = useMemo(
() => css`
${tw`absolute text-white`}
width: ${chart_w}px;
height: ${chart_h}px;
`,
[chart_w]
);
const update = useCallback(
index => {
if (!ready) return;
const z_index = Z_INDEX_FENGSHUI_ER_SHI_SI_INFO + index * 1;
const chart_w_half = chart_w / 2;
const chart_h_half = chart_h / 2;
const angle = index * RADIAN_15 + deg_to_rad(alpha);
const radius = chart_w_half * RATIO;
const pos = get_position_clock(angle, radius, {
x: chart_w_half,
y: chart_h_half,
});
const { direction, sector } = get_twentyfour_direction_from_index(index);
const color =
xiang_xing &&
direction === xiang_xing.direction &&
sector === xiang_xing.sector
? 'color: #000000'
: '';
return {
style: css`
z-index: ${z_index};
top: ${fix2(pos.y)}px;
left: ${fix2(pos.x)}px;
font-size: ${world?.text_base}px;
${color}
`,
};
},
[ready, chart_w, alpha, xiang_xing]
);
useEffect(() => {
if (ready) {
const arr = [...new Array(24)].map((_, i) => {
const er_shi_si = get_twentyfour_data_from_index(i);
const name = er_shi_si?.name;
const degrees = i * 15;
const { direction, sector } =
get_twentyfour_direction_from_degrees(degrees);
const text = name?.zh_cn?.alphabet || '';
const text2 = `${direction.toUpperCase()}${sector}`;
// console.log(`[fengshui/twentyfour_info] [${i}] (${degrees} degrees)`);
// console.log(`[fengshui/twentyfour_info] --> ${text2}: ${text}`);
return {
key: gen_code_12(),
text,
text2,
};
});
setBoxes(arr);
}
}, [ready]);
useEffect(() => {
if (ready) {
setBoxes(prev => prev.map((p, i) => ({ ...p, ...update(i) })));
}
}, [ready, chart_w, alpha, update]);
return (
<div id="er-shi-si-info-wrapper" css={wrapperStyle}>
{boxes.map((box, i) => (
<div
id={`er-shi-si-info-box-${i}`}
key={box.key}
css={[boxBaseStyle, box.style]}
>
<div>{box.text}</div>
<div css={sectorStyle}>{box.text2}</div>
</div>
))}
</div>
);
};

139
docs/examples/utils.js Normal file
View File

@@ -0,0 +1,139 @@
import { compose } from 'ramda';
import { PIE_UNIT_HALF } from '@/constants';
export const int = Math.trunc;
export const noop = () => {};
export const fixed =
(decimals = 3) =>
n => {
const place = Math.pow(10, decimals);
return int(n * place) / place;
};
// export const to_fixed = (n, decimals = 3) => fixed(decimals)(n);
export const capitalize = s => s[0].toUpperCase() + s.slice(1);
export const gen_code_4 = () =>
Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
export const gen_code_12 = () =>
`${gen_code_4()}${gen_code_4()}${gen_code_4()}`;
export const rad_to_deg = rad => rad * (180 / Math.PI);
export const deg_to_rad = deg => deg * (Math.PI / 180);
export const normalize_degree = deg => ((deg % 360) + 360) % 360;
export const normalize_angle = normalize_degree;
export const normalize_radian = compose(
deg_to_rad,
normalize_degree,
rad_to_deg
);
export const get_position = (rad, radius = 1, center = { x: 0, y: 0 }) => ({
deg: normalize_degree(rad_to_deg(rad)),
x: center.x + Math.cos(rad) * radius,
y: center.y + Math.sin(rad) * radius,
});
// Compared to `get_position`, this function uses
// `Math.sin()` for `x`, and `Math.cos()` for `y`.
export const get_position_clock = (
rad,
radius = 1,
center = { x: 0, y: 0 }
) => ({
deg: normalize_degree(rad_to_deg(rad)),
x: center.x + Math.sin(rad) * radius,
y: center.y - Math.cos(rad) * radius,
});
export const euler_from_quaternion = (q = []) => {
/*
* alpha
* - Yew
* - Rotation around Z-axis (a ray coming out of your phone)
* - Screen-face rotation (like a car handle when driving)
*/
const alpha = normalize_degree(
rad_to_deg(
Math.atan2(
2 * q[0] * q[1] + 2 * q[2] * q[3],
1 - 2 * q[1] * q[1] - 2 * q[2] * q[2]
)
)
);
/*
* gamma
* - Roll
* - Rotation around Y-axis (phone's vertical axis)
* - Horizontal tilt (left-right tilt)
*/
const gamma = normalize_degree(
rad_to_deg(
Math.atan2(
2 * (q[3] * q[0] + q[1] * q[2]),
1 - 2 * (q[0] * q[0] + q[1] * q[1])
)
)
);
return [alpha, gamma];
};
export const get_utc_offset_in_hours = dt =>
int(Math.floor(dt.utcOffset() / 60));
export const str_to_hex = str => parseInt(str.slice(1), 16);
export const pad =
(digits = 2) =>
(n = 0) =>
n.toString().padStart(digits, '0');
export const is_leap_year = year => {
if (year % 4 == 0) {
if (year % 100 == 0) {
return year % 400 == 0;
} else {
return true;
}
} else {
return false;
}
};
export const is_iOS =
navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
navigator.userAgent.match(/AppleWebKit/);
let time = Date.now();
export const debounce = (f, delay) => {
let timeout = null;
let args = null;
const g = () => {
f.apply(null, args);
time = Date.now();
};
return function () {
args = arguments;
if (Date.now() >= time + delay) {
// Execute if the time has passed.
g();
} else {
// Cancel the previous ones, and execute only the last one.
!!timeout && clearTimeout(timeout);
timeout = setTimeout(g, delay);
}
};
};

264
docs/ganzhi.md Normal file
View File

@@ -0,0 +1,264 @@
# 干支 (Gan-Zhi)
Source: [src/ganzhi.rs](../src/ganzhi.rs)
Based on 5 elements in nature with its 陰 (Yin) and 陽 (Yang) for each,
ancient Chinese described the plant growth using 10 conventional symbols
known as "10 Gan" (十干). Also, they tracked the motion of Jupiter
(which has 12 year cycle) and so they did divided the night sky into 12 regions,
and this is known as "12 Zhi" (十二支). When they record time and space,
they used the combinations of 10 Gan (干) and 12 Zhi (支)
which makes 60 patterns, and this is called 干支 (Gan-Zhi).
10 Gan (干):
[0] 甲 (Jia)
[1] 乙 (Yi)
[2] 丙 (Bing)
[3] 丁 (Ding)
[4] 戊 (Wu)
[5] 己 (Ji)
[6] 庚 (Geng)
[7] 辛 (Xin)
[8] 壬 (Ren)
[9] 癸 (Gui)
12 Zhi (支):
[0] 子 (Zi)
[1] 丑 (Chou)
[2] 寅 (Yin)
[3] 卯 (Mao)
[4] 辰 (Chen)
[5] 巳 (Si)
[6] 午 (Wu)
[7] 未 (Wei)
[8] 申 (Shen)
[9] 酉 (You)
[10] 戌 (Xu)
[11] 亥 (Hai)
Reference:
- [Sexagenary cycle - Wiki](https://en.wikipedia.org/wiki/Sexagenary_cycle)
## ganzhi::Stem
A struct representing 干 (Gan) or "Stem" and stores its attributes.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Stem {
pub num: u8,
pub name: Language,
}
```
## ganzhi::Branch
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Branch {
pub num: u8,
pub name: Language,
}
```
A struct representing 支 (Zhi) or "Branch" and stores its attributes.
## ganzhi::StemRawData
A temporary struct for loading JSON data when defining a static const `STEMS`.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StemRawData {
pub num: u8,
pub name: LanguageData,
}
```
## ganzhi::BranchRawData
A temporary struct for loading JSON data when defining a static const `BRANCHES`.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchRawData {
pub num: u8,
pub name: LanguageData,
}
```
## ganzhi::GanZhi
A struct for holding `Stem` and `Branch`, or denoted as 干支 (Gan-Zhi).
```
#[derive(Debug, Serialize)]
pub struct GanZhi<'a> {
pub stem: &'a Stem,
pub branch: &'a Branch,
}
```
## ganzhi::Bazi
A struct representing 八字 (Bazi) and stores `GanZhi` as its attributes.
It is referred as _"The Four Pillars of Destiny"_ in English
mainly because the structure of 八字 (Bazi) necessary
for divinations in 四柱命理学 (_"The Four Pillars of Destiny"_).
```rust
#[derive(Debug, Serialize)]
pub struct Bazi<'a> {
pub year: GanZhi<'a>,
pub month: GanZhi<'a>,
pub day: GanZhi<'a>,
pub hour: GanZhi<'a>,
}
```
## ganzhi::STEMS
`Vec<Stem>`
A static vector with 10 items, each represents 干 (Gan).
Each stores associated attributes for the 干 (Gan).
[0] 甲 (Jia)
[1] 乙 (Yi)
[2] 丙 (Bing)
[3] 丁 (Ding)
[4] 戊 (Wu)
[5] 己 (Ji)
[6] 庚 (Geng)
[7] 辛 (Xin)
[8] 壬 (Ren)
[9] 癸 (Gui)
For attributes details stored in the vector is found in JSON file:
[json/ganzhi_stems.json](../json/ganzhi_stems.json)
## ganzhi::BRANCHES
`Vec<Branch>`
A static vector with 10 items, each represents 支 (Zhi).
Each stores associated attributes for the 支 (Zhi).
[0] 子 (Zi)
[1] 丑 (Chou)
[2] 寅 (Yin)
[3] 卯 (Mao)
[4] 辰 (Chen)
[5] 巳 (Si)
[6] 午 (Wu)
[7] 未 (Wei)
[8] 申 (Shen)
[9] 酉 (You)
[10] 戌 (Xu)
[11] 亥 (Hai)
For attributes details stored in the vector is found in JSON file:
`src/json/ganzhi_branches.json`
## ganzhi::GANZHI_SEXAGESIMAL
`Vec<(usize, usize)>`
A static vector with 60 items. `Vec<usize, usize>` where the first
`usize` being the `STEMS` index, and the second for the `BRANCHES`.
It is simply the combination of 10 stems and 12 branches
which eventually adds up to 60 patterns.
## ganzhi::HOUR_STEM_TABLE
`[[usize; 5]; 12]`
This is a table used when finding "Hour Stem".
Columns represents "Day Stem" groups, and there are 5 groups.
For insntace, if you have 甲 for "Day Stem",
you are looking into the first column (group).
Rows represents "Hour Branches" for which there are 12.
For instance, if you have 子 for "Hour Branch",
you are looking into the first row.
So, when you have 甲 for "Day Stem",
and 子 for "Hour Branch", "Hour Stem" is located
in the first column in the first row, which is 甲.
&nbsp; &nbsp; &nbsp; 甲乙丙丁戊
&nbsp; &nbsp; &nbsp; 己庚辛壬癸
&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;
子: 甲丙戊庚壬
丑: 乙丁己辛癸
寅: 丙戊庚壬甲
卯: 丁己辛癸乙
辰: 戊庚壬甲丙
巳: 己辛癸乙丁
午: 庚壬甲丙戊
未: 辛癸乙丁己
申: 壬甲丙戊庚
酉: 癸乙丁己辛
戌: 甲丙戊庚壬
亥: 乙丁己辛癸
## ganzhi::Bazi::from_local
Returns `Bazi` from localtime (`DateTime`) and zone (`i8`).
Example:
```rust
use mikaboshi::time::{ Month, DateTime };
use mikaboshi::ganzhi::{ Bazi, GanZhi };
let zone: i8 = 9;
let lt = DateTime {
year: 2021,
month: Month::Jul,
day: 7.0,
hour: 0,
min: 0,
sec: 0.0,
};
let bazi: Bazi = Bazi::from_local(&lt, zone);
let year: GanZhi = bazi.year;
let month: GanZhi = bazi.month;
let day: GanZhi = bazi.day;
let hour: GanZhi = bazi.hour;
println!("年: {} ({})", year.alphabet(), year.alphabet_ja());
println!("月: {} ({})", month.alphabet(), month.alphabet_ja());
println!("日: {} ({})", day.alphabet(), day.alphabet_ja());
println!("時: {} ({})", hour.alphabet(), hour.alphabet_ja());
// 年: 辛丑 (かのと・うし)
// 月: 甲午 (きのえ・うま)
// 日: 乙卯 (きのと・う)
// 時: 癸未 (みずのと・ひつじ)
```
Example using `js_sys`:
```rust
use mikaboshi::ganzhi::Bazi;
use mikaboshi::time::{DateTime, Month};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn get_bazi(params: &JsValue) -> JsValue {
let localtime = DateTime {
year: 1985,
month: Month::Nov,
day: 5.0,
hour: 1,
min: 35,
sec: 0.0,
};
let zone: i8 = 9;
JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
}
```

301
docs/jiuxing.md Normal file
View File

@@ -0,0 +1,301 @@
# 九星 (Jiu-Xing)
Source: [src/jiuxing.rs](../src/jiuxing.rs)
At the beginning, the law governing the universe was simple.
Yet, as man acquired the faculty of thought, was it no longer so.
It was not the universe which changed, but was about how man began
to see the universe differently. Thought, after all, is nothing
but reflections of the outer world. In another word, the outer
world we perceive could only be understood via patterns
that are innate to man's thought. Just like "Malkuth" in _Kabbalha_
is about both the earthly kingdom and the man himself,
as ancient Chinese attempted describing patterns in the universe,
they introduced another artificial element "metal"
(or "earth" when it is deployed in actual reality).
For the ancient Chinese, the former is called
先天八卦 ("the Primordial Heaven"), and the latter,
後天八卦 ("the Manifested Heaven").
To study the patterns peculiar to each universe, a conventional
board with 8 directions and 1 in the center has been in use,
where "8 Gua" (八卦) are assigned for slots on the board.
However, for many 風水 (Feng-Shui) systems, we are normally
dealing with the latter, or 後天八卦 ("the Manifested Heaven").
For 後天八卦 ("the Manifested Heaven") has a specific name
in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui), and is called
地盤 (Di-Pan). However, there are 3 more boards in
玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui)
in addition to 地盤 (Di-Pan), namely:
(1) 運盤 (Un-Pan) (or 天盤 (Tien-Pan))
(2) 山星 (Shan-Xing)
(3) 向星 (Xiang-Xing)
In practice, for all the above 3 boards, 九星 (Jiu-Xing)
or "the Nine Stars" are assigned. While "8 Gua" (八卦)
has fixed positions, 九星 (Jiu-Xing) changes
over time for spatial constraints given.
When their positions change, the movement is called
飞泊 (Fei-Po) or "flying" because of how it appears
to our eyes when they move.
For the first board 運盤 (Un-Pan), positions of 九星 (Jiu-Xing)
are determined by building's construction year,
and calculated based on 三元九運 (Sang-Yuan Jiu-Yun)
or "9 Yearly Cycles". We could say that 運盤 (Un-Pan)
essentially describes of the temporal aspect of the building
For 山星 (Shan-Xing) and 向星 (Xiang-Xing) are determined
by spatial aspects of the building, though, temporal aspects
are also associated indirectly in calculations.
When 運盤 (Un-Pan), 山星 (Shan-Xing), and 向星 (Xiang-Xing)
are added to 地盤 (Di-Pan) at the bottom, it is called
下卦図 (Xia-Gua-Tu), or simply referred as
飞星図 (Fei-Xing-Tu; "the Flying Star Chart").
Jiu-Xing (九星):
[0] 一白水星 (1 White)
[1] 二黒土星 (2 Black)
[2] 三碧木星 (3 Jade)
[3] 四緑木星 (4 Green)
[4] 五黄土星 (5 Yellow)
[5] 六白金星 (6 White)
[6] 七赤金星 (7 Red)
[7] 八白土星 (8 White)
[8] 九紫火星 (9 Purple)
Reference:
- [Flying Star Feng Shui - Wiki](https://en.wikipedia.org/wiki/Flying_Star_Feng_Shui)
## jiuxing::JiuXing
A struct representing 九星 (Jiu-Xing).
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JiuXing {
pub num: u8,
pub direction: String,
pub name: Language,
pub color: String,
pub element: WuXing,
pub planet: Planet,
}
```
## jiuxing::JiuXingRawData
A temporary struct for loading JSON data when defining a static const `JIU_XING`.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JiuXingRawData {
pub num: u8,
pub direction: String,
pub name: LanguageData,
pub color: String,
pub element: u8,
pub planet: u8,
}
```
## jiuxing::XiaGuaTuKind
```rust
#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
pub enum XiaGuaTuKind {
UnPanXing, // 運盤
ShanXing, // 山星
XiangXing, // 向星
}
```
## jiuxing::XiaGuaTu
A struct representing 下卦図 (Xia-Gua-Tu).
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XiaGuaTu<'a> {
pub kind: XiaGuaTuKind,
pub center: Option<usize>,
pub direction: Option<&'a str>,
pub sector: Option<usize>,
pub chart: Option<[usize; 9]>,
}
```
## jiuxing::DIRECTION_TO_JIU_XING
`HashMap<&str, usize>`
## jiuxing::JIU_XING
`[JiuXing; 9]`
A static vector with 9 items, each represents 九星 (Jiu-Xing).
[0] 一白水星 (1 White)
[1] 二黒土星 (2 Black)
[2] 三碧木星 (3 Jade)
[3] 四緑木星 (4 Green)
[4] 五黄土星 (5 Yellow)
[5] 六白金星 (6 White)
[6] 七赤金星 (7 Red)
[7] 八白土星 (8 White)
[8] 九紫火星 (9 Purple)
For attributes details stored in the vector is found in JSON file:
[json/jiuxing.json](../json/jiuxing.json)
## jiuxing::JIU_XING_DI_PAN_POSITIONS
`HashMap<&str, [usize; 9]>`
Although 洛書 (Lo-Shu) order is fixed, when 地盤 (Di-Pan)
is drawn on a device screen, the mapping for
九星 (Jiu-Xing) changes as the device rotates.
For example, 一白水星 (1 White) usually comes to the top
of the board when a device is pointing north. However,
when pointing north east, 一白水星 (1 White) moves
to the top left (which is north west).
For 8 compass directions, this constant provides
a mapping for the 洛書 (Lo-Shu) order.
For "n", 一白水星 (1 White) is the 2nd in the array.
For "ne", 一白水星 (1 White) is the 1st in the array.
It would look like this:
[5] 六白 [0] 一白 [7] 八白
[6] 七赤 [4] 五黄 [2] 三碧
[1] 二黒 [8] 九紫 [3] 四緑
n: [5, 0, 7, 6, 4, 2, 1, 8, 3]
[0] 一白 [7] 八白 [2] 三碧
[5] 六白 [4] 五黄 [3] 四緑
[6] 七赤 [1] 二黒 [8] 九紫
ne: [0, 7, 2, 5, 4, 3, 6, 1, 8]
[7] 八白 [2] 三碧 [3] 四緑
[0] 一白 [4] 五黄 [8] 九紫
[5] 六白 [6] 七赤 [1] 二黒
e: [7, 2, 3, 0, 4, 8, 5, 6, 1]
[2] 三碧 [3] 四緑 [8] 九紫
[7] 八白 [4] 五黄 [1] 二黒
[0] 一白 [5] 六白 [6] 七赤
se: [2, 3, 8, 7, 4, 1, 0, 5, 6]
[3] 四緑 [8] 九紫 [1] 二黒
[2] 三碧 [4] 五黄 [6] 七赤
[7] 八白 [0] 一白 [5] 六白
s: [3, 8, 1, 2, 4, 6, 7, 0, 5]
[8] 九紫 [1] 二黒 [6] 七赤
[3] 四緑 [4] 五黄 [5] 六白
[2] 三碧 [7] 八白 [0] 一白
sw: [8, 1, 6, 3, 4, 5, 2, 7, 0]
[1] 二黒 [6] 七赤 [5] 六白
[8] 九紫 [4] 五黄 [0] 一白
[3] 四緑 [2] 三碧 [7] 八白
w: [1, 6, 5, 8, 4, 0, 3, 2, 7]
[6] 七赤 [5] 六白 [0] 一白
[1] 二黒 [4] 五黄 [7] 八白
[8] 九紫 [3] 四緑 [2] 三碧
nw: [6, 5, 0, 1, 4, 7, 8, 3, 2]
## jiuxing::get_jiuxing_dipan_positions_from_direction
A getter for `JIU_XING_DI_PAN_POSITIONS`.
## jiuxing::get_jiuxing_from_index
A getter for `JIU_XING`.
Example:
```rust
use mikaboshi::jiuxing::{get_jiuxing_from_index, JiuXing};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(index: usize) -> JsValue {
let dir: &JiuXing = get_jiuxing_from_index(index);
JsValue::from_serde(dir).unwrap()
}
```
## jiuxing::normalize_jiuxing
Given incorrect value for Jiu-Xing index, applies a modulo
to normalize it to fit within the range of 0 to 8.
Example:
0 --> 0 ... Stays the same. "0" being "一白水星 (1 White)".
8 --> 8 ... Stays the same. "8" being "九紫火星 (9 Purple)".
9 --> 0 ... "9" is too much for the range, and becoming "0" which is "一白水星".
10 --> 1 ... "10" is too much, and becoming "1" which is "二黒土星 (2 Black)".
-1 --> 8 ... Making it positive. "8" being "九紫火星 (9 Purple)".
-2 --> 7 ... Making it positive. "8" being "八白土星 (8 White)".
## jiuxing::fly_flying_stars
This is a function for 飞泊 (Fei-Po) or "flying".
The idea is quite simple. Given the order (which is
the second argument `order` in array) of
九星 (Jiu-Xing) indexes, increments or decrements
each in the array, and simply return the array.
Depending on whichever currently resides in the center of
the board (which is the first argument `center`),
the value to increment or decrement changes.
For `order` is fundamentally that of the Lo-Shu order
(which is defined in `JIU_XING_DI_PAN_POSITIONS`),
however, the layout is always different since
the position changes depending on which direction
the device is pointing as the device rotates.
## jiuxing::get_xiaguatu_from_unpan_index
Calculates for 下卦図 (Xia-Gua-Tu). 1st and 2nd
arguments (`unpan_xing_center` and `unpan_xing_order`)
are required for all. For calculating a chart
for 運盤星 (Un-Pan Xing), that is all we need.
However, to calculate charts for 山星 (Shan-Xing)
and 向星 (Xiang-Xing), requires 3rd and 4th arguments
(`xiang_xing_direction` and `xiang_xing_sector`.
Example:
```rust
use std::collections::HashMap;
use std::convert::TryInto;
use mikaboshi::jiuxing::{get_xiaguatu_from_unpan_index, XiaGuaTu};
use mikaboshi::test_mods::XiaGuaTuParams;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(params: &JsValue) -> JsValue {
let params: XiaGuaTuParams = params.into_serde().unwrap();
let unpan_xing_order: [usize; 9] =
params
.unpan_xing_order
.try_into()
.unwrap_or_else(|v: Vec<usize>| {
panic!("Expected a Vec of length 9 but it was {}", v.len())
});
let xia_gua_tu: HashMap<&str, XiaGuaTu> = get_xiaguatu_from_unpan_index(
params.unpan_xing_center,
&unpan_xing_order,
params.xiang_xing_direction.as_str(),
params.xiang_xing_sector,
);
JsValue::from_serde(&xia_gua_tu).unwrap()
}
```

68
docs/planet.md Normal file
View File

@@ -0,0 +1,68 @@
# Planets
Source: [src/planet.rs](../src/planet.rs)
Information about planets in our solar system.
Notice the planets in `PLANETS` are stored in a special order
known as _the Ptolemaic Order_. In many ancient traditions,
when a man is deceased, he will depart the Earth,
and head toward the Moon. Leaving the Moon behind,
the Mercury, the Venus, and the Sun. He will continue
his journey after the Sun, this time, to _the outer planets_,
that are the Mars, the Jupiter, and the Saturn.
After all, this library provides methodologies
_NOT_ for _"astronomy"_, but for _"astrology"_, hence,
follows the tradition which was common to the ancients.
Also noteworthy that, according to Rudolf Steiner,
"Mercury" was formerly known as "Venus" in ancient times.
Yet, it is only so when we are talking about the order
of the _physical_ planets, not in its _symbolical_ sense.
For instance, when ancients mentioned of "Mercury",
it was simply about "Mercury" and not "Venus".
## planet::Planet
A struct representing a planet and stores its attributes.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Planet {
pub name: Language,
}
```
## planet::PlanetRawData
A temporary struct for loading JSON data when defining a static const `PLANETS`.
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanetRawData {
pub name: LanguageData,
}
```
## planet::PLANETS
`Vec<Planet>`
A static vector with 11 items, each represents a planet
in our solar system. Planets are in Ptolemaic order.
[0] Earth
[1] Moon
[2] Mercury
[3] Venus
[4] Sun
[5] Mars
[6] Jupiter
[7] Saturn
[8] Uranus
[9] Neptune
[10] Pluto
For attributes details stored in the vector is found in JSON file:
[json/planets.json](../json/planets.json)

BIN
docs/sample_bagua.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/sample_shengsi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/sample_twentyfour.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

104
docs/shengsi.md Normal file
View File

@@ -0,0 +1,104 @@
# 生死衰旺 (Sheng-Si Shuai-Wang)
Source: [src/shengsi.rs](../src/shengsi.rs)
![sample_shengsi](./sample_shengsi.png)
生死衰旺 (Sheng-Si Shuai-Wang) is just a combination
of 4 Chinese characters, each being:
(1) Growing --> 生 (Sheng)
(2) Deadly --> 死 (Si)
(3) Perishing --> 衰 (Shuai)
(4) Prosperous --> 旺 (Wang)
They are often used in 四柱命理学 (The Four Pillars of Destiny),
but used in Feng-Shui as well. It simply suggests
that there are 4 states to the energy occupying the space.
In 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
it describes the state for the target year
in 三元九運 (Sang-Yuan Jiu-Yun),
especially, for its 向星 (Xiang-Xing).
## shengsi::ShengSi
A struct representing 生死衰旺 (Sheng-Si Shuai-Wang).
`key` would be: "sheng", "si", "shuai", or "wang".
```rust
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ShengSi<'a> {
pub key: &'a str,
pub kanji: &'a str,
pub meaning: &'a str,
}
```
## shengsi::ShengSiYearlyAlloc
A struct holding allocations of 生死衰旺 (Sheng-Si Shuai-Wang) for the given year.
For `usize` (in `Vec<usize>`) is 九星 (Jiu-Xing) index.
```rust
#[derive(Debug, Clone)]
pub struct ShengSiYearlyAlloc {
pub wang: Vec<usize>,
pub sheng: Vec<usize>,
pub shuai: Vec<usize>,
pub si: Vec<usize>,
}
```
## shengsi::SHENG_SI
`HashMap<&str, ShengSi>`
A HashMap for 生死衰旺 (Sheng-Si Shuai-Wang) by key
(for each holds `ShengSi`).
## shengsi::SHENG_SI_ALLOC
`Vec<ShengSiYearlyAlloc>`
For every year, some 九星 (Jiu-Xing) maybe in 旺 (Wang = Prospering)
phase, but some maybe in 死 (Si = Dying). 生死衰旺 (Sheng-Si Shuai-Wang)
for 九星 (Jiu-Xing) is no random, but has certain patterns,
and is repeated every 9 years. This cycle is called
三元九運 (Sang-Yuan Jiu-Yun), and given the 運盤星 (Un-Pan Xing) index
for the specific year, you can tell of 生死衰旺 (Sheng-Si Shuai-Wang)
for all the other 九星 (Jiu-Xing). Here, it is constructing
the patterns for 9 years, and making them into a static vector
for which each index being the 運盤星 (Un-Pan Xing) index.
If you know the 運盤星 (Un-Pan Xing) index for the year,
this static vector will tell you 生死衰旺 (Sheng-Si Shuai-Wang)
for all 九星 (Jiu-Xing).
## shengsi::get_shengsi_mapping
Given 運盤 (Un-Pan) index and given a layout for the current
運盤 (Un-Pan) positions (`&[usize; 9]`), returns the corresponding
生死衰旺 (Sheng-Si Shuai-Wang) situation.
Example:
```rust
use std::convert::TryInto;
use mikaboshi::shengsi::{get_shengsi_mapping, ShengSi};
use mikaboshi::test_mods::ShengSiParams;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(params: &JsValue) -> JsValue {
let params: ShengSiParams = params.into_serde().unwrap();
let unpan_id: usize = params.unpan_id;
let chart: [usize; 9] = params
.unpan_xing_chart
.try_into()
.unwrap_or_else(|v: Vec<usize>| {
panic!("Expected a Vec of length 9 but it was {}", v.len())
});
let mapping: Vec<Option<&ShengSi>> = get_shengsi_mapping(unpan_id, &chart);
JsValue::from_serde(&mapping).unwrap()
}
```

56
docs/solar_terms.md Normal file
View File

@@ -0,0 +1,56 @@
# 二十四节气 (Er-Shi-Si Jie-Qi) and 立春 (Li-Chun)
Source: [src/solar_terms.rs](../src/solar_terms.rs)
A module for 二十四节气 (Er-Shi-Si Jie-Qi).
Or, for calculating 立春 (Li-Chun).
Reference:
- [Solar term - Wiki](https://en.wikipedia.org/wiki/Solar_term)
## solar_terms::SolarTerm
```rust
#[derive(Debug)]
pub struct SolarTerm {
pub id: u8,
pub name: Language,
pub angle: u16,
}
```
## solar_terms::SolarTermRawData
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolarTermRawData {
pub id: u8,
pub name: LanguageData,
pub angle: u16,
}
```
## solar_terms::SOLAR_TERMS
`Vec<SolarTerm>`
## solar_terms::get_last_term
## solar_terms::get_lichun
Example:
```rust
use mikaboshi::solar_terms::get_lichun;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn xx(year: i16) -> JsValue {
let lichun = get_lichun(year);
JsValue::from_str(&format!(
"{:04}-{:02}-{:02}",
lichun.year as u16, lichun.month as u8, lichun.day as u8
))
}
```

44
docs/time.md Normal file
View File

@@ -0,0 +1,44 @@
# Time
Source: [src/time.rs](../src/time.rs)
All the time related. Currently, has only 1 function.
## time::Date
## time::DateTime
## time::Time
## time::ut_from_local
You may convert your local time into _UT_:
```rust
use mikaboshi::time::{
Month,
DateTime,
ut_from_local,
};
let zone: i8 = 9;
let local = DateTime {
year: 2021,
month: Month::Jul,
day: 7.0,
hour: 0,
min: 0,
sec: 0.0,
};
let ut: DateTime = ut_from_local(&local, zone);
println!("ut: {:?}", ut);
// {
// year: 2021,
// month: Jul,
// day: 6.0,
// hour: 14,
// min: 57,
// sec: 17.13778432735566
// }
```

View File

@@ -4,7 +4,7 @@
"name": {
"en": "kan",
"zh_cn": ["坎", "kǎn"],
"zh_cn": ["坎", "kǎn"],
"zh_tw": ["坎", "kǎn"],
"ja": ["かん", "kan"],
"vi": []
},

View File

@@ -1,6 +1,6 @@
[
{
"no": 1,
"num": 1,
"name": {
"en": "zi",
"ja": ["ね", "ne"],
@@ -10,7 +10,7 @@
}
},
{
"no": 2,
"num": 2,
"name": {
"en": "chou",
"ja": ["うし", "ushi"],
@@ -20,7 +20,7 @@
}
},
{
"no": 3,
"num": 3,
"name": {
"en": "",
"ja": ["とら", "tora"],
@@ -30,7 +30,7 @@
}
},
{
"no": 4,
"num": 4,
"name": {
"en": "mao",
"ja": ["う", "u"],
@@ -40,7 +40,7 @@
}
},
{
"no": 5,
"num": 5,
"name": {
"en": "",
"ja": ["たつ", "tatsu"],
@@ -50,7 +50,7 @@
}
},
{
"no": 6,
"num": 6,
"name": {
"en": "si",
"ja": ["み", "mi"],
@@ -60,7 +60,7 @@
}
},
{
"no": 7,
"num": 7,
"name": {
"en": "wu",
"ja": ["うま", "uma"],
@@ -70,7 +70,7 @@
}
},
{
"no": 8,
"num": 8,
"name": {
"en": "wei",
"ja": ["ひつじ", "hitsuji"],
@@ -80,7 +80,7 @@
}
},
{
"no": 9,
"num": 9,
"name": {
"en": "shen",
"ja": ["さる", "saru"],
@@ -90,7 +90,7 @@
}
},
{
"no": 10,
"num": 10,
"name": {
"en": "you",
"ja": ["とり", "tori"],
@@ -100,7 +100,7 @@
}
},
{
"no": 11,
"num": 11,
"name": {
"en": "xu",
"ja": ["いぬ", "inu"],
@@ -110,7 +110,7 @@
}
},
{
"no": 12,
"num": 12,
"name": {
"en": "hai",
"ja": ["い", "i"],

View File

@@ -1,6 +1,6 @@
[
{
"no": 1,
"num": 1,
"name": {
"en": "jia",
"ja": ["きのえ", "kinoe"],
@@ -10,7 +10,7 @@
}
},
{
"no": 2,
"num": 2,
"name": {
"en": "yi",
"ja": ["きのと", "kinoto"],
@@ -20,7 +20,7 @@
}
},
{
"no": 3,
"num": 3,
"name": {
"en": "bing",
"ja": ["ひのえ", "hinoe"],
@@ -30,7 +30,7 @@
}
},
{
"no": 4,
"num": 4,
"name": {
"en": "ding",
"ja": ["ひのと", "hinoto"],
@@ -40,7 +40,7 @@
}
},
{
"no": 5,
"num": 5,
"name": {
"en": "wu",
"ja": ["つちのえ", "tsuchinoe"],
@@ -50,7 +50,7 @@
}
},
{
"no": 6,
"num": 6,
"name": {
"en": "ji",
"ja": ["つちのと", "tsuchinoe"],
@@ -60,7 +60,7 @@
}
},
{
"no": 7,
"num": 7,
"name": {
"en": "geng",
"ja": ["かのえ", "kanoe"],
@@ -70,7 +70,7 @@
}
},
{
"no": 8,
"num": 8,
"name": {
"en": "xin",
"ja": ["かのと", "kanoto"],
@@ -80,7 +80,7 @@
}
},
{
"no": 9,
"num": 9,
"name": {
"en": "ren",
"ja": ["みずのえ", "mizunoe"],
@@ -90,7 +90,7 @@
}
},
{
"no": 10,
"num": 10,
"name": {
"en": "gui",
"ja": ["みずのと", "mizunoto"],

View File

@@ -57,7 +57,7 @@
},
{
"num": 5,
"direction": null,
"direction": "",
"name": {
"en": "5 Green",
"zh_cn": ["五黄土星", "wǔ huáng tǔ xīng"],

View File

@@ -1,79 +1,101 @@
[
{
"en": "earth",
"zh_cn": ["地球", "dì qiú"],
"zh_tw": ["地球", "dì qiú"],
"ja": ["ちきゅう", "chikyu"],
"vi": []
"name": {
"en": "earth",
"zh_cn": ["地球", "dì qiú"],
"zh_tw": ["地球", "dì qiú"],
"ja": ["ちきゅう", "chikyu"],
"vi": []
}
},
{
"en": "moon",
"zh_cn": ["月", ""],
"zh_tw": ["月", ""],
"ja": ["つき", "tsuki"],
"vi": []
"name": {
"en": "moon",
"zh_cn": ["月", ""],
"zh_tw": ["", ""],
"ja": ["つき", "tsuki"],
"vi": []
}
},
{
"en": "mercury"
"zh_cn": ["水星", "shuǐ xīng"],
"zh_tw": ["水星", "shuǐ xīng"],
"ja": ["すいせい", "suisei"],
"vi": []
"name": {
"en": "mercury",
"zh_cn": ["水星", "shuǐ xīng"],
"zh_tw": ["水星", "shuǐ xīng"],
"ja": ["すいせい", "suisei"],
"vi": []
}
},
{
"en": "venus",
"zh_cn": ["金星", "jīn xīng"],
"zh_tw": ["金星", "jīn xīng"],
"ja": ["きんせい", "kinsei"],
"vi": []
"name": {
"en": "venus",
"zh_cn": ["金星", "jīn xīng"],
"zh_tw": ["金星", "jīn xīng"],
"ja": ["きんせい", "kinsei"],
"vi": []
}
},
{
"en": "sun",
"zh_cn": ["太陽", ""],
"zh_tw": ["太陽", ""],
"ja": ["たいよう", "taiyo"],
"vi": []
"name": {
"en": "sun",
"zh_cn": ["太陽", ""],
"zh_tw": ["太陽", ""],
"ja": ["たいよう", "taiyo"],
"vi": []
}
},
{
"en": "mars",
"zh_cn": ["火星", "huǒ xīng"],
"zh_tw": ["火星", "huǒ xīng"],
"ja": ["かせい", "kasei"],
"vi": []
"name": {
"en": "mars",
"zh_cn": ["火星", "huǒ xīng"],
"zh_tw": ["火星", "huǒ xīng"],
"ja": ["かせい", "kasei"],
"vi": []
}
},
{
"en": "jupiter",
"zh_cn": ["木星", "mù xīng"],
"zh_tw": ["木星", "mù xīng"],
"ja": ["もくせい", "mokusei"],
"vi": []
"name": {
"en": "jupiter",
"zh_cn": ["木星", "mù xīng"],
"zh_tw": ["木星", "mù xīng"],
"ja": ["もくせい", "mokusei"],
"vi": []
}
},
{
"en": "saturn",
"zh_cn": ["土星", "tǔ xīng"],
"zh_tw": ["土星", "tǔ xīng"],
"ja": ["どせい", "dosei"],
"vi": []
"name": {
"en": "saturn",
"zh_cn": ["土星", "tǔ xīng"],
"zh_tw": ["土星", "tǔ xīng"],
"ja": ["どせい", "dosei"],
"vi": []
}
},
{
"en": "uranus",
"zh_cn": ["天王星", "tiānwáng xīng"],
"zh_tw": ["天王星", "tiānwáng xīng"],
"ja": ["てんのうせい", ""],
"vi": []
"name": {
"en": "uranus",
"zh_cn": ["天王星", "tiānwáng xīng"],
"zh_tw": ["天王星", "tiānwáng xīng"],
"ja": ["てんのうせい", ""],
"vi": []
}
},
{
"en": "neptune",
"zh_cn": ["海王星", "hǎiwáng xīng"],
"zh_tw": ["海王星", "hǎiwáng xīng"],
"ja": ["かいおうせい", "kaiosei"],
"vi": []
"name": {
"en": "neptune",
"zh_cn": ["海王星", "hǎiwáng xīng"],
"zh_tw": ["海王星", "hǎiwáng xīng"],
"ja": ["かいおうせい", "kaiosei"],
"vi": []
}
},
{
"en": "pluto",
"zh_cn": ["冥王星", "míngwáng xīng"],
"zh_tw": ["冥王星", "míngwáng xīng"],
"ja": ["めいおうせい", "meiosei"],
"vi": []
"name": {
"en": "pluto",
"zh_cn": ["冥王星", "míngwáng xīng"],
"zh_tw": ["冥王星", "míngwáng xīng"],
"ja": ["めいおうせい", "meiosei"],
"vi": []
}
}
]

View File

@@ -1,37 +1,47 @@
[
{
"en": "wood",
"zh_cn": ["木", "mù"],
"zh_tw": ["木", "mù"],
"ja": ["もく", "moku", "ki"],
"vi": []
"name": {
"en": "wood",
"zh_cn": ["木", "mù"],
"zh_tw": ["", "mù"],
"ja": ["もく", "moku", "ki"],
"vi": []
}
},
{
"en": "fire",
"zh_cn": ["火", "huǒ"],
"zh_tw": ["火", "huǒ"],
"ja": ["か", "ka", "hi"],
"vi": []
"name": {
"en": "fire",
"zh_cn": ["火", "huǒ"],
"zh_tw": ["", "h"],
"ja": ["か", "ka", "hi"],
"vi": []
}
},
{
"en": "earth",
"zh_cn": ["土", "tǔ"],
"zh_tw": ["土", "tǔ"],
"ja": ["", "do"],
"vi": []
"name": {
"en": "earth",
"zh_cn": ["土", "tǔ"],
"zh_tw": ["", ""],
"ja": ["ど", "do"],
"vi": []
}
},
{
"en": "metal",
"zh_cn": ["金", "jīn"],
"zh_tw": ["金", "jīn"],
"ja": ["ごん", "gon"],
"vi": []
"name": {
"en": "metal",
"zh_cn": ["金", "jīn"],
"zh_tw": ["", "n"],
"ja": ["ごん", "gon"],
"vi": []
}
},
{
"en": "water",
"zh_cn": ["水", "shuǐ"],
"zh_tw": ["水", "shuǐ"],
"ja": ["すい", "sui"],
"vi": []
"name": {
"en": "water",
"zh_cn": ["水", "shuǐ"],
"zh_tw": ["", "shuǐ"],
"ja": ["すい", "sui"],
"vi": []
}
}
]

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,7 +1,28 @@
//! Although 八卦 (Ba-Gua) is a concept in 易経 (I-Ching),
//! when used in Feng-Shui, it is often associated with 九星 (Jiu-Xing).
//! While everthing in this world is (said to be) divided into either 陰 (Yin)
//! or 陽 (Yang), each could be further divided into lesser Yin and Yang.
//! For Yang, some may be abundant in Yang. Or, some may slightly lean toward Yin.
//! This goes for Yin as well. Here, the initial Yin and Yang,
//! now divided into 4, or becomes 4 patterns. Then, for each,
//! there are further divisions, and for this time, it now makes it 8 patterns.
//! Ancient Chinese had a specific term for these 8 patterns,
//! and it is called, 八卦 (Ba-Gua), or "8 Gua".
//!
//! [0] 坎 (Kan)
//! [1] 坤 (Kun)
//! [2] 震 (Zhen)
//! [3] 巽 (Xun)
//! [4] 乾 (Qian)
//! [5] 兌 (Dui)
//! [6] 艮 (Gen)
//! [7] 離 (Li)
use serde::{Deserialize, Serialize};
use crate::language::{Language, LanguageTrait};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::utils::{get_json, make_sort};
/// A struct representing 卦 (Gua) and stores its attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bagua {
pub num: u8,
@@ -10,8 +31,124 @@ pub struct Bagua {
pub element: u8,
}
/// A temporary struct for loading JSON data when defining a static const `BAGUA`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaguaRawData {
pub num: u8,
pub name: LanguageData,
pub direction: String,
pub element: u8,
}
impl LanguageTrait for Bagua {
fn name(&self) -> Box<Language> {
Box::new(self.name.clone())
}
}
impl NameDataTrait for BaguaRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
lazy_static! {
/// A static vector with 9 items, each represents 卦 (Gua) of 八卦 (Ba-Gua),
/// or what known as "Eight Trigrams". Each stores associated attributes
/// for the 卦 (Gua), and are in the order of Ba-Gua Numbers. For instance,
/// 坎 (Kan) is the first in Ba-Gua, so it comes first in the vector.
/// However, be careful when you refer to each 卦 (Gua) as the program
/// refers 卦 (Gua) not by Ba-Gua Numbers, but by vector indexes.
/// For 坎 (Kan), for instance, while it has the Ba-Gua Number of 1,
/// it is referred as 0 because that is the index is for 坎 (Kan).
///
/// [0] 坎 (1 = Kan)
/// [1] 坤 (2 = Kun)
/// [2] 震 (3 = Zhen)
/// [3] 巽 (4 = Xun)
/// [4] 中 (5 = Zhong)
/// [5] 乾 (6 = Qian)
/// [6] 兌 (7 = Dui)
/// [7] 艮 (8 = Gen)
/// [8] 離 (9 = Li)
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/bagua.json`
pub static ref BAGUA: Vec<Bagua> = {
let json = &include_str!("../json/bagua.json");
let data: Vec<BaguaRawData> = get_json::<BaguaRawData>(json);
data.iter().map(|item| {
let item = item.clone();
Bagua {
num: item.num,
name: item.language_from_data(),
direction: item.direction,
element: item.element,
}
}).collect()
};
/// Another static vector for 八卦 (Ba-Gua) (and is `Vec<u8>`).
/// However, this time, it has only 8 items because this is used
/// for compass rotation. When using a compass, only 8 directions matter,
/// and the middle does not mean anything. When 八卦 (Ba-Gua) are
/// placed in 洛書 (Lo-Shu) order, 中 (Zhong) comes in the middle.
/// So, 中 (Zhong) is missing for this vector. For 八卦 (Ba-Gua),
/// it begins with 坎 (Kan) which resides in the north
/// when they are placed in Lo-Shu order. Moving clockwise,
/// what you see next in the north-east, is 艮 (Gen).
/// Then, comes 震 (3 = Zhen) which is in the east, and, so on.
/// Note that it is `Vec<u8>`, and not `Vec<Bagua>`.
///
/// [0] 坎 (1 = Kan)
/// [1] 艮 (8 = Gen)
/// [2] 震 (3 = Zhen)
/// [3] 巽 (4 = Xun)
/// [4] 離 (9 = Li)
/// [5] 坤 (2 = Kun)
/// [6] 兌 (7 = Dui)
/// [7] 乾 (6 = Qian)
pub static ref BAGUA_START_NORTH_INDEXES: Vec<u8> = vec![0, 7, 2, 3, 8, 1, 6, 5];
/// The order is the same as `BAGUA_START_NORTH_INDEXES`,
/// however, it is not `Vec<u8>` but `Vec<Bagua>` for this time.
pub static ref BAGUA_START_NORTH: Vec<Bagua> = make_sort(
BAGUA_START_NORTH_INDEXES.to_vec()
)(
BAGUA.to_vec()
);
}
/// A getter for `BAGUA_START_NORTH`.
///
/// Example:
/// ```rust
/// use mikaboshi::bagua::{get_bagua_start_north, Bagua};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(index: usize) -> JsValue {
/// let bagua: Option<&Bagua> = get_bagua_start_north(index);
/// JsValue::from_serde(&bagua).unwrap()
/// }
/// ```
pub fn get_bagua_start_north(index: usize) -> Option<&'static Bagua> {
match BAGUA_START_NORTH.get(index) {
Some(bagua) => Some(bagua),
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constant_bagua() {
assert_eq!(BAGUA[0].num, 1);
}
// TODO: BAGUA_START_NORTH_INDEXES
// TODO: BAGUA_START_NORTH
// TODO: get_get_bagua_start_north
}

486
src/compass.rs Normal file
View File

@@ -0,0 +1,486 @@
//! A module for compass directions. When dividing 360 degrees into 8,
//! we get 45 degrees. Ancient Chinese further divided them each into 3
//! (called "sectors"), each having 15 degrees. Meaning, there are
//! 24 sectors as a total. This is called, 二十四山向 (Er-Shi-Si Shan-Xiang).
//! Not only for 8 directions, but these 24 directions (sectors)
//! are used in Feng-Shui, and this is the module for these directions.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::bagua::{Bagua, BAGUA};
use crate::ganzhi::{Branch, Stem, BRANCHES, STEMS};
/// 二十四山向 (Er-Shi-Si Shan-Xiang) can be
/// either 卦 (Gua), 干 (Gan), or 支 (Zhi).
pub enum TwentyFourType<'a> {
Bagua(&'a Bagua),
Stem(&'a Stem),
Branch(&'a Branch),
}
/// A struct representing compass direction.
/// For each direction, there are 3 sectors.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Direction {
pub direction: String,
pub sector: usize,
}
impl Direction {
pub fn new(direction: &str, sector: usize) -> Direction {
Direction {
direction: direction.to_string(),
sector,
}
}
}
/// An array for 8 directions.
pub const DIRECTIONS: [&str; 8] = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
lazy_static! {
/// A hash map with 9 items.
/// Say, we have 9 boxes displayed on a device screen.
/// Except for the box in the middle, we have 8 boxes
/// around the middle to represent 8 compass directions.
/// When facing "n" (north), for the first row,
/// we have "nw", "n", and "ne". For the second row,
/// we have "w", "", and "e" (where "" being the middle box).
/// For the last, we have "sw", "s", and "se".
///
/// [0] nw [1] n [2] ne
/// [3] w [4] [5] e
/// [6] sw [7] s [8] se
///
/// Now, consider when the device rotates.
/// Depending on which direction the device is facing,
/// we have different labels. For all 8 directions,
/// this HashMap provides a map for the positions.
pub static ref DIRECTION_POSITIONS_IN_CHART: HashMap<&'static str, [&'static str; 9]> = [
("n", ["nw", "n", "ne", "w", "", "e", "sw", "s", "se"]),
("ne", ["n", "ne", "e", "nw", "", "se", "w", "sw", "s"]),
("e", ["ne", "e", "se", "n", "", "s", "nw", "w", "sw"]),
("se", ["e", "se", "s", "ne", "", "sw", "n", "nw", "w"]),
("s", ["se", "s", "sw", "e", "", "w", "ne", "n", "nw"]),
("sw", ["s", "sw", "w", "se", "", "nw", "e", "ne", "n"]),
("w", ["sw", "w", "nw", "s", "", "n", "se", "e", "ne"]),
("nw", ["w", "nw", "n", "sw", "", "ne", "s", "se", "e"]),
].iter().cloned().collect();
}
/// An getter for `DIRECTION_POSITIONS_IN_CHART`.
///
/// Example:
/// ```rust
/// use mikaboshi::compass::get_direction_positions_in_chart;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(direction: &str) -> JsValue {
/// JsValue::from(
/// (match get_direction_positions_in_chart(direction) {
/// Some(positions) => positions.to_vec(),
/// _ => Vec::new(),
/// })
/// .into_iter()
/// .map(JsValue::from)
/// .collect::<js_sys::Array>(),
/// )
/// }
/// ```
pub fn get_direction_positions_in_chart(direction: &str) -> Option<&[&str; 9]> {
DIRECTION_POSITIONS_IN_CHART.get(direction)
}
lazy_static! {
/// A hash map for the opposite direction.
pub static ref OPPOSITE_DIRECTION: HashMap<&'static str, &'static str> = [
("n", "s"),
("ne", "sw"),
("e", "w"),
("se", "nw"),
("s", "n"),
("sw", "ne"),
("w", "e"),
("nw", "se"),
]
.iter()
.cloned()
.collect();
}
/// A getter for `OPPOSITE_DIRECTION`.
///
/// Example:
/// ```rust
/// use mikaboshi::compass::get_opposite_direction;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(direction: &str) -> JsValue {
/// JsValue::from(get_opposite_direction(direction))
/// }
/// ```
pub fn get_opposite_direction(dir: &str) -> &str {
if !OPPOSITE_DIRECTION.contains_key(dir) {
panic!("Invalid direction: {}", dir);
}
OPPOSITE_DIRECTION[dir]
}
/// An array with 24 items. Imagine having a circlar disc displayed
/// on a device screen. When dividing 360 by 8 directions, we get
/// 45 degrees for each. When each direction is further divided
/// into 3, then each is called a "sector", and it has 15 degrees
/// for each "sector". Sectors are placed in clockwise order
/// (left to right) for each direction, so that you see
/// the sector 1 being placed on your very left. Then, you see
/// the sector 2 in the middle, and the sector 3 on your right.
/// Imagine the device pointing north. On the circular disc,
/// what you see at the very top is the sector 2 of "N" (north),
/// denoted as "N2". On your left, you see "N1".
/// On your right, "N3".
///
/// When we want to express all the 24 sectors, we want
/// an array with 24 items. For the first item in the array [0],
/// it is convenient to have "N2". Then, for the second item
/// in the array [1], we want "N3". For [2], we want "NE1".
/// For [3], we want "NE2". And, so on. As you can imagine,
/// "N1" comes to the very last in the array, or [23].
pub const TWENTYFOUR_SECTORS: [u8; 24] = [
2, // 0: n
3, // 1: n
1, // 2: ne
2, // 3: ne
3, // 4: ne
1, // 5: e
2, // 6: e
3, // 7: e
1, // 8: se
2, // 9: se
3, // 10: se
1, // 11: s
2, // 12: s
3, // 13: s
1, // 14: sw
2, // 15: sw
3, // 16: sw
1, // 17: w
2, // 18: w
3, // 19: w
1, // 20: nw
2, // 21: nw
3, // 22: nw
1, // 23: n
];
lazy_static! {
/// An array with 24 items, for each represents
/// each in 二十四山向 (Er-Shi-Si Shan-Xiang).
/// Note, the array begins with "N2"
/// (and "N1" is stored at the very last, or [23]).
/// Ex.
/// 0: Direction { direction: "n", sector: 2 }
/// 1: Direction { direction: "n", sector: 3 }
/// 2: Direction { direction: "ne", sector: 1 }
/// 3: Direction { direction: "ne", sector: 2 }
pub static ref TWENTYFOUR_INDEX_TO_DIRECTIONS: Vec<Direction> = {
let mut vec: Vec<Direction> = DIRECTIONS
.iter()
.fold(Vec::new(), |mut acc: Vec<Direction>, &direction: &&str| {
acc.append(
&mut (1..4).map(|sector: usize| {
Direction {
direction: direction.to_string(),
sector,
}
}).collect()
);
acc
});
vec.rotate_left(1);
vec
};
}
/// A getter for `TWENTYFOUR_INDEX_TO_DIRECTIONS`
///
/// Example:
/// ```rust
/// use mikaboshi::compass::{get_twentyfour_direction_from_index, Direction};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(index: usize) -> JsValue {
/// let dir: &Direction = get_twentyfour_direction_from_index(index);
/// JsValue::from_serde(dir).unwrap()
/// }
/// ```
pub fn get_twentyfour_direction_from_index(index: usize) -> &'static Direction {
&TWENTYFOUR_INDEX_TO_DIRECTIONS[index]
}
lazy_static! {
/// A HashMap mapping direction (combination of "direction" and "sector")
/// to the corresponding index.
///
/// n2: 0
/// n3: 1
/// ne1: 2
/// ne2: 3
/// ...
/// ...
pub static ref TWENTYFOUR_DIRECTIONS_TO_INDEX: HashMap<String, usize> = TWENTYFOUR_INDEX_TO_DIRECTIONS
.iter()
.enumerate()
.map(|(i, dir)| {
let key = format!("{}{}", dir.direction, dir.sector);
(key, i)
})
.collect();
}
/// An array with 24 items, each being a tuple. For each tuple,
/// the first represents the type of 二十四山向 (Er-Shi-Si Shan-Xiang),
/// and the second is the index of the type.
/// The type being: [0] BAGUA, [1] STEM, or [2] BRANCH.
pub const TWENTYFOUR_ORDER_START_NORTH: [(usize, usize); 24] = [
(2, 0), // 0: [0] 子
(1, 9), // 1: [9] 癸
(2, 1), // 2: [1] 丑
(0, 7), // 3: [7] 艮
(2, 2), // 4: [2] 寅
(1, 0), // 5: [0] 甲
(2, 3), // 6: [3] 卯
(1, 1), // 7: [1] 乙
(2, 4), // 8: [4] 辰
(0, 3), // 9: [3] 巽
(2, 5), // 10: [5] 巳
(1, 2), // 11: [2] 丙
(2, 6), // 12: [6] 午
(1, 3), // 13: [3] 丁
(2, 7), // 14: [7] 未
(0, 1), // 15: [1] 坤
(2, 8), // 16: [8] 申
(1, 6), // 17: [6] 庚
(2, 9), // 18: [9] 酉
(1, 7), // 19: [7] 辛
(2, 10), // 20: [10] 戌
(0, 5), // 21: [5] 乾
(2, 11), // 22: [11] 亥
(1, 8), // 23: [8] 壬
];
/// From index, simply returns the corresponding `TwentyFourType`.
///
/// Example:
/// ```rust
/// use mikaboshi::compass::{get_twentyfour_data_from_index, TwentyFourType};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(index: usize) -> JsValue {
/// let t_type: TwentyFourType = get_twentyfour_data_from_index(index);
/// match t_type {
/// TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
/// TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
/// TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
/// }
/// }
/// ```
pub fn get_twentyfour_data_from_index(index: usize) -> TwentyFourType<'static> {
let (t_type, t_index) = TWENTYFOUR_ORDER_START_NORTH[index];
match t_type {
0 => TwentyFourType::Bagua(&BAGUA[t_index]),
1 => TwentyFourType::Stem(&STEMS[t_index]),
2 => TwentyFourType::Branch(&BRANCHES[t_index]),
_ => panic!("Unknown type: {}", t_type),
}
}
// ===========================================================
// From **DEGREES**
// ===========================================================
/// From the given degrees, returns the corresponding `Direction`.
///
/// Example:
/// ```rust
/// use mikaboshi::compass::{get_twentyfour_direction_from_degrees, Direction};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(degrees: f32) -> JsValue {
/// let dir: Direction = get_twentyfour_direction_from_degrees(degrees);
/// JsValue::from_serde(&dir).unwrap()
/// }
/// ```
pub fn get_twentyfour_direction_from_degrees(d: f32) -> Direction {
if !(7.5..352.5).contains(&d) {
// d >= 352.5 || d < 7.5
Direction::new("n", 2)
} else if d < 22.5 {
Direction::new("n", 3)
} else if d < 37.5 {
Direction::new("ne", 1)
} else if d < 52.5 {
Direction::new("ne", 2)
} else if d < 67.5 {
Direction::new("ne", 3)
} else if d < 82.5 {
Direction::new("e", 1)
} else if d < 97.5 {
Direction::new("e", 2)
} else if d < 112.5 {
Direction::new("e", 3)
} else if d < 127.5 {
Direction::new("se", 1)
} else if d < 142.5 {
Direction::new("se", 2)
} else if d < 157.5 {
Direction::new("se", 3)
} else if d < 172.5 {
Direction::new("s", 1)
} else if d < 187.5 {
Direction::new("s", 2)
} else if d < 202.5 {
Direction::new("s", 3)
} else if d < 217.5 {
Direction::new("sw", 1)
} else if d < 232.5 {
Direction::new("sw", 2)
} else if d < 247.5 {
Direction::new("sw", 3)
} else if d < 262.5 {
Direction::new("w", 1)
} else if d < 277.5 {
Direction::new("w", 2)
} else if d < 292.5 {
Direction::new("w", 3)
} else if d < 307.5 {
Direction::new("nw", 1)
} else if d < 322.5 {
Direction::new("nw", 2)
} else if d < 337.5 {
Direction::new("nw", 3)
} else {
// d < 352.5
Direction::new("n", 1)
}
}
// ===========================================================
// From **DIRECTION**
// ===========================================================
/// From the given direction and sector, finds the corresponding index
/// in `TWENTYFOUR_DIRECTIONS_TO_INDEX`
pub fn get_twentyfour_index_from_direction(direction: &str, sector: usize) -> usize {
*TWENTYFOUR_DIRECTIONS_TO_INDEX
.get(format!("{}{}", direction, sector).as_str())
.unwrap()
}
/// From the given direction and sector, returns `TwentyFourType`.
///
/// Example:
/// ```rust
/// use mikaboshi::compass::{get_twentyfour_data_from_direction, TwentyFourType};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(direction: &str, sector: usize) -> JsValue {
/// let t_type: TwentyFourType = get_twentyfour_data_from_direction(direction, sector);
/// match t_type {
/// TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
/// TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
/// TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
/// }
/// }
/// ```
pub fn get_twentyfour_data_from_direction(
direction: &str,
sector: usize,
) -> TwentyFourType<'static> {
get_twentyfour_data_from_index(get_twentyfour_index_from_direction(direction, sector))
}
/// From the given direction and sector, returns `Direction`.
pub fn get_twentyfour_direction_from_direction(direction: &str, sector: usize) -> &Direction {
&TWENTYFOUR_INDEX_TO_DIRECTIONS[get_twentyfour_index_from_direction(direction, sector)]
}
#[cfg(test)]
mod tests {
use super::*;
// TODO: DIRECTION
// TODO: DIRECTION_POSITIONS_IN_CHART
#[test]
fn test_get_direction_positions_in_chart() {
let exp = ["nw", "n", "ne", "w", "", "e", "sw", "s", "se"];
assert_eq!(get_direction_positions_in_chart("n").unwrap(), &exp);
}
// TODO: OPPOSITE_DIRECTION
// TODO: get_opposite_direction
// TODO: TWENTYFOUR_SECTORS
#[test]
fn test_constant_twentyfour_index_to_directions() {
assert_eq!(
TWENTYFOUR_INDEX_TO_DIRECTIONS[0],
Direction {
direction: String::from("n"),
sector: 2,
}
);
}
#[test]
fn test_get_twentyfour_direction_from_index() {
let exp = Direction {
direction: String::from("n"),
sector: 2,
};
assert_eq!(get_twentyfour_direction_from_index(0), &exp);
}
#[test]
fn test_constant_twentyfour_directions_to_index() {
assert_eq!(*TWENTYFOUR_DIRECTIONS_TO_INDEX.get("n2").unwrap(), 0_usize);
}
// Only for test
impl TwentyFourType<'static> {
fn is_branch(&self) -> bool {
match self {
TwentyFourType::Branch(_) => true,
_ => false,
}
}
}
// TODO: TWENTYFOUR_ORDER_START_NORTH
#[test]
fn test_get_twentyfour_data_from_index() {
assert!(get_twentyfour_data_from_index(0).is_branch());
}
#[test]
fn test_get_twentyfour_direction_from_degrees() {
assert_eq!(
get_twentyfour_direction_from_degrees(0_f32),
Direction {
direction: String::from("n"),
sector: 2,
}
);
}
// TODO: get_twentyfour_index_from_direction
// TODO: get_twentyfour_data_from_direction
// TODO: get_twentyfour_direction_from_direction
}

View File

@@ -1,128 +0,0 @@
use serde::{Deserialize};
use crate::ganzhi::{Stem, Branch, StemData, BranchData};
use crate::language::NameDataTrait;
use crate::solar_terms::{SolarTerm, SolarTermData};
use crate::wuxing::{WuXing, WuXingData};
fn get_json<'a, T: Deserialize<'a>>(json: &'a str) -> Vec<T> {
match serde_json::from_str(json) {
Ok(json) => json,
Err(err) => panic!("Error: {}", err),
}
}
lazy_static! {
pub static ref SOLAR_TERMS: Vec<SolarTerm> = {
let json = &include_str!("../json/solar_terms.json");
let data: Vec<SolarTermData> = get_json::<SolarTermData>(json);
data.iter().map(|item| {
let item = item.clone();
SolarTerm {
id: item.id,
name: item.language_from_data(),
angle: item.angle,
}
}).collect()
};
// Combination of Stems (10) and Branches (12) which makes 60 patterns.
pub static ref GANZHI_SEXAGESIMAL: Vec<(usize, usize)> = {
let mut v = vec![];
for i in 0..60 {
let stem = (i % 10) as usize;
let branch = (i % 12) as usize;
v.push((stem, branch));
}
v
};
pub static ref STEMS: Vec<Stem> = {
let json = &include_str!("../json/ganzhi_stems.json");
let data: Vec<StemData> = get_json::<StemData>(json);
data.iter().map(|item| {
let item = item.clone();
Stem {
no: item.no,
name: item.language_from_data(),
}
}).collect()
};
pub static ref BRANCHES: Vec<Branch> = {
let json = &include_str!("../json/ganzhi_branches.json");
let data: Vec<BranchData> = get_json::<BranchData>(json);
data.iter().map(|item| {
let item = item.clone();
Branch {
no: item.no,
name: item.language_from_data(),
}
}).collect()
};
pub static ref WUXING: Vec<WuXing> = {
let json = &include_str!("../json/wuxing.json");
let data: Vec<WuXingData> = get_json::<WuXingData>(json);
data.iter().map(|item| {
let item = item.clone();
WuXing {
no: item.no,
name: item.language_from_data(),
}
}).collect()
};
/// This is a table used when finding Hour Stem.
/// Columns represents Day Stem groups, and there are 5 groups.
/// For insntace, if you have "甲" for Day Stem,
/// you are looking into the first column (group).
/// Rows represents Hour Branches, and there are 12.
/// For instance, if you have "子" for Hour Branch,
/// you are looking into the first row.
/// Therefore, when you have "甲" for Day Stem,
/// and "子" for Hour Branch, Hour Stem is located
/// in the first column in the first row, which is "甲".
///
///   甲乙丙丁戊
///   己庚辛壬癸
/// -------------
/// 子: 甲丙戊庚壬
/// 丑: 乙丁己辛癸
/// 寅: 丙戊庚壬甲
/// 卯: 丁己辛癸乙
/// 辰: 戊庚壬甲丙
/// 巳: 己辛癸乙丁
/// 午: 庚壬甲丙戊
/// 未: 辛癸乙丁己
/// 申: 壬甲丙戊庚
/// 酉: 癸乙丁己辛
/// 戌: 甲丙戊庚壬
/// 亥: 乙丁己辛癸
pub static ref HOUR_STEM_TABLE: [[usize; 5]; 12] = [
// 子
[0, 2, 4, 6, 8], // 甲丙戊庚壬
// 丑
[1, 3, 5, 7, 9], // 乙丁己辛癸
// 寅
[2, 4, 6, 8, 0], // 丙戊庚壬甲
// 卯
[3, 5, 7, 9, 1], // 丁己辛癸乙
// 辰
[4, 6, 8, 0, 2], // 戊庚壬甲丙
// 巳
[5, 7, 9, 1, 3], // 己辛癸乙丁
// 午
[6, 8, 0, 2, 4], // 庚壬甲丙戊
// 未
[7, 9, 1, 3, 5], // 辛癸乙丁己
// 申
[8, 0, 2, 4, 6], // 壬甲丙戊庚
// 酉
[9, 1, 3, 5, 7], // 癸乙丁己辛
// 戌
[0, 2, 4, 6, 8], // 甲丙戊庚壬
// 亥
[1, 3, 5, 7, 9], // 乙丁己辛癸
];
}

View File

@@ -1,3 +1,39 @@
//! Based on 5 elements in nature with its 陰 (Yin) and 陽 (Yang) for each,
//! ancient Chinese described the plant growth using 10 conventional symbols
//! known as "10 Gan" (十干). Also, they tracked the motion of Jupiter
//! (which has 12 year cycle) and so they did divided the night sky into 12 regions,
//! and this is known as "12 Zhi" (十二支). When they record time and space,
//! they used the combinations of 10 Gan (干) and 12 Zhi (支)
//! which makes 60 patterns, and this is called 干支 (Gan-Zhi).
//!
//! 10 Gan (干):
//!
//! [0] 甲 (Jia)
//! [1] 乙 (Yi)
//! [2] 丙 (Bing)
//! [3] 丁 (Ding)
//! [4] 戊 (Wu)
//! [5] 己 (Ji)
//! [6] 庚 (Geng)
//! [7] 辛 (Xin)
//! [8] 壬 (Ren)
//! [9] 癸 (Gui)
//!
//! 12 Zhi (支):
//!
//! [0] 子 (Zi)
//! [1] 丑 (Chou)
//! [2] 寅 (Yin)
//! [3] 卯 (Mao)
//! [4] 辰 (Chen)
//! [5] 巳 (Si)
//! [6] 午 (Wu)
//! [7] 未 (Wei)
//! [8] 申 (Shen)
//! [9] 酉 (You)
//! [10] 戌 (Xu)
//! [11] 亥 (Hai)
#[cfg(test)]
use sowngwala::time::Month;
@@ -6,54 +42,62 @@ use sowngwala::time::{
julian_day, julian_day_from_ut, modified_julian_day_from_ut, Date, DateTime, Time,
};
use crate::constants::{BRANCHES, GANZHI_SEXAGESIMAL, HOUR_STEM_TABLE, STEMS};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::solar_terms::get_lichun;
use crate::time::ut_from_local;
use crate::utils::longitude_of_the_sun_from_date;
use crate::utils::{get_json, longitude_of_the_sun_from_date};
/// A struct representing 干 (Gan) or "Stem" and stores its attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Stem {
pub no: u8,
pub num: u8,
pub name: Language,
}
/// A struct representing 支 (Zhi) or "Branch" and stores its attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Branch {
pub no: u8,
pub num: u8,
pub name: Language,
}
/// A temporary struct for loading JSON data when defining a static const `STEMS`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StemData {
pub no: u8,
pub struct StemRawData {
pub num: u8,
pub name: LanguageData,
}
/// A temporary struct for loading JSON data when defining a static const `BRANCHES`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchData {
pub no: u8,
pub struct BranchRawData {
pub num: u8,
pub name: LanguageData,
}
impl NameDataTrait for StemData {
impl NameDataTrait for StemRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
impl NameDataTrait for BranchData {
impl NameDataTrait for BranchRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
/// A struct for holding `Stem` and `Branch`, or denoted as 干支 (Gan-Zhi).
#[derive(Debug, Serialize)]
pub struct GanZhi<'a> {
pub stem: &'a Stem,
pub branch: &'a Branch,
}
/// A struct representing 八字 (Bazi) and stores `GanZhi` as its attributes.
/// It is referred as "The Four Pillars of Destiny" in English
/// mainly because the structure of 八字 (Bazi) necessary
/// for divinations in 四柱命理学 (_"The Four Pillars of Destiny"_).
#[derive(Debug, Serialize)]
pub struct Bazi<'a> {
pub year: GanZhi<'a>,
@@ -104,28 +148,177 @@ impl<'a> Bazi<'a> {
}
}
/// Returns `Bazi` from localtime (`DateTime`) and zone (`i8`).
///
/// Example:
/// ```rust
/// use mikaboshi::ganzhi::Bazi;
/// use mikaboshi::time::{DateTime, Month};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn get_bazi(params: &JsValue) -> JsValue {
/// let localtime = DateTime {
/// year: 1985,
/// month: Month::Nov,
/// day: 5.0,
/// hour: 1,
/// min: 35,
/// sec: 0.0,
/// };
/// let zone: i8 = 9;
/// JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
/// }
/// ```
pub fn from_local(lt: &DateTime, zone: i8) -> Bazi {
let ut = ut_from_local(&lt, zone);
let ut = ut_from_local(lt, zone);
println!("ut: {:?}", ut);
let year = _year_ganzhi(Box::new(ut));
let month = _month_ganzhi(Box::new(ut), year.stem.no);
let day = _day_ganzhi(Box::new(ut));
let hour = _hour_ganzhi(Box::new(Time::from(lt)), day.stem.no);
let year = get_year_ganzhi(Box::new(ut));
let month = get_month_ganzhi(Box::new(ut), year.stem.num);
let day = get_day_ganzhi(Box::new(ut));
let hour = get_hour_ganzhi(Box::new(Time::from(lt)), day.stem.num);
Bazi::new(year, month, day, hour)
}
pub fn from_ut(ut: &DateTime, t: &Time) -> Bazi<'a> {
let year = _year_ganzhi(Box::new(*ut));
let month = _month_ganzhi(Box::new(*ut), year.stem.no);
let day = _day_ganzhi(Box::new(*ut));
let hour = _hour_ganzhi(Box::new(*t), day.stem.no);
let year = get_year_ganzhi(Box::new(*ut));
let month = get_month_ganzhi(Box::new(*ut), year.stem.num);
let day = get_day_ganzhi(Box::new(*ut));
let hour = get_hour_ganzhi(Box::new(*t), day.stem.num);
Bazi::new(year, month, day, hour)
}
}
lazy_static! {
/// A static vector with 60 items. `Vec<usize, usize>` where the first
/// `usize` being the `STEMS` index, and the second for the `BRANCHES`.
/// It is simply the combination of 10 stems and 12 branches
/// which eventually adds up to 60 patterns.
pub static ref GANZHI_SEXAGESIMAL: Vec<(usize, usize)> = {
let mut v = vec![];
for i in 0..60 {
let stem = (i % 10) as usize;
let branch = (i % 12) as usize;
v.push((stem, branch));
}
v
};
/// A static vector with 10 items, each represents 干 (Gan).
/// Each stores associated attributes for the 干 (Gan).
///
/// [0] 甲 (Jia)
/// [1] 乙 (Yi)
/// [2] 丙 (Bing)
/// [3] 丁 (Ding)
/// [4] 戊 (Wu)
/// [5] 己 (Ji)
/// [6] 庚 (Geng)
/// [7] 辛 (Xin)
/// [8] 壬 (Ren)
/// [9] 癸 (Gui)
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/ganzhi_stems.json`
pub static ref STEMS: Vec<Stem> = {
let json = &include_str!("../json/ganzhi_stems.json");
let data: Vec<StemRawData> = get_json::<StemRawData>(json);
data.iter().map(|item| {
let item = item.clone();
Stem {
num: item.num,
name: item.language_from_data(),
}
}).collect()
};
/// A static vector with 10 items, each represents 支 (Zhi).
/// Each stores associated attributes for the 支 (Zhi).
///
/// [0] 子 (Zi)
/// [1] 丑 (Chou)
/// [2] 寅 (Yin)
/// [3] 卯 (Mao)
/// [4] 辰 (Chen)
/// [5] 巳 (Si)
/// [6] 午 (Wu)
/// [7] 未 (Wei)
/// [8] 申 (Shen)
/// [9] 酉 (You)
/// [10] 戌 (Xu)
/// [11] 亥 (Hai)
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/ganzhi_branches.json`
pub static ref BRANCHES: Vec<Branch> = {
let json = &include_str!("../json/ganzhi_branches.json");
let data: Vec<BranchRawData> = get_json::<BranchRawData>(json);
data.iter().map(|item| {
let item = item.clone();
Branch {
num: item.num,
name: item.language_from_data(),
}
}).collect()
};
/// This is a table used when finding "Hour Stem".
/// Columns represents "Day Stem" groups, and there are 5 groups.
/// For insntace, if you have 甲 for "Day Stem",
/// you are looking into the first column (group).
/// Rows represents "Hour Branches" for which there are 12.
/// For instance, if you have 子 for "Hour Branch",
/// you are looking into the first row.
/// So, when you have 甲 for "Day Stem",
/// and 子 for "Hour Branch", "Hour Stem" is located
/// in the first column in the first row, which is 甲.
///
/// &nbsp; &nbsp; &nbsp; 甲乙丙丁戊
/// &nbsp; &nbsp; &nbsp; 己庚辛壬癸
/// &dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;&dash;
/// 子: 甲丙戊庚壬
/// 丑: 乙丁己辛癸
/// 寅: 丙戊庚壬甲
/// 卯: 丁己辛癸乙
/// 辰: 戊庚壬甲丙
/// 巳: 己辛癸乙丁
/// 午: 庚壬甲丙戊
/// 未: 辛癸乙丁己
/// 申: 壬甲丙戊庚
/// 酉: 癸乙丁己辛
/// 戌: 甲丙戊庚壬
/// 亥: 乙丁己辛癸
pub static ref HOUR_STEM_TABLE: [[usize; 5]; 12] = [
// 子
[0, 2, 4, 6, 8], // 甲丙戊庚壬
// 丑
[1, 3, 5, 7, 9], // 乙丁己辛癸
// 寅
[2, 4, 6, 8, 0], // 丙戊庚壬甲
// 卯
[3, 5, 7, 9, 1], // 丁己辛癸乙
// 辰
[4, 6, 8, 0, 2], // 戊庚壬甲丙
// 巳
[5, 7, 9, 1, 3], // 己辛癸乙丁
// 午
[6, 8, 0, 2, 4], // 庚壬甲丙戊
// 未
[7, 9, 1, 3, 5], // 辛癸乙丁己
// 申
[8, 0, 2, 4, 6], // 壬甲丙戊庚
// 酉
[9, 1, 3, 5, 7], // 癸乙丁己辛
// 戌
[0, 2, 4, 6, 8], // 甲丙戊庚壬
// 亥
[1, 3, 5, 7, 9], // 乙丁己辛癸
];
}
/// Year Ganzhi
fn _year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
fn get_year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
// Year Stem and Branch are easily found.
// However, we must watch out if it is before
// or after Lichun. The year begins from Lichun,
@@ -161,7 +354,7 @@ fn _year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
/// Month Ganzhi
#[allow(clippy::boxed_local)]
fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
fn get_month_ganzhi(ut: Box<DateTime>, year_stem_num: u8) -> GanZhi<'static> {
let lng: f64 = longitude_of_the_sun_from_date(&Date::from(&*ut));
// Branch is easily found by looking at the longitude of the sun.
@@ -198,13 +391,13 @@ fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
// you simply count up to the current month.
// This is done by adding 'branch_id' because 'branch_id'
// is nothing but how many month from the beginning (Lichun).
let stem_index: usize = if year_stem_no == 1 || year_stem_no == 6 {
let stem_index: usize = if year_stem_num == 1 || year_stem_num == 6 {
2 // 甲(jia:1) or 己(ji:6) ---> 丙(bing:3)
} else if year_stem_no == 2 || year_stem_no == 7 {
} else if year_stem_num == 2 || year_stem_num == 7 {
4 // 乙(yi:2) or 庚(geng:7) ---> 戊(wu:5)
} else if year_stem_no == 3 || year_stem_no == 8 {
} else if year_stem_num == 3 || year_stem_num == 8 {
6 // 丙(bing:3) or 辛(xin:8) ---> 庚(geng:7)
} else if year_stem_no == 4 || year_stem_no == 9 {
} else if year_stem_num == 4 || year_stem_num == 9 {
8 // 丁(ding:4) or 壬(ren:9) ---> 壬(ren:9)
} else {
0 // 戊(wu:5) or 癸(gui:10) ---> 甲(jia:1)
@@ -218,7 +411,7 @@ fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
/// Day Ganzhi
#[allow(clippy::boxed_local)]
fn _day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
fn get_day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
let mjd: f64 = modified_julian_day_from_ut(&*ut);
let index = ((mjd - 10.0) % 60.0).floor() as usize;
@@ -232,7 +425,7 @@ fn _day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
/// Hour Ganzhi
#[allow(clippy::boxed_local)]
fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
fn get_hour_ganzhi(t: Box<Time>, day_stem_num: u8) -> GanZhi<'static> {
// The branch is easily found by looking at the hour range of the day.
let branch_id: usize = if t.hour == 23 || t.hour == 0 {
0
@@ -263,16 +456,16 @@ fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
// The stem is found by looking at a special table.
// Read comments for 'HOUR_STEM_TABLE' for details.
let group_id: usize = if day_stem_no == 1 || day_stem_no == 6 {
let group_id: usize = if day_stem_num == 1 || day_stem_num == 6 {
0
} else if day_stem_no == 2 || day_stem_no == 7 {
} else if day_stem_num == 2 || day_stem_num == 7 {
1
} else if day_stem_no == 3 || day_stem_no == 8 {
} else if day_stem_num == 3 || day_stem_num == 8 {
2
} else if day_stem_no == 4 || day_stem_no == 9 {
} else if day_stem_num == 4 || day_stem_num == 9 {
3
} else {
4 // day_stem_no == 5 || day_stem_no == 10
4 // day_stem_num == 5 || day_stem_num == 10
};
let stem_id: usize = HOUR_STEM_TABLE[branch_id][group_id];
@@ -287,8 +480,22 @@ fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
mod tests {
use super::*;
// TODO: GANZHI_SEXAGESIMAL
#[test]
fn bazi_from_local_works() {
fn test_constant_stems() {
assert_eq!(STEMS[0].num, 1);
}
#[test]
fn test_constant_branches() {
assert_eq!(BRANCHES[0].num, 1);
}
// TODO: HOUR_STEM_TABLE
#[test]
fn test_bazi_from_local() {
let zone: i8 = 9;
let lt = DateTime {
@@ -320,7 +527,7 @@ mod tests {
}
#[test]
fn bazi_from_ut_works() {
fn test_bazi_from_ut() {
let ut = DateTime {
year: 2021,
month: Month::Jul,

View File

@@ -1,12 +1,98 @@
//! At the beginning, the law governing the universe was simple.
//! Yet, as man acquired the faculty of thought, was it no longer so.
//! It was not the universe which changed, but was about how man began
//! to see the universe differently. Thought, after all, is nothing
//! but reflections of the outer world. In another word, the outer
//! world we perceive could only be understood via patterns
//! that are innate to man's thought. Just like "Malkuth" in _Kabbalha_
//! is about both the earthly kingdom and the man himself,
//! as ancient Chinese attempted describing patterns in the universe,
//! they introduced another artificial element "metal"
//! (or "earth" when it is deployed in actual reality).
//! For the ancient Chinese, the former is called
//! 先天八卦 ("the Primordial Heaven"), and the latter,
//! 後天八卦 ("the Manifested Heaven").
//! To study the patterns peculiar to each universe, a conventional
//! board with 8 directions and 1 in the center has been in use,
//! where "8 Gua" (八卦) are assigned for slots on the board.
//! However, for many 風水 (Feng-Shui) systems, we are normally
//! dealing with the latter, or 後天八卦 ("the Manifested Heaven").
//!
//! For 後天八卦 ("the Manifested Heaven") has a specific name
//! in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui), and is called
//! 地盤 (Di-Pan). However, there are 3 more boards in
//! 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui)
//! in addition to 地盤 (Di-Pan), namely:
//!
//! (1) 運盤 (Un-Pan) (or 天盤 (Tien-Pan))
//! (2) 山星 (Shan-Xing)
//! (3) 向星 (Xiang-Xing)
//!
//! In practice, for all the above 3 boards, 九星 (Jiu-Xing)
//! or "the Nine Stars" are assigned. While "8 Gua" (八卦)
//! has fixed positions, 九星 (Jiu-Xing) changes
//! over time for spatial constraints given.
//! When their positions change, the movement is called
//! 飞泊 (Fei-Po) or "flying" because of how it appears
//! to our eyes when they move.
//!
//! For the first board 運盤 (Un-Pan), positions of 九星 (Jiu-Xing)
//! are determined by building's construction year,
//! and calculated based on 三元九運 (Sang-Yuan Jiu-Yun)
//! or "9 Yearly Cycles". We could say that 運盤 (Un-Pan)
//! essentially describes of the temporal aspect of the building
//! For 山星 (Shan-Xing) and 向星 (Xiang-Xing) are determined
//! by spatial aspects of the building, though, temporal aspects
//! are also associated indirectly in calculations.
//!
//! When 運盤 (Un-Pan), 山星 (Shan-Xing), and 向星 (Xiang-Xing)
//! are added to 地盤 (Di-Pan) at the bottom, it is called
//! 下卦図 (Xia-Gua-Tu), or simply referred as
//! 飞星図 (Fei-Xing-Tu; "the Flying Star Chart").
//!
//! Jiu-Xing (九星):
//!
//! [0] 一白水星 (1 White)
//! [1] 二黒土星 (2 Black)
//! [2] 三碧木星 (3 Jade)
//! [3] 四緑木星 (4 Green)
//! [4] 五黄土星 (5 Yellow)
//! [5] 六白金星 (6 White)
//! [6] 七赤金星 (7 Red)
//! [7] 八白土星 (8 White)
//! [8] 九紫火星 (9 Purple)
use serde::{Deserialize, Serialize};
use sowngwala::time::{julian_day, Date};
use std::collections::HashMap;
use std::convert::TryInto;
use crate::language::{Language, LanguageTrait};
use crate::compass::{get_opposite_direction, DIRECTIONS, DIRECTION_POSITIONS_IN_CHART};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::planet::{Planet, PLANETS};
use crate::utils::{get_json, make_positive};
use crate::wuxing::{WuXing, WU_XING};
pub const SAN_YUAN_JIU_YUN_START_YEAR: u16 = 1864;
/// A struct representing 九星 (Jiu-Xing).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JiuXing {
pub num: u8,
pub direction: String,
pub name: Language,
pub color: String,
pub element: WuXing,
pub planet: Planet,
}
/// A temporary struct for loading JSON data when defining a static const `JIU_XING`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JiuXingRawData {
pub num: u8,
pub direction: String,
pub name: LanguageData,
pub color: String,
pub element: u8,
pub planet: u8,
}
@@ -16,3 +102,634 @@ impl LanguageTrait for JiuXing {
Box::new(self.name.clone())
}
}
impl NameDataTrait for JiuXingRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
pub enum XiaGuaTuKind {
UnPanXing, // 運盤
ShanXing, // 山星
XiangXing, // 向星
}
/// A struct representing 下卦図 (Xia-Gua-Tu).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XiaGuaTu<'a> {
pub kind: XiaGuaTuKind,
pub center: Option<usize>,
pub direction: Option<&'a str>,
pub sector: Option<usize>,
pub chart: Option<[usize; 9]>,
}
lazy_static! {
/// A static vector with 9 items, each represents 九星 (Jiu-Xing).
///
/// [0] 一白水星 (1 White)
/// [1] 二黒土星 (2 Black)
/// [2] 三碧木星 (3 Jade)
/// [3] 四緑木星 (4 Green)
/// [4] 五黄土星 (5 Yellow)
/// [5] 六白金星 (6 White)
/// [6] 七赤金星 (7 Red)
/// [7] 八白土星 (8 White)
/// [8] 九紫火星 (9 Purple)
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/jiuxing.json`
pub static ref JIU_XING: [JiuXing; 9] = {
let json = &include_str!("../json/jiuxing.json");
get_json::<JiuXingRawData>(json)
.iter() // TODO: into_iter()?
.map(|item| {
JiuXing {
num: item.num,
direction: item.direction.clone(),
name: item.language_from_data(),
color: item.color.clone(),
element: WU_XING[item.element as usize].clone(),
planet: PLANETS[item.planet as usize].clone(),
}
})
.collect::<Vec<JiuXing>>()
.try_into()
.unwrap()
};
}
/// A getter for `JIU_XING`.
///
/// Example:
/// ```rust
/// use mikaboshi::jiuxing::{get_jiuxing_from_index, JiuXing};
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(index: usize) -> JsValue {
/// let dir: &JiuXing = get_jiuxing_from_index(index);
/// JsValue::from_serde(dir).unwrap()
/// }
/// ```
pub fn get_jiuxing_from_index(index: usize) -> &'static JiuXing {
&JIU_XING[index]
}
lazy_static! {
pub static ref DIRECTION_TO_JIU_XING: HashMap<&'static str, usize> = JIU_XING
.iter()
.enumerate()
.map(|(index, jiuxing)| {
let dir = jiuxing.direction.as_str();
match dir {
"" => ("", index),
_ => (jiuxing.direction.as_str(), index),
}
})
.collect();
}
fn jiuxing_index_from_direction(dir: &str) -> usize {
match dir {
"" => 4,
_ => DIRECTION_TO_JIU_XING[dir],
}
}
lazy_static! {
/// Although 洛書 (Lo-Shu) order is fixed, when 地盤 (Di-Pan)
/// is drawn on a device screen, the mapping for
/// 九星 (Jiu-Xing) changes as the device rotates.
/// For example, 一白水星 (1 White) usually comes to the top
/// of the board when a device is pointing north. However,
/// when pointing north east, 一白水星 (1 White) moves
/// to the top left (which is north west).
/// For 8 compass directions, this constant provides
/// a mapping for the 洛書 (Lo-Shu) order.
/// For "n", 一白水星 (1 White) is the 2nd in the array.
/// For "ne", 一白水星 (1 White) is the 1st in the array.
///
/// It would look like this:
///
/// [5] 六白 [0] 一白 [7] 八白
/// [6] 七赤 [4] 五黄 [2] 三碧
/// [1] 二黒 [8] 九紫 [3] 四緑
/// n: [5, 0, 7, 6, 4, 2, 1, 8, 3]
///
/// [0] 一白 [7] 八白 [2] 三碧
/// [5] 六白 [4] 五黄 [3] 四緑
/// [6] 七赤 [1] 二黒 [8] 九紫
/// ne: [0, 7, 2, 5, 4, 3, 6, 1, 8]
///
/// [7] 八白 [2] 三碧 [3] 四緑
/// [0] 一白 [4] 五黄 [8] 九紫
/// [5] 六白 [6] 七赤 [1] 二黒
/// e: [7, 2, 3, 0, 4, 8, 5, 6, 1]
///
/// [2] 三碧 [3] 四緑 [8] 九紫
/// [7] 八白 [4] 五黄 [1] 二黒
/// [0] 一白 [5] 六白 [6] 七赤
/// se: [2, 3, 8, 7, 4, 1, 0, 5, 6]
///
/// [3] 四緑 [8] 九紫 [1] 二黒
/// [2] 三碧 [4] 五黄 [6] 七赤
/// [7] 八白 [0] 一白 [5] 六白
/// s: [3, 8, 1, 2, 4, 6, 7, 0, 5]
///
/// [8] 九紫 [1] 二黒 [6] 七赤
/// [3] 四緑 [4] 五黄 [5] 六白
/// [2] 三碧 [7] 八白 [0] 一白
/// sw: [8, 1, 6, 3, 4, 5, 2, 7, 0]
///
/// [1] 二黒 [6] 七赤 [5] 六白
/// [8] 九紫 [4] 五黄 [0] 一白
/// [3] 四緑 [2] 三碧 [7] 八白
/// w: [1, 6, 5, 8, 4, 0, 3, 2, 7]
///
/// [6] 七赤 [5] 六白 [0] 一白
/// [1] 二黒 [4] 五黄 [7] 八白
/// [8] 九紫 [3] 四緑 [2] 三碧
/// nw: [6, 5, 0, 1, 4, 7, 8, 3, 2]
pub static ref JIU_XING_DI_PAN_POSITIONS: HashMap<&'static str, [usize; 9]> = DIRECTIONS
.iter()
.map(|dir| -> (&str, [usize; 9]) {
(
dir,
DIRECTION_POSITIONS_IN_CHART[dir]
.iter()
.map(|d| jiuxing_index_from_direction(d))
.collect::<Vec<usize>>()
.try_into()
.unwrap()
)
})
.collect();
}
/// A getter for `JIU_XING_DI_PAN_POSITIONS`.
pub fn get_jiuxing_dipan_positions_from_direction(direction: &str) -> Option<&[usize; 9]> {
JIU_XING_DI_PAN_POSITIONS.get(direction)
}
lazy_static! {
/*
* NOT IN USE
*
* Order for Jiu-Xing, starting north, CLOCK-WISE.
* Since it is starting north, see bellow for
* where each index is located:
*
* [7] xxx [0] xxx [1] xxx
* [6] xxx [_] ___ [2] xxx
* [5] xxx [4] xxx [3] xxx
*
* or, to be more specific, here are how Jiu-Xing
* will be placed on the board:
*
* [7] 六白金星 (6 White) [0] 一白水星 (1 White) [1] 八白土星 (8 White)
* [6] 七赤金星 (7 Red) [_] 五黄土星 (5 Yellow) [2] 三碧木星 (3 Jade)
* [5] 二黒土星 (2 Black) [4] 九紫火星 (9 Purple) [3] 四緑木星 (4 Green)
*
* For each index means:
*
* [0] --> N --> 一白水星 (1 White) [0]
* [1] --> NE --> 八白土星 (8 White) [7]
* [2] --> E --> 三碧木星 (3 Jade) [2]
* [3] --> SE --> 四緑木星 (4 Green) [3]
* [4] --> S --> 九紫火星 (9 Purple) [8]
* [5] --> SW --> 二黒土星 (2 Black) [1]
* [6] --> W --> 七赤金星 (7 Red) [6]
* [7] --> NW --> 六白金星 (6 White) [5]
* pub static ref JIU_XING_DI_PAN_POSITIONS_CLOCKWISE: [usize; 8] = [0, 7, 2, 3, 8, 1, 6, 5];
*/
}
/// Given incorrect value for Jiu-Xing index, applies a modulo
/// to normalize it to fit within the range of 0 to 8.
/// Ex.
/// 0 -> 0 ... Stays the same. "0" being 一白水星 (1 White).
/// 8 -> 8 ... Stays the same. "8" being 九紫火星 (9 Purple).
/// 9 -> 0 ... "9" is too much for the range and becoming "0" which is 一白水星 (1 White).
/// 10 -> 1 ... "10" is too much, and becoming "1" which is 二黒土星 (2 Black).
/// -1 -> 8 ... Making it positive. "8" being 九紫火星 (9 Purple).
/// -2 -> 7 ... Making it positive. "8" being 八白土星 (8 White).
pub fn normalize_jiuxing(index: i32) -> usize {
let tmp = (make_positive(9)(index) + 1) % 9;
match tmp {
0 => 8,
_ => (tmp - 1) as usize,
}
}
/// Sang-Yuan 三元九運 (Jiu-Yun), or _"9 YEARLY CYCLES"_, is the core
/// concept in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
/// and it tells how 九星 (Jiu-Xing) fly throughout 180 years.
/// This function will calculate from the given:
///
/// 1. CURRENT LOCALTIME, and
/// 2. LIU-CHUN (for the year)
///
/// for the corresponding Jiu-Xing, for which the generated board
/// is usually referred as 運盤 (Un-Pan).
///
/// Example:
/// ```rust
/// use mikaboshi::jiuxing::unpan_xing_index;
/// use mikaboshi::test_mods::DateParams;
/// use mikaboshi::time::Date;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(current: &JsValue, lichun: &JsValue) -> JsValue {
/// let params_1: DateParams = current.into_serde().unwrap();
/// let params_2: DateParams = lichun.into_serde().unwrap();
/// let current = Date::from(&params_1);
/// let lichun = Date::from(&params_2);
/// let index: usize = unpan_xing_index(&current, &lichun);
/// JsValue::from_f64(index as f64)
/// }
pub fn unpan_xing_index(&current: &Date, &lichun: &Date) -> usize {
let year: i16 = if (julian_day(&current) - julian_day(&lichun)) < 0_f64 {
current.year - 1
} else {
current.year
};
let dt: i16 = year - SAN_YUAN_JIU_YUN_START_YEAR as i16;
let norm: i16 = dt % 180;
(norm / 20) as usize
}
// /// Returns Jiu-xing (for Un-Pan)
// pub fn unpan_xing_data(&current: &Date, &lichun: &Date) -> JiuXing {
// JIU_XING[unpan_xing_index(&current, &lichun)].clone()
// }
/// This is a function for 飞泊 (Fei-Po) or "flying".
/// The idea is quite simple. Given the order (which is
/// the second argument `order` in array) of
/// 九星 (Jiu-Xing) indexes, increments or decrements
/// each in the array, and simply return the array.
/// Depending on whichever currently resides in the center of
/// the board (which is the first argument `center`),
/// the value to increment or decrement changes.
/// For `order` is fundamentally that of the Lo-Shu order
/// (which is defined in `JIU_XING_DI_PAN_POSITIONS`),
/// however, the layout is always different since
/// the position changes depending on which direction
/// the device is pointing as the device rotates.
pub fn fly_flying_stars(center: usize, order: &[usize; 9], reverse: bool) -> [usize; 9] {
let diff: usize = center - 4;
order
.iter()
.map(|index: &usize| -> usize {
let index: usize = match reverse {
true => 8 - *index,
false => *index,
} + diff;
normalize_jiuxing(index as i32)
})
.collect::<Vec<usize>>()
.try_into()
.unwrap()
}
// fn flying_stars_chart(reverse: bool, center: usize, order: &[usize; 9]) -> [JiuXing; 9] {
// fly_flying_stars(center, order, reverse)
// .iter()
// .map(|index: &usize| -> JiuXing { JIU_XING[*index].clone() })
// .collect::<Vec<JiuXing>>()
// .try_into()
// .unwrap()
// }
/// This is a useful well known formula for finding out
/// whether 山星 (Shan-Xing) or 向星 (Xiang-Xing) is flying
/// in normal order.
///
/// IMPORTANT:
/// It does not work when 九星 (Jiu-Xing) is "5". If such the case,
/// center index for Un-Pan must be fed.
fn is_shan_xiang_flying_normal(index: usize, sector: usize) -> bool {
let num: usize = index + 1;
let rem = num % 2;
(rem != 0 && sector == 1) || (rem == 0 && sector > 1)
}
/// Looking at the 地盤 (Di-Pan) order, finds the current direction facing.
fn direction_from_dipan_order(order: &[usize; 9]) -> &'static str {
JIU_XING_DI_PAN_POSITIONS
.iter()
.find_map(
|(dir, dipan_order)| match order.iter().eq(dipan_order.iter()) {
true => Some(*dir),
_ => None,
},
)
.unwrap_or("")
}
/// Calculates for 下卦図 (Xia-Gua-Tu). 1st and 2nd
/// arguments (`unpan_xing_center` and `unpan_xing_order`)
/// are required for all. For calculating a chart
/// for 運盤星 (Un-Pan Xing), that is all we need.
/// However, to calculate charts for 山星 (Shan-Xing)
/// and 向星 (Xiang-Xing), requires 3rd and 4th arguments
/// (`xiang_xing_direction` and `xiang_xing_sector`.
///
/// Example:
/// ```rust
/// use std::collections::HashMap;
/// use std::convert::TryInto;
/// use mikaboshi::jiuxing::{get_xiaguatu_from_unpan_index, XiaGuaTu};
/// use mikaboshi::test_mods::XiaGuaTuParams;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(params: &JsValue) -> JsValue {
/// let params: XiaGuaTuParams = params.into_serde().unwrap();
/// let unpan_xing_order: [usize; 9] =
/// params
/// .unpan_xing_order
/// .try_into()
/// .unwrap_or_else(|v: Vec<usize>| {
/// panic!("Expected a Vec of length 9 but it was {}", v.len())
/// });
/// let xia_gua_tu: HashMap<&str, XiaGuaTu> = get_xiaguatu_from_unpan_index(
/// params.unpan_xing_center,
/// &unpan_xing_order,
/// params.xiang_xing_direction.as_str(),
/// params.xiang_xing_sector,
/// );
/// JsValue::from_serde(&xia_gua_tu).unwrap()
/// }
/// ```
pub fn get_xiaguatu_from_unpan_index<'a>(
unpan_xing_center: usize,
unpan_xing_order: &'a [usize; 9],
xiang_xing_direction: &'a str,
xiang_xing_sector: usize,
) -> HashMap<&'a str, XiaGuaTu<'a>> {
let mut xgtu = HashMap::new();
// `chart` for 運盤星 (Un-Pan Xing) is straight forward.
// The center 九星 (Jiu-Xing) is already given,
// and you simply have to fly the given chart.
xgtu.insert(
"unpan_xing",
XiaGuaTu {
kind: XiaGuaTuKind::UnPanXing,
center: Some(unpan_xing_center),
direction: None,
sector: None,
chart: Some(
fly_flying_stars(
unpan_xing_center,
unpan_xing_order,
false
)
),
},
);
// `chart` for 山星 (Shan-Xing) to be later calculated.
// `direction` for 山星 (Shan-Xing) is just the opposite
// of 向星 (Xiang-Xing).
xgtu.insert(
"shan_xing",
XiaGuaTu {
kind: XiaGuaTuKind::ShanXing,
center: None,
direction: Some(
get_opposite_direction(
xiang_xing_direction
)
),
sector: Some(xiang_xing_sector),
chart: None,
},
);
// `chart` for 向星 (Xiang-Xing) to be later calculated.
// `direction` is already given.
xgtu.insert(
"xiang_xing",
XiaGuaTu {
kind: XiaGuaTuKind::XiangXing,
center: None,
direction: Some(xiang_xing_direction),
sector: Some(xiang_xing_sector),
chart: None,
},
);
// First, we need to find out which direction
// the device is currently pointing to.
let curr_dir: &str = direction_from_dipan_order(unpan_xing_order);
// Once the direction is found, then we will obtain the compass direction mapping.
let compass_positions: [&str; 9] = DIRECTION_POSITIONS_IN_CHART[curr_dir];
// Find `center` and `chart` for both 山星 (Shan-Xing) and 向星 (Xiang-Xing).
for key in ["shan_xing", "xiang_xing"] {
// Since we only know directions for 山星 (Shan-Xing)
// and 向星 (Xiang-Xing), we will look into the Un-Pan chart,
// and will find out what 九星 (Jiu-Xing) we have for the direction.
// Initially, we only know the direction.
let dir: &str = xgtu.get(key).unwrap().direction.unwrap();
// For the direction, find out its array index.
let pos: usize = compass_positions.iter().position(|&d| d == dir).unwrap();
// Once the index is found, then find out its 九星 (Jiu-Xing).
let center: usize = (xgtu.get("unpan_xing").unwrap().chart.unwrap())[pos];
let prev: &XiaGuaTu = xgtu.get(key).unwrap();
// It is important to figure out whether it is flying
// in normal or reverse order. To do so, we will
// use a useful well known formula.
let normal: bool = is_shan_xiang_flying_normal(
// Having 五黄土星 (5 Yellow) is a special case,
// and the formula does not work. Therefore,
// replacing it with 運盤星 (Un-Pan Xing) index.
if center == 4 {
xgtu.get("unpan_xing").unwrap().center.unwrap()
} else {
center
},
xgtu.get(key).unwrap().sector.unwrap(),
);
// Now, calculate for the flying chart.
let chart: [usize; 9] = fly_flying_stars(
center,
unpan_xing_order,
normal
);
*xgtu.get_mut(key).unwrap() = XiaGuaTu {
center: Some(center),
chart: Some(chart),
..*prev
};
}
xgtu
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constant_jiuxing() {
assert_eq!(JIU_XING[0].num, 1);
}
#[test]
fn test_get_jiuxing_from_index() {
assert_eq!(get_jiuxing_from_index(0).num, 1);
}
#[test]
fn test_constant_direction_to_jiuxing() {
assert_eq!(*DIRECTION_TO_JIU_XING.get("n").unwrap(), 0);
}
// TODO: jiuxing_index_from_direction
// TODO: JIU_XING_DI_PAN_POSITIONS
#[test]
fn test_get_jiuxing_dipan_positions_from_direction() {
let exp = [5, 0, 7, 6, 4, 2, 1, 8, 3];
assert_eq!(
get_jiuxing_dipan_positions_from_direction("n").unwrap(),
&exp
);
}
// TODO: JIU_XING_DI_PAN_POSITIONS_CLOCKWISE <-- NOT NEEDED
#[test]
fn normalize_jiuxing_single() {
assert_eq!(normalize_jiuxing(9), 0);
}
#[test]
fn normalize_jiuxing_all() {
let arr: [usize; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let exp: [usize; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1];
let res: [usize; 11] = arr
.iter()
.map(|index: &usize| normalize_jiuxing(*index as i32))
.collect::<Vec<usize>>()
.try_into()
.unwrap();
assert_eq!(res, exp);
}
// TODO: unpan_xing_index
// TODO: unpan_xing_data <--- NOT IN USE
// TODO: flying_stars_chart <--- NOT IN USE
// TODO: is_shan_xiang_flying_normal
// TODO: get_xiaguatu_from_unpan_index
// ===================================================================
// LO-SHU ORDER - Normal Order
// ===================================================================
// Fly *NORMAL* for Lo-Shu Order: [4] 五黄土星 (5 Green)
#[test]
fn fly_stars_normal_lo_shu_5_green() {
let center: usize = 4; // [4] 五黄土星 (5 Green)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [5, 0, 7, 6, 4, 2, 1, 8, 3];
assert_eq!(fly_flying_stars(center, order, false), exp);
}
// Fly *NORMAL* for Lo-Shu Order: [5] 六白金星 (6 White)
#[test]
fn fly_stars_normal_lo_shu_6_white() {
let center: usize = 5; // [5] 六白金星 (6 White)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [6, 1, 8, 7, 5, 3, 2, 0, 4];
assert_eq!(fly_flying_stars(center, order, false), exp);
}
// Fly *NORMAL* for Lo-Shu Order: [6] 七赤金星 (7 Red)
#[test]
fn fly_stars_normal_lo_shu_7_red() {
let center: usize = 6; // [6] 七赤金星 (7 Red)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [7, 2, 0, 8, 6, 4, 3, 1, 5];
assert_eq!(fly_flying_stars(center, order, false), exp);
}
// Fly *NORMAL* for Lo-Shu Order: [7] 八白土星 (8 White)
#[test]
fn fly_stars_normal_lo_shu_8_white() {
let center: usize = 7; // [7] 八白土星 (8 White)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [8, 3, 1, 0, 7, 5, 4, 2, 6];
assert_eq!(fly_flying_stars(center, order, false), exp);
}
// Fly *NORMAL* for Lo-Shu Order: [8] 九紫火星 (9 Purple)
#[test]
fn fly_stars_normal_lo_shu_9_purple() {
let center: usize = 8; // [8] 九紫火星 (9 Purple)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [0, 4, 2, 1, 8, 6, 5, 3, 7];
assert_eq!(fly_flying_stars(center, order, false), exp);
}
// ===================================================================
// LO-SHU ORDER - Reverse Order
// ===================================================================
// Fly *REVERSE* for Lo-Shu Order (North): [4] 五黄土星 (5 Green)
#[test]
fn fly_stars_reverse_5_green_north() {
let center: usize = 4; // [4] 五黄土星 (5 Green)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
let exp: [usize; 9] = [3, 8, 1, 2, 4, 6, 7, 0, 5];
assert_eq!(fly_flying_stars(center, order,true), exp);
}
// Fly *REVERSE* for Lo-Shu Order (North-East): [4] 五黄土星 (5 Green)
#[test]
fn fly_stars_reverse_lo_shu_5_green_north_east() {
let center: usize = 4; // [4] 五黄土星 (5 Green)
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("ne").unwrap();
let exp: [usize; 9] = [8, 1, 6, 3, 4, 5, 2, 7, 0];
assert_eq!(fly_flying_stars(center, order, true), exp);
}
// ===================================================================
// COMPASS CLOCKWISE - Normal Order
// ===================================================================
// Fly *NORMAL* for COMPASS CLOCKWISE: [4] 五黄土星 (5 Yellow)
// #[test]
// fn fly_stars_normal_lo_shu_5_yellow() {
// let center: usize = 4; // [4] 五黄土星 (5 Yellow)
// let order: &[usize; 8] = &JIU_XING_DI_PAN_POSITIONS_CLOCKWISE;
// let exp: [usize; 8] = [0, 7, 2, 3, 8, 1, 6, 5];
// assert_eq!(fly_flying_stars(center, order,false), exp);
// }
}

View File

@@ -55,13 +55,22 @@ pub trait LanguageTrait {
pub trait NameDataTrait {
fn name(&self) -> Box<LanguageData>;
fn language_details(details: &[String]) -> LanguageDetails {
if details.is_empty() {
LanguageDetails::new("", "")
} else {
LanguageDetails::new(&details[0], &details[1])
}
}
fn language_from_data(&self) -> Language {
let name = &self.name();
Language {
en: self.name().en,
ja: LanguageDetails::new(&self.name().ja[0], &self.name().ja[1]),
vi: LanguageDetails::new(&self.name().vi[0], &self.name().vi[1]),
zh_cn: LanguageDetails::new(&self.name().zh_cn[0], &self.name().zh_cn[1]),
zh_tw: LanguageDetails::new(&self.name().zh_tw[0], &self.name().zh_tw[1]),
ja: Self::language_details(&name.ja),
vi: Self::language_details(&name.vi),
zh_cn: Self::language_details(&name.zh_cn),
zh_tw: Self::language_details(&name.zh_tw),
}
}
}

View File

@@ -1,10 +1,16 @@
#[macro_use]
extern crate lazy_static;
pub mod constants;
pub mod bagua;
pub mod compass;
pub mod ganzhi;
pub mod jiuxing;
pub mod language;
pub mod planet;
pub mod shengsi;
pub mod solar_terms;
pub mod time;
pub mod utils;
pub mod wuxing;
pub mod test_mods;

View File

@@ -1,14 +1,37 @@
//! Information about planets in our solar system.
//! Notice the planets in `PLANETS` are stored in a special order
//! known as _the Ptolemaic Order_. In many ancient traditions,
//! when a man is deceased, he will depart the Earth,
//! and head toward the Moon. Leaving the Moon behind,
//! the Mercury, the Venus, and the Sun. He will continue
//! his journey after the Sun, this time, to _the outer planets_,
//! that are the Mars, the Jupiter, and the Saturn.
//!
//! After all, this library provides methodologies
//! _NOT_ for _"astronomy"_, but for _"astrology"_, hence,
//! follows the tradition which was common to the ancients.
//!
//! Also noteworthy that, according to Rudolf Steiner,
//! "Mercury" was formerly known as "Venus" in ancient times.
//! Yet, it is only so when we are talking about the order
//! of the _physical_ planets, not in its _symbolical_ sense.
//! For instance, when ancients mentioned of "Mercury",
//! it was simply about "Mercury" and not "Venus".
use serde::{Deserialize, Serialize};
use crate::language::{Language, LanguageTrait};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::utils::get_json;
/// A struct representing a planet and stores its attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Planet {
pub num: u8,
pub name: Language,
pub color: String,
pub element: u8,
pub planet: u8,
}
/// A temporary struct for loading JSON data when defining a static const `PLANETS`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanetRawData {
pub name: LanguageData,
}
impl LanguageTrait for Planet {
@@ -16,3 +39,38 @@ impl LanguageTrait for Planet {
Box::new(self.name.clone())
}
}
impl NameDataTrait for PlanetRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
lazy_static! {
/// A static vector with 11 items, each represents a planet
/// in our solar system. Planets are in Ptolemaic order.
///
/// [0] Earth
/// [1] Moon
/// [2] Mercury
/// [3] Venus
/// [4] Sun
/// [5] Mars
/// [6] Jupiter
/// [7] Saturn
/// [8] Uranus
/// [9] Neptune
/// [10] Pluto
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/planets.json`
pub static ref PLANETS: Vec<Planet> = {
let json = &include_str!("../json/planet.json");
let data: Vec<PlanetRawData> = get_json::<PlanetRawData>(json);
data.iter()
.map(|item| Planet {
name: item.language_from_data(),
})
.collect()
};
}

267
src/shengsi.rs Normal file
View File

@@ -0,0 +1,267 @@
//! 生死衰旺 (Sheng-Si Shuai-Wang) is just a combination
//! of 4 Chinese characters, each being:
//!
//! (1) Growing --> 生 (Sheng)
//! (2) Deadly --> 死 (Si)
//! (3) Perishing --> 衰 (Shuai)
//! (4) Prosperous --> 旺 (Wang)
//!
//! They are often used in 四柱命理学 (The Four Pillars of Destiny),
//! but used in Feng-Shui as well. It simply suggests
//! that there are 4 states to the energy occupying the space.
//! In 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
//! it describes the state for the target year
//! in 三元九運 (Sang-Yuan Jiu-Yun),
//! especially, for its 向星 (Xiang-Xing).
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::jiuxing::normalize_jiuxing;
/// A struct representing 生死衰旺 (Sheng-Si Shuai-Wang).
/// `key` would be: "sheng", "si", "shuai", or "wang".
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct ShengSi<'a> {
pub key: &'a str,
pub kanji: &'a str,
pub meaning: &'a str,
}
/// A struct holding allocations of 生死衰旺 (Sheng-Si Shuai-Wang) for the given year.
/// For `usize` (in `Vec<usize>`) is 九星 (Jiu-Xing) index.
#[derive(Debug, Clone)]
pub struct ShengSiYearlyAlloc {
pub wang: Vec<usize>,
pub sheng: Vec<usize>,
pub shuai: Vec<usize>,
pub si: Vec<usize>,
}
impl ShengSiYearlyAlloc {
pub fn accessor(&self, name: &str) -> Option<&Vec<usize>> {
match name {
"wang" => Some(&self.wang),
"sheng" => Some(&self.sheng),
"shuai" => Some(&self.shuai),
"si" => Some(&self.si),
_ => None,
}
}
}
lazy_static! {
/// A HashMap for 生死衰旺 (Sheng-Si Shuai-Wang) by key
/// (for each holds `ShengSi`).
pub static ref SHENG_SI: HashMap<&'static str, ShengSi<'static>> = HashMap::from([
("sheng", ShengSi { key: "sheng", kanji: "", meaning: "growth" }),
("si", ShengSi { key: "si", kanji: "", meaning: "death" }),
("shuai", ShengSi { key: "shuai", kanji: "", meaning: "perishing" }),
("wang", ShengSi { key: "wang", kanji: "", meaning: "prosperous" }),
]);
/// For every year, some 九星 (Jiu-Xing) maybe in 旺 (Wang = Prospering)
/// phase, but some maybe in 死 (Si = Dying). 生死衰旺 (Sheng-Si Shuai-Wang)
/// for 九星 (Jiu-Xing) is no random, but has certain patterns,
/// and is repeated every 9 years. This cycle is called
/// 三元九運 (Sang-Yuan Jiu-Yun), and given the 運盤星 (Un-Pan Xing) index
/// for the specific year, you can tell of 生死衰旺 (Sheng-Si Shuai-Wang)
/// for all the other 九星 (Jiu-Xing). Here, it is constructing
/// the patterns for 9 years, and making them into a static vector
/// for which each index being the 運盤星 (Un-Pan Xing) index.
/// If you know the 運盤星 (Un-Pan Xing) index for the year,
/// this static vector will tell you 生死衰旺 (Sheng-Si Shuai-Wang)
/// for all 九星 (Jiu-Xing).
pub static ref SHENG_SI_ALLOC: Vec<ShengSiYearlyAlloc> = (0..9)
.map(|i: i32| {
// 旺 (Wang)
let unpan_id = i;
// 生 (Sheng)
let sheng: Vec<usize> = [1, 2]
.iter()
.map(|num| {
normalize_jiuxing((unpan_id + num) as i32)
})
.collect::<Vec<usize>>();
// 衰 (Shuai)
let shuai: Vec<usize> = [1, 2]
.iter()
.map(|num| {
normalize_jiuxing((unpan_id - num) as i32)
})
.collect::<Vec<usize>>();
// 死 (Si)
let si: Vec<usize> = [1, 2, 3, 4]
.iter()
.map(|num| -> usize{
normalize_jiuxing(shuai[1] as i32 - num)
})
.collect::<Vec<usize>>();
ShengSiYearlyAlloc {
// 運盤星 (Un-Pan Xing) is always the 旺 (wang) for the year.
wang: vec!(unpan_id as usize),
// Two 九星 (Jiu-Xing) that *proceed* 運盤星 (Un-Pan Xing)
// is always the 生 (Sheng).
sheng,
// Two 九星 (Jiu-Xing) that *preceed* 運盤星 (Un-Pan Xing)
// is always the 衰 (Shuai). However, there is
// an exceptional case when 一白水星 (1 White) were
// given for the 運盤星 (Un-Pan Xing) because
// it should be converted to 九紫火星 (9 Purple).
shuai: if unpan_id == 0 {
vec!(8)
} else {
shuai
},
// Calculation for 死 (Si) is a bit tricky...
si: si
.iter()
.filter_map(|&index| {
if unpan_id < 7 {
if index != 0 && index != 7 {
Some(index)
} else {
None
}
} else {
Some(index)
}
})
.collect::<Vec<usize>>(),
}
})
.collect();
}
/// Given 運盤 (Un-Pan) index and a layout for the current
/// 運盤 (Un-Pan) positions (`&[usize; 9]`), returns the corresponding
/// 生死衰旺 (Sheng-Si Shuai-Wang) situation.
///
/// Example:
/// ```rust
/// use std::convert::TryInto;
/// use mikaboshi::shengsi::{get_shengsi_mapping, ShengSi};
/// use mikaboshi::test_mods::ShengSiParams;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(params: &JsValue) -> JsValue {
/// let params: ShengSiParams = params.into_serde().unwrap();
/// let unpan_id: usize = params.unpan_id;
/// let chart: [usize; 9] = params
/// .unpan_xing_chart
/// .try_into()
/// .unwrap_or_else(|v: Vec<usize>| {
/// panic!("Expected a Vec of length 9 but it was {}", v.len())
/// });
/// let mapping: Vec<Option<&ShengSi>> = get_shengsi_mapping(unpan_id, &chart);
/// JsValue::from_serde(&mapping).unwrap()
/// }
/// ```
pub fn get_shengsi_mapping(
unpan_id: usize,
unpan_xing_chart: &[usize; 9],
) -> Vec<Option<&ShengSi>> {
// At first, we will get 生死衰旺 (Sheng-Si Shuai-Wang)
// for the given 運盤星 (Un-Pan Xing).
let yearly_allocs: &ShengSiYearlyAlloc = &SHENG_SI_ALLOC[unpan_id];
// Now, 生死衰旺 (Sheng-Si Shuai-Wang) just obtained
// is mapped by "sheng", "si", "shuai", and "wang".
// However, we rather want to look up by 九星 (Jiu-Xing) index.
// So, we are creating a temporary mapping here.
// Though, in the next line, we are just initializing
// each in the mapping with `None`.
let mut lookup: HashMap<usize, Option<&ShengSi>> = (0..9)
.map(|index: usize| (index, None))
.into_iter()
.collect();
// Once the mapping being initialized, we are
// creating the mapping.
for key in ["sheng", "si", "shuai", "wang"] {
let data: Option<&ShengSi> = SHENG_SI.get(key);
for index in yearly_allocs.accessor(key).unwrap() {
*lookup.get_mut(index).unwrap() = data;
}
}
// We have 運盤 (Un-Pan) chart given. All we want is
// to find 生死衰旺 (Sheng-Si Shuai-Wang)
// for each 九星 (Jiu-Xing) in the 運盤 (Un-Pan) chart
// (using the temporary mapping just created).
unpan_xing_chart
.iter()
.map(|index: &usize| *lookup.get(index).unwrap())
.into_iter()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constant_sheng_si() {
assert_eq!(SHENG_SI.get("sheng").unwrap().key, "sheng");
}
#[test]
fn test_constant_sheng_si_alloc_for_wang() {
assert_eq!(SHENG_SI_ALLOC[0].wang[0], 0);
}
#[test]
fn test_constant_sheng_si_alloc_for_shuai() {
assert_eq!(SHENG_SI_ALLOC[6].shuai[0], 5);
assert_eq!(SHENG_SI_ALLOC[6].shuai[1], 4);
}
#[test]
fn test_get_shengsi_mapping() {
let res = get_shengsi_mapping(6, &[2, 0, 4, 7, 6, 5, 8, 3, 1]);
assert_eq!(res[0].unwrap().key, "si"); // 2
assert_eq!(res[1], None); // 0
assert_eq!(res[2].unwrap().key, "shuai"); // 4
assert_eq!(res[3].unwrap().key, "sheng"); // 7
assert_eq!(res[4].unwrap().key, "wang"); // 6
assert_eq!(res[5].unwrap().key, "shuai"); // 5
assert_eq!(res[6].unwrap().key, "sheng"); // 8
assert_eq!(res[7].unwrap().key, "si"); // 3
assert_eq!(res[8].unwrap().key, "si"); // 1
}
}
// 生入 Sheng-Ru (Shēng Rù)
// 剋入 Ke-Ru (Kè Rù)
// 生出 Sheng-Chu (Shēng Chū)
// 剋出 Ke-Chu (Kè Chū)
// 差錯 Cha-Cuo
// 旺 Prosperous
// 相 Supportive
// 休 Rest
// 囚 Inprisoned
// 死 Death
// Chang Sheng 12 Qi Phase (十二運)
//
// 长生 chang sheng
// 沐浴 mu yu
// 冠带 guan dai
// 临官 lin guān
// 帝旺 di wang
// 衰 Shuāi
// 病 Bing
// 死 Si
// 墓 Mu
// 绝 Jue
// 胎 Tai
// 养 Yang

View File

@@ -1,8 +1,10 @@
//! A module for "二十四节气" (Er-Shi-Si Jie-Qi).
//! Or, for calculating "立春" (Li-Chun).
use serde::{Deserialize, Serialize};
use sowngwala::time::{add_date, Date, Month};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::utils::longitude_of_the_sun_from_date;
use crate::utils::{get_json, longitude_of_the_sun_from_date};
#[derive(Debug)]
pub struct SolarTerm {
@@ -12,7 +14,7 @@ pub struct SolarTerm {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolarTermData {
pub struct SolarTermRawData {
pub id: u8,
pub name: LanguageData,
pub angle: u16,
@@ -24,15 +26,32 @@ impl LanguageTrait for SolarTerm {
}
}
impl NameDataTrait for SolarTermData {
impl NameDataTrait for SolarTermRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
lazy_static! {
pub static ref SOLAR_TERMS: Vec<SolarTerm> = {
let json = &include_str!("../json/solar_terms.json");
let data: Vec<SolarTermRawData> = get_json::<SolarTermRawData>(json);
data.iter()
.map(|item| {
let item = item.clone();
SolarTerm {
id: item.id,
name: item.language_from_data(),
angle: item.angle,
}
})
.collect()
};
}
#[allow(clippy::many_single_char_names)]
pub fn get_last_term(date: &Date) -> (f64, Date) {
let lng_0: f64 = longitude_of_the_sun_from_date(&date);
let lng_0: f64 = longitude_of_the_sun_from_date(date);
// For the unit of 15, we want the last term.
// Ex.
// 317.435511 --> 315.0
@@ -61,8 +80,20 @@ pub fn get_last_term(date: &Date) -> (f64, Date) {
(target, term.unwrap())
}
/// Given the year, returns the year's Lichun in date.
/// * `date` - &Date
/// Example:
/// ```rust
/// use mikaboshi::solar_terms::get_lichun;
/// use wasm_bindgen::prelude::*;
///
/// #[wasm_bindgen]
/// pub fn xx(year: i16) -> JsValue {
/// let lichun = get_lichun(year);
/// JsValue::from_str(&format!(
/// "{:04}-{:02}-{:02}",
/// lichun.year as u16, lichun.month as u8, lichun.day as u8
/// ))
/// }
/// ```
#[allow(clippy::many_single_char_names)]
pub fn get_lichun(year: i16) -> Date {
let d: Date = Date {
@@ -79,7 +110,7 @@ mod tests {
use super::*;
#[test]
fn check_get_last_term() {
fn test_get_last_term() {
let date = Date {
year: 2022,
month: Month::Feb,

61
src/test_mods.rs Normal file
View File

@@ -0,0 +1,61 @@
/**
* Modules used ONLY from tests.
*/
use serde::{Deserialize, Serialize};
use sowngwala::time::{Date, DateTime, Month};
use std::convert::{From, TryFrom};
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct DateParams {
pub year: u16,
pub month: u8,
pub day: u8,
}
impl From<&DateParams> for Date {
fn from(&params: &DateParams) -> Self {
Date {
year: params.year as i16,
month: Month::try_from(params.month as i32).unwrap(),
day: params.day as f64,
}
}
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct DateTimeParams {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub min: u8,
pub sec: u8,
pub zone: i8,
}
impl From<&DateTimeParams> for DateTime {
fn from(&params: &DateTimeParams) -> Self {
DateTime {
year: params.year as i16,
month: Month::try_from(params.month as i32).unwrap(),
day: params.day as f64,
hour: params.hour as i16,
min: params.min as i16,
sec: params.sec as f64,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct XiaGuaTuParams {
pub unpan_xing_center: usize,
pub unpan_xing_order: Vec<usize>,
pub xiang_xing_direction: String,
pub xiang_xing_sector: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ShengSiParams {
pub unpan_id: usize,
pub unpan_xing_chart: Vec<usize>,
}

View File

@@ -1,6 +1,44 @@
// use core::iter::FromIterator;
// use std::iter::FromIterator;
use serde::Deserialize;
use sowngwala::sun::ecliptic_position_of_the_sun_from_date;
use sowngwala::time::Date;
pub fn longitude_of_the_sun_from_date(date: &Date) -> f64 {
ecliptic_position_of_the_sun_from_date(&date).lng
pub fn get_json<'a, T: Deserialize<'a>>(json: &'a str) -> Vec<T> {
match serde_json::from_str(json) {
Ok(json) => json,
Err(err) => panic!("Error: {}", err),
}
}
/// For the given Vec, sorts by given order.
pub fn make_sort<T: Clone>(order: Vec<u8>) -> Box<dyn Fn(Vec<T>) -> Vec<T>> {
Box::new(move |source: Vec<T>| -> Vec<T> {
order
.clone()
.into_iter()
.map(|index| source[index as usize].clone())
.collect()
})
}
/// Increments by the given step until it becomes more than 0.
pub fn make_positive(step: u32) -> Box<dyn Fn(i32) -> u32> {
Box::new(move |mut num: i32| -> u32 {
let limit = 10000;
let mut cnt = 0;
while num < 0 {
if cnt > limit {
panic!("Iteration reached: {}", limit);
}
num += step as i32;
cnt += 1;
}
num as u32
})
}
pub fn longitude_of_the_sun_from_date(date: &Date) -> f64 {
ecliptic_position_of_the_sun_from_date(date).lng
}

View File

@@ -1,16 +1,18 @@
//! A module for 五行 (Wu-Xing).
use serde::{Deserialize, Serialize};
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
use crate::utils::get_json;
/// A struct representing 五行 (Wu-Xing).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WuXing {
pub no: u8,
pub name: Language,
}
/// A temporary struct for loading JSON data when defining a static const `WU_XING`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WuXingData {
pub no: u8,
pub struct WuXingRawData {
pub name: LanguageData,
}
@@ -20,8 +22,27 @@ impl LanguageTrait for WuXing {
}
}
impl NameDataTrait for WuXingData {
impl NameDataTrait for WuXingRawData {
fn name(&self) -> Box<LanguageData> {
Box::new(self.name.clone())
}
}
lazy_static! {
/// A static vector with 5 items, each represents 五行 (Wu-Xing).
///
/// For attributes details stored in the vector is found in JSON file:
/// `src/json/wuxing.json`
pub static ref WU_XING: Vec<WuXing> = {
let json = &include_str!("../json/wuxing.json");
let data: Vec<WuXingRawData> = get_json::<WuXingRawData>(json);
data.iter()
.map(|item| {
let item = item.clone();
WuXing {
name: item.language_from_data(),
}
})
.collect()
};
}

BIN
tricky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB