koba::blog

小林聡: プログラマです

麻雀AIのプログラム構造

今回は 電脳麻将 の思考ルーチン(麻雀AI)のプログラム構造について。

電脳麻将 では対局者を表現するクラスは以下の2つ。

Majiang.Player
コンピュータのプレーヤーを実現するクラス
Majiang.UI
人間のプレーヤーを実現するクラス

Majiang.PlayerMajiang.UI には共通するコードが多いので、Majiang.UIMajiang.Player のサブクラスとした。

Majiang.Player は5つの層からなる。

第1層 通知受信層

Majiang.Game からの通知メッセージを受信し、第2層のメソッドを呼出す。この層に属するメソッドは唯一の入り口である action() のみ。サブクラスの Majiang.UI と共通のコードとする。
呼出す第2層のメソッド名は通知メッセージのtypeと一致する。*1

第2層 状態管理層

通知メッセージに応じて内部状態を変更し、第3層のメソッドを呼出す。この層もサブクラスの Majiang.UI と共通のコード。
Majiang.Game は牌山、4人の手牌、河の情報を持っているが Majiang.Player に通知するのはその断片的な情報のみなので、Majiang.Player 側でも通知されたメッセージから手牌などの情報を組み立てて維持しなければならない。維持する情報は手牌以外に、

  • 現在第一巡目か(九種九牌で流せるかの判断に使う)
  • 残り自摸の牌数(4枚以下の場合立直はかけられない)
  • 副露しているか(立直できるかの判断に使う)
  • 立直しているか(立直後は和了以外は自摸切りしかできない)
  • フリテン状態か

など。
呼出す第3層のメソッド名は第2層のメソッド名の先頭にaction_を付加したもの。zimo() からは action_zimo() を呼出す。

第3層 応答送信層

Majiang.Game から送られたcallback関数を呼出して応答を返す層。この層は2つのクラスで実装が異なる。Majiang.Player は第4層の思考ルーチンを呼出して得られた結果を応答として返す。Majiang.UI はUIを通して人間が選択した応答を返す。

第4層 思考ルーチン層

Majiang.Player にのみ存在する層。第5層のメソッドを呼出すことで得られた可能な行動から適切と判断する行動を選択する。
第4層に属するメソッドは以下の6つ。

select_fulou()
副露するか否か。現在は副露しない。
select_gang()
暗槓、加槓するか。現在は向聴数が増えない限り槓する。
select_lizhi()
立直するか否か。現在は聴牌即立直。
select_hule()
和了するか否か。現在は和了できるときは必ずする(見逃しはしないということ)。
select_pingju()
九種九牌で流すか否か。現在は3向聴以内なら流さず国士無双を狙う。
select_dapai()
どの牌を打牌するか。打牌選択のアルゴリズムは次回説明する。

第5層 行動提示層

第2層で作成した内部状態を利用し、ルールにしたがって可能な行動の一覧を返すメソッド。サブクラスの Majiang.UI と共通のコード。Majiang.UI は可能な行動がある場合、ボタンを表示して人間に行動選択をうながす。

第5層に属するメソッドは以下の7つ。

get_dapai()
打牌可能な牌の一覧を返す。喰い替となる場合、その牌は一覧に含めない。
get_chi_mianzi()
チー可能な面子の一覧を返す。
get_peng_mianzi()
ポン可能な面子を返す(1つしかないはず)。
get_gang_mianzi()
大明槓、暗槓、加槓可能な面子の一覧を返す。
allow_lizhi()
ルール上立直可能か否かを返す。
allow_hule()
ルール上和了可能か否かを返す。
allow_pingju()
ルール上流局させることが可能か否かを返す。

次回は ver.0.3 までの打牌選択のアルゴリズムと、ver.0.4 での改良について。

*1:自摸(zimo)の場合、呼出すメソッドは zimo()