HexCast は、中国古典『易経』(周易) の六十四卦を、古典の三枚銭法を再現して引くブラウザ占いです。質問と名前から決定的に爻を立て、本卦・変爻・之卦まで出します。引く処理はすべて端末の中で、入力はサーバーに送りません。
#🎯 「ランダムに 64 個から選ぶ」 では易にならない
最初に決めたのは、64 卦から 1 つを直接ランダムに選ばないということでした。
易は「6 本の爻(こう)を下から順に立て、その積み重ねが卦になる」という構造をしています。結果だけ見れば 64 通りですが、途中に
- どの爻が陽で、どの爻が陰か
- どの爻が「変わる爻」(変爻) か
という情報があり、そこから 之卦(しか) ── 変化したあとの卦 ── が決まります。64 個から 1 つ引く実装だと、この変爻と之卦が原理的に作れません。易の読み方の半分が消えます。
なので、爻を 6 回立てる過程そのものを実装しました。
#🪙 三枚銭法をそのまま数値にする
古典的な立て方のひとつが、コインを 3 枚投げるのを 6 回繰り返す方法です。
- 表 = 3、裏 = 2 として 3 枚の合計を取る
- 合計は 6・7・8・9 のいずれかになる
この 4 つの数がそのまま爻の種類です。
| 数 | 名前 | 陰陽 | 変爻か |
|---|---|---|---|
| 6 | 老陰 | 陰 | 変わる (→ 陽) |
| 7 | 少陽 | 陽 | 変わらない |
| 8 | 少陰 | 陰 | 変わらない |
| 9 | 老陽 | 陽 | 変わる (→ 陰) |
コードにするとそのままです。
for (let i = 0; i < 6; i++) {
// 3 枚のコイン: 表=3 / 裏=2 を 3 回足す → 6〜9
let v = 0
for (let c = 0; c < 3; c++) {
s = lcgNext(s)
const heads = s & 1
v += heads ? 3 : 2
}
const yang = v === 7 || v === 9
const changing = v === 6 || v === 9
lines.push({ pos: i + 1, value: v, yang, changing })
}「老いた陰陽は変わる」という考え方が、v === 6 || v === 9 の一行に落ちます。伝統の側にすでにルールがあるので、実装は決めごとを写すだけでした。
なお三枚銭法は、6 と 9 (変爻) の出る確率が 8 と 7 に比べて低くなります。等確率ではないのが元々の性質なので、そこも均さずそのままにしています。
#🎲 同じ問いには同じ答えを返す
乱数を Math.random() にすると、リロードのたびに違う卦が出ます。占いとしては「引き直せてしまう」ので軽くなります。
そこで 質問文と名前から決定的にシードを作りました。
const baseSeed = fnvHash(`${question}@@${salt}`) || 1
let s = baseSeed
// 以降は線形合同法 (LCG) で 1 ビットずつ取り出すFNV-1a で文字列を 32bit 整数にし、それを線形合同法の初期値にします。これで
- 同じ問いを同じ人が立てれば、何度開いても同じ卦
- 問いを一文字変えれば、まったく別の卦
になります。「一度立てた卦を引き直さない」という易の作法が、実装として自然に守られます。サーバーもデータベースも要りません。
#🏗️ 上卦と下卦から 64 卦を引き当てる
6 本の爻が立ったら、下 3 本が下卦(内卦)、上 3 本が上卦(外卦) です。それぞれが八卦のどれかになります。
const lowerLines = [lines[0].yang?1:0, lines[1].yang?1:0, lines[2].yang?1:0]
const upperLines = [lines[3].yang?1:0, lines[4].yang?1:0, lines[5].yang?1:0]
const primary = hexByUpperLower(upper.id, lower.id)八卦は 3 本 × 陰陽 = 2³ = 8 通りで、これが上下 2 つ組み合わさって 8 × 8 = 64。六十四卦が 64 個ある理由がここにあります。データも「上卦 id と下卦 id」で持たせて、そこから引くようにしました。
八卦は ☰乾(天) ☱兌(沢) ☲離(火) ☳震(雷) ☴巽(風) ☵坎(水) ☶艮(山) ☷坤(地) の 8 つで、それぞれに「健・説・麗・動・入・陥・止・順」という性質が対応します。この属性を表示に出すと、卦名だけ見るより意味が繋がります。
#🔀 変爻から之卦を作る
変爻があれば、その爻だけ陰陽を反転させて、もう一度卦を引き直します。
for (let i = 0; i < 6; i++) {
if (lines[i].changing) {
const flip = lines[i].yang ? 0 : 1
if (i < 3) changedLowerLines[i] = flip
else changedUpperLines[i - 3] = flip
}
}これが 之卦 = 変化したあとの状況です。本卦が「いまの状況」、之卦が「向かう先」という読み方になります。変爻がなければ之卦は作らず、本卦だけで読みます。
#😓 苦労したところ
八卦の記号が環境によって出ない。 ☰☱☲☳☴☵☶☷ は Unicode にありますが、フォントによっては豆腐(□)になります。実機で確認したうえで、記号と一緒に必ず漢字(乾・兌・離…)と読みを併記する構成にしました。記号が出なくても意味が伝わります。
卦辞をどこまで現代語にするか。 原文のままだと読めず、噛み砕きすぎると別物になります。「元亨利貞」のような原文の要約を残しつつ、その下に現代の助言を別行で置く二段構えにしました。原典を消さずに、読める形にするための折衷です。
当たる占いにしない。 易は本来「決断の補助」で、当てものではありません。断定的な吉凶を強めると、読んだ人の判断を歪めます。助言は「〜せよ」ではなく「〜すると良い / 〜は避ける」の温度に揃えました。
#🔭 今後の拡張
- 筮竹(ぜいちく)による本式の立て方(十八変筮法)の選択
- 変爻の数による読み分け(1 本 / 2 本 / 3 本以上で読む爻が変わる伝統的な規則)
- 384 爻すべての爻辞(現在は位と陰陽から 12 通りに簡略化)
- 立てた卦を端末内に保存して、後から振り返る
#💡 このサービスから言えること
古典を題材にするときは、結果ではなく手続きを実装するほうが良いものになります。 64 卦からランダムに選ぶ実装は 10 行で書けますが、それだと変爻も之卦も作れず、易として読めるものになりません。三枚銭法という手続きを写したから、変爻が自然に出てきました。
もうひとつは、決定的な乱数は「引き直せない」という体験そのものを作れるということ。技術的にはハッシュを 1 つ挟むだけですが、占いとしての手応えがまったく変わりました。
[ ./next_action ]
読んだら、 HexCast を実際に動かす。
この開発ログは HexCast をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。