AI Dev Lab
HexCast

HexCast ができるまで — 易経 六十四卦 を三枚銭法で引く

易経のオンライン占いの開発ログ。 三枚銭法を再現して 6 爻を順に出し、 上卦と下卦から六十四卦を特定、 変爻があれば之卦まで計算する流れと、 八卦盤の描き方を記録しています。

HexCast は、中国古典『易経』(周易) の六十四卦を、古典の三枚銭法を再現して引くブラウザ占いです。質問と名前から決定的に爻を立て、本卦・変爻・之卦まで出します。引く処理はすべて端末の中で、入力はサーバーに送りません。

#🎯 「ランダムに 64 個から選ぶ」 では易にならない

最初に決めたのは、64 卦から 1 つを直接ランダムに選ばないということでした。

易は「6 本の爻(こう)を下から順に立て、その積み重ねが卦になる」という構造をしています。結果だけ見れば 64 通りですが、途中に

  • どの爻が陽で、どの爻が陰か
  • どの爻が「変わる爻」(変爻) か

という情報があり、そこから 之卦(しか) ── 変化したあとの卦 ── が決まります。64 個から 1 つ引く実装だと、この変爻と之卦が原理的に作れません。易の読み方の半分が消えます。

なので、爻を 6 回立てる過程そのものを実装しました。

#🪙 三枚銭法をそのまま数値にする

古典的な立て方のひとつが、コインを 3 枚投げるのを 6 回繰り返す方法です。

  • 表 = 3、裏 = 2 として 3 枚の合計を取る
  • 合計は 6・7・8・9 のいずれかになる

この 4 つの数がそのまま爻の種類です。

名前陰陽変爻か
6老陰変わる (→ 陽)
7少陽変わらない
8少陰変わらない
9老陽変わる (→ 陰)

コードにするとそのままです。

ts
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() にすると、リロードのたびに違う卦が出ます。占いとしては「引き直せてしまう」ので軽くなります。

そこで 質問文と名前から決定的にシードを作りました。

ts
const baseSeed = fnvHash(`${question}@@${salt}`) || 1
let s = baseSeed
// 以降は線形合同法 (LCG) で 1 ビットずつ取り出す

FNV-1a で文字列を 32bit 整数にし、それを線形合同法の初期値にします。これで

  • 同じ問いを同じ人が立てれば、何度開いても同じ卦
  • 問いを一文字変えれば、まったく別の卦

になります。「一度立てた卦を引き直さない」という易の作法が、実装として自然に守られます。サーバーもデータベースも要りません。

#🏗️ 上卦と下卦から 64 卦を引き当てる

6 本の爻が立ったら、下 3 本が下卦(内卦)、上 3 本が上卦(外卦) です。それぞれが八卦のどれかになります。

ts
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 つで、それぞれに「健・説・麗・動・入・陥・止・順」という性質が対応します。この属性を表示に出すと、卦名だけ見るより意味が繋がります。

#🔀 変爻から之卦を作る

変爻があれば、その爻だけ陰陽を反転させて、もう一度卦を引き直します。

ts
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 をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — ブラウザ内で完結する embeddings 意味検索

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →