Python排盘小工具

458 阅读5分钟

前段时间,闲来无事,就用ChatGPT写了一个PythonQT排盘工具,经过不断测试和优化,终于有了一个雏形,GPT果然NB,coding过程如行云流水般顺畅,感觉挺好玩的。

代码运行后展示效果:

image.png

附上源码:

from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QTextEdit, QDateEdit
from PyQt5.QtCore import QDate
from lunarcalendar import Converter, Solar
from datetime import datetime, timedelta
import swisseph as swe

# 天干和地支的列表
TIANGAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
DIZHI = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]

# 十神表
TEN_GODS = {
    "甲": ["比肩", "劫财", "食神", "伤官", "偏财", "正财", "七杀", "正官", "偏印", "正印"],
    "乙": ["劫财", "比肩", "伤官", "食神", "正财", "偏财", "正官", "七杀", "正印", "偏印"],
    "丙": ["偏印", "正印", "比肩", "劫财", "食神", "伤官", "偏财", "正财", "七杀", "正官"],
    "丁": ["正印", "偏印", "劫财", "比肩", "伤官", "食神", "正财", "偏财", "正官", "七杀"],
    "戊": ["七杀", "正官", "偏印", "正印", "比肩", "劫财", "食神", "伤官", "偏财", "正财"],
    "己": ["正官", "七杀", "正印", "偏印", "劫财", "比肩", "伤官", "食神", "正财", "偏财"],
    "庚": ["偏财", "正财", "七杀", "正官", "偏印", "正印", "比肩", "劫财", "食神", "伤官"],
    "辛": ["正财", "偏财", "正官", "七杀", "正印", "偏印", "劫财", "比肩", "伤官", "食神"],
    "壬": ["食神", "伤官", "偏财", "正财", "七杀", "正官", "偏印", "正印", "比肩", "劫财"],
    "癸": ["伤官", "食神", "正财", "偏财", "正官", "七杀", "正印", "偏印", "劫财", "比肩"]
}

# 藏干表
HIDDEN_STEMS = {
    "子": ["癸"],
    "丑": ["己", "癸", "辛"],
    "寅": ["甲", "丙", "戊"],
    "卯": ["乙"],
    "辰": ["戊", "乙", "癸"],
    "巳": ["丙", "庚", "戊"],
    "午": ["丁", "己"],
    "未": ["己", "丁", "乙"],
    "申": ["庚", "壬", "戊"],
    "酉": ["辛"],
    "戌": ["戊", "辛", "丁"],
    "亥": ["壬", "甲"]
}

