725 lines
27 KiB
TypeScript
725 lines
27 KiB
TypeScript
// 천간 (天干) - 10개
|
|
export const HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'] as const;
|
|
export const HEAVENLY_STEMS_KR = ['갑', '을', '병', '정', '무', '기', '경', '신', '임', '계'] as const;
|
|
|
|
// 지지 (地支) - 12개
|
|
export const EARTHLY_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'] as const;
|
|
export const EARTHLY_BRANCHES_KR = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해'] as const;
|
|
|
|
// 오행 (五行)
|
|
export const FIVE_ELEMENTS = {
|
|
'甲': '木', '乙': '木',
|
|
'丙': '火', '丁': '火',
|
|
'戊': '土', '己': '土',
|
|
'庚': '金', '辛': '金',
|
|
'壬': '水', '癸': '水',
|
|
'寅': '木', '卯': '木',
|
|
'巳': '火', '午': '火',
|
|
'辰': '土', '戌': '土', '丑': '土', '未': '土',
|
|
'申': '金', '酉': '金',
|
|
'子': '水', '亥': '水',
|
|
} as const;
|
|
|
|
export const FIVE_ELEMENTS_KR = {
|
|
'木': '목', '火': '화', '土': '토', '金': '금', '水': '수'
|
|
} as const;
|
|
|
|
// 십성 (十星)
|
|
export const TEN_GODS = {
|
|
same: { yang: '비견', yin: '겁재' }, // 같은 오행
|
|
produce: { yang: '식신', yin: '상관' }, // 내가 생하는 오행
|
|
overcome: { yang: '편재', yin: '정재' }, // 내가 극하는 오행
|
|
overcome_me: { yang: '편관', yin: '정관' }, // 나를 극하는 오행
|
|
produce_me: { yang: '편인', yin: '정인' } // 나를 생하는 오행
|
|
} as const;
|
|
|
|
// 십이운성 (十二運星)
|
|
export const TWELVE_FORTUNES = [
|
|
'장생', '목욕', '관대', '건록', '제왕', '쇠', '병', '사', '묘', '절', '태', '양'
|
|
] as const;
|
|
|
|
// 간지 계산을 위한 기준일 (1900년 1월 1일 = 경자년 정축월 병인일)
|
|
const BASE_YEAR = 1900;
|
|
const BASE_YEAR_STEM = 6; // 庚
|
|
const BASE_YEAR_BRANCH = 0; // 子
|
|
|
|
/**
|
|
* 년도의 간지를 계산
|
|
*/
|
|
export function getYearGanzi(year: number): { stem: string; branch: string; stemKr: string; branchKr: string } {
|
|
const yearDiff = year - BASE_YEAR;
|
|
const stemIndex = (BASE_YEAR_STEM + yearDiff) % 10;
|
|
const branchIndex = (BASE_YEAR_BRANCH + yearDiff) % 12;
|
|
|
|
return {
|
|
stem: HEAVENLY_STEMS[stemIndex],
|
|
branch: EARTHLY_BRANCHES[branchIndex],
|
|
stemKr: HEAVENLY_STEMS_KR[stemIndex],
|
|
branchKr: EARTHLY_BRANCHES_KR[branchIndex]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 월의 간지를 계산 (절기 기준)
|
|
*/
|
|
export function getMonthGanzi(year: number, month: number, day: number): { stem: string; branch: string; stemKr: string; branchKr: string } {
|
|
// 절기 기준으로 월 지지 계산
|
|
const { getSolarTermMonthBranch } = require('./solar-terms');
|
|
const branchIndex = getSolarTermMonthBranch(year, month, day);
|
|
|
|
// 월 천간 계산 (년간에 따라 달라짐)
|
|
const yearStem = getYearGanzi(year).stem;
|
|
const yearStemIndex = HEAVENLY_STEMS.indexOf(yearStem as any);
|
|
|
|
// 월 천간 공식: (년간 * 2 + 월지지) % 10
|
|
const stemIndex = (yearStemIndex * 2 + branchIndex) % 10;
|
|
|
|
return {
|
|
stem: HEAVENLY_STEMS[stemIndex],
|
|
branch: EARTHLY_BRANCHES[branchIndex],
|
|
stemKr: HEAVENLY_STEMS_KR[stemIndex],
|
|
branchKr: EARTHLY_BRANCHES_KR[branchIndex]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 일의 간지를 계산 (만세력 기준)
|
|
*/
|
|
export function getDayGanzi(year: number, month: number, day: number): { stem: string; branch: string; stemKr: string; branchKr: string } {
|
|
// 기준일 (1900-01-01) 부터의 일수 계산
|
|
const baseDate = new Date(1900, 0, 1);
|
|
const targetDate = new Date(year, month - 1, day);
|
|
const daysDiff = Math.floor((targetDate.getTime() - baseDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
|
// 1900-01-01 = 丙寅일
|
|
const baseDayStem = 2; // 丙
|
|
const baseDayBranch = 2; // 寅
|
|
|
|
const stemIndex = (baseDayStem + daysDiff) % 10;
|
|
const branchIndex = (baseDayBranch + daysDiff) % 12;
|
|
|
|
return {
|
|
stem: HEAVENLY_STEMS[stemIndex < 0 ? stemIndex + 10 : stemIndex],
|
|
branch: EARTHLY_BRANCHES[branchIndex < 0 ? branchIndex + 12 : branchIndex],
|
|
stemKr: HEAVENLY_STEMS_KR[stemIndex < 0 ? stemIndex + 10 : stemIndex],
|
|
branchKr: EARTHLY_BRANCHES_KR[branchIndex < 0 ? branchIndex + 12 : branchIndex]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 시의 간지를 계산
|
|
*/
|
|
export function getHourGanzi(dayGanzi: { stem: string }, hour: number): { stem: string; branch: string; stemKr: string; branchKr: string } {
|
|
// 시 지지: 자시(23-01)=0, 축시(01-03)=1, ...
|
|
let branchIndex: number;
|
|
|
|
if (hour >= 23 || hour < 1) branchIndex = 0; // 子
|
|
else if (hour >= 1 && hour < 3) branchIndex = 1; // 丑
|
|
else if (hour >= 3 && hour < 5) branchIndex = 2; // 寅
|
|
else if (hour >= 5 && hour < 7) branchIndex = 3; // 卯
|
|
else if (hour >= 7 && hour < 9) branchIndex = 4; // 辰
|
|
else if (hour >= 9 && hour < 11) branchIndex = 5; // 巳
|
|
else if (hour >= 11 && hour < 13) branchIndex = 6; // 午
|
|
else if (hour >= 13 && hour < 15) branchIndex = 7; // 未
|
|
else if (hour >= 15 && hour < 17) branchIndex = 8; // 申
|
|
else if (hour >= 17 && hour < 19) branchIndex = 9; // 酉
|
|
else if (hour >= 19 && hour < 21) branchIndex = 10; // 戌
|
|
else branchIndex = 11; // 亥
|
|
|
|
// 시 천간 계산 (일간에 따라 달라짐)
|
|
const dayStemIndex = HEAVENLY_STEMS.indexOf(dayGanzi.stem as any);
|
|
const stemIndex = (dayStemIndex * 2 + branchIndex) % 10;
|
|
|
|
return {
|
|
stem: HEAVENLY_STEMS[stemIndex],
|
|
branch: EARTHLY_BRANCHES[branchIndex],
|
|
stemKr: HEAVENLY_STEMS_KR[stemIndex],
|
|
branchKr: EARTHLY_BRANCHES_KR[branchIndex]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 십성 계산
|
|
*/
|
|
export function getTenGod(dayStem: string, targetStem: string, isYang: boolean): string {
|
|
const dayElement = FIVE_ELEMENTS[dayStem as keyof typeof FIVE_ELEMENTS];
|
|
const targetElement = FIVE_ELEMENTS[targetStem as keyof typeof FIVE_ELEMENTS];
|
|
|
|
// 같은 오행
|
|
if (dayElement === targetElement) {
|
|
return isYang ? '비견' : '겁재';
|
|
}
|
|
|
|
// 오행 상생/상극 관계 확인
|
|
const produceMap: { [key: string]: string } = {
|
|
'木': '火', '火': '土', '土': '金', '金': '水', '水': '木'
|
|
};
|
|
const overcomeMap: { [key: string]: string } = {
|
|
'木': '土', '火': '金', '土': '水', '金': '木', '水': '火'
|
|
};
|
|
|
|
// 내가 생하는 오행
|
|
if (produceMap[dayElement] === targetElement) {
|
|
return isYang ? '식신' : '상관';
|
|
}
|
|
|
|
// 내가 극하는 오행
|
|
if (overcomeMap[dayElement] === targetElement) {
|
|
return isYang ? '편재' : '정재';
|
|
}
|
|
|
|
// 나를 극하는 오행
|
|
if (overcomeMap[targetElement] === dayElement) {
|
|
return isYang ? '편관' : '정관';
|
|
}
|
|
|
|
// 나를 생하는 오행
|
|
if (produceMap[targetElement] === dayElement) {
|
|
return isYang ? '편인' : '정인';
|
|
}
|
|
|
|
return '비견';
|
|
}
|
|
|
|
/**
|
|
* 십이운성 계산
|
|
*/
|
|
export function getTwelveFortune(dayStem: string, branch: string): string {
|
|
// 간단한 십이운성 계산 (실제로는 더 복잡함)
|
|
const fortuneMap: { [key: string]: { [key: string]: number } } = {
|
|
'甲': { '亥': 11, '子': 0, '丑': 1, '寅': 2, '卯': 3, '辰': 4, '巳': 5, '午': 6, '未': 7, '申': 8, '酉': 9, '戌': 10 },
|
|
'乙': { '午': 11, '未': 0, '申': 1, '酉': 2, '戌': 3, '亥': 4, '子': 5, '丑': 6, '寅': 7, '卯': 8, '辰': 9, '巳': 10 },
|
|
'丙': { '寅': 11, '卯': 0, '辰': 1, '巳': 2, '午': 3, '未': 4, '申': 5, '酉': 6, '戌': 7, '亥': 8, '子': 9, '丑': 10 },
|
|
'丁': { '酉': 11, '戌': 0, '亥': 1, '子': 2, '丑': 3, '寅': 4, '卯': 5, '辰': 6, '巳': 7, '午': 8, '未': 9, '申': 10 },
|
|
'戊': { '寅': 11, '卯': 0, '辰': 1, '巳': 2, '午': 3, '未': 4, '申': 5, '酉': 6, '戌': 7, '亥': 8, '子': 9, '丑': 10 },
|
|
'己': { '酉': 11, '戌': 0, '亥': 1, '子': 2, '丑': 3, '寅': 4, '卯': 5, '辰': 6, '巳': 7, '午': 8, '未': 9, '申': 10 },
|
|
'庚': { '巳': 11, '午': 0, '未': 1, '申': 2, '酉': 3, '戌': 4, '亥': 5, '子': 6, '丑': 7, '寅': 8, '卯': 9, '辰': 10 },
|
|
'辛': { '子': 11, '丑': 0, '寅': 1, '卯': 2, '辰': 3, '巳': 4, '午': 5, '未': 6, '申': 7, '酉': 8, '戌': 9, '亥': 10 },
|
|
'壬': { '申': 11, '酉': 0, '戌': 1, '亥': 2, '子': 3, '丑': 4, '寅': 5, '卯': 6, '辰': 7, '巳': 8, '午': 9, '未': 10 },
|
|
'癸': { '卯': 11, '辰': 0, '巳': 1, '午': 2, '未': 3, '申': 4, '酉': 5, '戌': 6, '亥': 7, '子': 8, '丑': 9, '寅': 10 }
|
|
};
|
|
|
|
const index = fortuneMap[dayStem as keyof typeof fortuneMap]?.[branch as keyof typeof fortuneMap['甲']] ?? 0;
|
|
return TWELVE_FORTUNES[index];
|
|
}
|
|
|
|
/**
|
|
* 사주팔자 전체 계산
|
|
*/
|
|
export interface SajuData {
|
|
year: { stem: string; branch: string; stemKr: string; branchKr: string; element: string; tenGod: string; fortune: string };
|
|
month: { stem: string; branch: string; stemKr: string; branchKr: string; element: string; tenGod: string; fortune: string };
|
|
day: { stem: string; branch: string; stemKr: string; branchKr: string; element: string; tenGod: string; fortune: string };
|
|
hour?: { stem: string; branch: string; stemKr: string; branchKr: string; element: string; tenGod: string; fortune: string };
|
|
dayStem: string;
|
|
birthDate: { year: number; month: number; day: number; hour?: number };
|
|
gender: 'male' | 'female';
|
|
}
|
|
|
|
export function calculateSaju(
|
|
year: number,
|
|
month: number,
|
|
day: number,
|
|
hour: number | null,
|
|
gender: 'male' | 'female'
|
|
): SajuData {
|
|
const yearGanzi = getYearGanzi(year);
|
|
const monthGanzi = getMonthGanzi(year, month, day);
|
|
const dayGanzi = getDayGanzi(year, month, day);
|
|
const hourGanzi = hour !== null ? getHourGanzi(dayGanzi, hour) : null;
|
|
|
|
const dayStem = dayGanzi.stem;
|
|
const dayStemIndex = HEAVENLY_STEMS.indexOf(dayStem as any);
|
|
const isDayYang = dayStemIndex % 2 === 0;
|
|
|
|
const result: SajuData = {
|
|
year: {
|
|
...yearGanzi,
|
|
element: FIVE_ELEMENTS[yearGanzi.stem as keyof typeof FIVE_ELEMENTS],
|
|
tenGod: getTenGod(dayStem, yearGanzi.stem, (HEAVENLY_STEMS.indexOf(yearGanzi.stem as any) % 2 === 0) === isDayYang),
|
|
fortune: getTwelveFortune(dayStem, yearGanzi.branch)
|
|
},
|
|
month: {
|
|
...monthGanzi,
|
|
element: FIVE_ELEMENTS[monthGanzi.stem as keyof typeof FIVE_ELEMENTS],
|
|
tenGod: getTenGod(dayStem, monthGanzi.stem, (HEAVENLY_STEMS.indexOf(monthGanzi.stem as any) % 2 === 0) === isDayYang),
|
|
fortune: getTwelveFortune(dayStem, monthGanzi.branch)
|
|
},
|
|
day: {
|
|
...dayGanzi,
|
|
element: FIVE_ELEMENTS[dayGanzi.stem as keyof typeof FIVE_ELEMENTS],
|
|
tenGod: '일간',
|
|
fortune: getTwelveFortune(dayStem, dayGanzi.branch)
|
|
},
|
|
dayStem,
|
|
birthDate: { year, month, day, hour: hour ?? undefined },
|
|
gender
|
|
};
|
|
|
|
if (hourGanzi) {
|
|
result.hour = {
|
|
...hourGanzi,
|
|
element: FIVE_ELEMENTS[hourGanzi.stem as keyof typeof FIVE_ELEMENTS],
|
|
tenGod: getTenGod(dayStem, hourGanzi.stem, (HEAVENLY_STEMS.indexOf(hourGanzi.stem as any) % 2 === 0) === isDayYang),
|
|
fortune: getTwelveFortune(dayStem, hourGanzi.branch)
|
|
};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ============================================================
|
|
// 지장간 (藏干) - 각 지지에 숨어있는 천간
|
|
// ============================================================
|
|
export const HIDDEN_STEMS: { [key: string]: string[] } = {
|
|
'子': ['癸'],
|
|
'丑': ['己', '癸', '辛'],
|
|
'寅': ['甲', '丙', '戊'],
|
|
'卯': ['乙'],
|
|
'辰': ['戊', '乙', '癸'],
|
|
'巳': ['丙', '庚', '戊'],
|
|
'午': ['丁', '己'],
|
|
'未': ['己', '丁', '乙'],
|
|
'申': ['庚', '壬', '戊'],
|
|
'酉': ['辛'],
|
|
'戌': ['戊', '辛', '丁'],
|
|
'亥': ['壬', '甲'],
|
|
};
|
|
|
|
/**
|
|
* 지지의 지장간(숨은 천간) 반환
|
|
*/
|
|
export function getHiddenStems(branch: string): string[] {
|
|
return HIDDEN_STEMS[branch] || [];
|
|
}
|
|
|
|
/**
|
|
* 4주 전체의 지장간 정보 반환
|
|
*/
|
|
export function getAllHiddenStems(saju: SajuData): { pillar: string; branch: string; branchKr: string; stems: { stem: string; stemKr: string; element: string; role: string }[] }[] {
|
|
const pillars = [
|
|
{ pillar: '년주', branch: saju.year.branch, branchKr: saju.year.branchKr },
|
|
{ pillar: '월주', branch: saju.month.branch, branchKr: saju.month.branchKr },
|
|
{ pillar: '일주', branch: saju.day.branch, branchKr: saju.day.branchKr },
|
|
];
|
|
if (saju.hour) {
|
|
pillars.push({ pillar: '시주', branch: saju.hour.branch, branchKr: saju.hour.branchKr });
|
|
}
|
|
|
|
return pillars.map(p => {
|
|
const hidden = getHiddenStems(p.branch);
|
|
return {
|
|
...p,
|
|
stems: hidden.map((stem, idx) => {
|
|
const stemIndex = HEAVENLY_STEMS.indexOf(stem as any);
|
|
const role = idx === 0 ? '정기(본기)' : idx === 1 ? '중기' : '여기';
|
|
return {
|
|
stem,
|
|
stemKr: HEAVENLY_STEMS_KR[stemIndex],
|
|
element: FIVE_ELEMENTS[stem as keyof typeof FIVE_ELEMENTS],
|
|
role,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// 지지 상호작용 (合/沖/刑/破/害)
|
|
// ============================================================
|
|
|
|
export interface BranchInteraction {
|
|
type: string; // 육합, 삼합, 방합, 충, 형, 파, 해
|
|
branches: string[]; // 관련 지지 (한자)
|
|
branchesKr: string[]; // 관련 지지 (한글)
|
|
pillars: string[]; // 관련 기둥 (년주, 월주 등)
|
|
description: string;
|
|
resultElement?: string; // 합의 결과 오행 (해당 시)
|
|
}
|
|
|
|
const YUKAP_PAIRS: [string, string, string][] = [
|
|
['子', '丑', '土'], ['寅', '亥', '木'], ['卯', '戌', '火'],
|
|
['辰', '酉', '金'], ['巳', '申', '水'], ['午', '未', '火'],
|
|
];
|
|
|
|
const SAMHAP_GROUPS: [string, string, string, string][] = [
|
|
['申', '子', '辰', '水'], ['亥', '卯', '未', '木'],
|
|
['寅', '午', '戌', '火'], ['巳', '酉', '丑', '金'],
|
|
];
|
|
|
|
const BANGHAP_GROUPS: [string, string, string, string][] = [
|
|
['寅', '卯', '辰', '木'], ['巳', '午', '未', '火'],
|
|
['申', '酉', '戌', '金'], ['亥', '子', '丑', '水'],
|
|
];
|
|
|
|
const CHUNG_PAIRS: [string, string][] = [
|
|
['子', '午'], ['丑', '未'], ['寅', '申'],
|
|
['卯', '酉'], ['辰', '戌'], ['巳', '亥'],
|
|
];
|
|
|
|
const HYUNG_GROUPS: { branches: string[]; name: string }[] = [
|
|
{ branches: ['寅', '巳', '申'], name: '무은지형(無恩之刑)' },
|
|
{ branches: ['丑', '戌', '未'], name: '지세지형(恃勢之刑)' },
|
|
{ branches: ['子', '卯'], name: '무례지형(無禮之刑)' },
|
|
];
|
|
const JAHYUNG_BRANCHES = ['辰', '午', '酉', '亥'];
|
|
|
|
const PA_PAIRS: [string, string][] = [
|
|
['子', '酉'], ['丑', '辰'], ['寅', '亥'],
|
|
['卯', '午'], ['巳', '申'], ['未', '戌'],
|
|
];
|
|
|
|
const HAE_PAIRS: [string, string][] = [
|
|
['子', '未'], ['丑', '午'], ['寅', '巳'],
|
|
['卯', '辰'], ['申', '亥'], ['酉', '戌'],
|
|
];
|
|
|
|
const ELEMENT_NAMES_KR: { [key: string]: string } = { '木': '목', '火': '화', '土': '토', '金': '금', '水': '수' };
|
|
|
|
/**
|
|
* 지지 상호작용 분석
|
|
*/
|
|
export function analyzeBranchInteractions(saju: SajuData): BranchInteraction[] {
|
|
const interactions: BranchInteraction[] = [];
|
|
|
|
// 기둥별 지지 수집
|
|
const pillarBranches: { branch: string; pillar: string; branchKr: string }[] = [
|
|
{ branch: saju.year.branch, pillar: '년주', branchKr: saju.year.branchKr },
|
|
{ branch: saju.month.branch, pillar: '월주', branchKr: saju.month.branchKr },
|
|
{ branch: saju.day.branch, pillar: '일주', branchKr: saju.day.branchKr },
|
|
];
|
|
if (saju.hour) {
|
|
pillarBranches.push({ branch: saju.hour.branch, pillar: '시주', branchKr: saju.hour.branchKr });
|
|
}
|
|
|
|
const branches = pillarBranches.map(p => p.branch);
|
|
|
|
// 육합 (六合) 검사
|
|
for (const [a, b, elem] of YUKAP_PAIRS) {
|
|
const idxA = branches.indexOf(a);
|
|
const idxB = branches.indexOf(b);
|
|
if (idxA !== -1 && idxB !== -1) {
|
|
interactions.push({
|
|
type: '육합(六合)',
|
|
branches: [a, b],
|
|
branchesKr: [pillarBranches[idxA].branchKr, pillarBranches[idxB].branchKr],
|
|
pillars: [pillarBranches[idxA].pillar, pillarBranches[idxB].pillar],
|
|
description: `${pillarBranches[idxA].branchKr}${pillarBranches[idxB].branchKr} 육합 → ${ELEMENT_NAMES_KR[elem]}(${elem}) 기운 생성. 조화와 화합의 관계.`,
|
|
resultElement: elem,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 삼합 (三合) 검사
|
|
for (const [a, b, c, elem] of SAMHAP_GROUPS) {
|
|
const found = [a, b, c].filter(x => branches.includes(x));
|
|
if (found.length >= 2) {
|
|
const foundPillars = found.map(x => {
|
|
const idx = branches.indexOf(x);
|
|
return pillarBranches[idx];
|
|
});
|
|
const isComplete = found.length === 3;
|
|
interactions.push({
|
|
type: isComplete ? '삼합(三合)' : '반삼합(半三合)',
|
|
branches: found,
|
|
branchesKr: foundPillars.map(p => p.branchKr),
|
|
pillars: foundPillars.map(p => p.pillar),
|
|
description: `${foundPillars.map(p => p.branchKr).join('')} ${isComplete ? '삼합' : '반삼합'} → ${ELEMENT_NAMES_KR[elem]}(${elem})국. ${isComplete ? '강력한 합의 기운.' : '삼합의 기운이 부분적으로 작용.'}`,
|
|
resultElement: elem,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 방합 (方合) 검사
|
|
for (const [a, b, c, elem] of BANGHAP_GROUPS) {
|
|
const found = [a, b, c].filter(x => branches.includes(x));
|
|
if (found.length === 3) {
|
|
const foundPillars = found.map(x => {
|
|
const idx = branches.indexOf(x);
|
|
return pillarBranches[idx];
|
|
});
|
|
interactions.push({
|
|
type: '방합(方合)',
|
|
branches: found,
|
|
branchesKr: foundPillars.map(p => p.branchKr),
|
|
pillars: foundPillars.map(p => p.pillar),
|
|
description: `${foundPillars.map(p => p.branchKr).join('')} 방합 → ${ELEMENT_NAMES_KR[elem]}(${elem}) 방국. 매우 강한 오행 기운.`,
|
|
resultElement: elem,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 충 (沖) 검사
|
|
for (const [a, b] of CHUNG_PAIRS) {
|
|
const idxA = branches.indexOf(a);
|
|
const idxB = branches.indexOf(b);
|
|
if (idxA !== -1 && idxB !== -1) {
|
|
interactions.push({
|
|
type: '충(沖)',
|
|
branches: [a, b],
|
|
branchesKr: [pillarBranches[idxA].branchKr, pillarBranches[idxB].branchKr],
|
|
pillars: [pillarBranches[idxA].pillar, pillarBranches[idxB].pillar],
|
|
description: `${pillarBranches[idxA].branchKr}${pillarBranches[idxB].branchKr} 충 → 변동, 갈등, 변화의 에너지. ${pillarBranches[idxA].pillar}와 ${pillarBranches[idxB].pillar} 사이의 긴장 관계.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 형 (刑) 검사
|
|
for (const group of HYUNG_GROUPS) {
|
|
const found = group.branches.filter(x => branches.includes(x));
|
|
if (found.length >= 2) {
|
|
const foundPillars = found.map(x => {
|
|
const idx = branches.indexOf(x);
|
|
return pillarBranches[idx];
|
|
});
|
|
interactions.push({
|
|
type: '형(刑)',
|
|
branches: found,
|
|
branchesKr: foundPillars.map(p => p.branchKr),
|
|
pillars: foundPillars.map(p => p.pillar),
|
|
description: `${foundPillars.map(p => p.branchKr).join('')} ${group.name} → 시련과 갈등의 기운. 주의가 필요한 관계.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 자형 (自刑) 검사
|
|
for (const jb of JAHYUNG_BRANCHES) {
|
|
const count = branches.filter(x => x === jb).length;
|
|
if (count >= 2) {
|
|
const brKr = EARTHLY_BRANCHES_KR[EARTHLY_BRANCHES.indexOf(jb as any)];
|
|
interactions.push({
|
|
type: '자형(自刑)',
|
|
branches: [jb, jb],
|
|
branchesKr: [brKr, brKr],
|
|
pillars: pillarBranches.filter(p => p.branch === jb).map(p => p.pillar),
|
|
description: `${brKr}${brKr} 자형 → 자기 자신과의 갈등, 내면의 갈등 기운.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 파 (破) 검사
|
|
for (const [a, b] of PA_PAIRS) {
|
|
const idxA = branches.indexOf(a);
|
|
const idxB = branches.indexOf(b);
|
|
if (idxA !== -1 && idxB !== -1) {
|
|
interactions.push({
|
|
type: '파(破)',
|
|
branches: [a, b],
|
|
branchesKr: [pillarBranches[idxA].branchKr, pillarBranches[idxB].branchKr],
|
|
pillars: [pillarBranches[idxA].pillar, pillarBranches[idxB].pillar],
|
|
description: `${pillarBranches[idxA].branchKr}${pillarBranches[idxB].branchKr} 파 → 관계의 균열, 계획의 차질 가능성.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 해 (害) 검사
|
|
for (const [a, b] of HAE_PAIRS) {
|
|
const idxA = branches.indexOf(a);
|
|
const idxB = branches.indexOf(b);
|
|
if (idxA !== -1 && idxB !== -1) {
|
|
interactions.push({
|
|
type: '해(害)',
|
|
branches: [a, b],
|
|
branchesKr: [pillarBranches[idxA].branchKr, pillarBranches[idxB].branchKr],
|
|
pillars: [pillarBranches[idxA].pillar, pillarBranches[idxB].pillar],
|
|
description: `${pillarBranches[idxA].branchKr}${pillarBranches[idxB].branchKr} 해 → 은근한 방해, 원망의 기운.`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return interactions;
|
|
}
|
|
|
|
// ============================================================
|
|
// 신살 (神煞) 계산
|
|
// ============================================================
|
|
|
|
export interface Shinsal {
|
|
name: string;
|
|
nameHanja: string;
|
|
branch: string;
|
|
branchKr: string;
|
|
pillar: string;
|
|
description: string;
|
|
}
|
|
|
|
// 일지 삼합국 기준 신살 매핑
|
|
const SAMHAP_GROUP_MAP: { [key: string]: string } = {
|
|
'申': '申子辰', '子': '申子辰', '辰': '申子辰',
|
|
'寅': '寅午戌', '午': '寅午戌', '戌': '寅午戌',
|
|
'巳': '巳酉丑', '酉': '巳酉丑', '丑': '巳酉丑',
|
|
'亥': '亥卯未', '卯': '亥卯未', '未': '亥卯未',
|
|
};
|
|
|
|
const YEOKMA_MAP: { [key: string]: string } = {
|
|
'申子辰': '寅', '寅午戌': '申', '巳酉丑': '亥', '亥卯未': '巳',
|
|
};
|
|
const DOHWA_MAP: { [key: string]: string } = {
|
|
'申子辰': '酉', '寅午戌': '卯', '巳酉丑': '午', '亥卯未': '子',
|
|
};
|
|
const HWAGAE_MAP: { [key: string]: string } = {
|
|
'申子辰': '辰', '寅午戌': '戌', '巳酉丑': '丑', '亥卯未': '未',
|
|
};
|
|
|
|
// 천을귀인 (天乙貴人) - 일간 기준
|
|
const CHEONUL_MAP: { [key: string]: string[] } = {
|
|
'甲': ['丑', '未'], '乙': ['子', '申'], '丙': ['亥', '酉'], '丁': ['亥', '酉'],
|
|
'戊': ['丑', '未'], '己': ['子', '申'], '庚': ['丑', '未'], '辛': ['寅', '午'],
|
|
'壬': ['卯', '巳'], '癸': ['卯', '巳'],
|
|
};
|
|
|
|
// 문창귀인 (文昌貴人) - 일간 기준
|
|
const MUNCHANG_MAP: { [key: string]: string } = {
|
|
'甲': '巳', '乙': '午', '丙': '申', '丁': '酉',
|
|
'戊': '申', '己': '酉', '庚': '亥', '辛': '子',
|
|
'壬': '寅', '癸': '卯',
|
|
};
|
|
|
|
// 천덕귀인 (天德貴人) - 월지 기준
|
|
const CHEONDUK_MAP: { [key: string]: string } = {
|
|
'寅': '丁', '卯': '申', '辰': '壬', '巳': '辛',
|
|
'午': '亥', '未': '甲', '申': '癸', '酉': '寅',
|
|
'戌': '丙', '亥': '乙', '子': '巳', '丑': '庚',
|
|
};
|
|
|
|
/**
|
|
* 신살 계산
|
|
*/
|
|
export function calculateShinsal(saju: SajuData): Shinsal[] {
|
|
const result: Shinsal[] = [];
|
|
const dayBranch = saju.day.branch;
|
|
const dayStem = saju.dayStem;
|
|
const monthBranch = saju.month.branch;
|
|
|
|
// 4주의 지지 수집
|
|
const pillarBranches: { branch: string; branchKr: string; pillar: string }[] = [
|
|
{ branch: saju.year.branch, branchKr: saju.year.branchKr, pillar: '년주' },
|
|
{ branch: saju.month.branch, branchKr: saju.month.branchKr, pillar: '월주' },
|
|
{ branch: saju.day.branch, branchKr: saju.day.branchKr, pillar: '일주' },
|
|
];
|
|
if (saju.hour) {
|
|
pillarBranches.push({ branch: saju.hour.branch, branchKr: saju.hour.branchKr, pillar: '시주' });
|
|
}
|
|
|
|
const group = SAMHAP_GROUP_MAP[dayBranch];
|
|
|
|
// 역마살
|
|
if (group) {
|
|
const yeokma = YEOKMA_MAP[group];
|
|
for (const pb of pillarBranches) {
|
|
if (pb.branch === yeokma && pb.pillar !== '일주') {
|
|
result.push({
|
|
name: '역마살', nameHanja: '驛馬殺', branch: yeokma,
|
|
branchKr: pb.branchKr, pillar: pb.pillar,
|
|
description: '이동, 변동, 해외, 출장이 많은 기운. 활동적이고 한 곳에 머물지 못하는 성향.',
|
|
});
|
|
}
|
|
}
|
|
|
|
// 도화살
|
|
const dohwa = DOHWA_MAP[group];
|
|
for (const pb of pillarBranches) {
|
|
if (pb.branch === dohwa && pb.pillar !== '일주') {
|
|
result.push({
|
|
name: '도화살', nameHanja: '桃花殺', branch: dohwa,
|
|
branchKr: pb.branchKr, pillar: pb.pillar,
|
|
description: '매력, 인기, 예술적 감각. 이성에게 끌리는 기운이 강하며 대인관계가 화려함.',
|
|
});
|
|
}
|
|
}
|
|
|
|
// 화개살
|
|
const hwagae = HWAGAE_MAP[group];
|
|
for (const pb of pillarBranches) {
|
|
if (pb.branch === hwagae && pb.pillar !== '일주') {
|
|
result.push({
|
|
name: '화개살', nameHanja: '華蓋殺', branch: hwagae,
|
|
branchKr: pb.branchKr, pillar: pb.pillar,
|
|
description: '학문, 종교, 예술에 심취하는 기운. 고독을 즐기며 정신적 세계에 몰두하는 성향.',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 천을귀인
|
|
const cheonulBranches = CHEONUL_MAP[dayStem] || [];
|
|
for (const pb of pillarBranches) {
|
|
if (cheonulBranches.includes(pb.branch) && pb.pillar !== '일주') {
|
|
result.push({
|
|
name: '천을귀인', nameHanja: '天乙貴人', branch: pb.branch,
|
|
branchKr: pb.branchKr, pillar: pb.pillar,
|
|
description: '위기에서 귀인의 도움을 받는 길한 기운. 어려울 때 도움을 주는 사람이 나타남.',
|
|
});
|
|
}
|
|
}
|
|
|
|
// 문창귀인
|
|
const munchangBranch = MUNCHANG_MAP[dayStem];
|
|
if (munchangBranch) {
|
|
for (const pb of pillarBranches) {
|
|
if (pb.branch === munchangBranch && pb.pillar !== '일주') {
|
|
result.push({
|
|
name: '문창귀인', nameHanja: '文昌貴人', branch: pb.branch,
|
|
branchKr: pb.branchKr, pillar: pb.pillar,
|
|
description: '학문, 시험, 문서에 유리한 기운. 공부를 잘하며 시험운이 좋음.',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 천덕귀인 (월지 기준, 천간에서 확인)
|
|
const cheondukStem = CHEONDUK_MAP[monthBranch];
|
|
if (cheondukStem) {
|
|
const allStems = [
|
|
{ stem: saju.year.stem, pillar: '년주' },
|
|
{ stem: saju.day.stem, pillar: '일주' },
|
|
];
|
|
if (saju.hour) allStems.push({ stem: saju.hour.stem, pillar: '시주' });
|
|
for (const ps of allStems) {
|
|
if (ps.stem === cheondukStem) {
|
|
const stemIdx = HEAVENLY_STEMS.indexOf(ps.stem as any);
|
|
result.push({
|
|
name: '천덕귀인', nameHanja: '天德貴人', branch: monthBranch,
|
|
branchKr: EARTHLY_BRANCHES_KR[EARTHLY_BRANCHES.indexOf(monthBranch as any)],
|
|
pillar: ps.pillar,
|
|
description: '하늘의 덕을 받는 기운. 재난을 피하고 복을 받는 길신 중의 길신.',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ============================================================
|
|
// 공망 (空亡) 계산
|
|
// ============================================================
|
|
|
|
/**
|
|
* 60갑자에서 일주의 순(旬)을 찾아 공망 지지 2개를 반환
|
|
*/
|
|
export function calculateGongmang(dayStem: string, dayBranch: string): { branches: string[]; branchesKr: string[]; description: string } {
|
|
const stemIdx = HEAVENLY_STEMS.indexOf(dayStem as any);
|
|
const branchIdx = EARTHLY_BRANCHES.indexOf(dayBranch as any);
|
|
|
|
// 60갑자에서 해당 순(旬)의 시작점 = 천간이 甲인 지점
|
|
// 순의 시작 지지 인덱스 = (branchIdx - stemIdx + 12) % 12
|
|
const startBranchIdx = (branchIdx - stemIdx + 120) % 12;
|
|
|
|
// 공망 = 순에 포함되지 않는 2개의 지지
|
|
// 순은 10개의 간지 → 10개의 지지 사용, 2개가 남음
|
|
const gongmang1Idx = (startBranchIdx + 10) % 12;
|
|
const gongmang2Idx = (startBranchIdx + 11) % 12;
|
|
|
|
const branch1 = EARTHLY_BRANCHES[gongmang1Idx];
|
|
const branch2 = EARTHLY_BRANCHES[gongmang2Idx];
|
|
const branchKr1 = EARTHLY_BRANCHES_KR[gongmang1Idx];
|
|
const branchKr2 = EARTHLY_BRANCHES_KR[gongmang2Idx];
|
|
|
|
return {
|
|
branches: [branch1, branch2],
|
|
branchesKr: [branchKr1, branchKr2],
|
|
description: `${branchKr1}(${branch1})·${branchKr2}(${branch2}) 공망 → 해당 지지의 기운이 비어있어 허무하거나 집착이 없는 영역. 오히려 초월적 능력이 될 수 있음.`,
|
|
};
|
|
}
|