import { CONTROLS, ELEMENT_LABEL, STEM_INFO } from './constants'; import { createChart } from './chart'; import { BRANCH_THREE_COMBINATION_RULES, BRANCH_TRIPLE_PUNISHMENT_RULES, SELF_PUNISHMENT_BRANCHES, branchPairKey, getBranchSixCombinationElement, getStemCombinationElement, isBranchClash, isBranchHarm, } from './relation-rules'; import type { BaziChart, BranchChartRelation, BranchRelationMember, ChartRelations, FourPillarsInput, Pillar, StemChartRelation, StemRelationMember, } from './types'; const PILLAR_LABEL = { year: '年', month: '月', day: '日', hour: '时', } as const; function stemMember(pillar: Pillar): StemRelationMember { return { pillar: pillar.key, position: 'stem', value: pillar.stem.name }; } function branchMember(pillar: Pillar): BranchRelationMember { return { pillar: pillar.key, position: 'branch', value: pillar.branch.name }; } function locationLabel(member: StemRelationMember | BranchRelationMember): string { return `${PILLAR_LABEL[member.pillar]}${member.position === 'stem' ? '干' : '支'}${member.value}`; } function allPairs(items: readonly T[]): Array<[T, T]> { const pairs: Array<[T, T]> = []; for (let i = 0; i < items.length; i++) { for (let j = i + 1; j < items.length; j++) { const left = items[i]; const right = items[j]; if (left !== undefined && right !== undefined) pairs.push([left, right]); } } return pairs; } function detectStemRelations(pillars: Pillar[]): StemChartRelation[] { const relations: StemChartRelation[] = []; let combinationIndex = 0; let controlIndex = 0; for (const [leftPillar, rightPillar] of allPairs(pillars)) { const left = stemMember(leftPillar); const right = stemMember(rightPillar); const combinationElement = getStemCombinationElement(left.value, right.value); if (combinationElement) { combinationIndex++; relations.push({ id: `stem-combination-${String(combinationIndex).padStart(2, '0')}`, type: 'stem-combination', subtype: 'five-combination', members: [left, right], candidateElement: combinationElement, description: `${locationLabel(left)}与${locationLabel(right)}构成天干五合,候选化${ELEMENT_LABEL[combinationElement]};relations-v1 不判断是否合化成功。`, }); } const leftElement = STEM_INFO[left.value].element; const rightElement = STEM_INFO[right.value].element; let controller: StemRelationMember | null = null; let controlled: StemRelationMember | null = null; if (CONTROLS[leftElement] === rightElement) { controller = left; controlled = right; } else if (CONTROLS[rightElement] === leftElement) { controller = right; controlled = left; } if (controller && controlled) { controlIndex++; relations.push({ id: `stem-control-${String(controlIndex).padStart(2, '0')}`, type: 'stem-control', members: [left, right], controller, controlled, description: `${locationLabel(controller)}${ELEMENT_LABEL[STEM_INFO[controller.value].element]}克${locationLabel(controlled)}${ELEMENT_LABEL[STEM_INFO[controlled.value].element]}。`, }); } } return relations; } function cartesianMembers( groups: BranchRelationMember[][], index = 0, current: BranchRelationMember[] = [], ): BranchRelationMember[][] { if (index === groups.length) return [current]; const group = groups[index] ?? []; return group.flatMap((member) => cartesianMembers(groups, index + 1, [...current, member]) ); } function detectTriplePunishments( members: BranchRelationMember[], startIndex: number, ): BranchChartRelation[] { const relations: BranchChartRelation[] = []; let offset = startIndex; for (const definition of BRANCH_TRIPLE_PUNISHMENT_RULES) { const groups = definition.branches.map((branch) => members.filter((member) => member.value === branch) ); if (groups.some((group) => group.length === 0)) continue; for (const relationMembers of cartesianMembers(groups)) { offset++; relations.push({ id: `branch-punishment-${String(offset).padStart(2, '0')}`, type: 'branch-punishment', subtype: 'triple', pattern: definition.pattern, members: relationMembers, description: `${relationMembers.map(locationLabel).join('、')}构成${definition.label}。`, }); } } return relations; } function detectThreeCombinations( members: BranchRelationMember[], ): BranchChartRelation[] { const relations: BranchChartRelation[] = []; let index = 0; for (const definition of BRANCH_THREE_COMBINATION_RULES) { const groups = definition.branches.map((branch) => members.filter((member) => member.value === branch) ); if (groups.some((group) => group.length === 0)) continue; for (const relationMembers of cartesianMembers(groups)) { const [first, second, third] = relationMembers; if (!first || !second || !third) continue; index++; relations.push({ id: `branch-three-combination-${String(index).padStart(2, '0')}`, type: 'branch-three-combination', members: [first, second, third], candidateElement: definition.element, description: `${relationMembers.map(locationLabel).join('、')}构成${definition.label},候选化${ELEMENT_LABEL[definition.element]};relations-v1 不判断是否合化成功。`, }); } } return relations; } function detectBranchRelations(pillars: Pillar[]): BranchChartRelation[] { const members = pillars.map(branchMember); const relations: BranchChartRelation[] = []; let combinationIndex = 0; let clashIndex = 0; let harmIndex = 0; let punishmentIndex = 0; for (const [left, right] of allPairs(members)) { const key = branchPairKey(left.value, right.value); const combinationElement = getBranchSixCombinationElement(left.value, right.value); if (combinationElement) { combinationIndex++; relations.push({ id: `branch-six-combination-${String(combinationIndex).padStart(2, '0')}`, type: 'branch-six-combination', members: [left, right], candidateElement: combinationElement, description: `${locationLabel(left)}与${locationLabel(right)}构成六合,候选化${ELEMENT_LABEL[combinationElement]};relations-v1 不判断是否合化成功。`, }); } if (isBranchClash(left.value, right.value)) { clashIndex++; relations.push({ id: `branch-clash-${String(clashIndex).padStart(2, '0')}`, type: 'branch-clash', members: [left, right], description: `${locationLabel(left)}与${locationLabel(right)}构成六冲(${key}冲)。`, }); } if (isBranchHarm(left.value, right.value)) { harmIndex++; relations.push({ id: `branch-harm-${String(harmIndex).padStart(2, '0')}`, type: 'branch-harm', members: [left, right], description: `${locationLabel(left)}与${locationLabel(right)}构成六害(${key}害)。`, }); } if (key === '子卯') { punishmentIndex++; relations.push({ id: `branch-punishment-${String(punishmentIndex).padStart(2, '0')}`, type: 'branch-punishment', subtype: 'mutual', pattern: 'zi-mao', members: [left, right], description: `${locationLabel(left)}与${locationLabel(right)}构成子卯刑。`, }); } if (left.value === right.value && SELF_PUNISHMENT_BRANCHES.includes(left.value)) { punishmentIndex++; relations.push({ id: `branch-punishment-${String(punishmentIndex).padStart(2, '0')}`, type: 'branch-punishment', subtype: 'self', pattern: 'self', members: [left, right], description: `${locationLabel(left)}与${locationLabel(right)}构成${left.value}${left.value}自刑。`, }); } } const triplePunishments = detectTriplePunishments(members, punishmentIndex); relations.push(...detectThreeCombinations(members)); relations.push(...triplePunishments); return relations; } export function detectRelations(chart: BaziChart): ChartRelations { const pillars = [ chart.pillars.year, chart.pillars.month, chart.pillars.day, chart.pillars.hour, ]; return { model: 'relations-v1', stems: detectStemRelations(pillars), branches: detectBranchRelations(pillars), }; } export function detectPillarRelations(pillars: FourPillarsInput): ChartRelations { return detectRelations(createChart(pillars)); }