freeeの開発情報ポータルサイト

人生は決断の連続である。故に私は麻雀牌を切った。

こんにちは。この記事は freee Developers Advent Calendar 2025 12/02(2日目) の記事です。

adventar.org

昨日は bucyou さんの RSpec の allow のコードを読んでみよう - freee Developers Hub でした。
RSpec の allow、何気なく利用しているばかりでしたが、思った以上に奥の深いものであることが分かります。
AI Agent を利用してのコードリーディングで物事を深ぼる難易度が格段に下がったと感じます。どんどん活用していきたいですね。

今日は IAM チームでエンジニアをしている 新岡 が書きます。
先日も ユニットテストに時限爆弾を作らないためのベストプラクティス - freee Developers Hub を書かせていただきました。再びの登場です。よろしくお願いします。


はじめに

みなさん、人生において日々決断はしていますか?
毎日のランチに何を食べるか、夕飯の献立は何にしようか、今日の入浴剤は何にしようか、etc …

このように、人は日々なにかしらを決断しながら生きています。えらいですね。

「決断をする」という行為は「取捨選択する」にも通じ、仕事の世界でも同様のことが言えます。
ユーザー価値とはどういうものか、価値に繋げるための MVP をどこに定めるか、やること/やらないことは何か、etc …

この決断の訓練のために、麻雀の「何切る問題」を AI に作らせることにしました。
リポジトリはこちらです。

github.com

結論から言うと、想定していたよりも期待するアウトプットを得られなかった です。
今日はその期待するアウトプットに少しでも近づけるための試行錯誤などについて書きます。

ナニコレ

Claude Code の custom command で麻雀の「何切る問題」を作問・回答させるリポジトリです。
Python script も用意していますが、現状、こちらは特に利用していません(Claude Code が良しなに用意してくれた)。

アーキテクチャは↓のような感じです。
Claude Code custom command は直接 Claude Code AI を利用し作問・解答を行います。Python script は API Key を利用して SDK 経由で Claude API を呼び出し作問・解答を行うようになっています。

麻雀何切る問題集のシステムアーキテクチャを示すフローチャート。3つのセクションで構成される。 入力方法セクション:Claude Code カスタムコマンド(水色)とPython スクリプト直接実行(水色)の2つの入力手段。 コア処理セクション:generate_question.py 問題生成ロジック(薄紫)、generate_solution.py 解答生成ロジック(薄紫)、Claude API Anthropic SDK(黄色)、Claude Code AI Built-in Claude(黄色)の4つのコンポーネント。 出力先セクション:problems/NNN/question.md(緑)とproblems/NNN/solution.md(緑)。 データフロー:Claude Code カスタムコマンドは直接Claude Code AIを使用し、Python スクリプトはgenerate_question.pyとgenerate_solution.pyを経由してClaude APIを呼び出す。両方のパスとも最終的にquestion.mdとsolution.mdファイルを出力する。
mahjong_nanikiru の動作アーキテクチャ

意外と苦労の連続だった

無邪気に「麻雀何切る問題を作って欲しい」と言えば、それだけである程度「それっぽい」ものを出してくれました。 しかし、よくよく見てみると色々とおかしい…

例えば、

  • そもそも麻雀のルールを知らないのでは?という問題が作問される
  • 作問した牌姿に対して、Claude の読み取りがおかしい
  • シャンテン数の解釈がおかしい。多面待ちの解釈は厳しい
  • 押し引きの概念の解釈ができない

など、麻雀におけるコアな要素についての課題がいくつか見受けられました。
ここからは、それらの課題についてご紹介します。

AI 麻雀のルール知らない問題

麻雀は全世界で愛されるテーブルゲームの一つです。とりあえず四人集まったら行うことと言えば、大乱闘スマッシュブラザーズ か マリオカート か 麻雀 と世の中の理で定められています。
当然、世の中のことをたくさん学習済みである AI さんも麻雀のルールは把握済みであると思っていました。

が・・・・・
駄目っ・・・・・!

作ってもらった問題は麻雀のマーの字も知らないものだったのです。
作問には局面情報として 場 や 点数状況、巡目、河(捨て牌) の情報を作ってもらうようにしていました。これがチグハグだったのです。

場が「東1局0本場」という、ゲームが始まった状況にも関わらず、配給原点である 25000点 以外の点数が設定されていたり、
巡目が「10巡目」であるにも関わらず、捨て牌が整合性のない状態だったりしました。

これはひたすらに出てきた問題に対して状況設定がおかしいことをフィードバックし、CLAUDE.md やら custom command prompt に対して修正を行ってもらうようにしました。

牌姿がおかしい問題

作問には何切る問題ですから当然ながら牌姿が設定されます。
Claude Code は最初から Unicode で表現してくれていました。これが意外と罠でした。

🀇🀈🀙🀚🀛🀜🀜🀔🀕🀖🀆🀆🀆

例えば、↑ のように記述されますが Claude Code は 🀔🀕🀖(索子の 567 ) を 🀕🀖🀗(索子の 678 ) と一つずれて認識していました。 どういう仕組みか分かっていませんが、どうやら視覚的に画像として把握しているらしく、その認識が正しくないようでした。

これは Unicode に対する麻雀牌の対応表を作成することで対応をしてみました。

が・・・・・
駄目っ・・・・・!

