人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習、Deep Learning、Kerasに関する記事が多いです。



勝敗を判定する

勝敗判定機能をつけます。また黒と白の石の数が上部に表示されるようにします。

othello05.jar

石の数を数える

勝敗を判定するにはまず黒と白の石の数を数える必要があります。countStone()で数えています。

    private Counter countStone() {
        Counter counter = new Counter();

        for (int y = 0; y < MASU; y++) {
            for (int x = 0; x < MASU; x++) {
                if (board[y][x] == BLACK_STONE)
                    counter.blackCount++;
                if (board[y][x] == WHITE_STONE)
                    counter.whiteCount++;
            }
        }

        return counter;
    }

盤面を走査していって黒石が打たれていたら黒石の数(counter.blackCount)を1増やし、白石が打たれていたら白石の数(counter.whiteCount)を1増やしています。このメソッドでは黒石と白石の数の両方の値を返したいです。しかし、メソッドの戻り値は1 つであるため2つのint型の値を返すことはできません。こんな場合は2つの値(ここでは黒石と白石の数)をクラスで包み込んで値を設定し、オブジェクトを1つだけ返す手法がよく使われます。黒石と白石の数をを持つクラスがCounterクラスです。

    private class Counter {
        public int blackCount;
        public int whiteCount;

        public Counter() {
            blackCount = 0;
            whiteCount = 0;
        }
    }

簡単なクラスなので内部クラス(クラスの中のクラス)としました。countStone()の戻り値はcounterオブジェクト1つだけです。1 つのオブジェクトでblackCountとwhiteCountの2つの値が返せます。

情報パネル

黒白の石の数を表示する領域を作りました。InfoPanelとして実装し、フレームの上の方に配置しました。

    public Othello() {
        ・・・
        Container contentPane = getContentPane();

        // 情報パネルを作成する
        InfoPanel infoPanel = new InfoPanel();
        contentPane.add(infoPanel, BorderLayout.NORTH);

        // メインパネルを作成してフレームに追加
        MainPanel mainPanel = new MainPanel(infoPanel);
        contentPane.add(mainPanel, BorderLayout.CENTER);

        ・・・
    }

MainPanelにInfoPanelを渡している点に注意してください。MainPanelで石の数を数えてInfoPanelへ表示するので MainPanelからInfoPanelへの参照がないと情報パネルを操作できません。MainPanelの中のpaintComponent()でラベルを変更しています。

    // 盤面の石の数を数える
    Counter counter = countStone();
    // ラベルに表示
    infoPanel.setBlackLabel(counter.blackCount);
    infoPanel.setWhiteLabel(counter.whiteCount);

盤面の石の数を数えてラベルを変更しているだけです。

勝敗判定

オセロの勝敗は64個の石がすべて打たれたときに決まります。最初に4個の石があるため打たれる石の数は60個です。そこで打たれた石の数を数える変数を用意します。

    // 終了時の石の数(オセロは8x8-4=60手で終了する)
    private static final int END_NUMBER = 60;

    // 打たれた石の数
    private int putNumber;

putNumberが60個になったら終了します。勝敗判定を行うメソッドはmouseClicked()にあるendGame()です。石が打たれた後、毎回勝敗を判定しています。

    private void endGame() {
        // 打たれた石の数が60個(全部埋まった状態)以外は何もしない
        if (putNumber == END_NUMBER) {
            // 黒白両方の石を数える
            Counter counter;
            counter = countStone();
            // 黒が過半数(64/2=32)を取っていたら勝ち
            // 過半数以下なら負け
            // 同じ数なら引き分け
            if (counter.blackCount > 32) {
                gameState = YOU_WIN;
            } else if (counter.blackCount < 32) {
                gameState = YOU_LOSE;
            } else {
                gameState = DRAW;
            }
        }
    }

endGame()では打たれた石の数が60個以外のときは何もしません。60個のときは黒と白の石の数を数えます。黒が過半数取っていれば勝ちです。過半数取れなったら負けです。同じ数だった場合は引き分けです。プレイヤーは常に黒だと想定しています。今はどっちも自分で動かしてますが(笑)スタート・ゲームオーバー画面、スタート画面とゲームオーバー画面も簡単ですがつけて見ました。gameStateで状態を管理し、状態に応じて処理を変えるのが常套手段です。

    // ゲーム状態
    private static final int START = 0;
    private static final int PLAY = 1;
    private static final int YOU_WIN = 2;
    private static final int YOU_LOSE = 3;
    private static final int DRAW = 4;

    // ゲーム状態
    private int gameState;

paintComponent()ではgameStateに応じて描くものを変えています。

    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // 盤面を描く
        drawBoard(g);
        switch (gameState) {
            case START :
                drawTextCentering(g, "OTHELLO");
                break;
            case PLAY :
                // 石を描く
                drawStone(g);
                // 盤面の石の数を数える
                Counter counter = countStone();
                // ラベルに表示
                infoPanel.setBlackLabel(counter.blackCount);
                infoPanel.setWhiteLabel(counter.whiteCount);
                break;
            case YOU_WIN :
                drawTextCentering(g, "YOU WIN");
                break;
            case YOU_LOSE :
                drawTextCentering(g, "YOU LOSE");
                break;
            case DRAW :
                drawTextCentering(g, "DRAW");
                break;
        }

    }

マウスをクリックしたときの動作も画面に応じて変えています。

    public void mouseClicked(MouseEvent e) {
        switch (gameState) {
            case START :
                // START画面でクリックされたらゲーム開始
                gameState = PLAY;
                break;
            case PLAY :
                // どこのマスかを調べる
                int x = e.getX() / GS;
                int y = e.getY() / GS;

                // (x, y)に石がおける場合だけ打つ
                if (canPutDown(x, y)) {
                    // その場所に石を打つ
                    putDownStone(x, y);
                    // ひっくり返す
                    reverse(x, y);
                    // 手番を変える
                    flagForWhite = !flagForWhite;
                }
                // 終了したか調べる
                endGame();
                break;
            case YOU_WIN :
            case YOU_LOSE :
            case DRAW :
                // ゲーム終了時にクリックされたらスターとへ戻る
                gameState = START;
                // 盤面初期化
                initBoard();
                break;
        }

        // 再描画する
        repaint();
    }