人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

石が打てる場所か判定する

オセロで石が打てる場所は相手の石をひっくり返せるところだけです。今回は石が打てる場所を判定する処理を実装します。

othello03.jar

石が打てるか判定する

石を打つmouseClicked()に注目してください。

    public void mouseClicked(MouseEvent e) {
        ・・・
        // (x, y)に石が打てる場合だけ打つ
        if (canPutDown(x, y)) {
            // その場所に石を打つ
            putDownStone(x, y);
            ・・・
        }
        ・・・
    }

canPutDown(x,y)で(x,y)の位置に石が打てるか調べる処理が入っています。ここでtrueが返されたときだけputDownStone(x,y)で実際に石を打っています。canPutDown()は複雑なメソッドなので順番に説明します。

    private boolean canPutDown(int x, int y) {
        // (x,y)が盤面の外だったら打てない
        if (x >= MASU || y >= MASU)
            return false;
        // (x,y)にすでに石が打たれてたら打てない
        if (board[y][x] != BLANK)
            return false;
        // 8方向のうち一箇所でもひっくり返せればこの場所に打てる
        // ひっくり返せるかどうかはもう1つのcanPutDownで調べる
        if (canPutDown(x, y, 1, 0))
            return true; // 右
        if (canPutDown(x, y, 0, 1))
            return true; // 下
        if (canPutDown(x, y, -1, 0))
            return true; // 左
        if (canPutDown(x, y, 0, -1))
            return true; // 上
        if (canPutDown(x, y, 1, 1))
            return true; // 右下
        if (canPutDown(x, y, -1, -1))
            return true; // 左上
        if (canPutDown(x, y, 1, -1))
            return true; // 右上
        if (canPutDown(x, y, -1, 1))
            return true; // 左下

        // どの方向もだめな場合はここには打てない
        return false;
    }

(x,y)が盤面の外、またはすでに石がある場合打てないのは当たり前です。問題は次です。8方向すべてに対して打てるかどうか canPutDown()で順に調べています。オセロでは相手の石をひっくり返せる場所にしか石を打てないというルールがあります。8方向のどれか1つでひっくり返せればtrueが返されて石が打てます。

f:id:aidiary:20100509191705g:plain

canPutDown()ってのは2つあります。

    private boolean canPutDown(int x, int y)
    private boolean canPutDown(int x, int y, int vecX, int vecY)

上は(x,y)に石が打てるか調べるメソッドです。もう1つのは各方向で石が打てるか調べるメソッドです。このように引数は違う同じ名前のメソッドを定義することをオーバーロードと呼びます。(vecX, vecY)は1か-1を取ります。これはドラクエで勇者を移動させる方向を扱ったときと同じ方法です。たとえば、canPutDown(x,y,1,1) は(x+1,y+1)となり右下方向を調べます。もう1つのcanPutDown()は少し複雑です。

    private boolean canPutDown(int x, int y, int vecX, int vecY) {
        int putStone;

        // 打つ石はどれか
        if (flagForWhite) {
            putStone = WHITE_STONE;
        } else {
            putStone = BLACK_STONE;
        }

        // 隣の場所へ。どの隣かは(vecX, vecY)が決める。
        x += vecX;
        y += vecY;
        // 盤面外だったら打てない
        if (x < 0 || x >= MASU || y < 0 || y >= MASU)
            return false;
        // 隣が自分の石の場合は打てない
        if (board[y][x] == putStone)
            return false;
        // 隣が空白の場合は打てない
        if (board[y][x] == BLANK)
            return false;

        // さらに隣を調べていく
        x += vecX;
        y += vecY;
        // となりに石がある間ループがまわる
        while (x >= 0 && x < MASU && y >= 0 && y < MASU) {
            // 空白が見つかったら打てない(11つもはさめないから)
            if (board[y][x] == BLANK)
                return false;
            // 自分の石があればはさめるので打てる
            if (board[y][x] == putStone)
                return true;
            x += vecX;
            y += vecY;
        }
        // 相手の石しかない場合はいずれ盤面の外にでてしまうのでこのfalse
        return false;
    }

まず白か黒のどちらの石を打った場合かを調べてputStoneに入れておきます。次に(vecX,vecY)の方向にある隣のマスを調べます。そこが盤面外だったらはさめないのでfalseを返します。隣が自分の石だった場合もはさめないのでfalseを返します。空白の場合もはさめないので falseを返します。はさめる可能性があるのは相手の石がある場合のみです。その場合はさらに(vecX,vecY)の方向に進んで調べます。そこが空白の場合ははさめないのでfalseを返します。もし自分の石があれば間にある相手の石をはさめるのでtrueを返します。これを盤面外に出るまでwhileループで調べていきます。もし盤面外に出てしまったらはさめないのでfalseを返します。このような処理を8方向全部にわたって調べています。次回ははさんだ相手の石をひっくり返せるようにします。