Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

术数排演

一个跑在手表上的小六壬 / 六爻 / 梅花易数排盘工具,纯 HTML + CSS + JS。

在线试:https://banxia-126.github.io/hexagram-lab/ 手机上打开会自动变成窄屏版 (脱掉表壳、全屏铺满),电脑上打开是带 3D 表体的桌面演示。

第二版将原样移植到华为 WATCH FIT 4(Lite Wearable,同样是 HTML/CSS/JS)。

跑起来

不需要安装依赖、不需要联网、不需要构建,但必须用一个本地服务打开:

双击 start.bat

或者自己起一个:

python -m http.server 8777
# 打开 http://localhost:8777/index.html

不能双击 index.html 直接开。 代码用的是 ES module(import/export), 浏览器对 file:// 下的 module 按跨域处理、一律拒绝加载,页面会停在只有表壳、 时间和结果都是空的状态。任何静态服务都可以,不必是 Python。

调试用入口(录屏复现同一卦用):

index.html#liuyao             直接进入六爻
index.html#liuyao:789786      指定六爻爻值(6/7/8/9,初爻在前)
index.html#liuren             直接进入小六壬
index.html#meihua             直接进入梅花易数
index.html#settings           直接进入设置
index.html#sheet:liuyao       直接打开某一页的原理说明

操作:拖动表体 360° 旋转,拖动右侧表冠滚动本页(滚到头就停),点屏幕空白处回正, 页头右上角的圈 i 是这一页的原理与出处。

三个板块各自成页、彼此独立:进去就是从零开始的一局,表冠只在页内滚、不会翻到 别的板块,要换板块先按 ‹ 回首页。六爻点「摇卦」→ 铜钱抖起来 → 再点「落定」出一爻, 一爻两下、六爻共十二下,摇出来的爻自下而上垒着给你看。

设置

每一处流派分歧都给一个开关,默认取主流写法,界面把实际用到的算式原样打出来:

项 默认 另一派
晚子时(23:00–24:00 算哪天) 子时换日 夜子时(仍算当日,标注晚子时)
真太阳时 开 关(直接用民用时)
经度 东经 120° 省会/港澳台 34 城任选,也可手输
额外时差 0 分 ±720 分随意加减
梅花起卦 时间起卦 方法1(一串数字中间劈开)/ 方法2(三个数各定上卦下卦动爻)
六爻筮法 三钱法 大衍筮法
六宫五行 六神本(留连水、小吉木) 流传本(留连土、小吉水)

真太阳时取 民用时 + 经度修正 + 均时差。经度修正是 (经度 − 120) × 4 分钟, 西安 −44 分、乌鲁木齐 −130 分,西部足以把时辰整体挪掉一格,所以这不是锦上添花。 夏令时/冬令时不需要单独的开关:时区偏移是对具体时刻取的,本来就跟着变; 真要手动干预用「额外时差」兜底。

方法1 —— 一串数字,从中间劈开,两段各自「各位相加」。 输入 12 位以内 (再长 Number 就不安全了),从中间分成两段、前段位数 ≤ 后段位数 (12345 → 12 和 345),然后把每段的各位数字加起来得到两个数—— 12 得 1+2=3、345 得 3+4+5=12,不是把 12 和 345 当成两个整数。 前和除以 8 得上卦、后和除以 8 得下卦、两和相加(计时辰时再加时辰)除以 6 得动爻。 位数不足 2 劈不开,不出卦。余 0 一律作 8(坤)。

两种读法出的卦完全不一样:12345 按各位相加是火雷噬嗑(3→离、12→震), 当成两个整数则是天雷无妄(12÷8 余 4→震、345÷8 余 1→乾)。屏上的算式把 1 + 2 这一步原样写出来了,扫一眼就知道是哪种。test.mjs 里钉了这条回归。

方法2 —— 三个数各司其职。 第一个取上卦、第二个取下卦、第三个取动爻。 「计时辰」在这里只作用在动爻上,前两个数不受影响。

两种方法都把算式原样打在屏上(12 ÷ 8 余 4 上卦 震),跟 test.mjs 里的是同一份计算。

「动爻」就是老阴(6)和老阳(9)——一个卦里哪几爻动,决定了要看哪几条爻辞。

