この記事の練習問題で使用する知識:
基礎文法、制御構造(レッスン1~2)、関数の定義と呼出し、関数の戻り値、ジェネリクスの基礎、クラスの定義と使用、プライマリコンストラクタ、クラスメンバとインスタンスメンバ、クラスの継承、オーバーライド、抽象クラス
Kotlinのゲームコード一覧はこちら
<<前のページ | Kotlin記事一覧 |
次のページ>> |
確認問題:石取りゲームを作ろう
この課題では、2人のプレイヤーが交互に石を取り合う「石取りゲーム」を作成します。
各プレイヤーは1ターンで1~3個の石を取ることができます。最後の石を取ったプレイヤーが負けとなります。
この問題の要件
以下の要件に従ってプログラムを作成してください。
- 抽象クラス
Player
を定義することname
という名前のプロパティ(String
型)を持つこと。takeStones
という抽象関数を持つこと。この関数は、プレイヤーが石を取る処理を定義します。
Player
クラスを継承するHumanPlayer
クラスを定義することtakeStones
関数をオーバーライドし、プレイヤーに1~3個の石を取るように指示すること。
Player
クラスを継承するAIPlayer
クラスを定義することtakeStones
関数をオーバーライドし、ランダムに1~3個の石を取ること。
Game
クラスを定義し、ゲームのロジックを実装することtotalStones
という名前のプロパティを持ち、ゲーム開始時の石の数を格納すること。startGame
という名前のメンバ関数を定義し、ゲームの進行を制御すること。
main
関数でゲームを実行することGame
オブジェクトを作成し、石の数を設定すること(例えば20個)。HumanPlayer
とAIPlayer
のオブジェクトを作成し、ゲームを開始すること。
ただし、以下のような実行結果となること。
----- ↓出力される結果の例↓ -----
残りの石の数: 20 あなた さん、何個の石を取りますか? (1~3個): 2 残りの石の数: 18 コンピュータ は 3 個の石を取りました 残りの石の数: 15 あなた さん、何個の石を取りますか? (1~3個): 3 残りの石の数: 12 コンピュータ は 1 個の石を取りました 残りの石の数: 11 あなた さん、何個の石を取りますか? (1~3個): 2 残りの石の数: 9 コンピュータ は 2 個の石を取りました 残りの石の数: 7 あなた さん、何個の石を取りますか? (1~3個): 3 残りの石の数: 4 コンピュータ は 1 個の石を取りました 残りの石の数: 3 あなた さん、何個の石を取りますか? (1~3個): 3 あなた が最後の石を取りました! あなた の負けです!
この問題を解くヒント
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
正解のコードは上から順に以下のような構成となっています。
1:kotlin.random.Randomモジュールのインポート
2:抽象クラスPlayerの定義
□ プライマリコンストラクタでnameプロパティを初期化
□ takeStones抽象関数を定義
3:HumanPlayerクラスの定義
□ Playerクラスを継承
□ takeStones関数をオーバーライド
□ □ ユーザー入力を受け取り、1~3の範囲に制限して返す
4:AIPlayerクラスの定義
□ Playerクラスを継承
□ takeStones関数をオーバーライド
□ □ 1~3のランダムな数を生成して返す
5:Gameクラスの定義
□ プライマリコンストラクタでtotalStonesプロパティを初期化
□ remainingStonesプロパティを定義し初期化
□ startGameメソッドの定義
□ □ ゲームループを開始
□ □ □ 残りの石の数を出力
□ □ □ 現在のプレイヤーのtakeStones関数を呼び出して石を取る
□ □ □ remainingStonesを更新
□ □ □ remainingStonesが0以下なら敗者を出力しループ終了
□ □ □ プレイヤー交代
6:main関数の定義
□ Gameオブジェクトgameを生成
□ HumanPlayerオブジェクトhumanを生成
□ AIPlayerオブジェクトaiを生成
□ gameのstartGameメソッドを呼び出しゲームを開始
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
import kotlin.random.Random // 抽象クラス Player を定義 abstract class Player(val name: String) { // プレイヤーが石を取るための抽象関数 abstract fun takeStones(totalStones: Int): Int } // 人間プレイヤーを表すクラス class HumanPlayer(name: String) : Player(name) { // 人間プレイヤーが石を取る処理を実装 override fun takeStones(totalStones: Int): Int { println("$name さん、何個の石を取りますか? (1~3個): ") /* 【穴埋め問題1】 ここにユーザーの入力を受け取り、変数 stones に格納するコードを書いてください。 1~3の範囲に制限するための処理も含めてください。 */ } } // AIプレイヤーを表すクラス class AIPlayer(name: String) : Player(name) { // AIプレイヤーがランダムに石を取る処理を実装 override fun takeStones(totalStones: Int): Int { /* 【穴埋め問題2】 ここにAIが1~3個のランダムな数の石を取るコードを書いてください。 */ } } // ゲームのロジックを実装するクラス class Game(val totalStones: Int) { private var remainingStones = totalStones // ゲームを開始するメンバ関数 fun startGame(player1: Player, player2: Player) { var currentPlayer = player1 while (remainingStones > 0) { println("残りの石の数: $remainingStones") /* 【穴埋め問題3】 ここに現在のプレイヤーが石を取る処理と、 その結果、残りの石を減らすコードを書いてください。 最後の石を取ったプレイヤーを判定し、負けと表示する処理も含めてください。 */ // プレイヤー交代 currentPlayer = if (currentPlayer == player1) player2 else player1 } } } fun main() { // 石の数を設定してゲームを開始 val game = Game(20) // 人間プレイヤーとAIプレイヤーを作成 val human = HumanPlayer("あなた") val ai = AIPlayer("コンピュータ") // ゲーム開始 game.startGame(human, ai) }
この問題の穴埋めコードは以上です。
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
石取りゲームのKotlinコードと解説
この問題の一つの正解例とそのコードの解説を以下に示します。
正解コードの例
例えば以下のようなプログラムが考えられます。
import kotlin.random.Random // 抽象クラス Player を定義 abstract class Player(val name: String) { // プレイヤーが石を取るための抽象関数 abstract fun takeStones(totalStones: Int): Int } // 人間プレイヤーを表すクラス class HumanPlayer(name: String) : Player(name) { // 人間プレイヤーが石を取る処理を実装 override fun takeStones(totalStones: Int): Int { println("$name さん、何個の石を取りますか? (1~3個): ") val stones = readLine()?.toIntOrNull() ?: 1 return stones.coerceIn(1, 3) // 1~3の範囲に制限 } } // AIプレイヤーを表すクラス class AIPlayer(name: String) : Player(name) { // AIプレイヤーがランダムに石を取る処理を実装 override fun takeStones(totalStones: Int): Int { val stones = Random.nextInt(1, 4) // 1~3のランダムな数 println("$name は $stones 個の石を取りました") return stones } } // ゲームのロジックを実装するクラス class Game(val totalStones: Int) { private var remainingStones = totalStones // ゲームを開始するメンバ関数 fun startGame(player1: Player, player2: Player) { var currentPlayer = player1 while (remainingStones > 0) { println("残りの石の数: $remainingStones") val stonesTaken = currentPlayer.takeStones(remainingStones) remainingStones -= stonesTaken if (remainingStones <= 0) { println("${currentPlayer.name} が最後の石を取りました!") println("${currentPlayer.name} の負けです!") break } // プレイヤー交代 currentPlayer = if (currentPlayer == player1) player2 else player1 } } } fun main() { // 石の数を設定してゲームを開始 val game = Game(20) // 人間プレイヤーとAIプレイヤーを作成 val human = HumanPlayer("あなた") val ai = AIPlayer("コンピュータ") // ゲーム開始 game.startGame(human, ai) }
正解コードの解説
このコードは、複数のプレイヤー(人間やAI)が交互に石を取るゲームです。
残りの石が0になると負けとなります。以下にコードをブロックごとに解説します。
抽象クラス Player
の定義
abstract class Player(val name: String) { abstract fun takeStones(totalStones: Int): Int }
- 抽象クラス:
abstract
キーワードを使い、共通の振る舞いを持たせる基底クラスを定義しています。- 目的:名前プロパティを持つプレイヤーを統一的に扱う。
派生クラスで必ずtakeStones
メソッドを実装することを強制。
- 抽象メソッド:
abstract fun takeStones(totalStones: Int): Int
は未実装で、派生クラスで具体的な実装を行います。
HumanPlayer
クラス
class HumanPlayer(name: String) : Player(name) { override fun takeStones(totalStones: Int): Int { println("$name さん、何個の石を取りますか? (1~3個): ") val stones = readLine()?.toIntOrNull() ?: 1 return stones.coerceIn(1, 3) } }
- 継承:
HumanPlayer
はPlayer
クラスを継承しています。 override
キーワード:抽象クラスのtakeStones
メソッドを具体的に実装します。- 入力処理:
readLine()
でユーザー入力を取得します。- エラーハンドリング:
toIntOrNull()
で整数に変換し、失敗した場合はデフォルトで1
を返すようにしています。 - 範囲制限:
coerceIn(1, 3)
で、入力値を1~3の範囲内に制限します。
- エラーハンドリング:
AIPlayer
クラス
class AIPlayer(name: String) : Player(name) { override fun takeStones(totalStones: Int): Int { val stones = Random.nextInt(1, 4) println("$name は $stones 個の石を取りました") return stones } }
- ランダム処理:
Random.nextInt(1, 4)
で1~3のランダムな整数を生成します。 - 出力:AIの選択をコンソールに表示します。
ゲームロジック
a. Game
クラスの定義
class Game(val totalStones: Int) { private var remainingStones = totalStones }
totalStones
はゲーム開始時の石の総数。remainingStones
は現在残っている石の数を追跡します。
b. ゲーム進行ロジック
fun startGame(player1: Player, player2: Player) { var currentPlayer = player1 while (remainingStones > 0) { println("残りの石の数: $remainingStones") val stonesTaken = currentPlayer.takeStones(remainingStones) remainingStones -= stonesTaken if (remainingStones <= 0) { println("${currentPlayer.name} が最後の石を取りました!") println("${currentPlayer.name} の負けです!") break } currentPlayer = if (currentPlayer == player1) player2 else player1 } }
- 現在のプレイヤーの操作:
- 現在のプレイヤーが石を取る処理を実行。
remainingStones
を更新します。
- 終了条件:
- 石が0以下になった場合、現在のプレイヤーが負けと判定。
- プレイヤー交代:
if (currentPlayer == player1) player2 else player1
で手番を切り替えます。
main
関数
fun main() { val game = Game(20) val human = HumanPlayer("あなた") val ai = AIPlayer("コンピュータ") game.startGame(human, ai) }
- ゲーム設定:
Game(20)
で石の総数を20個に設定。 - プレイヤー作成:
HumanPlayer
とAIPlayer
のインスタンスを生成。 - ゲーム開始:
startGame
でゲームを開始します。
まとめ
このコードでは抽象クラスや継承、動的なプレイヤーの交代ロジックを利用して石取りゲームを実装しています。以下の学びが得られます:
- 抽象クラスの使用法とオーバーライド。
- ランダム処理や入力の安全な処理。
- シンプルなゲームループの構築方法。
初心者の方は、このコードを基にカスタマイズ(例えば石の数の変更やAIの戦略追加)を試してみてください!
Kotlinのゲームコード一覧はこちら
<<前のページ |
Kotlin記事一覧 |
次のページ>> |
この記事への質問・コメント
この記事を作成するにあたりAIを活用しています。
問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。
Kotlinのテキスト&問題集トップへ戻る
トップページへ戻る