それでも間違えることがあったので、最終的には数字表記を併記してもらうことで対応しました。
また、視覚的には間違える可能性があるため一枚ずつ確認するようにしてもらいました。

「あなたの手牌」と表記されている。Unicode によって 🀇🀈🀙🀚🀛🀜🀜🀔🀕🀖🀆🀆🀆 と表現されている。また、数字表記として (1m2m1p2p3p4p4p5s6s7s白白白) が添えられている。
現在の作問の表記

シャンテン数おかしい/多面待ちの解釈難しい問題

問題は13枚の手牌に対し、ツモ牌 1枚を持ってきて、それに対して何を切るかを選択させるフォーマットになっています。
ここで、生成された問題を一つ例に挙げましょう。

## あなたの手牌(13枚)

🀇🀈🀉🀙🀙🀛🀝🀔🀕🀖🀆🀆🀆
(1m2m3m1p1p3p5p5s6s7s白白白)

## ツモ牌

🀜
(4p)

あなたの手牌は白の暗刻があり、567sの順子が完成していますが、全体としてはイーシャンテンです。3p もしくは 6p を引けば4面子が完成し、その後…(以下略)

なんかそれっぽいことを言っています。

が・・・・・
駄目っ・・・・・!

これはダメですね。よくよく見ずともツモ上がりしています。
どうやら、手牌の13枚で状態を確認しているわけではなく、ツモ牌を含めて「雰囲気で」シャンテン数を計算しているようです。「3p もしくは 6p」の記述はツモ牌の 4p と手牌中の 5p を組み合わせての記述のようです。

これは LLM の悪いところが出たらしく、「もっともらしい文章」を生成する確率モデルであることが影響してそうでした。
すなわち、何かに対する「事実」を生成するわけではないということですね。そのため実際の手配と説明に対する整合性のチェックが行われていないが故の問題でした。

限界はありそうですが、プロンプトに手牌の妥当性チェックを行うことを明記するなどして対応を行いました。
https://github.com/nil0ka/mahjong_nanikiru/blob/38aa8f0d5e3c24aa63f52162d83f87313a0dc2fa/.claude/commands/create-question.md?plain=1#L132-L174

押し引きって何?問題

最後は押し引きの概念における問題を紹介します(他にもまだ書ける話はあるんだけど…!)。

麻雀は点数のやり取りを行うゲームであり、いかに期待値を追うかのゲームでもあります(諸説あり)。
故に、自分の手牌に対する期待値と相手の手の高さを推測し、状況から同じ牌姿でもどのように振る舞うかの駆け引きを楽しむのが醍醐味の一つとも言えます。

問題には「押し引き」「リーチ判断」「手作り・手役選択」「待ち選択」「安全牌選択」などのテーマを設けています。
この中の「押し引き」の問題において、いくつか問題を作らせてみると次の問題が浮かんできました。

私「こいつ、いつもベタオリしかしねぇ…!」

そうなんです、どこかのタイミングで「現物」の概念を教えたら現物しか切らなくなってしまったのです。
オリは大事なテクニックです。いかに放銃(他の人の当たり牌を出さないか)しないかは麻雀の世界において重要な概念です。
でも、でも、それじゃあダメでしょ!

押し引き問題においては、点差と場と巡目と、それらを総合して押すべきか引くべきかを判断すべきで、他家からリーチがかかったら即ベタオリは NG です。勝てるものも勝てません。
押し引きに対して、引くことしか知らない臆病者の Claude Code に「様子見」をすることを教えました。

が・・・・・
駄目っ・・・・・!

今度は様子見をすることしかしなくなってしまいました。
作問の内容も、リーチ宣言牌をたまたまツモってくるようなシチュエーションで作られるようになりました。

この問題については、どうにも解決策を見出せず、まだ解消できていません。

これからどうしたい?

元々は GitHub Actions などによって、毎日定期的に作問・解答がリポジトリに反映されると嬉しいな!と思って作り始めました。
ただ、これを実現するには API Key を払い出して Claude API を呼び出す他なさそうで、それなら何切る問題の問題集を買う方が圧倒的に安上がりやんけ…となってしまってます。

かといって、単純なスクリプトを作って動かすようにする…というのも、AI を利用して自由度の高い状況設定を元にした作問を作りたい意向にそぐわないなともなっています。
(まぁ、そもそもの作問のレベルが思っていたほどではないので、まずは作問クオリティの改善が大事そうではありますが…)

何かいいアイデアがあれば是非 contribute してもらえると嬉しいです!

おわりに

AI の力を使えば、簡単にいい感じの問題を作ってもらえるし、めっちゃ造詣の深い解答をもらえるのでは!?という思いつきで作ってみましたが、思った以上に AI は麻雀のことを知らないようでした。
利用した AI Agent が Coding 特化である Claude だったということも上手くいかなかった要因の一つかもしれません。これが例えば、Gemini であったり ChatGPT であればまたちょっと違った結果になったかもしれません。
将棋や碁の世界では AI の活用が進んでいるイメージがありますが、麻雀の世界はまだまだこれからなのでしょうか?

人生の決断力を上げるための道は険しいと思ったのでした。

明日は よしだ さんによる PHP の布教に関するお話です!
PHP の進化について語ってくれるようですよ!お楽しみに!