三个引擎

算法 代码
小六壬 落宫 =(月 + 日 + 时辰 − 3)mod 6,大安起月、月上起日、日上起时 core/liuren.js
六爻 三钱法或大衍筮法起卦 + 京房八宫装卦(纳甲、六亲、六神、世应、旬空) core/liuyao.js
梅花易数 时间 / 方法1 / 方法2 / 选卦四种起卦法,共用「卦以八除、爻以六除」 core/meihua.js

三个引擎吃的是同一个时刻:先由 core/time.js 把它换算成真太阳时,再推农历、 干支、时辰。农历用 core/vendor/lunar.js(闰月是负数,取 Math.abs())。

大衍筮法不必真去模拟十八变的分揲挂扐——每爻的分布就是 1/16、5/16、7/16、3/16, 按分布直接抽样与之严格同分布,还省掉一大坨易错的蓍草流程。

六爻的八宫归属和世爻是推导出来的,不是查表:本宫 → 一世(变初爻)→ 二世 → 三世 → 四世 → 五世 → 游魂(五世再变四爻)→ 归魂(游魂内卦全变),世爻位置依次是 6,1,2,3,4,5,4,3,应爻取世爻 ±3。64 卦的宫属因此可以自校验。

手动指定

起卦结果不合心意、或者只想看某一卦怎么解的时候,三个板块都能绕过起卦直接指定:

入口 怎么用
六爻 手排 六行自上而下是上爻→初爻,点一行就在老阴→少阳→少阴→老阳里循环,底下「成卦」
小六壬 手定 月 1–12、日 1–30、时辰 子–亥 三个步进器,改一下就重算;「按时」回到当前时刻
梅花易数 选卦 八个上卦、八个下卦,加一个 1–6 的动爻步进器

手动态和自动态互斥,三处都随时切得回来(六爻「回摇卦」、小六壬「按时」、梅花「重报」)。 手排会带着已经摇出来的爻进去,不必从头再点六下。

原文都出自哪

排出来的盘旁边直接附原文,不附现代白话:

  • 六爻结果页——本卦卦辞 + 彖、本卦大象、每一个动爻的爻辞 + 小象、变卦卦辞 + 大象。 六爻真正要看的是动爻爻辞,所以动爻有几条就列几条;六爻皆静时另给提示。
  • 梅花结果页——本卦卦辞 + 彖、本卦大象、动爻爻辞 + 小象、变卦卦辞 + 大象。 (本卦大象是这一版补的,两边到这才算同一套。)
  • 每页右上角的圈 i 里也引同一批原文。

结果页自上而下是三块独立的箱体:卦盘 / 原文 / 摇卦,各区互不挤在一起。

界面上几处讲究

梅花的卦象竖排。 本卦、互卦、变卦各占一箱,每箱里上卦、下卦各占一行, 各带自己的卦名与卦符。横着并排两个符号在 408px 的方屏上既挤、也看不出哪半是上卦。 本卦那两行再挂「体」「用」小标 —— 体用落在哪一半全看上卦下卦。

体用只标不生克,改成取象。 早先按「体克用/用生体」那套断语出一段吉凶,删掉了: 那套断法把六十四卦压成五个标签,读起来比卦本身还硬。现在只留「哪一卦是体、 哪一卦是用」这个定位(本卦那两行挂的小标就是干这个的),紧接着出一箱取象 —— 体卦、用卦各查一次〈八卦萬物屬類〉,把卦还原成具体的东西。

取象的数据是从原文里解出来的,不是手打的。 该节在《梅花易數·卷一》 象數易理篇之三底下,紧挨着〈八卦類象〉;它按八卦分八块,每块一串顿号 连到底的属类(乾 16、兑 13、离 19、震 20、巽 19、坎 25、艮 20、坤 20,共 152 条)。 原文在 https://zh.wikisource.org/zh-hans/梅花易数/卷一,公版;开发机取不到网络, 所以是先从本地那份导出的 PDF 抽文本层,再跑 tools/parse-wanwu.mjs 生成 core/gen/wanwu.js(脚本头部写了复现命令)。卦名用简体(兑/离),属类保持繁体原貌 —— 与 64 卦原文一个规矩,不做繁简转换就不引入转写错误。

⚠ 解析器里有两处必须这么写,不是随手写的: ① 页码要先滤掉,剩下的换行直接接上、不能换空格。 一个属类会被页面截断成两行 (「藤」在页尾、「生之物」在次页页首),中间插了换行就拼不回「藤生之物」,会变成两条残条。 ② 分隔符是顿号加句号两种。 属类之间用「、」,但艮那一块中间还夹了个「。黃色。」, 只按顿号切会把「鼻。黃色」粘成一条。切完还要卡「条数 ≥ 10」「卦内无重复」 「没有残留的标点或数字」——切错位置时表现就是某卦少几条或者串到隔壁卦去, 肉眼扫一遍是看不出来的,所以 test.mjs 第 13 节把这几条连同 「兑 13 条收在婢」「坎 25 条收在黑色」「艮含跨页拼回的藤生之物」一起钉住了。

铜钱是真 3D 翻滚、两面。 正面「乾隆通宝」、背面素面,perspective + preserve-3d

  • backface-visibility,rotateX/rotateY 一起转。落定时三枚停在真掷出来的那一面上: 字(2)、背(3)分开掷、再求和,所以那一面不是装饰,是这一爻值的一半。 (反过来说,若先求和再反推每枚是哪面,是推不出来的。) 大衍筮法那边画的是蓍草,不画铜钱。

天干地支按五行上色。 木青 火赤 土黄 金白 水蓝(水在 OLED 上不能真给黑, 取蓝作替身,排盘软件的通行画法)。干与支各上各的色——纳甲里「甲子」的甲属木、 子属水,本来就不同行,六亲正是由支那一半的五行推出来的。

这五个色是按类别色的规矩挑的,不靠眼睛:跑 dataviz 的验证器校过,暗色带 L 0.48–0.67 全在带内、相邻对最差 deutan ΔE 12.9(≥12 算过线)、对卡面对比度全部 ≥3:1。 只有「金」是故意留灰的(彩度 0.03,低于 0.10 的彩度下限):金=白是惯例, 真给它上色就会跟土(黄)或跟本项目的金色强调撞成一团;好在这些颜色只用在文字上, 而字本身永远就在颜色旁边,认出来从来不只靠颜色。 改任何一个色值都要重跑验证器,别凭手感调。

.wx-* 这五个类带 !important。它们是 utility 类,(0,1,0) 的性子太软:样式表里 曾经写过 .now .sizhu span(0,2,1)—— 一个后代选择器,除了「年/月/日/时」四个标签, 连 <b> 里包干支的八个字也一起命中,五行色全军覆没、四柱看上去是一坨灰(踩过)。 那处现已改成 .now .sizhu > div > span(0,2,2);!important 留着当保险。

四柱:四根柱子并排,每根竖着写 —— 干在上、支在下,底下小字标年月日时。 干支横排的「丙午」读起来是一个词,竖起来才叫柱;排盘历来也是竖排的。 字用楷体(退化顺序 楷体 → 宋体 → 系统衬线),42px;日柱 46px、标签点亮成金色 —— 日干是「日主」,一身之主,四柱里就它该重一点,满屏都强调等于没强调。

竖过来之后,卡字号的不再是宽度,是高度。 横排时 CJK 一个字的步进宽度恒等于 字号(正好 1em),「两字 × 2 ≤ 列宽」直接定死上限——列宽 92px,46px 就到顶。 竖排一列只放一个字,92px 的格宽绰绰有余,瓶颈换成了行数:两行字加一个标签, 而屏只有 480。首页 .body 装的是「四柱 + 菜单」两兄弟,菜单占去 253, 四柱整块的预算就剩约 100px,两行 42px 正好。想要更大的字只能从菜单身上抠 (这一轮把 .menu / .entry 的内边距收掉 13px、.body 下内边距 22→16 才腾出来), 负外边距那套管的是宽度,在这儿一点用没有。

