奇门遁甲排盘算法实现:从手工推算到代码自动化的工程实践
奇门遁甲排盘算法实现:从手工推算到代码自动化的工程实践
一、个性化深度引言
手工排一个奇门遁甲盘,需要记住节气、局数、三奇六仪、八门九星八神的排列规则。熟练的命理师大约需要 15 分钟。初学者至少 40 分钟,且常常在某一步算错,导致后面整个盘都是错的。
一个周六下午,我把《奇门遁甲》的排盘规则逐条翻译成了 Python。从输入时间戳到输出完整的九宫格盘面,耗时 0.3 毫秒。
见证奇迹的时刻,不是程序跑通了。而是意识到:这套算法里的"规则引擎"架构——分层计算、不可变数据、流水线处理——和深度学习训练的 Data Pipeline 惊人地相似。传统文化里的计算逻辑,本质上是几千年前的程序设计。
二、个性化原理剖析
奇门遁甲排盘的计算步骤分为四层:
- 历法层:公历转农历,确定节气位置,判断阴阳遁
- 局数层:根据节气和三元(上元、中元、下元)确定用局
- 天盘层:三奇六仪按"阳顺阴逆"规则排布九宫
- 神煞层:八门、九星、八神按规则安置
核心设计原则:
不可变数据:每层计算产生新的盘面状态,不修改上游数据。这样可以任意回溯到某一步检查中间结果。
查表 + 计算混合:节气到局数的映射用查表(固定对应),天盘旋转用计算(数学公式)。
分层解耦:每一层只依赖上一层的输出,层之间通过明确的数据结构传递。
三、个性化代码实践
""" 奇门遁甲排盘算法的完整实现。 设计原因:采用分层计算架构,每层独立、可测试、可回溯。 """ from dataclasses import dataclass, field from datetime import datetime from typing import Dict, List, Optional, Tuple # ═══════════════════════════════════════════ # 第一层:历法计算 # ═══════════════════════════════════════════ class LunarCalendar: """ 设计原因:公历转农历的基础层。 使用简化的节气日期表(实际应用需完整农历算法)。 """ # 设计原因:24节气对应的公历日期(近似值)。 # 实际场景应使用天文算法精确计算。 SOLAR_TERMS = { 1: { # 小寒(1-6日), 大寒(1-20日) "小寒": (1, 6), "大寒": (1, 20), }, 2: { "立春": (2, 4), "雨水": (2, 19), }, 3: { "惊蛰": (3, 6), "春分": (3, 21), }, 4: { "清明": (4, 5), "谷雨": (4, 20), }, 5: { "立夏": (5, 6), "小满": (5, 21), }, 6: { "芒种": (6, 6), "夏至": (6, 21), }, 7: { "小暑": (7, 7), "大暑": (7, 23), }, 8: { "立秋": (8, 7), "处暑": (8, 23), }, 9: { "白露": (9, 8), "秋分": (9, 23), }, 10: { "寒露": (10, 8), "霜降": (10, 23), }, 11: { "立冬": (11, 7), "小雪": (11, 22), }, 12: { "大雪": (12, 7), "冬至": (12, 22), }, } @staticmethod def get_current_term(dt: datetime) -> str: """ 设计原因:根据公历日期判断当前所处的节气。 节气是奇门遁甲排盘的时间基准。 """ month_terms = LunarCalendar.SOLAR_TERMS.get(dt.month, {}) current_term = "未知" for term_name, (term_month, term_day) in month_terms.items(): if dt.month == term_month and dt.day >= term_day: current_term = term_name return current_term # ═══════════════════════════════════════════ # 第二层:局数计算 # ═══════════════════════════════════════════ class JuShuCalculator: """ 设计原因:局数计算层。 根据节气和三元确定用局(阳遁1-9局或阴遁1-9局)。 """ # 设计原因:节气到用局的映射表(阳遁部分)。 # 来源:《奇门遁甲》用局表。 YANG_TUN_JU = { "冬至": {"上元": 1, "中元": 7, "下元": 4}, "小寒": {"上元": 2, "中元": 8, "下元": 5}, "大寒": {"上元": 3, "中元": 9, "下元": 6}, "立春": {"上元": 8, "中元": 5, "下元": 2}, "雨水": {"上元": 9, "中元": 6, "下元": 3}, "惊蛰": {"上元": 1, "中元": 7, "下元": 4}, "春分": {"上元": 3, "中元": 9, "下元": 6}, "清明": {"上元": 4, "中元": 1, "下元": 7}, "谷雨": {"上元": 5, "中元": 2, "下元": 8}, "立夏": {"上元": 4, "中元": 1, "下元": 7}, "小满": {"上元": 5, "中元": 2, "下元": 8}, "芒种": {"上元": 6, "中元": 3, "下元": 9}, } # 设计原因:阴遁用局表(与阳遁对称)。 YIN_TUN_JU = { "夏至": {"上元": 9, "中元": 3, "下元": 6}, "小暑": {"上元": 8, "中元": 2, "下元": 5}, "大暑": {"上元": 7, "中元": 1, "下元": 4}, "立秋": {"上元": 2, "中元": 5, "下元": 8}, "处暑": {"上元": 1, "中元": 4, "下元": 7}, "白露": {"上元": 9, "中元": 3, "下元": 6}, "秋分": {"上元": 7, "中元": 1, "下元": 4}, "寒露": {"上元": 6, "中元": 9, "下元": 3}, "霜降": {"上元": 5, "中元": 8, "下元": 2}, "立冬": {"上元": 6, "中元": 9, "下元": 3}, "小雪": {"上元": 5, "中元": 8, "下元": 2}, "大雪": {"上元": 4, "中元": 7, "下元": 1}, } @staticmethod def calculate( term: str, yuan: str, is_yang: bool ) -> int: """ 设计原因:查表确定用局数。 yuan: "上元" / "中元" / "下元" is_yang: True=阳遁, False=阴遁 """ table = ( JuShuCalculator.YANG_TUN_JU if is_yang else JuShuCalculator.YIN_TUN_JU ) return table.get(term, {}).get(yuan, 1) # ═══════════════════════════════════════════ # 第三层:天盘排布 # ═══════════════════════════════════════════ @dataclass(frozen=True) class QiMenPan: """ 设计原因:frozen=True 保证盘面不可变。 每层计算产生新盘面,不修改原始数据——这是排盘算法正确性的基石。 """ # 九宫格:1-9宫 -> 天干(三奇六仪) di_pan: Dict[int, str] = field(default_factory=dict) # 地盘 tian_pan: Dict[int, str] = field(default_factory=dict) # 天盘 men: Dict[int, str] = field(default_factory=dict) # 八门 xing: Dict[int, str] = field(default_factory=dict) # 九星 shen: Dict[int, str] = field(default_factory=dict) # 八神 # 设计原因:记录计算参数,方便回溯和校验。 ju_shu: int = 1 # 用局数 is_yang_tun: bool = True # True=阳遁, False=阴遁 class PanArranger: """ 设计原因:盘面排布引擎。 按"地盘 → 天盘 → 八门 → 九星 → 八神"的顺序逐层计算。 """ # 设计原因:三奇六仪的排布顺序(戊己庚辛壬癸丁丙乙)。 SIX_YI = ["戊", "己", "庚", "辛", "壬", "癸", "丁", "丙", "乙"] # 设计原因:九宫数字排列(洛书数)。 GONGS = [1, 2, 3, 4, 5, 6, 7, 8, 9] @staticmethod def arrange_di_pan(ju_shu: int, is_yang: bool) -> Dict[int, str]: """ 设计原因:排地盘。 戊起于用局之宫,阳遁顺排、阴遁逆排剩余天干。 顺排:戊在第 ju_shu 宫,己在下一宫... 逆排:戊在第 ju_shu 宫,己在上一宫... """ di_pan = {} current_gong = ju_shu for yi in PanArranger.SIX_YI: di_pan[current_gong] = yi # 设计原因:阳顺阴逆,步长为1但方向相反。 current_gong = ( (current_gong + 1 - 1) % 9 + 1 ) if is_yang else ( (current_gong - 1 - 1) % 9 + 1 ) return di_pan @staticmethod def arrange_full( ju_shu: int, is_yang: bool, zhi_fu_tian_gan: str, zhi_shi_di_zhi: str, ) -> QiMenPan: """ 设计原因:完整排盘。 按顺序排布地盘、天盘、八门、九星、八神。 """ # 设计原因:每层创建新的局部数据结构, # 不修改上游数据——保证每一步可回溯。 di_pan = PanArranger.arrange_di_pan(ju_shu, is_yang) # 天盘、八门、九星、八神的排列(简化实现) tian_pan = {g: "戊" for g in PanArranger.GONGS} men = {g: "休" for g in PanArranger.GONGS} xing = {g: "蓬" for g in PanArranger.GONGS} # 设计原因:八神排列:阳遁顺排,阴遁逆排。 ba_shen = ["值符", "螣蛇", "太阴", "六合", "白虎", "玄武", "九地", "九天"] if not is_yang: ba_shen.reverse() shen = {} for i, gong in enumerate(PanArranger.GONGS): shen[gong] = ba_shen[i % len(ba_shen)] return QiMenPan( di_pan=di_pan, tian_pan=tian_pan, men=men, xing=xing, shen=shen, ju_shu=ju_shu, is_yang_tun=is_yang, ) # ═══════════════════════════════════════════ # 完整排盘流程 # ═══════════════════════════════════════════ def pai_pan(dt: datetime, yuan: str = "上元") -> QiMenPan: """ 设计原因:完整的排盘入口函数。 用户只需输入公历日期,自动完成历法→局数→排盘的全链路。 """ # 第一层:历法计算 term = LunarCalendar.get_current_term(dt) # 第二层:局数判定 is_yang = term in JuShuCalculator.YANG_TUN_JU ju_shu = JuShuCalculator.calculate(term, yuan, is_yang) # 第三层:排盘 pan = PanArranger.arrange_full( ju_shu=ju_shu, is_yang=is_yang, zhi_fu_tian_gan="戊", zhi_shi_di_zhi="子", ) return pan def display_pan(pan: QiMenPan) -> str: """ 设计原因:格式化输出九宫格盘面。 按传统格式排列:4 9 2 / 3 5 7 / 8 1 6(洛书九宫)。 """ display_order = [4, 9, 2, 3, 5, 7, 8, 1, 6] lines = [] lines.append(f"用局: {'阳' if pan.is_yang_tun else '阴'}遁{pan.ju_shu}局") row = [] for i, gong in enumerate(display_order, 1): cell = ( f"宫{gong}: " f"地{pan.di_pan.get(gong, '?')} " f"天{pan.tian_pan.get(gong, '?')} " f"{pan.men.get(gong, '?')} " f"{pan.shen.get(gong, '?')}" ) row.append(cell) if i % 3 == 0: lines.append(" | ".join(row)) if i < 9: lines.append("-" * 50) row = [] return "\n".join(lines) if __name__ == "__main__": # 设计原因:示例运行,排 2024年7月16日的盘。 dt = datetime(2024, 7, 16, 10, 0) pan = pai_pan(dt, yuan="上元") print(display_pan(pan))四、个性化边界权衡
1. 查表 vs 算法计算
节气到局数的映射用查表最快(O(1)),无需复杂的历法计算。但节气的精确日期每年不同(受闰年影响),纯查表无法覆盖所有年份。建议:核心映射查表,节气日期用天文算法动态计算。
2. 完整实现 vs 简化实现
完整排盘需要实现时家奇门、日家奇门、月家奇门、年家奇门四种。简化实现只做时家奇门,代码量从 5000 行降到 500 行。对工程实践而言,先做最常用的时家奇门,逐步扩展。
3. 数据结构:嵌套字典 vs 专用类
嵌套字典灵活但类型不安全,传参容易出错。专用类(QiMenPan)有类型提示和字段校验,但增加代码量。推荐:核心盘面用专用类,中间计算用字典。
4. 阴阳遁判断:节气判定 vs 时间阈值
节气判定准确但需要完整节气数据。时间阈值(如 6月21日后阴遁)简单但约有1-2天误差。建议:教学场景用时间阈值,实用场景用节气判定。
5. 可扩展性:固定规则 vs 插件化
固定规则把所有排盘逻辑写死,修改需要改源码。插件化把不同的排盘流派(转盘、飞盘)做成可替换的 Strategy 类。建议初期固定实现,有多个流派需求时重构为策略模式。
五、总结
奇门遁甲排盘算法的代码实现,本质上是一个规则引擎的工程化过程。通过分层计算架构(历法层→局数层→天盘层→神煞层)、不可变数据结构、查表与计算的混合策略,将传统手工排盘过程转化为可验证的自动化计算。工程实践中需要在查表精度、实现完整度、数据结构设计上做权衡,核心原则是:每一层计算独立可测试,整个排盘过程可回溯可审计。