人工知能プログラムの開発

人工知能のサンプルコードを編集して、独自の人工知能プログラムを作成します。具体的には、サンプルコードに対して以下の機能を追加していきます。

  1. AI部分に表示される名前を変更する
  2. 駒を置く場所を「ランダム」に決定するように変更する

前提条件

  • Java SDK (version 1.5 以上) がインストールされていること
  • Eclipse (ant-plugin を含む) がインストールされていること

サンプルコードの準備

「人工知能プログラムの作成」と同様に、人工知能のサンプルコードをeclipseのプロジェクトとして読み込みます。

今回は、javaプロジェクト「reversi-ai-program1」を作成し、そこに上記のサンプルコードをインポートしました。(インポート後にライブラリ等の設定が必要です。詳しくは「人工知能プログラムの作成」を参照。)


図1:人工知能サンプルコードを読み込んだeclipseのjavaプロジェクト
 
 

サンプルコードを読み込んだjavaプロジェクトのsrcフォルダに、人工知能のサンプルコードの本体「SampleProcessor.java」が含まれています。このjavaプログラムを変更することで、reversiの人工知能プログラムの動作が変化します。

サンプルコードの内容

ここでは、サンプルコードの内容について説明していきます。

SampleProcessor.java
import jp.takedarts.reversi.Board;
import jp.takedarts.reversi.Piece;
import jp.takedarts.reversi.Position;
import jp.takedarts.reversi.Processor;

/**
 * Reversi人工知能のサンプルプログラム。
 *
 * @author Atushi TAKEDA
 */
