指示
4人対戦の麻雀ができるWebアプリケーションを作って。東風戦と半荘戦に対応。 プレイヤーは1人で、他の3人はCPU。 CPUのキャラ設定は、「振り込みがちな初心者」「守りが堅い中級者」「打点高めを狙う中級者」「バランスの取れた中級者」「プロレベルの上級者」から選べるようにしたいです。
ユーザーへのヒアリング
Plan mode で指示したので確認があった。

ルールをユーザーが設定画面から選べるようにする方針もアリかなーと思ったけど、
実装難易度上げて完成しないのもちょっとヤダかなー、と思って、素直に全て推奨項目を選択した。

プランエージェントへの指示
プラン作成にあたって Claude Code は以下のような指示をプランエージェントに(?)投げていた
あなたはソフトウェアアーキテクトです。以下の要件で4人対戦リーチ麻雀Webアプリの詳細な実装計画を設計してください。 ## 要件 - 場所: ~/Programs/fable-5-try/mahjong-app(空ディレクトリ、グリーンフィールド) - 技術スタック: Vite + React + TypeScript、全てクライアントサイド完結(サーバー不要) - 4人対戦: 人間1人 + CPU 3人 - 東風戦と半荘戦を選択可能 - CPUキャラ設定(ゲーム開始時に各CPUへ割当可能): 1. 振り込みがちな初心者 2. 守りが堅い中級者 3. 打点高めを狙う中級者 4. バランスの取れた中級者 5. プロレベルの上級者 - ルール: 標準的なリーチ麻雀フル実装 - 喰いタンあり・後付けあり・赤ドラ3枚(5萬5筒5索 各1枚)・裏ドラ・カン裏あり - 鳴き(ポン・チー・カン[暗槓/明槓/加槓])、リーチ、フリテン(同巡フリテン・リーチ後フリテン含む) - 流局: 通常流局(ノーテン罰符)、九種九牌、四風連打、四家立直、四槓散了、流し満貫 - 連荘、本場、供託、トビ終了、オーラス和了止めは任意 - 役: 全標準役(1翻〜役満、ダブル役満は任意)、符計算、点数計算(切り上げなし標準) ## 設計してほしいこと 1. **ディレクトリ構成とモジュール分割**: ゲームエンジン(純粋ロジック、React非依存)とUI層の分離方針 2. **コアデータ構造**: 牌の表現(数値エンコーディング推奨か)、手牌・河・副露の表現、ゲーム状態マシン(局の進行: 配牌→ツモ→打牌→鳴き判定→...→和了/流局) 3. **アルゴリズム設計**: - シャンテン数計算(標準形・七対子・国士無双)の実装方針 - 和了判定・面子分解・役判定・符計算・点数計算の設計 - 待ち牌(テンパイ時の和了牌)列挙 4. **CPU AI設計**: 5キャラの性格をどうパラメータ化するか - 共通基盤: 牌効率(シャンテン数を下げる打牌選択)、安全牌判定(現物・スジ・壁・ワンチャンス) - キャラ差分: 押し引き閾値、ベタオリ判定、鳴き頻度、打点志向(ドラ・役狙い)、リーチ判断などのパラメータ表を具体的に提案 - 初心者キャラは「危険牌を平気で切る」「テンパイ即リー」など自然な弱さの表現方法 5. **ゲームフロー制御**: 人間の入力待ち(打牌・鳴き選択・リーチ宣言・和了宣言)とCPUの自動行動を統合する設計(async/await ベースか、イベントキューか、ステートマシンか) 6. **UI設計**: 麻雀卓レイアウト(自分の手牌は下、対面・左右のCPU)、牌の描画方法(Unicode麻雀牌文字 vs CSS/SVG自作)、アクションボタン(ポン/チー/カン/ロン/ツモ/リーチ/パス)、点数表示・結果画面 7. **実装フェーズ分割**: 段階的に動くものを作る順序(例: エンジン→AI→UI)とマイルストーン 8. **テスト方針**: シャンテン計算・点数計算など複雑なロジックのユニットテスト(Vitest) 既存ライブラリ(riichi 等の npm パッケージ)を使うか自作するかの判断も含めてください。自作推奨の場合はその理由を。教育目的というよりは「ちゃんと動く完成品」が目標です。 返答は日本語で、具体的なファイルパス・型定義例・パラメータ表を含む詳細な実装計画を返してください。
ライブラリの吟味
プランを作るに当たってライブラリを使用するか吟味する記録が残っていたのがおもしろかった。
ディレクトリは空(完全グリーンフィールド)であることを確認しました。以下、実装計画の全体設計です。 ## 4人対戦リーチ麻雀 Webアプリ 実装計画 ### 0. ライブラリ自作 vs 既存パッケージの判断 → 全面自作を推奨 | 候補 | 評価 | |------|------| | riichi (npm) | 文字列入力から役・点数を計算できるが、シャンテン計算・待ち列挙・フリテン・ゲーム進行は対象外。型定義が貧弱でルールオプションの細かい制御(カン裏・流し満貫・赤の枚数指定)に難あり。メンテも停滞気味 | | syanten / shanten-number 系 | シャンテン単機能。AI が必要とする「打牌ごとの受け入れ枚数」APIがなく、結局ラップ層を自作することになる | 自作推奨の理由(完成品志向の観点で): 1. AIがエンジン内部表現を直接食う必要がある。CPU の打牌選択は「14枚から1枚切った後のシャンテン数と受け入れ枚数」を毎ターン数十回計算する。外部ライブラリの文字列I/Fを挟むと変換コストとバグ面が増える。34種カウント配列で統一すれば全アルゴリズムが同じ表現を共有できる。 2. 点数計算と面子分解は役判定・符計算と密結合(高点法のため全分解を列挙して最大を取る)。ここを外部に出すと「平和+ツモ20符」「七対子25符」などの境界でルール差異の調停が発生する。 3. アルゴリズム自体は枯れており(シャンテン数のバックトラック法、標準形分解)、テストベクタで品質担保できる。フル実装でもコア部は合計 2,500 行程度。 依存は Vite + React + TypeScript + Vitest + Zustand の最小構成。牌画像も外部アセットに依存せず自前SVGにする(後述)。
riichi って npm が更新されていないのは調べに行ったのかな? と思ったけど、
検索してライブラリのページを見に行ったら最終更新が5年前だったので、検索せずに答え出した気がする。
(仮にここ数日以内に更新されていた場合には、更新履歴を見に行かなかったことで少し誤りに近い判断となっているのかもしれない)
作成されたプラン
# 4人対戦リーチ麻雀 Webアプリ 実装計画 ## Context `~/Programs/fable-5-try/mahjong-app`(空ディレクトリ)に、人間1人 + CPU 3人で遊べるリーチ麻雀Webアプリをゼロから作る。 - **モード**: 東風戦 / 半荘戦を選択可能 - **CPUキャラ**: 「振り込みがちな初心者」「守りが堅い中級者」「打点高めを狙う中級者」「バランスの取れた中級者」「プロレベルの上級者」の5種から各CPUに割当 - **ルール**: 標準ルールフル実装 — 喰いタン・後付けあり、赤ドラ3枚、裏ドラ・カン裏、鳴き(ポン/チー/カン全種)、リーチ、フリテン(同巡・リーチ後含む)、途中流局(九種九牌・四風連打・四家立直・四槓散了)、流し満貫、連荘・本場・供託・トビ終了、全標準役 + 符計算 + 点数計算(切り上げ満貫なし) - **技術**: Vite + React + TypeScript + Zustand + Vitest。全てクライアントサイド完結。**麻雀ロジックは全面自作**(外部ライブラリはAI用の受け入れ計算APIがなく、点数計算と面子分解が密結合のため) ## アーキテクチャ方針 - `src/core/`(ゲームエンジン)と `src/ai/`(CPU)は **React非依存の純粋TypeScript** - エンジンは純Reducer: `applyAction(state, action) → {state, events}` + `legalActions(state) → Map<Seat, Action[]>` - 進行の「待ち」はエンジン外の非同期ドライバ(controller)が担当。人間入力はPromise、CPUは思考遅延つき同期計算(Web Worker不要) - 依存方向: `ui → app → ai → core`(逆流禁止) - シード可能PRNGでリプレイ・バグ再現可能に ## ディレクトリ構成 ``` src/ ├── core/ │ ├── tiles.ts # 牌エンコーディング │ ├── wall.ts # 山・王牌・ドラ表示牌 │ ├── random.ts # シード可能PRNG │ ├── shanten/ │ │ ├── standard.ts # 標準形(バックトラック+色別メモ化)★最難関 │ │ ├── chiitoi.ts / kokushi.ts │ │ ├── index.ts # 3形式min + API │ │ └── ukeire.ts # 打牌別受け入れ枚数(AI用) │ ├── agari/ │ │ ├── decompose.ts # 和了形の全面子分解列挙 │ │ ├── waits.ts # 待ち牌列挙 │ │ ├── yaku.ts # 全役判定 ★ │ │ ├── fu.ts / score.ts # 符・点数計算 │ └── engine/ │ ├── types.ts # GameState / RoundState / PlayerState / Phase │ ├── actions.ts # Action / GameEvent │ ├── legal.ts # 合法アクション列挙 │ ├── reducer.ts # 状態遷移本体(鳴き調停・フリテン・流局)★ │ ├── furiten.ts / draws.ts │ └── match.ts # 連荘・本場・供託・トビ・東風/半荘 ├── ai/ │ ├── types.ts / personalities.ts # 5キャラパラメータ表 │ ├── efficiency.ts # 牌効率 │ ├── danger.ts # 危険度推定(現物・スジ・壁・ワンチャンス) │ ├── value.ts # 打点見積もり │ └── decide.ts # 押し引き統合 ★ ├── app/ │ ├── store.ts # Zustand: スナップショット + UIプロンプト │ ├── controller.ts # ゲームループ駆動 ★ │ └── settings.ts ├── ui/ │ ├── TableView.tsx │ ├── components/ # Tile(自前SVG), Hand, River, MeldArea, │ │ # ActionBar, CenterInfo, ResultModal, │ │ # FinalResultModal, SetupScreen │ └── tiles/glyphs.ts # 牌SVGデータ └── main.tsx ``` ## コアデータ構造 **牌は二層エンコーディング**: - アルゴリズム用: `TileKind` 0..33(34種)+ `Counts = Uint8Array(34)` - 状態管理用: `TileId` 0..135(物理牌。`kind = id >> 2`、赤5は各色5の0番個体) **Phase(局の状態マシン)**: ``` dealing → draw(actor) → [九種九牌|ツモ和了|カン→(槍槓窓)→新ドラ→rinshanDraw|打牌/リーチ] → discarded(クレーム窓) → [ロン(ダブロン両者成立)|明槓|ポン/チー|全員パス] → [四風連打/四家立直/四槓散了チェック] → [山空→荒牌流局(ノーテン罰符/流し満貫)|次家draw] → roundEnd → (match.tsで連荘/次局/gameEnd) ``` 同時クレームは全席の応答を集めてから ロン > ポン/カン > チー の優先度で調停。 **PlayerState** は手牌(TileId[]ソート保持)・ツモ牌・副露・河(ツモ切り/リーチ/鳴かれフラグ付き)・リーチ状態(一発含む)・フリテン(permanent/temporary)・流し満貫資格を持つ。 ## 主要アルゴリズム - **シャンテン**: min(標準形, 七対子, 国士)。標準形は再帰バックトラック + 色ごと9要素部分問題のメモ化。テーブル事前計算は不要(メモヒット時 <0.05ms) - **受け入れ**: 「各牌を足す/引いてシャンテン再計算」の素朴実装で十分高速 - **和了パイプライン**: WinContext → 全面子分解列挙 → 分解ごとに役判定 → 符計算 → **高点法で最大採用** → 点数・支払い分配。待ちの取り方(両面/嵌張/単騎...)も分解ごとに列挙(平和・符に影響) - **符**: 副底20 + 待ち + 雀頭 + 面子符 + 門前ロン10/ツモ2、平和ツモ20符・七対子25符・喰い平和30符の特例。**切り上げ満貫なし**(7700/11600そのまま) - **ダブル役満**(国士13面・四暗刻単騎・大四喜・純正九蓮): デフォルトON ## CPU AI 設計 全キャラ共通パイプライン: 状況分析 → 押し引き判定(PUSH/FOLD/MIXED)→ 打牌選択(受け入れ最大 or 危険度最小)→ 鳴き/リーチ/和了判断 → ミス注入。**性格はパラメータのみで差別化**。 | パラメータ | 初心者 | 守備型中級 | 打点型中級 | バランス中級 | 上級 | |---|---|---|---|---|---| | safetyAwareness | 0.05 | 0.95 | 0.45 | 0.70 | 0.95 | | foldShanten | 99(降りない) | 1 | 2 | 2 | 動的EV | | pushDanger | 1.0 | 0.10 | 0.45 | 0.30 | 0.35 | | callEagerness | 0.8(鳴きすぎ) | 0.25 | 0.55 | 0.45 | 0.50 | | yakuAwareness | 0.2(役無し鳴き) | 0.9 | 0.85 | 0.9 | 1.0 | | valueOrientation | 0.1 | 0.3 | 0.85 | 0.5 | 0.6 | | riichiStyle | always(即リー) | standard | standard | standard | damaten-aware(跳満級ダマ) | | mistakeRate/depth | 0.25 / 3 | 0.05 / 1 | 0.07 / 1 | 0.05 / 1 | 0 | | readsWalls | ✗ | ✓ | ✗ | ✓ | ✓ | | considersFuriten読み | ✗ | ✗ | ✗ | ✗ | ✓ | - 初心者の弱さは「危険牌を選ぶ」のではなく「安全を**見ない**」結果として自然に振り込む設計 - 上級は閾値でなく簡易EV(和了率×打点 − 放銃率×失点期待)+ オーラス着順条件を考慮 - 危険度: 現物0 / 字牌0.05〜0.3 / スジ0.15〜0.3 / 無スジ中張0.6〜0.8、壁×0.4・ワンチャンス×0.6 ## UI 設計 - **牌は自前SVG**(Unicode麻雀牌は品質バラバラ・赤5表現不能のため不採用)。萬子=漢数字テキスト、筒子=円パターン生成、索子=竹形状、字牌=テキスト。裏向き・横向き(リーチ宣言牌・鳴き出所)はtransform - **卓レイアウト**: 自分=下(手牌大きめ・表)、対面=上、左右CPU=縦(河はrotate(±90deg))。中央に局数/本場/供託/残り山/ドラ表示/4人の点数。固定アスペクト比でスケール - **ActionBar**: 合法アクション時のみ ロン/ツモ/リーチ/ポン/チー/カン/パス を表示。リーチ宣言時は切れる牌のみハイライト。チー複数候補は組を提示して選択。リーチ後はツモ切り自動 - **ResultModal**: 手牌公開・役一覧・符翻・点数移動・裏ドラめくり。流局時はテンパイ者公開とノーテン罰符 - **SetupScreen**: 東風/半荘トグル、CPU 3枠 × キャラ5種ドロップダウン - 人間に合法クレームが無い鳴き窓は即パス(プロンプトしない)。CPU思考遅延500〜1200msは演出 ## 実装フェーズ | Phase | 内容 | 検証 | |---|---|---| | P1 | 牌・シャンテン・分解・待ち | Vitest: ランダム手 vs 素朴全探索リファレンス照合 10,000ケース | | P2 | 役・符・点数 | 点数表テーブル駆動テスト(全役成立/不成立 + 代表点数60ケース) | | P3 | 局エンジン(reducer/legal/furiten/draws) | シード固定シナリオテスト(槍槓・嶺上・海底・ダブロン・各流局・フリテン) | | P4 | match.ts + **ランダム自走1000半荘** | クラッシュ0・不変条件(牌136枚・点数+供託=100,000)assert | | P5 | 最小UI(卓・打牌のみ) | ブラウザで1局打てる | | P6 | フルUI(全アクション・結果画面・セットアップ) | 人間が半荘完走 | | P7 | AI 5キャラ | キャラ自走対戦で上級>中級>初心者の着順序列を統計確認 | | P8 | 仕上げ(アニメーション・レスポンシブ) | 完成 | P4の自走テストが品質の要 — UIとAIより先にエンジンを統計的に叩いて稀パスのバグを潰す。 ## 検証方法 1. `npm test`(Vitest): シャンテン照合・点数表・シナリオ・自走不変条件テスト全通過 2. `npm run dev` でブラウザ起動 → セットアップ画面でキャラ割当 → 半荘を人間として完走(鳴き・リーチ・カン・和了を実際に行使) 3. AI自走統計で性格差(初心者の放銃率が最高、上級の平均着順が最良)を確認
稀パスのバグを潰す
という変な日本語が誕生していた。
プラン自体は良さそうなのでそのまま auto mode で実装開始させたけれど、はたして完成するのか・・・!!!
実装を観察

平気で30分はかかっていて、「どうなるかな〜」の気持ち。

コンテキストのリミットを少し超えてもがんばってくれている・・・。

npm install は失敗し続けている……。どこかで私が手動でやらないといけなさそう。
claude-fable-5 is temporarily unavailable, so auto mode cannot determine the safety of Bash right now. Wait briefly and then try this action again. If it keeps failing, continue with other tasks that don't require this action and come back to it later. Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.

最後の方はずっと npm install を繰り返していたけど、
これは無理だとわかったのか、 ScheduleWakeup で再実行を予約していったん止まってくれた。
実装時間は60分くらい。
いったんこの状態で .gitignore だけ加え、 git init して全ファイルを commit。

Fable は git commit も許可されてないっぽい。
動作テスト
ほぼほぼ動いてた! すごい!
60分かかったとはいえ、この量のコードを記述したのすごい。
特に CPU の挙動を書いたのがすごい。そこまで出来るんだ……。
npm コマンドの実行をおこなえないから、
テストやローカルサーバの起動の試行をできないという、
なかなかハードモードの中の実装だったけど、それなのに動くものが作れていたのがすごい。
テストプレイしてみるといくつか問題があって、最初はこんな指示で直してもらった。
いくつか問題点があります。 * 音が鳴らないため進行が少しわかりにくいです * チーやポンを選択できるとき、どのプレイヤーの捨て牌に対しての鳴きかがわからないです * リーチになったとき「リーチ」や「捨てる牌を選んでください」というメッセージウィンドウと手牌が重なってしまっていて、捨てる牌を選びにくいですし、自分の手牌がわからないので、何待ちかがわかりにくいです。現代の麻雀ゲームのように、どの牌を捨てると何待ちになるのか表示されるといいと思います * ツモなどの演出がないため、CPUの誰が上がったのかがわかりにくかったです
これ含めて、麻雀部分の修正指示に関しては5回くらいで満足いくものが出来上がったから優秀だった。
鳴きのときのメッセージウィンドウと手牌がかぶる問題、
最終的に盤面を少し小さくしてメッセージウィンドウを盤面の下に表示するという解決方法をとっていて、
スマホの麻雀ゲームでは見ないUIながらも「たしかにその方法があったか!」と感心した。
完成物(リポジトリ)
リポジトリはこちら ⬇️
コミットメッセージに、どういう指示で直したか書いてるときもあるのでご参考に。
git や npm のコマンドが使えなく、Web検索できなく、MCPも使えないという、
Fable 5 はなかなかハードな環境で動作するにも関わらず、
もともとのモデルの優秀さゆえに、早くいいものを作れていた感じがある。
(この問題、Claude Desktop のバージョンアップをしたら解消した気がする)
完成物(ウェブアプリ)
リポジトリの README にも記載しているけれど、麻雀ウェブアプリはこちら ⬇️

綿密な動作検証をしたわけではないけれど、
東風戦を通して違和感ないくらいにはなってたので大丈夫なはず。














