Kotlin練習問題4-☆4:マルバツゲームを作ろう

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

Kotlinの初心者向け問題4-☆4:マルバツゲームを作ろう

この問題を解くために必要な知識:

【レベル1~3の知識】
コメントの書き方、変数と定数、基本データ型、算術演算と論理演算、入力と出力、import文、配列、分岐処理(if、if~else、when)、繰り返し処理(for、while、do~while)、Null安全、スマートキャスト、関数の定義と呼び出し、関数の戻り値、関数のオーバーロード、ラベルとジャンプ、例外処理、クラスの定義と使用、インスタンス、コンストラクタ、プロパティ、クラスの継承、クラスの拡張、コレクションの基礎、リストコレクション(MutableList、ArrayList)、セットコレクション(HashSet、MutableSet、TreeSet)、マップコレクション(HashMap、MutableMap、TreeMap)

【レベル4の知識】
メンバ関数、ゲッターとセッター、カプセル化、クラスメンバ、抽象クラス、インターフェース、データクラス

<<前の問題 問題集Top 次の問題>>

Kotlin練習問題4-☆4:マルバツゲームを作ろう





「マルバツゲーム」を作成しましょう。

このゲームはプレイヤーとコンピュータが対戦し、交互にマス目に「○」または「×」を入れ、縦・横・斜めのいずれかに3つ並べたプレイヤーが勝ちとなります。

コンピュータの手はランダムで決まります。

この問題の要件

以下の要件に従ってプログラムを作成してください。

  1. Playerクラス: プレイヤーを表すデータクラスPlayerを作成してください。このクラスには、プレイヤーの名前を表すname(String型)とマークを表すmark(Char型)のプロパティを持たせてください。
  2. Boardクラス: 盤面を管理するクラスBoardを作成してください。このクラスには、3×3の盤面を表すboardという2次元配列(Array<CharArray>型)を持ち、以下のメソッドを実装してください:
    • displayBoard: 現在の盤面を表示するメソッド。
    • placeMark: 指定された行と列にマークを配置するメソッド。
    • checkWin: 指定されたマークが3つ揃っているかどうかを判定するメソッド。
    • isFull: 盤面がすべて埋まっているかどうかを判定するメソッド。
  3. Gameクラス: ゲームを管理するクラスGameを作成してください。このクラスには以下のメソッドを実装してください:
    • start: ゲームを開始するメソッド。このメソッドではプレイヤーの入力を受け付け、勝敗や引き分けを判定します。
    • getRandomMove: コンピュータのランダムな手を生成するメソッド。このメソッドはPair<Int, Int>型の値を返します。
  4. main関数: main関数でゲームを開始します。Playerクラスを使ってプレイヤーとコンピュータを作成し、Gameクラスのstartメソッドを呼び出してゲームを実行してください。

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

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

  |   |  
---------
  |   |  
---------
  |   |  
プレイヤーのターン (○)
行番号 (0-2) を入力してください:
0
列番号 (0-2) を入力してください:
0
○ |   |  
---------
  |   |  
---------
  |   |  
コンピュータのターン (×)
コンピュータが (1, 1) に配置しました
○ |   |  
---------
  | × |  
---------
  |   |  
プレイヤーのターン (○)
行番号 (0-2) を入力してください:
1
列番号 (0-2) を入力してください:
0
○ |   |  
---------
○ | × |  
---------
  |   |  
コンピュータのターン (×)
コンピュータが (2, 2) に配置しました
○ |   |  
---------
○ | × |  
---------
  |   | ×
プレイヤーのターン (○)
行番号 (0-2) を入力してください:
2
列番号 (0-2) を入力してください:
0
○ |   |  
---------
○ | × |  
---------
○ |   | ×
プレイヤーの勝ち!

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

この問題を解くヒント

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

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

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

1.import文
 1-1. Randomクラスのインポート

2.Playerデータクラスの定義
 2-1. プレイヤーの名前とマークを表すプロパティの定義

3.Boardクラスの定義
 3-1. 盤面を表す2次元配列の定義(board)
 3-2. 盤面を表示するメソッド(displayBoard)
 3-3. マークを指定位置に配置するメソッド(placeMark)
 3-4. 勝敗を判定するメソッド(checkWin)
 3-5. 盤面がいっぱいかどうかをチェックするメソッド(isFull)

4.Gameクラスの定義
 4-1. プレイヤーとコンピュータを受け取るプロパティの定義
 4-2. Boardオブジェクトの生成
 4-3. ゲームを開始するメソッド(start)
  4-3-1. 盤面の表示
  4-3-2. プレイヤーのターン
   4-3-2-1. 入力を受け取る(行と列)
   4-3-2-2. マークの配置
   4-3-2-3. 勝敗または引き分けの判定
  4-3-3. コンピュータのターン
   4-3-3-1. ランダムな手の生成と配置
   4-3-3-2. 勝敗または引き分けの判定
 4-4. コンピュータのランダムな手を生成するメソッド(getRandomMove)

5.main関数の定義
 5-1. プレイヤーとコンピュータの作成
 5-2. Gameオブジェクトの生成
 5-3. ゲームの開始

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

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

import kotlin.random.Random

// プレイヤーを表すデータクラス
data class Player(val name: String, val mark: Char)

// 盤面を管理するクラス
class Board {
    private val board: Array<CharArray> = Array(3) { CharArray(3) { ' ' } }

    // 盤面を表示するメソッド
    fun displayBoard() {
        for (row in board) {
            println(row.joinToString(separator = " | "))
            println("---------")
        }
    }

    // 指定した位置にマークを配置するメソッド
    fun placeMark(row: Int, col: Int, mark: Char): Boolean {
        /* 【穴埋め問題1】
        ここにif文を使って、指定された位置にマークが配置できるかを判定し、配置できた場合はTrueを返すコードを書いてください。
        */
        return false
    }

    // 勝敗を判定するメソッド
    fun checkWin(mark: Char): Boolean {
        /* 【穴埋め問題2】
        ここにfor文を使って、横のラインをチェックし、3つのマークが揃っている場合はTrueを返すコードを書いてください。
        */

        /* 【穴埋め問題3】
        ここにfor文を使って、縦のラインをチェックし、3つのマークが揃っている場合はTrueを返すコードを書いてください。
        */

        /* 【穴埋め問題4】
        ここにif文を使って、斜めのラインをチェックし、3つのマークが揃っている場合はTrueを返すコードを書いてください。
        */

        return false
    }

    // 盤面がいっぱいかどうかをチェックするメソッド
    fun isFull(): Boolean {
        /* 【穴埋め問題5】
        ここにboardの全てのマスが埋まっているかどうかを判定するコードを書いてください。
        */
        return false
    }
}

// ゲームを管理するクラス
class Game(private val player: Player, private val computer: Player) {
    private val board = Board()
    private var currentPlayer = player

    // ゲームを開始するメソッド
    fun start() {
        while (true) {
            board.displayBoard()
            if (currentPlayer == player) {
                println("${currentPlayer.name}のターン (${currentPlayer.mark})")
                println("行番号 (0-2) を入力してください:")
                val row = readLine()!!.toInt()
                println("列番号 (0-2) を入力してください:")
                val col = readLine()!!.toInt()

                /* 【穴埋め問題6】
                ここにif文を使って、指定された位置にマークを配置し、配置できた場合に勝敗をチェックするコードを書いてください。
                */
                currentPlayer = computer
            } else {
                println("${currentPlayer.name}のターン (${currentPlayer.mark})")
                val (row, col) = getRandomMove()
                board.placeMark(row, col, currentPlayer.mark)
                println("コンピュータが ($row, $col) に配置しました")

                /* 【穴埋め問題7】
                ここにif文を使って、コンピュータの手を配置した後に勝敗をチェックするコードを書いてください。
                */
                currentPlayer = player
            }
        }
    }

    // コンピュータのランダムな手を生成するメソッド
    private fun getRandomMove(): Pair<Int, Int> {
        while (true) {
            /* 【穴埋め問題8】
            ここにRandomクラスを使って、ランダムな行と列の番号を生成し、それを返すコードを書いてください。
            */
        }
    }
}

// メイン関数
fun main() {
    /* 【穴埋め問題9】
    ここにPlayerオブジェクトを生成するコードを書いてください。
    */
    val game = Game(player, computer)
    game.start()
}

 

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

解答例

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

正解コードの例

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

********************

import kotlin.random.Random

// プレイヤーを表すデータクラス
data class Player(val name: String, val mark: Char)

// 盤面を管理するクラス
class Board {
    private val board: Array<CharArray> = Array(3) { CharArray(3) { ' ' } }

    // 盤面を表示するメソッド
    fun displayBoard() {
        for (row in board) {
            println(row.joinToString(separator = " | "))
            println("---------")
        }
    }

    // 指定した位置にマークを配置するメソッド
    fun placeMark(row: Int, col: Int, mark: Char): Boolean {
        if (board[row][col] == ' ') {
            board[row][col] = mark
            return true
        }
        return false
    }

    // 勝敗を判定するメソッド
    fun checkWin(mark: Char): Boolean {
        // 横のラインをチェック
        for (row in board) {
            if (row.all { it == mark }) return true
        }
        // 縦のラインをチェック
        for (col in 0 until 3) {
            if (board.all { it[col] == mark }) return true
        }
        // 斜めのラインをチェック
        if ((board[0][0] == mark && board[ 1][ 1] == mark && board[ 2][ 2] == mark) ||
            (board[0][ 2] == mark && board[ 1][ 1] == mark && board[ 2][0] == mark)
        ) return true

        return false
    }

    // 盤面がいっぱいかどうかをチェックするメソッド
    fun isFull(): Boolean {
        return board.all { row -> row.all { it != ' ' } }
    }
}

// ゲームを管理するクラス
class Game(private val player: Player, private val computer: Player) {
    private val board = Board()
    private var currentPlayer = player

    // ゲームを開始するメソッド
    fun start() {
        while (true) {
            board.displayBoard()
            if (currentPlayer == player) {
                println("${currentPlayer.name}のターン (${currentPlayer.mark})")
                println("行番号 (0-2) を入力してください:")
                val row = readLine()!!.toInt()
                println("列番号 (0-2) を入力してください:")
                val col = readLine()!!.toInt()

                if (board.placeMark(row, col, currentPlayer.mark)) {
                    if (board.checkWin(currentPlayer.mark)) {
                        board.displayBoard()
                        println("${currentPlayer.name}の勝ち!")
                        break
                    } else if (board.isFull()) {
                        board.displayBoard()
                        println("引き分け!")
                        break
                    }
                    currentPlayer = computer
                } else {
                    println("その位置には配置できません。もう一度入力してください。")
                }
            } else {
                println("${currentPlayer.name}のターン (${currentPlayer.mark})")
                val (row, col) = getRandomMove()
                board.placeMark(row, col, currentPlayer.mark)
                println("コンピュータが ($row, $col) に配置しました")

                if (board.checkWin(currentPlayer.mark)) {
                    board.displayBoard()
                    println("${currentPlayer.name}の勝ち!")
                    break
                } else if (board.isFull()) {
                    board.displayBoard()
                    println("引き分け!")
                    break
                }
                currentPlayer = player
            }
        }
    }

    // コンピュータのランダムな手を生成するメソッド
    private fun getRandomMove(): Pair<Int, Int> {
        while (true) {
            val row = Random.nextInt(3)
            val col = Random.nextInt(3)
            if (board.placeMark(row, col, computer.mark)) {
                return row to col
            }
        }
    }
}

fun main() {
    val player = Player("プレイヤー", '○')
    val computer = Player("コンピュータ", '×')
    val game = Game(player, computer)
    game.start()
}

********************

コードの解説

ランダムクラスのインポート

import kotlin.random.Random

この行は、Kotlin標準ライブラリからRandomクラスをインポートするためのものです。このクラスは、ゲーム内でランダムな数値を生成するために使用されます。

プレイヤークラスの定義

data class Player(val name: String, val mark: Char)

このコードは、プレイヤーを表すデータクラスPlayerを定義しています。nameプロパティにはプレイヤーの名前が、markプロパティにはプレイヤーが使用するマーク(例:○や×)が格納されます。

data classは、データを保持するためのシンプルなクラスを定義するために使われます。

盤面を管理するクラスの定義

class Board {
    private val board: Array<CharArray> = Array(3) { CharArray(3) { ' ' } }
    // ...
}

Boardクラスは、ゲームの盤面を管理するために作成されます。ここでは、boardという名前の3×3の2次元配列を作成しています。

CharArrayを使って、各マスに文字を格納します。初期値は空白です。

盤面を表示するメソッド

fun displayBoard() {
    for (row in board) {
        println(row.joinToString(separator = " | "))
        println("---------")
    }
}

displayBoardメソッドは、現在の盤面を表示するためのメソッドです。各行を表示し、行と行の間には区切り線を表示しています。

指定した位置にマークを配置するメソッド

fun placeMark(row: Int, col: Int, mark: Char): Boolean {
    if (board[row][col] == ' ') {
        board[row][col] = mark
        return true
    }
    return false
}

placeMarkメソッドは、指定された位置にプレイヤーのマークを配置します。配置可能な場合はtrueを返し、そうでない場合はfalseを返します。

勝敗を判定するメソッド

checkWinメソッドは、プレイヤーが勝利したかどうかを判定するためのメソッドです。縦、横、斜めの3つのマークが揃ったかどうかをチェックします。

盤面がいっぱいかどうかをチェックするメソッド

fun isFull(): Boolean {
    return board.all { row -> row.all { it != ' ' } }
}

isFullメソッドは、盤面がすべて埋まっているかどうかを確認します。全てのマスが埋まっている場合はtrueを返します。

ゲームを管理するクラスの定義

Gameクラスは、ゲーム全体を管理するクラスです。playercomputerという2人のプレイヤーを保持し、ゲームの進行を制御します。

ゲームを開始するメソッド

fun start() {
    while (true) {
        board.displayBoard()
        // ...
    }
}

startメソッドは、ゲームを開始し、プレイヤーとコンピュータが交互にターンを行う処理を行います。ゲームが終了するまでループが続きます。

コンピュータのランダムな手を生成するメソッド

 

private fun getRandomMove(): Pair<Int, Int> {
    while (true) {
        val row = Random.nextInt(3)
        val col = Random.nextInt(3)
        // ...
    }
}

getRandomMoveメソッドは、コンピュータの次の手をランダムに選択します。ランダムに生成された位置にマークを配置し、その位置を返します。

メイン関数

main関数はプログラムのエントリーポイントであり、ゲームを開始します。

プレイヤーとコンピュータを初期化し、Gameクラスのstartメソッドを呼び出してゲームを実行します。

<<前の問題 問題集Top 次の問題>>

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

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

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






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