FateForge / fateforge /divination.py
FateForge
Deploy to HF Spaces: Vue SPA + FastAPI, Docker SDK, LLM offline fallback
c9e11de
Raw
History Blame Contribute Delete
5.17 kB
"""周易六爻起卦算法。
每爻由三枚铜钱决定:
正面 (字, face=3) — 两正一反为多,视为阳
反面 (背, face=2) — 两反一正为多,视为阴
三枚之和: 6(老阴·三背·动) | 7(少阴·两背一字) | 8(少阳·两字一背) | 9(老阳·三字·动)
爻的阴阳:
8(少阳), 9(老阳) → 阳爻
6(老阴), 7(少阴) → 阴爻
动爻 (变爻):
6(老阴) → 变阳, 9(老阳) → 变阴
"""
from __future__ import annotations
import random
from typing import List, Tuple
from .constants import HEXAGRAMS
# ---------------------------------------------------------------------------
# 基础枚举
# ---------------------------------------------------------------------------
LAO_YIN = 6 # 老阴 ━ ━ 动 (三背, 全反面)
SHAO_YANG = 8 # 少阳 ━━━ (两正一反 = 二字一背 = face3+face3+face2)
SHAO_YIN = 7 # 少阴 ━ ━ (两反一正 = 二背一字 = face2+face2+face3)
LAO_YANG = 9 # 老阳 ━━━ 动 (三字, 全正面)
YANG_LINES = (SHAO_YANG, LAO_YANG)
YIN_LINES = (LAO_YIN, SHAO_YIN)
MOVING_LINES = (LAO_YIN, LAO_YANG)
def cast_one_line() -> Tuple[int, Tuple[int, int, int]]:
"""起一爻: 随机三枚铜钱,返回 (爻值, (c1, c2, c3))。
钱币值: 字=2, 背=3
"""
coins = (random.randint(2, 3), random.randint(2, 3), random.randint(2, 3))
return sum(coins), coins
def cast_six_lines(seed: int | None = None) -> List[int]:
"""连起六爻,自下而上(返回顺序即 [初爻, 二爻, ..., 上爻])。"""
rng = random.Random(seed)
out: List[int] = []
for _ in range(6):
coins = (rng.randint(2, 3), rng.randint(2, 3), rng.randint(2, 3))
out.append(sum(coins))
return out
def is_yang(line: int) -> bool:
return line in YANG_LINES
def is_moving(line: int) -> bool:
return line in MOVING_LINES
# ---------------------------------------------------------------------------
# 卦象查表
# ---------------------------------------------------------------------------
def _to_key(lines: List[int]) -> str:
"""六爻 (自下而上) → '111000' 形式的二进制 key (1=阳 0=阴)。"""
return "".join("1" if is_yang(v) else "0" for v in lines)
def lines_to_hex(lines: List[int]) -> dict:
"""根据六爻查主卦信息。"""
key = _to_key(lines)
return HEXAGRAMS[key]
def get_changed_hex(lines: List[int]) -> dict | None:
"""动爻变卦: 老阳 9→阴, 老阴 6→阳; 无动爻返回 None。"""
has_moving = any(is_moving(v) for v in lines)
if not has_moving:
return None
new_lines = [
(SHAO_YANG if v == LAO_YIN else SHAO_YIN if v == LAO_YANG else v)
for v in lines
]
return lines_to_hex(new_lines)
def moving_indices(lines: List[int]) -> List[int]:
"""返回动爻位置 (1-indexed, 自下而上)。"""
return [i + 1 for i, v in enumerate(lines) if is_moving(v)]
# ---------------------------------------------------------------------------
# 爻画 (前端直接渲染的字符串)
# ---------------------------------------------------------------------------
def line_glyph(line: int) -> dict:
"""返回单爻的可视化信息。"""
yang = is_yang(line)
moving = is_moving(line)
return {
"value": line,
"yang": yang,
"moving": moving,
"mark": "○" if line == LAO_YANG else "×" if line == LAO_YIN else "",
"label": (
"老阳动爻" if line == LAO_YANG
else "老阴动爻" if line == LAO_YIN
else "少阳" if line == SHAO_YANG
else "少阴"
),
}
def all_lines_glyph(lines: List[int]) -> List[dict]:
"""自下而上,前端按数组顺序自上而下渲染。"""
return [line_glyph(v) for v in lines]
# ---------------------------------------------------------------------------
# 凶吉 (7 档详细等级 → 收束为 3 档大分类)
# ---------------------------------------------------------------------------
LEVEL_TO_CATEGORY = {
"大吉": "吉",
"吉": "吉",
"中平偏吉": "吉",
"中平": "平",
"中平偏凶": "凶",
"凶": "凶",
"大凶": "凶",
}
def hex_auspicious_level(hex_info: dict) -> str:
"""从卦名反查 7 档吉凶等级(来自 constants.HEX_LEVELS)。
找不到时,按卦象初判给一个 3 档粗结果再退回 `中平`。
"""
from .constants import HEX_LEVELS
name = hex_info["name"]
info = HEX_LEVELS.get(name)
if info:
return info["level"]
# 兜底:用经典 3 档集合粗分
return LEVEL_TO_CATEGORY.get(hex_auspicious(hex_info), "中平")
def hex_auspicious(hex_info: dict) -> str:
"""根据卦名给出一个粗略的吉凶倾向(3 档:吉/平/凶)。"""
name = hex_info["name"]
bad = {"讼", "否", "剥", "坎", "困", "明夷", "蹇", "小过", "大过", "夬"}
good = {"乾", "泰", "谦", "大有", "随", "晋", "复", "临", "升", "家人",
"益", "鼎", "震", "渐", "中孚", "既济", "需", "同人"}
if name in bad:
return "凶"
if name in good:
return "吉"
return "平"