【JAVA】確認問題5-☆3:マルバツゲーム(Tic-Tac-Toe)を作成しよう

記事内に商品プロモーションを含む場合があります

この記事の練習問題で使用する知識:
基礎文法と制御構造(レッスン1~2)メソッドの定義と呼び出しメソッドの戻り値真偽値を返すメソッドクラスの定義と使用コンストラクタカプセル化

JAVAのゲームコード一覧はこちら

<<前のページ JAVA Topへ 次のページ>>

JAVA練習問題5-☆3:マルバツゲーム(Tic-Tac-Toe)を作成しよう

簡単なマルバツゲーム(Tic-Tac-Toe)を作成しましょう。

このゲームでは、2人のプレイヤーが交互にマル(○)またはバツ(×)を入力し、縦、横、または斜めのいずれかで同じ記号が3つ並んだら勝ちとなります。

問題文から回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

この問題の要件

以下の要件に従ってコードを書いて下さい。

  • 3×3のゲームボードを作成するために、2次元配列 char[][] board を使用すること。
  • ゲームボードの初期状態は全て空白(’ ‘)に設定すること。
  • 各プレイヤーのターンごとに、現在のゲームボードを表示し、プレイヤーに行を指定させること。
  • プレイヤーが選んだ行と列にマル(○)またはバツ(×)を配置すること。
  • 勝利条件(縦、横、斜めのいずれかに同じ記号が3つ並ぶ)をチェックすること。
  • 勝者が決まった場合、勝者を表示してゲームを終了すること。
  • ゲームボードが全て埋まった場合は引き分けとし、ゲームを終了すること。
  • プレイヤーの入力が無効な場合は、再入力を促すこと。

ただし、以下のような実行結果となること。

----- ↓出力される結果の例↓ -----

-------------
|   |   |   | 
-------------
|   |   |   | 
-------------
|   |   |   | 
-------------
プレイヤー X、行と列を入力してください: 
0 0
-------------
| X |   |   | 
-------------
|   |   |   | 
-------------
|   |   |   | 
-------------
プレイヤー O、行と列を入力してください: 
1 1
-------------
| X |   |   | 
-------------
|   | O |   | 
-------------
|   |   |   | 
-------------
...
プレイヤー X の勝利です!

----- ↑出力される結果の例↑ -----

この問題を解くのヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。