⚠ 两行的行高必须写固定 px(42px),不能写 1 或 1.06。 日柱比别的柱大 4px, 行高要是跟着各自字号走,它一根柱子就能把整块顶高 8px,四根柱子的第二行还会错开基线。 钉死 42px 之后,46px 的日柱在 42px 的行盒里照样放得下(楷体墨迹只占 0.79em), 整块高度纹丝不动。验收时量的是 bTop(四根柱子首行上沿)与 bBot(末行下沿), 四个值必须全等——这一轮量到的是 0/0/0/0 与 84/84/84/84。

别看字号,看墨迹:同一个字号,楷体比黑体小一圈。canvas 的 actualBoundingBox 量出来,墨迹高占字号的比例——楷体 0.79、仿宋 0.85、宋体 0.89、微软雅黑 0.91。 那一版 23px 雅黑的墨迹约 20.9px,现在 42px 楷体是 33.2px。另外楷体、宋体 都只有一个字重,font-weight 必须写 400:写 600 会被伪粗,笔画糊成一坨。

这一套在手表上会退化:Lite Wearable 没有楷体,font-family 会落到系统字体, 靠的是字号、日柱那一档落差、竖排本身,字体只是锦上添花。

⚠ 踩了两轮的大坑,记在这里:样式表里原本写的是 .now .sizhu span{display:block; font-size:11px} —— 这是后代选择器,它不只命中「年/月/日/时」那四个标签, 连 <b> 里包干支的 <span class="wx-火">丙</span> 也一起命中。于是八个字一直按 11px、一个字一行渲染,而 <b> 的 font-size 照样是 46px: 量 getComputedStyle 完全看不出问题,只有量 offsetHeight(当时恒定 24px, 而不是 46×1.06≈49px)才露馅。 前两轮把 .sizhu b 从 23px 调到 46px 全打在了 一个到不了字上的属性上,截图当然一直没变化。上一轮给它加 !important 也只救了 颜色,display 和 font-size 两条照样生效。 现在写成 .now .sizhu > div > span,只命中标签。新增选择器时先想清楚它会命中谁。

卦名也换了楷体,但字号一位没动。 六爻结果页的「风水涣」、梅花三张卡上的卦名、 取象箱里的体卦/用卦,都改走 --kai 那套栈(写在 :root 里,跟四柱同一套), font-weight 从 600 落到 400 —— 楷体只有一个字重,600 会被伪粗。 font-size 没动不是偷懒:CJK 的步进宽度恒等于字号,换字体不改字宽;行高是 无单位的 1.4,只跟字号乘、与字体族无关,所以行盒也一模一样。两处合起来, 整页排版一格没挪,代价只是墨迹小一圈(楷体 0.79em,黑体 0.91em)。 验收时量的不是眼睛是算式 ——「卦名实宽 == 字数 × 字号 × (1 + 字距)」: 乾为天 3 字 × 19px × 1.06 = 60.42px,实测 60.42。

文件

