dsh-metaphysics 0.14.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/engine/meihua.js CHANGED
@@ -1,136 +1,137 @@
1
- // 梅花易数:时间/数字起卦、体用、互卦、变卦、错综卦
2
- import { BRANCHES } from './calendar.js';
3
- import { solarToLunar } from './lunar.js';
4
- import { hexagramFromBits } from './liuyao.js';
5
-
6
- // 先天八卦数:乾1 兑2 离3 震4 巽5 坎6 艮7 坤8
7
- const TRI_NAME = ['', '乾', '兑', '离', '震', '巽', '坎', '艮', '坤'];
8
- const TRI_BITS = {
9
- 乾: [1, 1, 1], 兑: [1, 1, 0], 离: [1, 0, 1], 震: [1, 0, 0],
10
- 巽: [0, 1, 1], 坎: [0, 1, 0], 艮: [0, 0, 1], 坤: [0, 0, 0],
11
- };
12
- const TRI_ELEMENT = { 乾: '金', 兑: '金', 离: '火', 震: '木', 巽: '木', 坎: '水', 艮: '土', 坤: '土' };
13
- const GENERATES = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
14
- const OVERCOMES = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
15
-
16
- const mod8 = (n) => ((n % 8) + 7) % 8 + 1; // 1..8,余 0 作 8
17
- const mod6 = (n) => ((n % 6) + 5) % 6 + 1; // 1..6,余 0 作 6
18
-
19
- function bitsOf(num) {
20
- return TRI_BITS[TRI_NAME[num]];
21
- }
22
-
23
- function hexName(upperNum, lowerNum) {
24
- const bits = [...bitsOf(lowerNum), ...bitsOf(upperNum)];
25
- const h = hexagramFromBits(bits);
26
- return { name: h.name, upper: TRI_NAME[upperNum], lower: TRI_NAME[lowerNum], bits };
27
- }
28
-
29
- /** 体用关系 */
30
- function relation(bodyEl, useEl) {
31
- if (bodyEl === useEl) return { level: '吉', text: '体用比和,事顺而稳' };
32
- if (GENERATES[useEl] === bodyEl) return { level: '大吉', text: `用生体(${useEl}生${bodyEl}),得外力相助` };
33
- if (GENERATES[bodyEl] === useEl) return { level: '小凶', text: `体生用(${bodyEl}生${useEl}),耗损付出` };
34
- if (OVERCOMES[bodyEl] === useEl) return { level: '吉', text: `体克用(${bodyEl}克${useEl}),事可为但费力` };
35
- return { level: '凶', text: `用克体(${useEl}克${bodyEl}),受阻受制` };
36
- }
37
-
38
- /**
39
- * 起卦
40
- * @param {object} input
41
- * mode: 'time' | 'number'
42
- * datetime: 'YYYY-MM-DDTHH:mm'(time 模式)
43
- * upper/lower/changing: 数字(number 模式,可给两数由程序取余)
44
- */
45
- export function buildMeihua(input = {}) {
46
- const mode = input.mode === 'number' ? 'number' : 'time';
47
- let upperNum;
48
- let lowerNum;
49
- let moving;
50
- let source;
51
-
52
- if (mode === 'number') {
53
- const a = Number(input.upper);
54
- const b = Number(input.lower);
55
- if (!Number.isFinite(a) || !Number.isFinite(b)) throw new Error('数字起卦需要两个数字(upper / lower)');
56
- upperNum = mod8(a);
57
- lowerNum = mod8(b);
58
- moving = Number.isFinite(Number(input.changing)) && Number(input.changing) > 0
59
- ? mod6(Number(input.changing))
60
- : mod6(a + b);
61
- source = `数字起卦:${a} / ${b}`;
62
- } else {
63
- const dt = parseDateTime(input.datetime);
64
- const lunar = solarToLunar(dt.year, dt.month, dt.day);
65
- const yearBranch = ((lunar.lunarYear - 4) % 12 + 12) % 12;
66
- const hb = Math.floor(((dt.hour + 1) % 24) / 2);
67
- const sum1 = yearBranch + 1 + lunar.month + lunar.day;
68
- const sum2 = sum1 + hb + 1;
69
- upperNum = mod8(sum1);
70
- lowerNum = mod8(sum2);
71
- moving = mod6(sum2);
72
- source = `时间起卦:${BRANCHES[yearBranch]}年${lunar.month}月${lunar.day}日${BRANCHES[hb]}时`;
73
- }
74
-
75
- const ben = hexName(upperNum, lowerNum);
76
- const bits = ben.bits;
77
-
78
- // 动爻:自下而上第 moving 爻
79
- const changedBits = bits.slice();
80
- changedBits[moving - 1] = changedBits[moving - 1] ? 0 : 1;
81
- const bian = hexagramFromBits(changedBits);
82
-
83
- // 互卦:2·3·4 为下互,3·4·5 为上互
84
- const huLower = [bits[1], bits[2], bits[3]];
85
- const huUpper = [bits[2], bits[3], bits[4]];
86
- const hu = hexagramFromBits([...huLower, ...huUpper]);
87
-
88
- // 体用:动爻所在之卦为用,另一卦为体
89
- const movingInUpper = moving >= 4;
90
- const bodyTri = movingInUpper ? ben.lower : ben.upper;
91
- const useTri = movingInUpper ? ben.upper : ben.lower;
92
- const rel = relation(TRI_ELEMENT[bodyTri], TRI_ELEMENT[useTri]);
93
-
94
- // 错卦=六爻全变;综卦=整卦倒转(覆卦),不是上下卦对调:天风姤 → 泽天夬
95
- const cuoBits = bits.map((b) => (b ? 0 : 1));
96
- const zongBits = bits.slice().reverse();
97
- const cuo = hexagramFromBits(cuoBits);
98
- const zong = hexagramFromBits(zongBits);
99
-
100
- return {
101
- system: 'meihua',
102
- mode,
103
- source,
104
- moving,
105
- movingName: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'][moving - 1],
106
- ben: { name: ben.name, upper: ben.upper, lower: ben.lower, element: `${TRI_ELEMENT[ben.upper]}/${TRI_ELEMENT[ben.lower]}` },
107
- hu: { name: hu.name, upper: hu.upper, lower: hu.lower },
108
- bian: { name: bian.name, upper: bian.upper, lower: bian.lower },
109
- cuo: { name: cuo.name },
110
- zong: { name: zong.name },
111
- bodyUse: {
112
- body: { trigram: bodyTri, element: TRI_ELEMENT[bodyTri] },
113
- use: { trigram: useTri, element: TRI_ELEMENT[useTri] },
114
- relation: rel.text,
115
- level: rel.level,
116
- },
117
- lines: bits.map((b, i) => ({
118
- position: i + 1,
119
- name: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'][i],
120
- yinYang: b ? '阳' : '阴',
121
- moving: i === moving - 1,
122
- })),
123
- };
124
- }
125
-
126
- function parseDateTime(text) {
127
- if (typeof text !== 'string' || !text.trim()) throw new Error('时间起卦需要 datetime');
128
- const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2}))?$/);
129
- if (!m) throw new Error(`无法解析 datetime:「${text}」`);
130
- return {
131
- year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
132
- hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
133
- };
134
- }
135
-
136
- export { TRI_NAME, TRI_ELEMENT, TRI_BITS };
1
+ // 梅花易数:时间/数字起卦、体用、互卦、变卦、错综卦
2
+ import { BRANCHES } from './calendar.js';
3
+ import { solarToLunar } from './lunar.js';
4
+ import { fail } from './fail.js';
5
+ import { hexagramFromBits } from './liuyao.js';
6
+
7
+ // 先天八卦数:乾1 兑2 离3 震4 巽5 坎6 艮7 坤8
8
+ const TRI_NAME = ['', '乾', '兑', '离', '震', '巽', '坎', '艮', '坤'];
9
+ const TRI_BITS = {
10
+ 乾: [1, 1, 1], 兑: [1, 1, 0], 离: [1, 0, 1], 震: [1, 0, 0],
11
+ 巽: [0, 1, 1], 坎: [0, 1, 0], 艮: [0, 0, 1], 坤: [0, 0, 0],
12
+ };
13
+ const TRI_ELEMENT = { 乾: '金', 兑: '金', 离: '火', 震: '木', 巽: '木', 坎: '水', 艮: '土', 坤: '土' };
14
+ const GENERATES = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
15
+ const OVERCOMES = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
16
+
17
+ const mod8 = (n) => ((n % 8) + 7) % 8 + 1; // 1..8,余 0 作 8
18
+ const mod6 = (n) => ((n % 6) + 5) % 6 + 1; // 1..6,余 0 作 6
19
+
20
+ function bitsOf(num) {
21
+ return TRI_BITS[TRI_NAME[num]];
22
+ }
23
+
24
+ function hexName(upperNum, lowerNum) {
25
+ const bits = [...bitsOf(lowerNum), ...bitsOf(upperNum)];
26
+ const h = hexagramFromBits(bits);
27
+ return { name: h.name, upper: TRI_NAME[upperNum], lower: TRI_NAME[lowerNum], bits };
28
+ }
29
+
30
+ /** 体用关系 */
31
+ function relation(bodyEl, useEl) {
32
+ if (bodyEl === useEl) return { level: '吉', text: '体用比和,事顺而稳' };
33
+ if (GENERATES[useEl] === bodyEl) return { level: '大吉', text: `用生体(${useEl}生${bodyEl}),得外力相助` };
34
+ if (GENERATES[bodyEl] === useEl) return { level: '小凶', text: `体生用(${bodyEl}生${useEl}),耗损付出` };
35
+ if (OVERCOMES[bodyEl] === useEl) return { level: '吉', text: `体克用(${bodyEl}克${useEl}),事可为但费力` };
36
+ return { level: '凶', text: `用克体(${useEl}克${bodyEl}),受阻受制` };
37
+ }
38
+
39
+ /**
40
+ * 起卦
41
+ * @param {object} input
42
+ * mode: 'time' | 'number'
43
+ * datetime: 'YYYY-MM-DDTHH:mm'(time 模式)
44
+ * upper/lower/changing: 数字(number 模式,可给两数由程序取余)
45
+ */
46
+ export function buildMeihua(input = {}) {
47
+ const mode = input.mode === 'number' ? 'number' : 'time';
48
+ let upperNum;
49
+ let lowerNum;
50
+ let moving;
51
+ let source;
52
+
53
+ if (mode === 'number') {
54
+ const a = Number(input.upper);
55
+ const b = Number(input.lower);
56
+ if (!Number.isFinite(a) || !Number.isFinite(b)) throw fail('E_INPUT', '数字起卦需要两个数字(upper / lower),例如 upper=3、lower=5');
57
+ upperNum = mod8(a);
58
+ lowerNum = mod8(b);
59
+ moving = Number.isFinite(Number(input.changing)) && Number(input.changing) > 0
60
+ ? mod6(Number(input.changing))
61
+ : mod6(a + b);
62
+ source = `数字起卦:${a} / ${b}`;
63
+ } else {
64
+ const dt = parseDateTime(input.datetime);
65
+ const lunar = solarToLunar(dt.year, dt.month, dt.day);
66
+ const yearBranch = ((lunar.lunarYear - 4) % 12 + 12) % 12;
67
+ const hb = Math.floor(((dt.hour + 1) % 24) / 2);
68
+ const sum1 = yearBranch + 1 + lunar.month + lunar.day;
69
+ const sum2 = sum1 + hb + 1;
70
+ upperNum = mod8(sum1);
71
+ lowerNum = mod8(sum2);
72
+ moving = mod6(sum2);
73
+ source = `时间起卦:${BRANCHES[yearBranch]}年${lunar.month}月${lunar.day}日${BRANCHES[hb]}时`;
74
+ }
75
+
76
+ const ben = hexName(upperNum, lowerNum);
77
+ const bits = ben.bits;
78
+
79
+ // 动爻:自下而上第 moving 爻
80
+ const changedBits = bits.slice();
81
+ changedBits[moving - 1] = changedBits[moving - 1] ? 0 : 1;
82
+ const bian = hexagramFromBits(changedBits);
83
+
84
+ // 互卦:2·3·4 为下互,3·4·5 为上互
85
+ const huLower = [bits[1], bits[2], bits[3]];
86
+ const huUpper = [bits[2], bits[3], bits[4]];
87
+ const hu = hexagramFromBits([...huLower, ...huUpper]);
88
+
89
+ // 体用:动爻所在之卦为用,另一卦为体
90
+ const movingInUpper = moving >= 4;
91
+ const bodyTri = movingInUpper ? ben.lower : ben.upper;
92
+ const useTri = movingInUpper ? ben.upper : ben.lower;
93
+ const rel = relation(TRI_ELEMENT[bodyTri], TRI_ELEMENT[useTri]);
94
+
95
+ // 错卦=六爻全变;综卦=整卦倒转(覆卦),不是上下卦对调:天风姤 → 泽天夬
96
+ const cuoBits = bits.map((b) => (b ? 0 : 1));
97
+ const zongBits = bits.slice().reverse();
98
+ const cuo = hexagramFromBits(cuoBits);
99
+ const zong = hexagramFromBits(zongBits);
100
+
101
+ return {
102
+ system: 'meihua',
103
+ mode,
104
+ source,
105
+ moving,
106
+ movingName: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'][moving - 1],
107
+ ben: { name: ben.name, upper: ben.upper, lower: ben.lower, element: `${TRI_ELEMENT[ben.upper]}/${TRI_ELEMENT[ben.lower]}` },
108
+ hu: { name: hu.name, upper: hu.upper, lower: hu.lower },
109
+ bian: { name: bian.name, upper: bian.upper, lower: bian.lower },
110
+ cuo: { name: cuo.name },
111
+ zong: { name: zong.name },
112
+ bodyUse: {
113
+ body: { trigram: bodyTri, element: TRI_ELEMENT[bodyTri] },
114
+ use: { trigram: useTri, element: TRI_ELEMENT[useTri] },
115
+ relation: rel.text,
116
+ level: rel.level,
117
+ },
118
+ lines: bits.map((b, i) => ({
119
+ position: i + 1,
120
+ name: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'][i],
121
+ yinYang: b ? '阳' : '阴',
122
+ moving: i === moving - 1,
123
+ })),
124
+ };
125
+ }
126
+
127
+ function parseDateTime(text) {
128
+ if (typeof text !== 'string' || !text.trim()) throw fail('E_INPUT', '时间起卦需要 datetime,例如 2026-10-24T11:43');
129
+ const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2}))?$/);
130
+ if (!m) throw fail('E_INPUT', `无法解析 datetime:「${text}」,请用 2026-10-24T11:43 这种格式`);
131
+ return {
132
+ year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
133
+ hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
134
+ };
135
+ }
136
+
137
+ export { TRI_NAME, TRI_ELEMENT, TRI_BITS };
@@ -0,0 +1,24 @@
1
+ // 六十甲子纳音(每两个干支一名)。
2
+ //
3
+ // 之前 bazi.js 与 ziwei.js 各存了一份完全相同的 30 项表:改一处漏改另一处,
4
+ // 就会出现「八字时柱纳音」与「紫微五行局」对不上的静默错误。这里收成单一来源。
5
+
6
+ export const NAYIN_NAMES = [
7
+ '海中金', '炉中火', '大林木', '路旁土', '剑锋金',
8
+ '山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
9
+ '泉中水', '屋上土', '霹雳火', '松柏木', '长流水',
10
+ '沙中金', '山下火', '平地木', '壁上土', '金箔金',
11
+ '覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木',
12
+ '大溪水', '沙中土', '天上火', '石榴木', '大海水',
13
+ ];
14
+
15
+ /**
16
+ * 按六十甲子序号取纳音。序号可越界/为负,内部先归一到 0–59。
17
+ * @param {number} ganzhiIdx 0 = 甲子 … 59 = 癸亥
18
+ * @returns {{name: string, element: '金'|'木'|'水'|'火'|'土'}}
19
+ */
20
+ export function nayinOf(ganzhiIdx) {
21
+ const i = ((Number(ganzhiIdx) % 60) + 60) % 60;
22
+ const name = NAYIN_NAMES[Math.floor(i / 2)];
23
+ return { name, element: name[2] };
24
+ }