1:Scannerクラスのインポート
2:TicTacToeクラスの定義
□ boardという2次元char配列のフィールドを定義し、サイズを3×3に設定
□ currentPlayerというchar型のフィールドを定義し、’X’に初期化
□ TicTacToeのコンストラクタを定義
□ □ initializeBoardメソッドを呼び出し、ゲームボードを初期化
□ initializeBoardメソッドの定義
□ □ forループで行を3回繰り返す
□ □ □ forループで列を3回繰り返す
□ □ □ □ 各セルに空白文字を代入し、ゲームボードを初期化
□ printBoardメソッドの定義
□ □ 「————-」を出力
□ □ for-eachループでboardの各行を取り出す
□ □ □ 「| 」を出力
□ □ □ for-eachループで各セルを取り出す
□ □ □ □ セルの内容と「 | 」を出力
□ □ □ 改行を出力
□ □ □ 「————-」を出力
□ placeMarkメソッドの定義
□ □ isInBoundsメソッドを呼び出して、行と列の位置が有効範囲かを確認
□ □ □ 真の場合、boardの指定位置にcurrentPlayerのマークを配置
□ □ □ trueを返す
□ □ 偽の場合、falseを返す
□ isInBoundsメソッドの定義
□ □ 行と列が0から2の範囲内であるかを確認し、結果を返す
□ checkForWinメソッドの定義
□ □ checkRows、checkColumns、checkDiagonalsメソッドを順に呼び出し、いずれかがtrueならtrueを返す
□ checkRowsメソッドの定義
□ □ for-eachループでboardの各行を取り出す
□ □ □ 行の0、1、2列目の値がcurrentPlayerと一致しているか確認
□ □ □ □ 真の場合、trueを返す
□ □ □ 偽の場合、falseを返す
□ checkColumnsメソッドの定義
□ □ forループで列を3回繰り返す
□ □ □ 各列の0、1、2行目の値がcurrentPlayerと一致しているか確認
□ □ □ □ 真の場合、trueを返す
□ □ □ 偽の場合、falseを返す
□ checkDiagonalsメソッドの定義
□ □ 2つの対角線のいずれかのセルがcurrentPlayerと一致しているか確認
□ □ □ いずれかが一致した場合、trueを返す
□ changePlayerメソッドの定義
□ □ currentPlayerの値を’X’から’O’、または’O’から’X’に切り替え
□ isBoardFullメソッドの定義
□ □ for-eachループでboardの各行を取り出す
□ □ □ for-eachループで各セルを取り出す
□ □ □ □ セルが空白である場合、falseを返す
□ □ 全てのセルが埋まっている場合、trueを返す
□ mainメソッドの定義
□ □ TicTacToeオブジェクトgameを初期化
□ □ Scannerオブジェクトscannerを初期化
□ □ boolean型変数gameWonをfalseに初期化
□ □ whileループでgameWonがfalseの間、ゲームを繰り返し進行
□ □ □ printBoardメソッドを呼び出して、ボードを表示
□ □ □ int型のrow、col変数を宣言
□ □ □ do-whileループを開始し、ユーザーに行と列を入力させる
□ □ □ □ プレイヤーに行と列の入力を求めて出力
□ □ □ □ 入力値をrowとcolに代入
□ □ □ ループ終了後、placeMarkメソッドを呼び出してマークを配置
□ □ □ checkForWinメソッドを呼び出して勝利条件を確認し、gameWonを更新
□ □ □ gameWonがtrueの場合、ゲームボードを表示し、勝利メッセージを出力
□ □ □ else ifでisBoardFullメソッドを呼び出して引き分けを確認
□ □ □ □ 引き分けの場合、ボードを表示し、引き分けメッセージを出力
□ □ □ breakでループを終了

ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

import java.util.Scanner;

public class TicTacToe {
    private final char[][] board = new char;
    private char currentPlayer = 'X';

    // コンストラクタ:ゲームボードを初期化
    public TicTacToe() {
        initializeBoard();
    }

    // ゲームボードを空白で初期化するメソッド
    /*【穴埋め問題1】
    ここにinitializeBoardメソッドの内容をすべて記述し、ゲームボードの各セルを空白にしてください。
    */

    // ゲームボードを表示するメソッド
    private void printBoard() {
        System.out.println("-------------");
        for (char[] row : board) {
            System.out.print("| ");
            for (char cell : row) {
                System.out.print(cell + " | ");
            }
            System.out.println();
            System.out.println("-------------");
        }
    }

    // マークを配置するメソッド
    /*【穴埋め問題2】
    ここにplaceMarkメソッドの内容をすべて記述し、指定位置にマークを配置してください。
    */

    // 勝利条件をチェックするメソッド
    private boolean checkForWin() {
        return checkRows() || checkColumns() || checkDiagonals();
    }

    // プレイヤーを切り替えるメソッド
    /*【穴埋め問題3】
    ここにchangePlayerメソッドの内容を記述し、プレイヤーを切り替えてください。
    */

    // メインメソッド:ゲームの進行を制御
    public static void main(String[] args) {
        TicTacToe game = new TicTacToe();
        Scanner scanner = new Scanner(System.in);
        boolean gameWon = false;

        while (!gameWon) {
            game.printBoard();
            int row, col;
            do {
                System.out.println("プレイヤー " + game.currentPlayer + "、行と列を入力してください: ");
                row = scanner.nextInt();
                col = scanner.nextInt();
            } while (!game.placeMark(row, col));

            /*【穴埋め問題4】
            ここにcheckForWinメソッドとisBoardFullメソッドの呼び出し、ゲームの結果を出力するコードを書いてください。
            */
        }
    }
}

この問題の穴埋めコードは以上です。

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

マルバツゲームのJAVAコード

この問題の一つの正解例とそのコードの解説を以下に示します。

正解コードの例

例えば以下のようなプログラムが考えられます。

import java.util.Scanner;

public class TicTacToe {
    private final char[][] board = new char[ 3][ 3];
    private char currentPlayer = 'X';

