列挙:SimplestAIPlayer

SimplestAIPlayer は最も単純なAIプレーヤーです。

リバーシ盤を左上から順に調べて、打てる場所が見つかればそこに打ちます。
打てる場所が1つも見つからなければ、パスを宣言します。

SimplestAIPlayer はリバーシ盤を左上から順に走査します。

とても単純ですね。しかし、侮るなかれ! すべての可能性を順番にピックアップして調べ上げるというのは、色々な処理の基本となる考え方です。アルゴリズムの基本中の基本ですので、十分、手に馴染ませておきましょう!

余談:常に左上から調べるのは効率が悪いかもしれません。より良い方法はないでしょうか?
あるいは、たとえば将棋の SimplestAIPlayer を作るとしたら「すべての可能性」をどのようにピックアップしたらよいでしょうか??
考えてみると面白いかもしれません。

さて、SimplestAIPlayer はソースコードもとてもシンプルです。

@Override
public Point decide(Board board, Color color, long givenMillisPerTurn, long remainingMillisInGame) {
    
    return Point.stream()
            .filter(p -> Rule.canPutAt(board, color, p))
            .findFirst().orElse(null);
}

ただ、上のソースコードでは Java8 で導入された Stream やラムダ式、Optional クラスを利用しているので、少しわかりにくいかもしれません。

上記のコードと同じ振る舞いをする代替実装例をいくつか挙げてみます。
上記のコードが分かりにくいと感じる方は、順を追って見ていくと理解しやすいかもしれません。

代替実装例1

次のコードは正規のコードと同じ結果を返します。

@Override
public Point decide(Board board, Color color, long givenMillisPerTurn, long remainingMillisInGame) {
    
    // リバーシ盤の左上から順番に調べる。
    for (int i = 0; i < Point.HEIGHT; i++) {
        for (int j = 0; j < Point.WIDTH; j++) {
            Point p = Point.of(i, j);
            if (Rule.canPutAt(board, color, p) {
                // 石を打てる場合は、その場所を選択する。
                return p;
            }
        }
    }
    
    // 打てる場所が見つからなかった場合は、パスを宣言する。
    return null;
}

Point#of は位置 (i, j) を表す Point オブジェクトを返します。
Rule#canPutAt は指定された位置に石を打てるかを返します。
詳細は javadoc を参照してください。

代替実装例2

次の例では、代替実装例1の (i, j) によるループの代わりに、Java 5 で導入された拡張 for 文(for-each ループ)を利用しています。

@Override
public Point decide(Board board, Color color, long givenMillisPerTurn, long remainingMillisInGame) {
    
    // リバーシ盤の左上から順に調べる。
    for (Point p : Point.values()) {
        if (Rule.canPutAt(board, color, p) {
            // 石を打てる場合は、その場所を選択する。
            return p;
        }
    }
    
    // 打てる場所が見つからなかった場合は、パスを宣言する。
    return null;
}

Point#values は、すべての Point オブジェクトを格納した配列(Point[])を返します。

代替実装例1と比べるとループの方法を変えただけですが、随分すっきりしましたね。

代替実装例3

さて、いよいよ Stream、ラムダ式、Optional クラスを導入します。
代替実装例1, 2とはガラッと変わります。

@Override
public Point decide(Board board, Color color, long givenMillisPerTurn, long remainingMillisInGame) {
    
    // リバーシ盤上のすべての場所のうち、
    Stream<Point> points1 = Point.stream();
    
    // 石を打てる場所だけに絞り込み、
    Stream<Point> points2 = points1.filter(p -> Rule.canPutAt(board, color, p));
    
    // 最初に見つかった打てる場所を選択する。
    Optional<Point> p1 = points2.findFirst();
    
    // 打てる場所が見つからなかった場合は、パスを宣言する。
    Point p2 = p1.orElse(null);
    
    return p2;
}

ステップごとに行を分けましたが、これを連結すると冒頭に挙げた正規のコードになります。

代替実装例1, 2が「どのような手順で処理を行うか」という手続き指向のコードなのに対して、正規のコードや代替実装例3は「何をしたいのか」「どのような結果が欲しいのか」という、いわば目的指向のコードになっています。
そのため、Stream やラムダ式、Optional クラスなどを使いこなすと、とても読みやすく理解しやすいコードになります。

しかし、従来のコードとはかなり異なる概念を把握する必要があり、バグを含まない安全なコードを実装するためには、そこそこの知識が必要になります。

そのため、個人的には、Java 初級者の人が無理に利用しようとする必要はないと思います。代替実装例2のコードをすらすら書けるようになれば、まずは十分でしょう。

スポンサーリンク
reversi_main 336*280
reversi_main 336*280

フォローする