def get_tiangandizhi(year, month, day, hour, minute):
    # 动态获取立春时间
    li_chun_time = get_li_chun_time(year)
    print(f"立春时间: {li_chun_time}")

    # 将公历日期转换为农历日期
    solar_date = Solar(year, month, day)
    lunar_date = Converter.Solar2Lunar(solar_date)
    print(f"农历日期: {lunar_date}")

    # 确定年柱
    if datetime(year, month, day, hour, minute) >= li_chun_time:
        solar_year = solar_date.year
    else:
        solar_year = solar_date.year - 1

    year_gan = TIANGAN[(solar_year - 4) % 10]
    year_zhi = DIZHI[(solar_year - 4) % 12]
    
    # 月柱(假设使用正统的月份推算法)
    year_gan_index = (lunar_date.year - 3) % 10
    month_gan = TIANGAN[(year_gan_index * 2 + lunar_date.month - 1) % 10]
    month_zhi = DIZHI[(lunar_date.month + 1) % 12]

    # 日柱(假设使用正统的日干支推算法,通常需要查万年历)
    base_date = datetime(1900, 1, 31).date()
    days_passed = (solar_date.to_date() - base_date).days
    day_gan_index = (days_passed + 40) % 10
    day_gan = TIANGAN[(days_passed + 40) % 10]
    day_zhi = DIZHI[(days_passed + 40) % 12]

    # 时柱
    total_hours = hour + minute / 60.0
    hour_index = int((total_hours + 1) // 2) % 12
    hour_gan_index = (day_gan_index * 2 + hour_index) % 10
    hour_gan = TIANGAN[hour_gan_index]
    hour_zhi = DIZHI[hour_index]

    return {
        "年柱": year_gan + year_zhi,
        "月柱": month_gan + month_zhi,
        "日柱": day_gan + day_zhi,
        "时柱": hour_gan + hour_zhi
    }

def get_stem_yinyang(stem):
    yang_stems = ["甲", "丙", "戊", "庚", "壬"]
    return 1 if stem in yang_stems else 0

def calculate_fortune_years(bazi, is_male):
    birth_year_stem = bazi["年柱"][0]
    birth_month_stem = bazi["月柱"][0]
    birth_month_branch = bazi["月柱"][1]

    stem_yinyang = get_stem_yinyang(birth_year_stem)
    # 只有阳年干男生和阴年干女生,大运顺序排列
    is_forward = (stem_yinyang == 1 and is_male) or (stem_yinyang == 0 and not is_male)

    month_stem_index = TIANGAN.index(birth_month_stem)
    month_branch_index = DIZHI.index(birth_month_branch)

    fortune_years = []
    for i in range(1, 11):
        if is_forward:
            gan_index = (month_stem_index + i) % 10
            zhi_index = (month_branch_index + i) % 12
        else:
            gan_index = (month_stem_index - i) % 10
            zhi_index = (month_branch_index - i) % 12
        fortune_years.append(TIANGAN[gan_index] + DIZHI[zhi_index])

    return fortune_years

def get_ten_god(day_master, stem):
    return TEN_GODS[day_master][TIANGAN.index(stem)]

def get_hidden_stems_and_gods(day_master, branch):
    hidden_stems = HIDDEN_STEMS[branch]
    hidden_gods = [get_ten_god(day_master, stem) for stem in hidden_stems]
    return hidden_stems, hidden_gods

def find_geju(bazi, day_master):
    month_branch = bazi['月柱'][1]
    hidden_stems = HIDDEN_STEMS[month_branch]
    geju = []

    # Check if any hidden stems appear in the heavenly stems
    for hidden_stem in hidden_stems:
        for pillar in ['年柱', '月柱', '日柱', '时柱']:
            tgan = bazi[pillar][0]
            # 比肩、劫财不入格
            if hidden_stem in tgan and (get_ten_god(day_master, hidden_stem) not in ("比肩","劫财")):
                return [get_ten_god(day_master, hidden_stem)]
                # 循环月支所有的藏干是否天干透出 
                # geju.append(get_ten_god(day_master, hidden_stem))

    return geju

def get_li_chun_time(year):
    jd_start = swe.julday(year, 2, 3, 0)  # Start search from February 3rd
    jd_end = swe.julday(year, 2, 5, 0)  # End search on February 5th

    for jd in range(int(jd_start), int(jd_end) + 1):
        for minute in range(1440):  # 1440 minutes in a day
            jd_current = jd + minute / 1440.0  # Increment by minutes
            lon, _ = swe.calc_ut(jd_current, swe.SUN)
            if lon[0] >= 315:  # Check if the longitude is 315 degrees (立春)
                gregorian_date = swe.revjul(jd_current, swe.GREG_CAL)
                year, month, day, hour = gregorian_date[:4]
                minute = int((hour - int(hour)) * 60)
                second = int(((hour - int(hour)) * 60 - minute) * 60)
                return datetime(year, month, day, int(hour), minute, second) + timedelta(hours=8)
    return None  # If no transit found

class BaziCalculator(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        layout = QVBoxLayout()

        date_layout = QHBoxLayout()
        date_label = QLabel('出生日期:')
        self.date_edit = QDateEdit(QDate.currentDate())
        self.date_edit.setCalendarPopup(True)
        date_layout.addWidget(date_label)
        date_layout.addWidget(self.date_edit)

        time_layout = QHBoxLayout()
        hour_label = QLabel('出生时间:')
        self.hour_combo = QComboBox()
        self.hour_combo.addItems([f'{i:02d}' for i in range(24)])
        self.minute_combo = QComboBox()
        self.minute_combo.addItems([f'{i:02d}' for i in range(60)])
        time_layout.addWidget(hour_label)
        time_layout.addWidget(self.hour_combo)
        time_layout.addWidget(self.minute_combo)

        gender_layout = QHBoxLayout()
        gender_label = QLabel('性别:')
        self.gender_combo = QComboBox()
        self.gender_combo.addItems(['男', '女'])
        gender_layout.addWidget(gender_label)
        gender_layout.addWidget(self.gender_combo)

        self.calculate_button = QPushButton('计算')
        self.calculate_button.clicked.connect(self.calculate_bazi)

        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)

        layout.addLayout(date_layout)
        layout.addLayout(time_layout)
        layout.addLayout(gender_layout)
        layout.addWidget(self.calculate_button)
        layout.addWidget(self.result_text)

        self.setLayout(layout)
        self.setWindowTitle('排盘工具')
        self.resize(400, 500)

    def calculate_bazi(self):
        birth_date = self.date_edit.date().toPyDate()
        birth_hour = int(self.hour_combo.currentText())
        birth_minute = int(self.minute_combo.currentText())
        gender = self.gender_combo.currentText()

        bazi = get_tiangandizhi(birth_date.year, birth_date.month, birth_date.day, birth_hour, birth_minute)
        day_master = bazi['日柱'][0]  # 日主

        if gender == '男':
            result = "八字乾造:\n\n"
        else:
            result = "八字坤造:\n\n"

        # Displaying the BaZi in vertical format with Ten Gods and Hidden Stems
        result += "年柱\t月柱\t日柱\t时柱\n\n"
        result += f"{get_ten_god(day_master, bazi['年柱'][0])}\t{get_ten_god(day_master, bazi['月柱'][0])}\t{gender}\t{get_ten_god(day_master, bazi['时柱'][0])}\n"
        result += f"{bazi['年柱'][0]}\t{bazi['月柱'][0]}\t{bazi['日柱'][0]}\t{bazi['时柱'][0]}\n"
        result += f"{bazi['年柱'][1]}\t{bazi['月柱'][1]}\t{bazi['日柱'][1]}\t{bazi['时柱'][1]}\n\n"
        
        # Get hidden stems and gods for each pillar
        hidden_stems_gods = {'年柱': [], '月柱': [], '日柱': [], '时柱': []}
        for pillar in hidden_stems_gods.keys():
            branch = bazi[pillar][1]
            hidden_stems, hidden_gods = get_hidden_stems_and_gods(day_master, branch)
            hidden_stems_gods[pillar] = list(zip(hidden_stems, hidden_gods))

        # Determine the maximum number of hidden stems for proper alignment
        max_hidden = max(len(v) for v in hidden_stems_gods.values())

        for i in range(max_hidden):
            for pillar in ['年柱', '月柱', '日柱', '时柱']:
                if i < len(hidden_stems_gods[pillar]):
                    stem, god = hidden_stems_gods[pillar][i]
                    result += f"{stem} {god}\t"
                else:
                    result += "\t"
            result += "\n"

        result += '\n'
          
        # Find and display 格局
        geju = find_geju(bazi, day_master)
        if geju:
            result += "格局: " + ", ".join(geju) + "\n"

        is_male = (gender == '男')
        fortune_years = calculate_fortune_years(bazi, is_male)
        result += "\n大运:\n"
        for i, fortune in enumerate(fortune_years):
            result += f"第{i*10+1}-{(i+1)*10}年: {fortune}\n"

        self.result_text.setText(result)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = BaziCalculator()
    ex.show()
    sys.exit(app.exec_())