    // コンストラクタ:ゲームボードを初期化
    public TicTacToe() {
        initializeBoard();
    }

    // ゲームボードを空白で初期化するメソッド
    private void initializeBoard() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                board[i][j] = ' ';
            }
        }
    }

    // ゲームボードを表示するメソッド
    private void printBoard() {
        System.out.println("-------------");
        for (char[] row : board) {
            System.out.print("| ");
            for (char cell : row) {
                System.out.print(cell + " | ");
            }
            System.out.println();
            System.out.println("-------------");
        }
    }

    // マークを配置するメソッド
    private boolean placeMark(int row, int col) {
        if (isInBounds(row, col) && board[row][col] == ' ') {
            board[row][col] = currentPlayer;
            return true;
        }
        return false;
    }

    // 座標が有効か確認するヘルパーメソッド
    private boolean isInBounds(int row, int col) {
        return row >= 0 && row < 3 && col >= 0 && col < 3;
    }

    // 勝利条件をチェックするメソッド
    private boolean checkForWin() {
        return checkRows() || checkColumns() || checkDiagonals();
    }

    // 行をチェックするメソッド
    private boolean checkRows() {
        for (char[] row : board) {
            if (row[ 0] == currentPlayer && row[ 1] == currentPlayer && row[ 2] == currentPlayer) {
                return true;
            }
        }
        return false;
    }

    // 列をチェックするメソッド
    private boolean checkColumns() {
        for (int col = 0; col < 3; col++) {
            if (board[ 0][col] == currentPlayer && board[ 1][col] == currentPlayer && board[ 2][col] == currentPlayer) {
                return true;
            }
        }
        return false;
    }

    // 斜めをチェックするメソッド
    private boolean checkDiagonals() {
        return (board[ 0][ 0] == currentPlayer && board[ 1][ 1] == currentPlayer && board[ 2][ 2] == currentPlayer) ||
               (board[ 0][ 2] == currentPlayer && board[ 1][ 1] == currentPlayer && board[ 2][ 0] == currentPlayer);
    }

    // プレイヤーを切り替えるメソッド
    private void changePlayer() {
        currentPlayer = (currentPlayer == 'X') ? 'O' : 'X';
    }

    // ボードが満杯かチェックするメソッド
    private boolean isBoardFull() {
        for (char[] row : board) {
            for (char cell : row) {
                if (cell == ' ') {
                    return false;
                }
            }
        }
        return true;
    }

    // メインメソッド:ゲームの進行を制御
    public static void main(String[] args) {
        TicTacToe game = new TicTacToe();
        Scanner scanner = new Scanner(System.in);
        boolean gameWon = false;

        while (!gameWon) {
            game.printBoard();
            int row, col;
            do {
                System.out.println("プレイヤー " + game.currentPlayer + "、行と列を入力してください: ");
                row = scanner.nextInt();
                col = scanner.nextInt();
            } while (!game.placeMark(row, col));

            if (gameWon = game.checkForWin()) {
                game.printBoard();
                System.out.println("プレイヤー " + game.currentPlayer + " の勝利です!");
            } else if (game.isBoardFull()) {
                game.printBoard();
                System.out.println("引き分けです!");
                break;
            } else {
                game.changePlayer();
            }
        }
    }
}

正解コードの解説

このJavaコードはシンプルなマルバツ(Tic-Tac-Toe)ゲームの実装です。

以下ではこのコードをブロックごとに分けて詳しく説明していきます。

クラスとフィールドの定義

public class TicTacToe {
    private final char[][] board = new char[ 3][ 3];
    private char currentPlayer = 'X';

このコードはTicTacToeというクラスを定義しています。

boardはゲームの盤面を表す3×3の二次元配列で、currentPlayerは現在のプレイヤーを示す文字です。

このフィールドを使ってプレイヤーXとOを交互に表示していきます。

  • private final:このキーワードにより、他のクラスから直接アクセスできず、一度初期化された後に値を変更できないことが保証されています。

コンストラクタとゲームボードの初期化

    public TicTacToe() {
        initializeBoard();
    }

    private void initializeBoard() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                board[i][j] = ' ';
            }
        }
    }

