【Python】レッスン3-S2:石取りゲームを作ろう

一つ前のLessonでは丁半賭博ゲームを作成しました。
今回は石取りゲームを作成しましょう。
Lesson1:基礎文法編
Lesson2:制御構造編
Lesson3:関数とスコープ編
・Lesson3-1:関数の基本を理解しよう
・Lesson3-2:関数の戻り値とデフォルト引数を理解しよう
・Lesson3-3:スコープの概念を理解しよう
・Lesson3-4:高階関数を理解しよう
・Lesson3-5:無名関数を理解しよう
・練習問題3-1:丁半賭博ゲームを作ろう
・練習問題3-2:石取りゲームを作ろう ◁今回はココ
Lesson4:データ構造編
Lesson5:オブジェクト指向編
次のステップ:Python基礎習得者にお勧めの道5選(実務or副業)
Pythonのゲームコード一覧は こちらをクリック
Pythonでのゲームアプリ開発は こちらをクリック
石取りゲームを作ろう|入力検証と関数分割、高階関数の実践
2人のプレイヤーが交互に石を取り合い、最後の石を取った側が負けとなる石取りゲームを作成しましょう。
このゲームでは15個の石を山に置き、プレイヤーが1ターンに取れる石の数は1個から3個までです。
プレイヤーは交互に石を取り、最後の石を取ったプレイヤーが負けとなります。
関数の分割とスコープの扱いに加え、入力検証を関数として受け渡す(高階関数)設計を体験しましょう。
要件と仕様|関数分割・入力検証の方針
以下の要件に従ってコードを完成させてください。
- 初期設定:石の数は 15 個で開始する。
- 手番と勝敗:プレイヤーは交互に石を取り、最後の石を取ったプレイヤーが負けとなる。
- 取得数の制約:1ターンに取れる石は 1〜3 個。残りの石より多くは取れない。
- 取得数が妥当かどうかを判定する検証用の関数を用意する。
- プレイヤーの入力を受け取り、その検証用関数を引数として受け取ってチェックに使う(高階関数)。
- 1ターン分の処理を行い、残りの石数を更新する関数を用意する。
- ゲーム全体の進行(手番の交代・終了判定)を管理する関数を用意する。
ただし、以下のような実行結果となるコードを書くこと。
石取りゲームへようこそ! プレイヤー1の番です。残りの石は 15 個です。 プレイヤー1, 何個の石を取りますか?(1~3個まで選べます):2 プレイヤー2の番です。残りの石は 13 個です。 プレイヤー2, 何個の石を取りますか?(1~3個まで選べます):3 ... プレイヤー1が最後の石を取りました。プレイヤー1の負けです!
ヒント|難しいと感じる人だけ見よう
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント1【コードの構成を見る】
-
正解のコードは上から順に以下のような構成となっています。
1:入力がルールを満たしているかを真偽で判定する関数
・2つの引数(残り個数と取得希望数)を受け取る関数にする。
・戻り値は真偽値(条件を満たすなら真、満たさないなら偽)。
・取得数は下限と上限の両方をチェック(下限1、上限3)。
・残り個数を超えていないかも確認する。
・これらの条件は論理積でまとめて一度に判定してよい。
・判定だけに責務を絞り、数値の更新などは行わない。2:プレイヤーの入力を受け取り、外部の検証関数で妥当性を確認する関数
・引数は「プレイヤー名」「残りの石の数」「検証を行う関数」の三つを受け取る。
・ループで「入力 → 検証 → 合格なら返す」を繰り返す。
・数字以外の入力は例外処理で受け止め、メッセージを出してやり直す。
・検証用の関数は「残りの石の数」と「入力値」を受け取り、妥当なら真を返す想定。
・妥当でないときは案内メッセージを表示して次の入力に進む。
・ここでは石の減算などの状態更新は行わず、正しい取得数だけを返す。3:1ターンの流れをまとめ、入力→検証→残数更新を行い新しい石の数を返す
・受け取る引数は「プレイヤー名」「現在の残り石」「検証用の関数」の3つ。
・手番開始時に、プレイヤー名と残り石を表示して状況を明確にする。
・取得数は専用の入力関数から受け取り、検証はその中で済ませる。
・新しい残り石は「現在の残り − 取得数」で求め、結果を戻り値として返す。
・元の残り石を直接書き換えず、値を返して外側で更新するイメージを持つ。
・手番の交代や終了判定はメインループ側の責務であり、この関数では扱わない。
・画面表示の体裁(空行など)は、実行例に合わせて必要最小限に。4:ゲーム全体を制御し、手番の交代と終了判定を行うメイン処理
・ゲーム開始時に「残り石」と「手番」を初期化してからループを始める。
・ループの継続条件は「石が残っているかどうか」で判断する。
・現在の手番からプレイヤー名を決め、1ターン処理を呼び出して残り石を更新する。
・残り石がゼロになったら、その時点のプレイヤーが最後の石を取った扱いで負けを宣言し終了する。
・手番の交代は「1⇔2」をシンプルに切り替える算術で行うと分岐が減って読みやすい。
・メイン処理は進行と判定に集中し、入力取得や検証ロジックは下位の関数に任せる設計を保つ。5:スクリプトとして直接実行されたときだけゲームを起動するためのエントリポイント
・「このファイルが直接動かされているか」を判定してから、ゲーム開始の関数を呼び出す。
・ほかのファイルからインポートされた場合は、ゲームが勝手に始まらないようにするのが目的。
・テストや再利用を考えると、エントリポイントを用意しておく書き方が標準的で扱いやすい。
・大きなプログラムでも同じ考え方が使えるため、早い段階で慣れておくと良い。
- ヒント2【穴埋め問題にする】
-
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
# 入力された石の数が有効かどうかを判定する関数 def is_valid_move(stones, move): '''(穴埋め)取得数が1〜3かつ残り以下かを判定して真偽を返す''' # プレイヤーの入力を受け取り、バリデーション関数でチェックする関数(高階関数) def get_move(player, stones, validator): while True: try: '''(穴埋め)プレイヤーから整数入力を受け取り move に代入する''' '''(穴埋め)検証関数を呼び出して妥当なら返す条件分岐''' return move else: print("無効な選択です。もう一度選んでください。") except ValueError: print("数値を入力してください。") # 1ターン分の処理を行い、残りの石の数を更新する関数 def take_turn(player, stones, validator): print(f"\n{player}の番です。残りの石は {stones} 個です。") '''(穴埋め)入力取得関数を呼び出し、検証関数を渡して move を得る''' return stones - move # ゲーム全体の進行を管理するメイン関数 def play_game(): print("石取りゲームへようこそ!") stones = 15 # ゲーム開始時の石の数 turn = 1 # 1ならプレイヤー1、2ならプレイヤー2 while stones > 0: current_player = "プレイヤー1" if turn == 1 else "プレイヤー2" stones = take_turn(current_player, stones, '''(穴埋め)''') if stones == 0: print(f"\n{current_player}が最後の石を取りました。{current_player}の負けです!") break '''(穴埋め)手番をもう一方に切り替える算術(1⇔2)''' # ゲームの実行 if __name__ == "__main__": play_game()
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
当サイトではテキストベースのゲームコードを多数紹介していますが、それだけでなく 画面上で遊べるゲームアプリ の作り方を紹介たコースも用意してあります。
Pythonの実践力強化にもなりますので、ぜひ挑戦して下さい^^
解答と解説|石取りゲームのサンプルコード
この問題の一つの正解例とそのコードの解説を以下に示します。
解答例|石取りゲームプログラム
例えば以下のようなプログラムが考えられます。
- 正解コード
-
# 入力された石の数が有効かどうかを判定する関数 def is_valid_move(stones, move): return 1 <= move <= 3 and move <= stones # プレイヤーの入力を受け取り、バリデーション関数でチェックする関数(高階関数) def get_move(player, stones, validator): while True: try: move = int(input(f"{player}, 何個の石を取りますか?(1~3個まで選べます):")) if validator(stones, move): # validator関数を利用 return move else: print("無効な選択です。もう一度選んでください。") except ValueError: print("数値を入力してください。") # 1ターン分の処理を行い、残りの石の数を更新する関数 def take_turn(player, stones, validator): print(f"\n{player}の番です。残りの石は {stones} 個です。") move = get_move(player, stones, validator) # 高階関数の利用 return stones - move # ゲーム全体の進行を管理するメイン関数 def play_game(): print("石取りゲームへようこそ!") stones = 15 # ゲーム開始時の石の数 turn = 1 # 1ならプレイヤー1、2ならプレイヤー2 while stones > 0: current_player = "プレイヤー1" if turn == 1 else "プレイヤー2" stones = take_turn(current_player, stones, is_valid_move) if stones == 0: print(f"\n{current_player}が最後の石を取りました。{current_player}の負けです!") break turn = 3 - turn # 1⇔2を切り替え # ゲームの実行 if __name__ == "__main__": play_game()
解答例の解説|石取りゲームの考え方
この解説では、Pythonの基本構文を使用したシンプルな「石取りゲーム」を、各部分ごとにわかりやすく説明します。
- 詳細解説
-
入力がルールを満たしているかを真偽で判定する関数
# 入力された石の数が有効かどうかを判定する関数 def is_valid_move(stones, move): return 1 <= move <= 3 and move <= stones
この部分はプレイヤーが宣言した取得数がゲームのルールに合っているかどうかをチェックする役割です。
関数は「残っている石の数」と「プレイヤーが取りたい数」を受け取り、条件をすべて満たしていれば真、どれか一つでも外れていれば偽を返します。
こうした判定関数を用意しておくと、入力の良し悪しを一箇所で管理でき、ほかの処理からも再利用しやすくなります。プレイヤーの入力を受け取りチェックする関数(高階関数)
# プレイヤーの入力を受け取り、バリデーション関数でチェックする関数(高階関数) def get_move(player, stones, validator): while True: try: move = int(input(f"{player}, 何個の石を取りますか?(1~3個まで選べます):")) if validator(stones, move): # validator関数を利用 return move else: print("無効な選択です。もう一度選んでください。") except ValueError: print("数値を入力してください。")
この部分は、プレイヤーが何個の石を取るかを入力し、その値がルールに合っているかを確認する役割です。
まず数値として入力を受け取り、数字でない場合はエラーメッセージを出してやり直します。
数字だった場合は、外から渡された検証用の関数を使って妥当性をチェックします。
条件に合っていればその数を返し、合っていなければ理由を伝えて再入力を促します。
検証のやり方を関数として受け取ることで、ルールの差し替えや再利用がしやすい設計になっています。残りの石の数を更新する関数
# 1ターン分の処理を行い、残りの石の数を更新する関数 def take_turn(player, stones, validator): print(f"\n{player}の番です。残りの石は {stones} 個です。") move = get_move(player, stones, validator) # 高階関数の利用 return stones - move
この部分は、プレイヤーの「1回の手番」を完結させる役割です。
まず、誰の番で、いくつ石が残っているかを画面に知らせます。
続いて、入力の取得とルールチェックは専用の処理に任せ、妥当な取得数が得られてから残りの石を計算します。
ここでは「手番を進めて新しい残数を返す」ことに責務を絞っており、手番の切り替えや勝敗の判定は他の箇所に任せます。
こうして役割を分けると、読みやすく変更に強い構成になります。ゲーム全体を制御するメイン処理
# ゲーム全体の進行を管理するメイン関数 def play_game(): print("石取りゲームへようこそ!") stones = 15 # ゲーム開始時の石の数 turn = 1 # 1ならプレイヤー1、2ならプレイヤー2 while stones > 0: current_player = "プレイヤー1" if turn == 1 else "プレイヤー2" stones = take_turn(current_player, stones, is_valid_move) if stones == 0: print(f"\n{current_player}が最後の石を取りました。{current_player}の負けです!") break turn = 3 - turn # 1⇔2を切り替え
ここはゲームの司令塔です。最初にあいさつを表示し、石の初期個数と最初の手番を用意します。
石が残っているあいだは繰り返し処理を回し、現在のプレイヤー名を決めて1ターン分の処理を呼び出します。
ターンが終わったら石の残りを確認し、ゼロならその時点のプレイヤーが最後の石を取ったことになるため負けを表示して終了します。
続行する場合は手番をもう一方へ切り替えます。
役割としては「状態の初期化」「ループの継続条件の管理」「勝敗の確定」「手番の交代」に専念し、入力やバリデーションの詳細は他の関数に委ねています。ゲームのエントリポイント
if __name__ == "__main__": play_game()
この部分は、ファイルが「直接実行された場合」と「ほかのファイルから読み込まれた場合」を区別する役割です。
直接実行のときだけメインの処理を呼び出してゲームを開始し、モジュールとして読み込んだときには自動で起動しないようにします。
これにより、コードを他のプログラムから再利用したり、テストを行ったりするときに都合がよく、実行時の挙動を安全にコントロールできます。
Pythonのゲームコード一覧は こちらをクリック
Pythonでのゲームアプリ開発は こちらをクリック
- サイト改善アンケート|ご意見をお聞かせください(1分で終わります)
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、みなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。1分だけ、ご協力いただけますと幸いです。
【Python】サイト改善アンケート