public class SampleProcessor
  extends Processor
{
  /**
   * 手番が来たときに、次の手を決定するメソッド。
* * @param board 盤面の状態 * @param piece 自分が打つ駒 * @param thinkingTime 思考時間 * @return 次の手を置く場所 */ @Override public Position nextPosition(Board board, Piece piece, long thinkingTime) { // 次に置ける場所を探す int x = -1; int y = -1; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (board.isEnablePosition(i, j, piece)) { x = i; y = j; } } } // 置く場所をログに出力 log(String.format("next -> (%d, %d)", x, y)); // 置く場所をPositionオブジェクトに変換して返す return new Position(x, y); } /** * この人工知能の名前を返す。 * * @return 人工知能の名前 */ @Override public String getName() { return "サンプルプログラム"; } }

11行目~12行目

人工知能クラスは、jp.takedarts.reversi.Processorクラスを継承したクラスとして作成します。

23行目

人工知能クラスは、必ずnextPositionメソッドを実装する必要があります。nextPositionメソッドの引数と戻り値は以下の通りです。

public Position nextPosition(
  Board board, Piece piece, long thinkingTime)
第1引数: board
現在の盤面の状態を格納したオブジェクトです。詳しくは後述します。
第2引数: piece
自分が置く駒の種類です。列挙型オブジェクトになっており、Piece.BLACKPiece.WHITEと比較して、自分の置く駒が黒なのか白なのかを判断します。
第3引数: thinkingTime
駒を置く場所を計算するために与えられた時間(単位はミリ秒)です。この時間を経過してもnextPositionメソッドが終了しなかった場合、思考時間のタイムアウトとみなされ、相手方の勝利となります。
戻り値
次に駒を置く場所を返します。戻り値の方はPositionクラスです。

26行目~36行目

次に駒を置ける場所を探しています。boardには現在の盤面の状態が格納されており、isEnablePositionメソッドを呼び出すことにより、指定された場所に駒をおけるかどうかを判定することができます。具体的には、31行目で「置けるor置けない」の判定を行っています。ここで、ij は盤面の座標であり、pieceは次に置く駒の種類を指定しています。

39行目

ログを出力しています。文字列を指定してlogメソッドを呼び出すと、その文字列はReversiプログラムのGUI部分に表示されます。

42行目

次に駒を置く場所をPosition型のオブジェクトとして返してします。26行目~36行目の判定処理により、「最後に見つかった駒の置ける場所」が xy に入っています。これをPosition型のオブジェクトに変換し、次に駒を置く場所として返します。

51行目

getNameメソッドを実装することで、この人工知能の名前を指定できます。ここでは、人工知能の名前として「サンプルプログラム」を指定しています。

人工知能の表示名の変更

まずは、Reversiプログラムに表示される人工知能の名前を変更します。サンプルプログラムの51行目~54行目を以下のコードに変更してください。

  public String getName()
  {
    return "改良版のサンプルプログラム";
  }

これをantでビルドし、人工知能ファイルreversi-ai-sample.jarを作成します。新たに作成した人工知能ファイルをReversiプログラムから読み込むと、人工知能(AI)の表示名が「改良版のサンプルプログラム」に変わります。


図2:作成した人工知能ファイルをReversiプログラムから読み込んだときの画面
 
 

getNameメソッドが返す文字列を変更することにより、Reversiプログラムに表示される人工知能の名前を任意に変更できます。

盤面の状態を調べる

人工知能の動作内容を記述するnextPositionメソッドの引数は以下の3個です。

public Position nextPosition(
  Board board, Piece piece, long thinkingTime)
  • 第1引数 board: 盤面の状態を表すオブジェクト
  • 第2引数 piece: 自分が置く駒を表す列挙型オブジェクト
  • 第3引数 thinkingTime: 思考に使える時間の長さ(単位はミリ秒)

盤面の状態はnextPositionメソッドの引数 board から得ることができます。

盤面の状態を確認するためには、盤面の座標を指定しなくてはなりません。このReversiプログラムでは、盤面の位置をX-Yの2次元座標として管理しています。盤面の座標は図3のように設定されれており、左上が座標(0, 0)となり、右下が座標(7, 7)となります。


図3:盤面の位置を表す座標値
 
 

boardオブジェクトのgetPieceメソッドを使うことで、盤面の任意の位置に置かれている駒を調べることができます。

public Piece getPiece(int x, int y)

このメソッドの引数は、盤面のX-Y座標です。また、戻り値は置かれている駒の種類です。戻り値の方はPiece列挙クラスとなっていますので、これをPiece.BLACK(黒)かPiece.WHITE(白)と比較することにより、置かれている駒を調べることができます。

例えば、以下のコードをnextPositionメソッドに追加すると、右下の角に入っている駒についてログに表示するようになります。

    if (board.getPiece(7, 7) == Piece.BLACK) {
      log("右下は黒が取っています");
    }
    else if (board.getPiece(7, 7) == Piece.WHITE) {
      log("右下は白が取っています");
    }
    else {
      log("右下は空いています");
    }

また、boardオブジェクトのisEnablePositionメソッドを使うことにより、任意の場所に駒を置くことができるかどうかを調べることができます。

例えば、以下のコードをnextPositionメソッドに追加すると、四隅(左上、右上、左下、右下)に駒が置けるかどうかを判別し、その結果をログに表示するようになります。

    if (board.isEnablePosition(0, 0, piece)){
      log("左上に置けます");
    }
    if (board.isEnablePosition(7, 0, piece)){
      log("右上に置けます");
    }
    if (board.isEnablePosition(0, 7, piece)){
      log("左下に置けます");
    }
    if (board.isEnablePosition(7, 7, piece)){
      log("右下に置けます");
    }

上記以外のメソッドについてはJavaDocに含まれるのBoardクラスの説明を参考にしてください。

ランダムに手を選ぶ人工知能プログラムの作成

サンプルプログラムは「最後に見つけた駒を置ける場所」に駒を置いていくプログラムでした。これを、「駒を置ける場所が複数ある場合は、ランダムに場所を決定する」プログラムに変更します。

サンプルプログラムの内容を以下のプログラムコードに変更してみてください。

具体的な変更箇所は次の2ヶ所です。

18行目
乱数を発生させるためのオブジェクトを用意
31行目~48行目
駒を置ける場所の一覧を作成(32行目~43行目)し、その中から1つの場所をランダムに選ぶ(46行目~48行目)

このプログラムをantでビルドして作成された人工知能ファイルをReversiプログラムから読み込むと、毎回異なる動作をするようになります。

SampleProcessor.java
import java.util.Random;
import jp.takedarts.reversi.Board;
import jp.takedarts.reversi.Piece;
import jp.takedarts.reversi.Position;
import jp.takedarts.reversi.Processor;

/**
 * Reversi人工知能のサンプルプログラム。
 *
 * @author Atushi TAKEDA
 */
public class SampleProcessor
  extends Processor
{
  /**
   * 乱数を発生させるオブジェクト。
   */
  private Random _random = new Random(System.currentTimeMillis());

  /**
   * 手番が来たときに、次の手を決定するメソッド。
* * @param board 盤面の状態 * @param piece 自分が打つ駒 * @param thinkingTime 思考時間 * @return 次の手を置く場所 */ @Override public Position nextPosition(Board board, Piece piece, long thinkingTime) { // 次に置ける場所の一覧を探す int[][] positions = new int[64][2]; int count = 0; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (board.isEnablePosition(i, j, piece)) { positions[count][0] = i; positions[count][1] = j; count++; } } } // 次に置く場所をランダムに決定する int index = _random.nextInt(count); int x = positions[index][0]; int y = positions[index][1]; // 置く場所をログに出力 log(String.format("next -> (%d, %d)", x, y)); // 置く場所をPositionオブジェクトに変換して返す return new Position(x, y); } /** * この人工知能の名前を返す。 * * @return 人工知能の名前 */ @Override public String getName() { return "改良版のサンプルプログラム"; } }

図4:ランダムに手を選ぶ人工知能を動作させた画面
 
 

実際に強い人工知能プログラムを作成するには、ランダムに手を選ぶのではなく、駒を置ける場所のなかから最も自分に有利な場所に駒を置くアルゴリズムが必要になります。