人工知能に関する断創録

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

はさんだ石をひっくり返す

はさんだ石がひっくり返るようにしてみます。

othello04.jar

石をひっくり返す

石をひっくり返す処理はmouseClicked()で呼び出しているreverse()です。

    public void mouseClicked(MouseEvent e) {
        // どこのマスかを調べる
        int x = e.getX() / GS;
        int y = e.getY() / GS;

        // (x, y)に石がおける場合だけ打つ
        if (canPutDown(x, y)) {
            // その場所に石を打つ
            putDownStone(x, y);
            // ひっくり返す
            reverse(x, y);
            // 手番を変える
            flagForWhite = !flagForWhite;
        }
        // 盤面が変化したので再描画する
        repaint();
    }

石を打ったあとにひっくり返しています。あと余談ですが手番を変える処理はputDownStone()の中に入れていたのですが外に出しました。 putDownStone()の中で手番が変わってしまうとreverse()を呼び出したときに相手の手番になってしまうからです。reverse(x,y)は(x,y)に打ったときひっくり返せる石をすべてひっくり返すメソッドです。

    private void reverse(int x, int y) {
        // ひっくり返せる石がある方向はすべてひっくり返す
        if (canPutDown(x, y, 1, 0))   reverse(x, y, 1, 0);
        if (canPutDown(x, y, 0, 1))   reverse(x, y, 0, 1);
        if (canPutDown(x, y, -1, 0))  reverse(x, y, -1, 0);
        if (canPutDown(x, y, 0, -1))  reverse(x, y, 0, -1);
        if (canPutDown(x, y, 1, 1))   reverse(x, y, 1, 1);
        if (canPutDown(x, y, -1, -1)) reverse(x, y, -1, -1);
        if (canPutDown(x, y, 1, -1))  reverse(x, y, 1, -1);
        if (canPutDown(x, y, -1, 1))  reverse(x, y, -1, 1);
    }

前回の石が打てるかどうか調べるcanPutDown()と同じ方法を取っています。各8方向に対して順にひっくり返す処理reverse()を呼び出しています。ここでもreverse()をオーバーロードしています。

    private void reverse(int x, int y, int vecX, int vecY) {
        int putStone;
        
        if (flagForWhite) {
            putStone = WHITE_STONE;
        } else {
            putStone = BLACK_STONE;
        }

        // 相手の石がある間ひっくり返し続ける
        // (x,y)に打てるのは確認済みなので相手の石は必ずある
        x += vecX;
        y += vecY;
        while (board[y][x] != putStone) {
            // ひっくり返す
            board[y][x] = putStone;
            // カチッ
            kachi.play();
            // 盤面が更新されたので再描画
            update(getGraphics());
            // 小休止を入れる(入れないと複数の石が一斉にひっくり返されてしまう)
            sleep();
            x += vecX;
            y += vecY;
        }
    }

自分の手番を調べて自分の石が出てくる間相手の石をひっくり返していきます。ひっくり返す=自分の石を打つことです。canPutDown()で相手の石があることが必ず保証されているので最低1つはひっくり返せるはずです。

小休止を入れる

reverse()で小休止を入れるsleep()を呼び出しているのに気づきました?sleep()は500ミリ秒だけ休止するメソッドです。

    // 小休止の時間
    private static final int SLEEP_TIME = 500;    
    
    private void sleep() {
        try {
            Thread.sleep(SLEEP_TIME);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

休止を入れないと2枚以上ひっくり返せる場合に一斉にひっくり返ってしまいます。1枚ずつカチッ、カチッってひっくり返ってほしかったため休止を入れました。相手の石が1つずつじわじわと死んでいくのを見るのは楽しいですよね?せっかちな人ははずしてください(笑)あともう1つputDownStone()でも休止を入れています。

    private void putDownStone(int x, int y) {
        int stone;

        // どっちの手番か調べて石の色を決める
        if (flagForWhite) {
            stone = WHITE_STONE;
        } else {
            stone = BLACK_STONE;
        }
        // 石を打つ
        board[y][x] = stone;
        // カチッ
        kachi.play();
        // 盤面が更新されたので再描画
        update(getGraphics());
        // 小休止を入れる(入れないとすぐにひっくり返しが始まってしまう)
        sleep();
    }

ここも同じ理由です。sleep()を入れないと打ったとたんにひっくり返されてしまいます。

すぐに描画する

reverse()とputDownStone()の中で再描画に

    // 盤面が更新されたので再描画
    update(getGraphics());

を使っていたのに気づきました?普通は再描画にrepaint()を使うんですがrepaint()ではうまくいきません。repaint()は呼び出してもすぐに再描画されるわけではないからです。1枚ひっくり返したらすぐにそのタイミングで再描画してほしかったのでこれでは都合が悪いです。すぐに再描画したいときは上のように書きましょう。次は黒白の石の数を表示するパネルを追加して勝敗判定が行われるようにしてみます。