麻雀AIインターフェイスのjavadocはこちら

麻雀AIの仕様と作成の手引き

2007/12/20 改訂(細かい点を修正)
2003/06/25 改訂(Dummy K.I. のソース・バージョン0.83を公開)
2003/02/24 改訂(Dummy K.I. のソースを公開)
2003/02/22 改訂(インターフェイスバージョン3 に対応)
2003/02/15 改訂(インターフェイスバージョン2 に対応)

「まうじゃん for Java」では、コンピュータプレイヤーの思考ルーチンが本体プログラムと独立して作成できるようになっています。この思考ルーチンのことを「麻雀AI」と呼びます。ここでは麻雀AIの作り方を説明します。

1.麻雀AIってどんなもの?

 麻雀AIはJavaで実装される麻雀用思考ルーチンです。麻雀AIは jp.gr.java_conf.ishihata.mj_ai パッケージに用意されているクラス群を利用して作成します。これらのクラスは麻雀AIとそれを実際に利用する本体プログラムとのインターフェイスを提供します。ここで言う「本体プログラム」は基本的に「まうじゃん for Java」を指しますが、このインターフェイスはある程度の汎用性を持っているため、このインターフェイスに対応する麻雀プログラムが現れるかもしれません。その場合はそのプログラムもまた「本体プログラム」に該当します。つまり、このインターフェイスを使えば本体プログラムにあまり依存しない思考ルーチンを作ることができ、このインターフェイスに対応するすべての麻雀プログラムで動作させることができる、というわけです。(←希望的観測(^_^;)

 麻雀AIは JAR ファイルになります。基本的に1AIで1ファイルですが、仕様上は1ファイルに複数のAIを入れてしまうこともできます。麻雀AIのファイルは本体プログラムが指定する特定のディレクトリに置くだけで、本体プログラムによって自動的に認識されます。たとえば「まうじゃん for Java」では本体が置かれているディレクトリにある ais ディレクトリがそれです。

 麻雀AIに関するクラスについては仕様クラスパッケージファイルを公開します。また、これらの情報を利用して作成した独自の麻雀AI、あるいはこの仕様やソースを取り込んだ独自のプログラムは、私用/商用を問わず自由に利用、公開、配布可能とします。

2.必要な開発環境

 麻雀AIを開発するには以下の物が必要です。(テキストエディタにこだわらなければ)これらはすべて無償で手に入ります。

以下ではJDKのbinディレクトリにパスが通っているものとして説明します。

3.開発の流れ

 麻雀AIの開発の流れは次のようになります。

  1. ソースを書く。
  2. マニフェストファイルを作る。
  3. ソースをコンパイルする。
  4. class ファイルとマニフェストファイルを JAR ファイルにパックする。

4.とりあえず作ってみる

 それでは、3.で説明した手順で簡単なAIを作ってみましょう。ここでは「ただツモ切りするだけ」のAIを作ってみます。仕様は以下の通りです。

4.0 準備

 まずは開発をおこなうための下準備をします。といってもディレクトリを作るだけです。
 ディスクの適当な場所に適当な名前の新しいディレクトリを作ってそのディレクトリに移動します。さらにその下にjp/gr/java_conf/ishihata/maujong_ais/ までのディレクトリを作ります。たとえば、test というディレクトリの下で開発する場合は以下のようにします。

mkdir test
cd test
mkdir jp
mkdir jp/gr
mkdir jp/gr/java_conf
mkdir jp/gr/java_conf/ishihata
mkdir jp/gr/java_conf/ishihata/maujong_ais
4.1 ソースを書く

 以下のコードをテキストエディタで書いて、MJAI_Test.java というファイル名でディレクトリ jp/gr/java_conf/ishihata/maujong_ais に保存します。

package jp.gr.java_conf.ishihata.maujong_ais;

import jp.gr.java_conf.ishihata.mj_ai.*;

public class MJAI_Test extends MJ_AI {
    public String getName() {
        return "Test";
    }
}
4.2 マニフェストファイルを作る

 以下のテキストをテキストエディタで書いて、MANIFEST.MF というファイル名で保存します。

Manifest-Version: 1.0

Name: jp/gr/java_conf/ishihata/maujong_ais/MJAI_Test.class
Maujong-AI: true
AI-Version: 3
4.3 ソースをコンパイルする

 MJAI_Test.java をコンパイルします。このとき、クラスパスとして麻雀AIインターフェイスのクラスパッケージファイル mj_ai_classes.zip を指定します。たとえば mj_ai_classes.zip ファイルが ../maujong/ ディレクトリにあるなら、以下のようにします。

javac -classpath ../maujong/mj_ai_classes.zip jp/gr/java_conf/ishihata/maujong_ais/MJAI_Test.java
4.4 class ファイルとマニフェストファイルを JAR ファイルにパックする

 コンパイルしてできた MJAI_Test.class ファイルと MANIFEST.MF ファイルを Test.jar にパックします。

jar cvfm Test.jar MANIFEST.MF jp/gr/java_conf/ishihata/maujong_ais/MJAI_Test.class
4.5 動かしてみる

 それでは出来上がったAIを実際に動かしてみましょう。
 できたjarファイル(Test.jar)を「まうじゃん for Java」のディレクトリにある ais というディレクトリの中に入れて、「まうじゃん for Java」を起動します。ちゃんとにできていれば、面子の選択で「Test」というコンピュータプレイヤーを選択できるようになっているはずです。それを選んでゲームを開始してみましょう。

5.開発の詳細

5.1 概要

 麻雀AIの実体はJavaのclassファイルです。このclassファイルと、それに定義されているAIのクラス名を本体プログラムに知らせるためのマニフェストファイルと JAR ファイルにパックすることで麻雀AIファイルが出来上がります。

5.2 AIクラス

 AIの実装は、麻雀AIインターフェイスのクラスパッケージファイル mj_ai_classes.zip に含まれる jp.gr.java_conf.ishihata.mj_ai パッケージのクラス群を利用しておこないます。以下の説明では jp.gr.java_conf.ishihata.mj_ai.* をインポートしているものと仮定して話を進めます。

 麻雀AIはclassファイルになることからもわかるように、AIはクラスとして実装します。AIクラスは MJ_AI クラスを継承している必要があります。MJ_AI クラス自体は「ツモ切り」するだけの思考ルーチンを提供します。独自のAIを開発するには、このクラスを継承して必要なメソッドをオーバーライドします。
 ここでは実際にAIを開発する際に特に重要な箇所について説明します。AIインターフェイスの詳細については javadoc を参照してください。

 まず最初にオーバーライドするべきは MJ_AI#getName() メソッドです。このメソッドはそのAIの名前を返す必要があります。たとえば hogehoge という名前のAIにしたい場合は以下のようにします。

public String getName() {
    return "hogehoge";
}

 次に MJ_AI#initialize() をオーバーライドします。このメソッドはゲーム開始前に一度だけ呼び出され、その際に MIPIface クラスのインスタンスが渡されます。AIはこのインスタンスのメソッドを呼び出すことで、ゲーム中のあらゆる情報を得ることができるようになります。MIPIface のインスタンスはこのにときしか取得できないので、インスタンス変数に保存しておきます。

private MIPIface iface;

public boolean initialize(MIPIface i)
{
    iface = i;
    return true;
}

 これからが本番です。捨て牌を選ぶアルゴリズムを書きます。これは MJ_AI#onSutehai() メソッドをオーバーライドする形でおこないます。このメソッドは引数として現在の自分の手牌と今つもってきた牌の情報を受け取ります。牌の情報は MJIHaiReader クラスで、手牌の情報は MJITehaiReader クラスで表現されます。これらの情報を利用して捨て牌を選び、その情報を返します。「ツモ」、「流し」、「カン」、「リーチ」を指定して返すこともできます。一般的には次のようなインプリメントになります。

public int onSutehai(MJITehaiReader te, MJIHaiReader tsumohai)
{
    if (流す条件) return MJPIR_NAGASHI;
    if (ツモの条件) return MJPIR_TSUMO;
    if (カンの条件) return MJPIR_KAN | (カンする牌の牌番号);
    if (リーチする条件) return MJPIR_REACH | (捨てる牌のインデックス);
    return MJPIR_SUTEHAI | (捨てる牌のインデックス);
}

リスト中の(捨てる牌のインデックス)は、引数 te の MJITehaiReader#getTehai() メソッドによって返される MJIHai[] 配列の添字に対応します。この配列は自分の純手牌をそっくりそのまま表していますので、その中の何番目の牌を捨てるか、ということを返すわけです。ただしこの配列には今つもってきたばかりの牌は含まれていません。「ツモ切り」する場合は、このインデックスには 13 を指定します。なお、高度な思考アルゴリズムを構築する場合には自分の手牌情報以外の情報(対戦相手の捨て牌など)も考慮して捨て牌を選ぶことになるでしょう。このような情報をゲットするためには initialize() で保存しておいた MIPIface インターフェイスを利用します。

 次に必要になるのは相手の捨て牌に対する自分のリアクションです。つまり、だれかが捨てた牌が自分の当たり牌だったら「ロン」しなければいけません。必要であれば「ポン」「チー」「カン」もしなければなりません。そのためには MJ_AI#onAction() メソッドをオーバーライドします。このメソッドは、基本的には自分を含めた誰かが何かしらの行動をおこなったときに呼び出されるものですが、同時にその行動に対する自分のリアクションを返すことができるように設計されています。

public int onAction(int action, int player_no, int target_no, MJIHaiReader hai)
{
    switch(action){
    case MJPIR_REACH :    // リーチ
    case MJPIR_SUTEHAI :  // 捨て牌
        if (player_no != 0){    // 自分以外の捨て牌だったら
            if (ロンの条件) return MJPIR_RON;
            if (ポンの条件) return MJPIR_PON;
            if (カンの条件) return MJPIR_KAN;
            if (左端にチーする条件) return MJPIR_CHII1;    // たとえば 2,3 に 1 をチーする場合
            if (右端にチーする条件) return MJPIR_CHII2;    // たとえば 2,3 に 4 をチーする場合
            if (中間をチーする条件) return MJPIR_CHII3;    // たとえば 2,4 に 3 をチーする場合
        }
        return 0;    // 何もしない
    }
    return 0;    // 何もしない
}

 とりあえず、以上が必要最低限のインプリメント箇所です。必要であれば MJ_AI#onStartKyoku() メソッド(局開始時に呼ばれます)もオーバーライドして初期化作業をおこなってください。また、MIPIface インターフェイスを使うと独自のメッセージ(セリフ)を表示させることもできるので、おもしろいことをしゃべらせるのも楽しいですよ。

 なお、作成したAIを広く公開する場合は、クラス名の一意性を保証するためにAIクラスを世界的に一意なパッケージに入れてください。世界的に一意なパッケージ名の付け方については以下のようなページを参照してください。

Sunのドキュメント(英語)
http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#40169

Java FAQ のパッケージに関するページ(日本語)
http://www.gimlay.org/~javafaq/S017.html#S017-02

5.3 マニフェスト

 マニフェストは JAR ファイルに含まれるファイルに関する情報をまとめたテキストファイルで、JAR の共通仕様です。マニフェストファイルをアーカイブに含めることにより、その JAR に含まれるファイルがどのような働きをするのかを明示することができます。マニフェストの詳細については JAR ファイルの仕様 (Sun のサイト)などを参照してください。
 麻雀AI仕様ではこのマニフェストを利用してAIクラスを明示することにしています。つまり、「〜〜というclassファイルが麻雀AIのクラスですよ」という情報を JAR ファイルに入れておくわけです。そうすることによって、その JAR ファイルを見た本体プログラムが簡単にAIクラスを検索できるようになるって寸法です。

 具体的には、マニフェストファイルにAIクラスに関する以下のようなセクションを追加します。

Name: [クラスファイルの完全なパス]
Maujong-AI: true
AI-Version: [使用しているAIインターフェイスのバージョン]

[クラスファイルの完全なパス]にはAIクラスのclassファイルのパスを含めたファイル名を書きます。クラス名ではなくファイル名であることに注意してください。javaではパッケージ名がそのままディレクトリ名になるので、4.で説明した例では jp.gr.java_conf.ishihata.maujong_ais.MJAI_Test クラスに対して classファイルのパスは jp/gr/java_conf/ishihata/maujong_ais/MJAI_Test.class となります。[使用しているAIインターフェイスのバージョン]には、そのAIクラスが使用しているインターフェイスのバージョン番号(整数)を書きます。本体プログラムがそのバージョンのインターフェイスを実装していない場合は、このAIは認識されません。

 なお、ひとつのJARファイルに複数のAIを入れる場合は、各AIごとにこのセクションをマニフェスとに追加します。つまりAIの数だけこのセクションを書くことになります。

5.4 コンパイル

 コンパイルする際は jp.gr.java_conf.ishihata.mj_ai パッケージがコンパイラに見えてないといけないので、クラスパスに麻雀AIインターフェイスのクラスパッケージファイルファイル mj_ai_classes.zip を指定する必要があります。具体的には以下のようにします。

javac -classpath [mj_ai_classes.zipファイルへのパス] [AIのソースファイルへのパス]
5.5 JAR ファイルの作成

 classファイルとマニフェストファイルが用意できたら JAR ファイルにパックします。マニフェストファイルは「このファイルはマニフェストだよ」とアーカイバに教えてあげないとただのテキストファイルと同様に扱われてしまうことに注意してください。具体的には以下のようなコマンドを使います。

jar cvfm [JARファイル名] [マニフェストファイル名] [クラスファイルのパス]

6.サンプルプログラム

 ここではAIのサンプルとして「まうじゃん for Java」に同梱されているAI「Dummy K.I.」のソースを公開します。開発の参考にしてください。

「Dummy K.I.」ソース・バージョン 0.83 ダウンロード (ZIPファイル)(2003年6月25日)

Copyright (C) 2003 Kyohei Ishihata
この文書を書いた人 : 石畑恭平
この文書への質問等は下記のアドレスにメールしてください。
ishihata@amy.hi-ho.ne.jp