【Kotlin】確認問題5-☆2:マルバツゲームを作ろう

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

この記事の練習問題で使用する知識:
基礎文法、制御構造(レッスン1~2)関数の定義と呼出し関数の戻り値ジェネリクスの基礎クラスの定義と使用プライマリコンストラクタクラスメンバとインスタンスメンバデータクラス

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

<<前のページ Kotlin記事一覧 次のページ>>

確認問題:マルバツゲームを作ろう

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

このゲームはプレイヤーとコンピュータが対戦し、交互にマス目に「○」または「×」を入れ、縦・横・斜めのいずれかに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:kotlin.random.Randomモジュールのインポート
2:Playerデータクラスの定義
  □ nameプロパティとmarkプロパティを定義
3:Boardクラスの定義
  □ 3×3のChar配列boardを初期化
  □ displayBoardメソッドの定義
  □ □ 盤面を行ごとに表示し、区切り線を出力
  □ placeMarkメソッドの定義
  □ □ 指定位置が空の場合にマークを配置しtrueを返す
  □ □ 配置できない場合はfalseを返す
  □ checkWinメソッドの定義
  □ □ 横ラインの勝利条件をチェック
  □ □ 縦ラインの勝利条件をチェック
  □ □ 斜めラインの勝利条件をチェック
  □ isFullメソッドの定義
  □ □ 盤面がすべて埋まっているかをチェック
4:Gameクラスの定義
  □ playerとcomputerプロパティを初期化
  □ Boardクラスのインスタンスboardを生成
  □ 現在のプレイヤーをplayerに設定
  □ startメソッドの定義
  □ □ ゲームループを開始
  □ □ □ 盤面を表示
  □ □ □ 現在のプレイヤーがplayerの場合の処理
  □ □ □ □ 行番号と列番号をユーザーに入力させる
  □ □ □ □ 入力位置にマークを配置し成功した場合
  □ □ □ □ □ 勝利条件をチェックし勝者メッセージを出力
  □ □ □ □ □ 盤面が満杯の場合、引き分けメッセージを出力
  □ □ □ □ □ 次のターンをcomputerに切り替え
  □ □ □ □ 失敗した場合、再入力を促す
  □ □ □ 現在のプレイヤーがcomputerの場合の処理
  □ □ □ □ getRandomMoveメソッドを呼び出しランダムな手を生成
  □ □ □ □ 生成された手を出力
  □ □ □ □ 勝利条件をチェックし勝者メッセージを出力
  □ □ □ □ 盤面が満杯の場合、引き分けメッセージを出力
  □ □ □ □ 次のターンをplayerに切り替え
  □ getRandomMoveメソッドの定義
  □ □ ランダムな行と列を生成し、配置可能であればその手を返す
5:main関数の定義
  □ Playerオブジェクトplayerを生成
  □ Playerオブジェクトcomputerを生成
  □ Gameオブジェクトgameを生成
  □ gameのstartメソッドを呼び出しゲームを開始

ヒント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()
}

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

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

マルバツゲームのKotlinコードと解説

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

正解コードの例

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

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()
}

正解コードの解説

このコードはプレイヤーとコンピュータが交互に○×ゲームを行い、勝敗や引き分けを判定するプログラムです。

盤面を管理し、勝利条件やゲーム終了条件をチェックするロジックが含まれています。

データクラスの定義

data class Player(val name: String, val mark: Char)
  1. data class:
    • Kotlinのデータクラスで、簡単にデータを保持するための構造体のような役割を果たします。
    • namemark の2つのプロパティを持つプレイヤーを表現します。
  2. 用途:
    • プレイヤー名とその使用するマーク(○や×)を管理します。

盤面を管理するクラス

class Board {
    private val board: Array<CharArray> = Array(3) { CharArray(3) { ' ' } }
}
  • 盤面の初期化:3×3の配列で盤面を表現します。' 'で初期化して空の状態を示します。
  • 配列の初期化方法:Array(3) { CharArray(3) { ' ' } }は、外側が行、内側が列を表す2次元配列です。

盤面の操作

a. 表示メソッド
fun displayBoard() {
    for (row in board) {
        println(row.joinToString(separator = " | "))
        println("---------")
    }
}

forループ:各行をループし、行を|で区切って表示します。

b. マークの配置
fun placeMark(row: Int, col: Int, mark: Char): Boolean {
    if (board[row][col] == ' ') {
        board[row][col] = mark
        return true
    }
    return false
}

条件付き配置:

  • 指定位置が空ならマークを配置し、trueを返します。
  • 配置済みの場合はfalseを返します。
c. 勝敗の判定
fun checkWin(mark: Char): Boolean {
    // 横、縦、斜めを確認
    ...
}

判定ロジック:横、縦、斜めのいずれかが全て同じマークで揃った場合に勝利を返します。

ゲームの進行管理

class Game(private val player: Player, private val computer: Player) {
    private val board = Board()
    private var currentPlayer = player
}
  • player: ユーザーのプレイヤーデータ。
  • computer: コンピュータのプレイヤーデータ。
  • currentPlayer: 現在の手番のプレイヤー。

ゲームの実行ロジック

a. メインループ
fun start() {
    while (true) {
        board.displayBoard()
        if (currentPlayer == player) {
            // プレイヤーのターン
        } else {
            // コンピュータのターン
        }
    }
}

手番管理:currentPlayerによってプレイヤーまたはコンピュータの行動を切り替えます。

b. プレイヤーの入力
println("行番号 (0-2) を入力してください:")
val row = readLine()!!.toInt()
  • 標準入力:readLineでユーザーの入力を受け付け、整数に変換します。
  • エラー防止:範囲外や既に配置済みの場合、再入力を求めます。
c. コンピュータの行動
val (row, col) = getRandomMove()
board.placeMark(row, col, currentPlayer.mark)

ランダムな位置選択:getRandomMove()でランダムな空の位置を取得し、マークを配置します。

ゲーム終了条件

a. 勝利時
if (board.checkWin(currentPlayer.mark)) {
    println("${currentPlayer.name}の勝ち!")
    break
}
  • 勝敗判定:現在のプレイヤーが勝利条件を満たしているかを判定します。
b. 引き分け時
else if (board.isFull()) {
    println("引き分け!")
    break
}

盤面いっぱいの確認:全てのマスが埋まった場合に引き分けとします。

まとめ

このプログラムでは、Kotlinの基本構文やオブジェクト指向プログラミングを学べます。特に以下のポイントが重要です:

  1. データクラスでデータを簡潔に管理。
  2. 2次元配列で盤面を表現し、ロジックを実装。
  3. 条件分岐とループでゲーム進行を制御。 このコードを理解することで、より複雑なゲームやアプリケーション作成へのステップアップが可能です。

ぜひ自分で改良や機能追加を試してみてください!

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

<<前のページ Kotlin記事一覧 次のページ>>

この記事への質問・コメント

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

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






    Kotlinのテキスト&問題集トップへ戻る
    トップページへ戻る