【Kotlin】確認問題5-☆3:石取りゲームを作ろう

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

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

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

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

確認問題:石取りゲームを作ろう

この課題では、2人のプレイヤーが交互に石を取り合う「石取りゲーム」を作成します。

各プレイヤーは1ターンで1~3個の石を取ることができます。最後の石を取ったプレイヤーが負けとなります。

この問題の要件

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

  1. 抽象クラス Player を定義すること
    • name という名前のプロパティ(String 型)を持つこと。
    • takeStones という抽象関数を持つこと。この関数は、プレイヤーが石を取る処理を定義します。
  2. Player クラスを継承する HumanPlayer クラスを定義すること
    • takeStones 関数をオーバーライドし、プレイヤーに1~3個の石を取るように指示すること。
  3. Player クラスを継承する AIPlayer クラスを定義すること
    • takeStones 関数をオーバーライドし、ランダムに1~3個の石を取ること。
  4. Game クラスを定義し、ゲームのロジックを実装すること
    • totalStones という名前のプロパティを持ち、ゲーム開始時の石の数を格納すること。
    • startGame という名前のメンバ関数を定義し、ゲームの進行を制御すること。
  5. main 関数でゲームを実行すること
    • Game オブジェクトを作成し、石の数を設定すること(例えば20個)。
    • HumanPlayerAIPlayer のオブジェクトを作成し、ゲームを開始すること。

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

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

残りの石の数: 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【コードの構成を見る】

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

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メソッドを呼び出しゲームを開始

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

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

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
}
  1. 抽象クラス:
    • abstractキーワードを使い、共通の振る舞いを持たせる基底クラスを定義しています。
    • 目的:名前プロパティを持つプレイヤーを統一的に扱う。
      派生クラスで必ずtakeStonesメソッドを実装することを強制。
  2. 抽象メソッド:
    • 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)
    }
}
  1. 継承:HumanPlayerPlayerクラスを継承しています。
  2. overrideキーワード:抽象クラスのtakeStonesメソッドを具体的に実装します。
  3. 入力処理: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
    }
}
  1. 現在のプレイヤーの操作:
    • 現在のプレイヤーが石を取る処理を実行。
    • remainingStonesを更新します。
  2. 終了条件:
    • 石が0以下になった場合、現在のプレイヤーが負けと判定。
  3. プレイヤー交代:
    • 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個に設定。
  • プレイヤー作成:HumanPlayerAIPlayerのインスタンスを生成。
  • ゲーム開始:startGameでゲームを開始します。

まとめ

このコードでは抽象クラスや継承、動的なプレイヤーの交代ロジックを利用して石取りゲームを実装しています。以下の学びが得られます:

  1. 抽象クラスの使用法とオーバーライド。
  2. ランダム処理入力の安全な処理
  3. シンプルなゲームループの構築方法。

初心者の方は、このコードを基にカスタマイズ(例えば石の数の変更やAIの戦略追加)を試してみてください!

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

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

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

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

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






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