私たちaduceが開発した麻雀ゲーム雀天(janten.net)には、4種類の性格を持つAIが搭載されている。バランス型、鉄壁型、速攻型、大物狙い――それぞれ異なる打ち筋で対局を楽しめる。本記事では、機械学習を使わずにルールベースで麻雀AIの思考ルーチンを実装した方法を解説する。
なぜ機械学習ではなくルールベースAIを選んだのか
麻雀AIの実装方針として、深層強化学習やモンテカルロ木探索といった手法も検討した。しかし、以下の理由からルールベースを採用した。
- ブラウザ上でリアルタイム動作が必須 -- 雀天はWebアプリケーションとして動作する。推論にGPUやサーバ通信が必要なモデルは採用できない
- 行動の説明可能性 -- 打牌理由を「現物切り」「受入枚数最大」のようにテキストで表示する機能があり、ブラックボックスなモデルでは実現が難しい
- チューニングの容易さ -- パラメータを数値で管理すれば、性格ごとの振る舞いを直感的に調整できる
結果として、向聴数や受入枚数といった麻雀理論に基づく評価関数を組み合わせる設計に落ち着いた。
4つのAI性格の設計(パラメータ駆動アーキテクチャ)
AIの性格は AITypeConfig という型で定義し、6つの数値パラメータで制御する。
export interface AITypeConfig {
name: string;
defW: number; // 守備重み
atkW: number; // 攻撃重み
ponTh: number; // ポン判断閾値
riichiAg: number; // リーチ積極度
yakuB: number; // 役狙いボーナス
spdB: number; // 速度ボーナス
}4タイプの具体的なパラメータは以下の通りである。
| タイプ | defW | atkW | yakuB | spdB | 特徴 |
|---|---|---|---|---|---|
| BALANCE(バランス型) | 1.0 | 1.0 | 1.0 | 0.0 | 状況判断で攻守を切り替える |
| TEPPEKI(鉄壁型) | 2.0 | 0.8 | 0.5 | 0.0 | 守備重視、放銃率最小 |
| SPEED(速攻型) | 0.3 | 1.5 | 0.2 | 2.0 | 鳴き多用で最速テンパイ |
| BIGHAND(大物狙い) | 0.5 | 1.0 | 3.0 | -1.0 | 染め手・高打点志向 |
同じスコアリング関数に異なる重みを渡すだけで、全く異なる打ち筋を実現している。例えばSPEEDは spdB: 2.0 によりテンパイに近い牌の評価が大幅に上がり、BIGHANDは yakuB: 3.0 により手役に関わる牌を強く保持する。
打牌選択アルゴリズム:向聴数と受入数の評価
打牌選択の核となる aiDiscard 関数では、手牌の各牌を仮想的に切った場合のスコアを算出し、最高スコアの牌を選ぶ。
for (let i = 0; i < sorted.length; i++) {
const candidate = sorted[i];
const remaining = [...sorted];
remaining.splice(i, 1);
let score = 0;
const shantenAfter = calcShanten(remaining);
const acceptance = countAcceptance(remaining, visibleTiles);
const shantenDelta = shantenAfter - currentShanten;
// 向聴数が悪化する牌は大きく減点
if (shantenDelta > 0) score -= 80 * attackWeight;
else if (shantenDelta < 0) score += 40 * attackWeight;
// 受入枚数が多いほど高評価
score += acceptance * 3 * attackWeight;
}向聴数(あがりまでに必要な有効牌の枚数 - 1)は通常形、七対子、国士無双の3パターンで最小値を取る。受入枚数は「向聴数を1つ下げる有効牌が場に何枚残っているか」を数えた値で、手の広さを定量化する指標になる。
守備ロジック:安全牌の6段階分類
他家のリーチや攻撃的副露を検出すると、守備ロジックが起動する。安全度は以下の6段階で分類される。
export type SafetyCategory =
| "genbutsu" // 現物(その人の捨て牌にある)
| "ryou_suji" // 両スジ(両側のスジ牌が切られている)
| "kata_suji" // 片スジ(片側のスジ牌のみ切られている)
| "one_chance" // ワンチャンス(同一牌3枚見え等)
| "unknown" // 情報なし
| "dangerous"; // 無スジ中張牌この分類は、複数の脅威プレイヤーに対して最も危険なカテゴリを採用する。さらに危険度スコア(0〜100)で細かい順位付けも行い、同カテゴリ内での優先度判断に使う。壁(場に3枚以上見えている牌)、リーチ宣言牌の近傍、染め手疑いといった要素も加味される。
局面フェーズによる攻守バランスの動的調整
残り牌数に応じて局面を3フェーズに分け、攻守の重みを動的に調整する。
export function getGamePhase(wallLen: number): GamePhase {
if (wallLen >= 50) return "early"; // 序盤: 手広く構える
if (wallLen >= 20) return "mid"; // 中盤: 向聴数で攻守分岐
return "late"; // 終盤: テンパイなら押す、遠ければオリ
}調整は5段階のステップで適用される。
- タイプデフォルト -- 各AIの基本重み
- 点棒状況 -- BALANCEのみ、リード/ビハインドで攻守を切り替え
- オーラス補正 -- 順位と点差に基づき全タイプで上書き(トップ目は守備、ラス目は全力攻撃)
- 巡目補正 -- 終盤で遠い手なら守備を強化(乗算)
- 脅威プレイヤー補正 -- リーチ者や攻撃的副露者がいれば守備を強化
この多段階の調整により、BALANCEは「大幅リードの終盤はベタオリ」「ビハインドのオーラスは全力攻撃」といった状況適応的な判断を行う。一方でTEPPEKIは常に高い defW を持つため、オーラスのラス目以外は一貫して守備的に振る舞う。
ヘッドレスシミュレータによるAI強度の検証
AIのパラメータ調整には、UIなしで数千局を高速に回すヘッドレスシミュレータを使用した。
export interface SimConfig {
games: number; // 対局数
gameLength: "tonpu" | "hanchan";
players: AITypeConfig[]; // 4人分のAI設定
}シミュレータは1局ごとに配牌、ツモ、打牌、副露、リーチ、和了判定をすべてプログラムで実行し、順位・和了率・放銃率・平均得点などの統計を収集する。4タイプ総当たりで数千半荘を回した結果を見ながら、パラメータの微調整を繰り返した。
この手法により、「SPEEDの放銃率が高すぎる」「BIGHANDの和了率が低すぎる」といった問題を定量的に把握し、各タイプが個性を持ちつつも極端に弱くならないバランスに収束させることができた。
まとめ
ルールベースAIは、向聴数・受入枚数・安全度といった麻雀理論の基本概念をスコアリング関数に落とし込み、パラメータの重み付けで性格を分ける設計にすることで、機械学習なしでも十分に対局を楽しめる強さを実現できる。
特に重要なのは以下の3点である。
- パラメータ駆動設計 -- 1つのアルゴリズムで複数の性格を表現できる
- 多段階の攻守調整 -- 局面・点棒状況・他家の動向を重みの乗算で反映する
- シミュレータ駆動の調整 -- ヘッドレスで大量対局を回し、統計ベースでバランスを取る
4種類のAIと実際に対局してみたい方は、雀天で無料でプレイできる。
ゲームAIの開発や、ルールベースAIの設計にご興味がある方は、お問い合わせからお気軽にご相談ください。aduceではAI・ゲーム開発のコンサルティングを承っています。