TicTacToe()はコンストラクタでinitializeBoard()メソッドを呼び出してボードを初期化します。

initializeBoardメソッドはボードの全てのセルに空白を代入して初期状態を設定します。

  • forループ:二重ループで3×3の全セルにアクセスし、空白文字をセットします。

ボードを表示するメソッド

    private void printBoard() {
        System.out.println("-------------");
        for (char[] row : board) {
            System.out.print("| ");
            for (char cell : row) {
                System.out.print(cell + " | ");
            }
            System.out.println();
            System.out.println("-------------");
        }
    }

printBoardメソッドは、ゲームの盤面をコンソールに表示します。

forループを使って各行を出力し行と列を分けるために「|」と「-」を使って視覚的に区切りを付けています。

  • for-eachループ:Javaの簡単なループ構文で、配列board内の各行やセルを順に取り出します。

マークを配置するメソッド

    private boolean placeMark(int row, int col) {
        if (isInBounds(row, col) && board[row][col] == ' ') {
            board[row][col] = currentPlayer;
            return true;
        }
        return false;
    }

placeMarkメソッドは指定されたセルに現在のプレイヤーのマーク(XまたはO)を配置します。

このメソッドは指定の位置がボード内であること、かつ空白であることを確認してからマークを配置します。

  • &&(論理AND):両方の条件が満たされる場合にのみtrueを返します。

勝利条件をチェックするメソッド

    private boolean checkForWin() {
        return checkRows() || checkColumns() || checkDiagonals();
    }

checkForWinメソッドはプレイヤーが勝利したかどうかを確認します。

勝利条件として、行、列、または対角線のいずれかに3つのマークが揃う必要があります。

  • ||(論理OR):いずれかの条件がtrueであれば全体としてtrueを返します。

各勝利条件の確認メソッド

    private boolean checkRows() { /* 行をチェックするコード */ }
    private boolean checkColumns() { /* 列をチェックするコード */ }
    private boolean checkDiagonals() { /* 斜めをチェックするコード */ }

checkRowscheckColumns、およびcheckDiagonalsメソッドは、それぞれ行、列、斜めのマークが同一かどうかを確認します。

どれかが揃ったら勝利と判断します。

プレイヤーの切り替えメソッド

    private void changePlayer() {
        currentPlayer = (currentPlayer == 'X') ? 'O' : 'X';
    }

changePlayerメソッドは現在のプレイヤーを切り替えます。現在がXならOに、OならXに変更します。

  • ? :(三項演算子):条件が真であれば左側、偽であれば右側の値を返します。

メインメソッドとゲーム進行

    public static void main(String[] args) {
        TicTacToe game = new TicTacToe();
        Scanner scanner = new Scanner(System.in);
        boolean gameWon = false;

        while (!gameWon) { /* ゲームを進行させるコード */ }
    }

mainメソッドはゲームのエントリーポイントであり、ゲームの進行を制御します。

プレイヤーが交互にマークを配置し、勝利条件や引き分けかどうかを判定します。

  • whileループ:ゲームが終了するまで繰り返し実行されます。

勝利や引き分けの判定と表示

            if (gameWon = game.checkForWin()) { /* 勝利メッセージの出力 */ }
            else if (game.isBoardFull()) { /* 引き分けメッセージの出力 */ }
            else { game.changePlayer(); }

各ターンの終わりにcheckForWinメソッドで勝利条件をチェックし、isBoardFullで引き分けかを確認します。

いずれも該当しなければchangePlayerでプレイヤーを切り替えてゲームを続行します。

まとめ

このTic-Tac-ToeコードはJavaプログラムでのクラスやメソッドの基本的な使い方、条件文、ループなどを学ぶ良い例です。

ゲームを通して2次元配列やループの使用方法、メソッドを使った処理の分割など、プログラミングの基本を身につけることができます。

このコードが理解できたら、次は是非プレーヤーとコンピュータとで対戦できるコードに挑戦してみて下さい。

JAVAのゲームコード一覧はこちら

<<前のページ JAVA Topへ 次のページ>>

この問題への質問・コメント

この問題を作成するにあたりAIを活用しています。

問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。






    JAVA練習問題集へ戻る
    トップページへ戻る