index.html          五个页面(首页 + 三个板块 + 设置)+ 表壳 + ⓘ 弹层
style.css           深色配色(OLED 底 #0d0d0d,数据色阶 #a87a18 → #f2d98c)+ 3D 表体
app.js              界面逻辑:3D 旋转、表冠滚动、ⓘ 弹层、古籍渲染、三处手动指定
start.bat           起本地服务并打开浏览器
core/data.js        卦名表、纳甲表、五行生克、六神、旬空
core/liuyao.js      六爻引擎
core/liuren.js      小六壬引擎
core/meihua.js      梅花易数引擎
core/time.js        真太阳时:经度修正 + 均时差、晚子时换日
core/settings.js    用户设置的默认值与读写(全项目唯一碰浏览器存储的地方)
core/cities.js      省会/直辖市/港澳台经度表
core/gen/yijing.js  64 卦卦辞 + 彖 + 大象,每卦六条爻辞 + 小象(由 tools/parse-yijing.mjs 生成)
core/gen/wanwu.js   八卦万物属类,共 152 条,供梅花取象(由 tools/parse-wanwu.mjs 生成)
core/vendor/        lunar.js(农历/干支,见下方许可)+ lunar-javascript.LICENSE
                    + package.json(只有一行 {"type":"commonjs"},**不能删**:
                    根 package.json 声明了 "type":"module",没有它 Node 会把
                    lunar.js 当 ESM 解析,test.mjs 直接报错)
tools/parse-yijing.mjs  从公版《易經》文本抽取卦辞 / 彖 / 大象 / 爻辞 / 小象,自带校验
tools/parse-wanwu.mjs   从《梅花易數·卷一》抽取〈八卦萬物屬類〉,自带校验
test.mjs            三个引擎 + 时间换算的自校验

跑测试(不需要安装任何东西,lunar.js 用的是 core/vendor/ 里那份):

node test.mjs

校验内容:64 卦全覆盖、乾宫八卦装卦、小六壬落宫、四种起卦法的屏上算式与结果同源 (算式里每个和都按输入重算一遍,改了数就对不上);方法1 的劈数规则(劈成两段后 各位相加、前段位数 ≤ 后段位数、一位数不出卦,另钉了一条回归:若把两段当成两个 整数就会算成另一个卦)、方法2 三个数各管一段、「计时辰」只作用在动爻上; 均时差对四个公认值、四城经度修正、关掉真太阳时后必须退回本地墙钟而不是 UTC (这条是回归用例,早先漏掉时区项时东八区整体差 8 小时);两种筮法各 20 万次抽样; 两套六宫五行只在留连/小吉上不同。

浏览器里还有一套端到端验收(临时脚本驱动真事件,跑完即删):三板块互不串页、 表冠转到底不越界、一爻两下连点十二下、手排循环、三处手动入口、设置开关高亮; 本轮新加的是——方法1 输 12345 屏上算式有 1 + 2 且出火雷噬嗑、 三卦竖排每卦上卦下卦各一行、体用只剩两枚标注且生克字样全无、 摇动中三枚铜钱动画在跑且未落面、五次落定各自的面数与爻画相符(值 = 6 + 背数)、 三枚同面才是动爻、四柱八个字的算出色与它那一行相符(!important 真的赢了 .now .sizhu > div > span 的灰)。

截图验色时留意:Chrome 复用同一个 --user-data-dir 会吃旧的 style.css, 改了 CSS 之后必须换一个干净的配置目录(或加 --disk-cache-size=1), 否则会看到改之前的样子(踩过)。

还有一个更容易误判的:headless 一律走 http://,不要用 file:// —— ES module 在 file:// 下会被浏览器按跨域拒载,app.js 整个不执行,页面只剩表壳、 四柱全空、控制台里只有一条没有消息也没有文件名的 error,看着像代码坏了, 其实只是打开方式不对(踩过,白查半天)。

--dump-dom 配 --virtual-time-budget=3000 拿到的是 JS 跑完之后的 DOM, 可以直接拿去量几何;不加这个参数才是跑之前的源码。

古籍原文从哪来

卦辞、彖、大象、爻辞、小象全是洗出来的,不是敲出来的:把公版《易經》 (Project Gutenberg eBook #25501)下载下来,用 tools/parse-yijing.mjs 抽取, 输出 core/gen/yijing.js。

# 先把公版文本存到 /tmp/yijing.txt
node tools/parse-yijing.mjs

脚本里写死了文王卦序(第 N 卦 = 上卦/下卦),并拿它自校验:正文里的短卦名转简后 必须正好是 guaName(上,下) 的子串,否则说明卦序排错了,直接报错。 另外还检查每一条卦辞、彖、大象、爻辞都以 。!? 收尾,防止原文换行导致截断。

最强的一条校验是爻题对卦画:由 YAO[下] + YAO[上] 推出六爻阴阳, 阳爻那一行的题必须是「初九 / 九二 / … / 上九」、阴爻必须是「初六 / 六二 / … / 上六」, 一条对不上就报错。这一条同时盯住了「卦序没错」和「爻辞没串行」,比只数条数硬得多。 当前 64/64 卦、386 条爻辞与 386 条小象全过,零告警。

原文保持繁体原貌,只对卦名用字做了繁→简(否则 64 卦名和代码里的名字对不上)。

版权:古籍原文属公有领域,可自由使用;但现代人的白话注释、翻译是有版权的, 本项目一律不收,界面上只出现原文。另外小六壬的六宫断辞取自《玉匣记》一系的传本, 同样只列原文。

为移植做的约束

代码里刻意不用任何浏览器专有 API——没有 fetch、ServiceWorker、 IndexedDB、PWA manifest。这是为了让第二版搬到 Lite Wearable 的 HML/CSS/JS 时 不用重写逻辑。

有一处例外,是有意放宽的:设置要持久化,否则每次重开都得重选一遍, 所以 core/settings.js 用了 localStorage。这是全项目唯一碰浏览器存储的文件, load/save 两个函数之外没有第二处引用;移植时把这两个函数换成华为对应的 存储 API 即可,其余代码一行都不用动。

core/ 下七个文件是纯函数模块,与界面完全解耦,可以直接搬。 (settings.js 和 cities.js 之外都是零副作用、零依赖的纯计算。)

待确认,而且是动第二版之前必须先验的两件事: 一是 Lite Wearable 的 JS 引擎是否支持 ES module(import/export)——支持就能原样搬, 不支持就得把模块改成普通脚本 + 全局变量,或者加一个拼单文件的构建步骤; 二是语法版本:查到的说法是只到 ES5.1,那么箭头函数、模板字符串、const/let 现在这套写法一个都不能用,改动量比换模块机制还大。这条没有实测过,先别当真。

Lite Wearable 的 CSS 功能表比想象的窄。 下面是查文档得到的,同样没有实测, 动手前照单子核一遍(右边的用量是按当前代码数的):

用不了 本项目的用量
display:grid 5 处
position(含 absolute) 11 处
flex:1 / flex-shrink / align-self 认的只有裸的 display:flex
transition 7 处,写了也不会动
3D 变换 / preserve-3d / backface-visibility 硬币翻转动画整个靠它
@media 比较符 < > 只能用 min-width / max-width 那套

@media 还有 512 字符上限——style.css 末尾那条窄屏查询约 450 字符,已经贴着顶了, 而且它是给手机浏览器看的:手表上本来就该是无壳的, 移植时整段删掉即可(app.js 里配对的 matchMedia 断点也要一起删)。

3D 表体是纯 CSS 3D(沿 Z 轴叠同形圆角矩形),没有 WebGL、没有 Three.js—— Lite Wearable 没有 WebGL,真 3D 在手表上跑不起来。 这一条其实已经在为移植铺路:表体本来就是用 CSS 假造的, 真到手表上把厚度层一藏,剩下的界面不需要任何 3D 能力——跟窄屏那条查询是一回事。

加速度计的位置已经留好了。 摇卦的所有触发都走 shakeTrigger() 这一个入口 (app.js),它是开关语义——摇一下开始、再摇一下落定。演示版里 bindMotion() 是空实现:桌面上没有加速度计,装了监听只是白占一份开销。注释里写好了阈值 (SHAKE_G = 18,静止时加速度模长约 9.8,抬手晃一下能过 20)和 devicemotion 监听的完整写法,移植时只改这一个函数,上层一行都不用动。表冠同理—— step() 吃的是「一格」这个抽象量,传感器换成手表自己的转轴事件即可。表冠只滚本页, 滚到头就停——不跨板块,所以没有需要一并移植的翻页状态机。

关于内容

工具只做排盘和古籍原文展示,不出结论性解读、不收费、不上架。 需要解读的话,界面把排好的盘整理成结构化文本,复制出去问自己的模型即可—— 所以这个工具本身不接任何 API,也不需要联网。

许可

保留所有权利(All rights reserved)。 本仓库公开只为展示与留存,不授予任何使用许可。

  • 可以读、可以看、可以拿来了解思路;
  • 不可以复制、修改、分发、部署,或刷进任何设备;
  • 需要授权请开 issue 联系。

所以 GitHub 不会把它识别成任何 OSI 许可,这是刻意的。

core/vendor/lunar.js 是唯一的例外:它来自 lunar-javascript,MIT 许可,版权归 6tail。 MIT 允许商用且不允许附加限制,因此这一个文件不受上面条款约束—— 许可证全文见同目录的 core/vendor/lunar-javascript.LICENSE, 声明也已保留在该文件头部。除该文件外,本仓库其余部分均为保留所有权利。

About

术数排盘工具:小六壬 · 六爻 · 梅花易数(面向方屏手表的网页 demo)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages