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つ並べたプレイヤーが勝ちとなります。
コンピュータの手はランダムで決まります。
この問題の要件
以下の要件に従ってプログラムを作成してください。
- Playerクラス: プレイヤーを表すデータクラス
Player
を作成してください。このクラスには、プレイヤーの名前を表すname
(String型)とマークを表すmark
(Char型)のプロパティを持たせてください。 - Boardクラス: 盤面を管理するクラス
Board
を作成してください。このクラスには、3×3の盤面を表すboard
という2次元配列(Array<CharArray>
型)を持ち、以下のメソッドを実装してください:displayBoard
: 現在の盤面を表示するメソッド。placeMark
: 指定された行と列にマークを配置するメソッド。checkWin
: 指定されたマークが3つ揃っているかどうかを判定するメソッド。isFull
: 盤面がすべて埋まっているかどうかを判定するメソッド。
- Gameクラス: ゲームを管理するクラス
Game
を作成してください。このクラスには以下のメソッドを実装してください:start
: ゲームを開始するメソッド。このメソッドではプレイヤーの入力を受け付け、勝敗や引き分けを判定します。getRandomMove
: コンピュータのランダムな手を生成するメソッド。このメソッドはPair<Int, Int>
型の値を返します。
- 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.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. ゲームの開始
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
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
クラスは、ゲーム全体を管理するクラスです。player
とcomputer
という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を活用しています。
問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。