【Python】レッスン4-S1:宝探しゲームを作ろう
一つ前のLessonでは集合(セット)の基本について学習しました。
今回はLesson4で学習した内容を応用して宝探しゲームを作成してみましょう。
Lesson1:基礎文法編
Lesson2:制御構造編
Lesson3:関数とスコープ編
Lesson4:データ構造編
・Lesson4-1:リストの定義と要素の追加を理解しよう ◁今回はココ
・Lesson4-2:リストの要素を削除しよう
・Lesson4-3:リストの情報を調べよう
・Lesson4-4:リストの集計・並べ替えを理解しよう
・Lesson4-5:リストのスライスを理解しよう
・Lesson4-6:リストのループ処理を理解しよう
・Lesson4-7:リストの内包表記を理解しよう
・Lesson4-8:リスト・タプル・辞書・集合の概要と違いを理解しよう
・Lesson4-9:タプルの基本を理解しよう
・Lesson4-10:タプルのアンパックを理解しよう
・Lesson4-11:辞書の基本を理解しよう
・Lesson4-12:辞書のループ処理を理解しよう
・Lesson4-13:辞書の内包表記を使ってリストから辞書を作ろう
・Lesson4-13:集合(セット)の基本を理解しよう
・練習問題4-1:宝探しゲームを作ろう
・練習問題4-2:ナインゲームを作ろう
・練習問題4-3:マルバツゲームを作ろう
Lesson5:オブジェクト指向編
次のステップ:Python基礎習得者にお勧めの道5選(実務or副業)
Pythonのゲームコード一覧は こちらをクリック
Pythonでのゲームアプリ開発は こちらをクリック
宝探しゲームを作ろう|リストと集合を使う練習問題
ランダムな位置にある宝を探し当てるゲームを作成しましょう。
プレイヤーは番号でマスを指定し、各手のたびにグリッドを更新しながら、方角ヒントを手掛かりに 3 回以内の発見を目指します。
宝探しゲームのルールとプログラムの仕様
以下の要件に従ってコードを完成させてください。
- ゲーム開始時に案内を表示し、5×5 のグリッドを出力すること。
- グリッドの各マスは 1〜25 の番号で管理し、2桁表記(01〜25) で表示すること。
- 宝の位置はゲーム開始時に ランダム に決定すること。
- プレイヤーは 1〜25 を入力し、最大 3 回 の試行で宝の発見を目指すこと。
- 選んだマスは 再選択不可。選択済みは 「*」、発見時は 「☆」 をグリッド上に反映すること。
- 宝が見つからない場合は、選択位置と宝の位置の関係に応じて 「上/下/左/右」 を組み合わせたヒントを表示すること。
また、以下のようにデータ構造を使用すること。
データ構造 | 使い方 |
---|---|
リスト | グリッドは入れ子のリストで管理すること(行ごとにリストを持つ)。 マスの状態更新は grid[y][x] に代入して行うこと(選択時 "*" 、発見時 "☆" )。表示は 各行を空白区切りで連結して出力すること。 |
集合 | 選択済みマスの管理に使用すること(要素は 座標ペア (x, y) )。入力されたマスが集合に すでに含まれていれば再入力を促すこと。 新規選択は 集合へ追加して管理を更新すること。 |
ただし、以下のような実行結果となるコードを書くこと。
宝探しゲームへようこそ!1から25までの数字で宝を探し当ててください。 宝を探すチャンスは3回です。 現在のグリッド: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1から25のマス番号を入力してください: 13 下 に宝があります。 残りのチャンス: 2回 現在のグリッド: 01 02 03 04 05 06 07 08 09 10 11 12 * 14 15 16 17 18 19 20 21 22 23 24 25 1から25のマス番号を入力してください: 23 おめでとうございます!宝を見つけました! 現在のグリッド: 01 02 03 04 05 06 07 08 09 10 11 12 * 14 15 16 17 18 19 20 21 22 ☆ 24 25
【ヒント】難しいと感じる人だけ見よう
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント1【コードの構成を見る】
-
正解のコードは上から順に以下のような構成となっています。
1:乱数機能を読み込み、グリッドの一辺の大きさを定数として決める。
・乱数を使うので、最初に標準ライブラリの該当モジュールを読み込む。
・グリッドの一辺の長さは定数として宣言し、後続の計算で参照する前提にする。
・定数は大文字名で表記する慣例に従うと可読性が上がる。2:5×5 の番号付きグリッドを入れ子のリストとして連番で生成し、返す。
・一辺の長さを基準に、外側のループで行、内側のループで列を回す。
・連番は変数で持ち上げて増やしながら、常に二桁表示に整形して追加する。
・1行ぶんを一旦まとめてから盤面の入れ物に追加し、最後に完成品を返す。3:盤面データを行ごとに整形して、見やすいテキストのグリッドとして出力する。
・先頭で現在の盤面であることを示す見出し文を出力する。
・外側は行の単位で繰り返し、行の中の要素は空白でつないで1行として表示する。
・最後に空行を入れて出力結果に区切りを付ける。4:プレイヤーの位置と宝の位置を比べて、上・下・左・右の方角ヒントを組み合わせて返す。
・横方向は x 座標を比較して「右」か「左」を必要に応じて追加する。
・縦方向は y 座標を比較して「下」か「上」を必要に応じて追加する。
・集めた語をスペースで結合し、最後に固定の説明文を付けて返す。5:ゲーム開始時の状態を整える。
・盤面は別関数で生成し、宝の座標は 0〜(GRID_SIZE-1) の範囲から乱数で決める。
・選択済みマスは(x, y)
の組で集合に追加して重複選択を防ぐ。
・開始メッセージと初期表示の後、試行回数のカウンタと発見フラグを初期化する。6:入力の受け付けから判定・盤面更新・ヒント表示・残回数管理までまとめて処理
・入力番号は内部で0始まりに直し、範囲チェック後に列と行へ分解する。
・選択済みは座標の組で集合に記録し、重複なら再入力を促し、新規なら追加する。
・一致なら発見の印とフラグ、外れなら通常の印とヒントを出し、外れたときだけ残り回数を減らす。7:宝が見つからなかった場合の締めメッセージを出してゲームを終える。
・ループの外で発見フラグを判定し、未発見なら終了メッセージを表示する。
・当たり時は別の箇所で処理済みなので、ここでは外れ時だけを扱う。
・表示は1回で済むよう、条件分岐を簡潔に保つ。8:ゲーム本体を起動するエントリーポイントを用意する。
・直接実行時だけ通る慣用句を使い、インポート時は何もしないようにする。
- ヒント2【穴埋め問題にする】
-
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
import random # グリッドサイズを定義 GRID_SIZE = 5 # グリッドを初期化する関数(リスト使用) def initialize_grid(): '''(穴埋め)グリッド全体を格納する空のリストを用意する''' count = 1 for i in range(GRID_SIZE): '''(穴埋め)1行ぶんの要素を入れる空のリスト row を用意する''' for j in range(GRID_SIZE): '''(穴埋め)二桁文字列に整形した連番を row に追加する(例: "01")''' count += 1 '''(穴埋め)完成した row を grid に追加する''' return grid # グリッドを表示する関数 def display_grid(grid): print("現在のグリッド:") '''(穴埋め)グリッドの各行をループし、要素を空白で結合して1行ずつ表示する''' print() # ヒントを提供する関数 def give_hint(player_x, player_y, treasure_x, treasure_y): '''(穴埋め)方角語を追加していくための空のリスト hint を用意する''' if player_x < treasure_x: hint.append("右") elif player_x > treasure_x: hint.append("左") if player_y < treasure_y: hint.append("下") elif player_y > treasure_y: hint.append("上") return " ".join(hint) + " に宝があります。" # 宝探しゲームを実行する関数 def treasure_hunt_game(): # グリッド(リスト)と宝の位置を初期化 grid = initialize_grid() treasure_x = random.randint(0, GRID_SIZE - 1) treasure_y = random.randint(0, GRID_SIZE - 1) # 選択済みマスを集合で記録 '''(穴埋め)選択済みマスを格納する空の集合 chosen_cells を作成する''' print("宝探しゲームへようこそ!1から25までの数字で宝を探し当ててください。") print("宝を探すチャンスは3回です。") display_grid(grid) attempts = 0 found_treasure = False while attempts < 3 and not found_treasure: try: choice = int(input("1から25のマス番号を入力してください: ")) - 1 if choice < 0 or choice >= GRID_SIZE * GRID_SIZE: print("無効な番号です。1から25までの数字を入力してください。") continue player_x = choice % GRID_SIZE player_y = choice // GRID_SIZE # 集合で既に選んだか確認 if (player_x, player_y) in chosen_cells: print("そのマスは既に選択されています。別のマスを選んでください。") continue # 選択済みに追加 '''(穴埋め)(player_x, player_y) のタプルを chosen_cells に追加する''' # 宝を見つけた場合 if player_x == treasure_x and player_y == treasure_y: print("🎉 おめでとうございます!宝を見つけました! 🎉") '''(穴埋め)宝を見つけた座標のマスに "☆" を代入して表示用に更新する''' found_treasure = True else: # 選んだ場所を*でマークし、ヒントを出す '''(穴埋め)選んだ座標のマスに "*" を代入して表示用に更新する''' print(give_hint(player_x, player_y, treasure_x, treasure_y)) attempts += 1 print(f"残りのチャンス: {3 - attempts}回") display_grid(grid) except ValueError: print("無効な入力です。1から25までの数字を入力してください。") if not found_treasure: print("💀 残念ながら、チャンスを使い切りました。宝は見つかりませんでした。") # ゲームを実行 if __name__ == "__main__": treasure_hunt_game()
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
当サイトではテキストベースのゲームコードを多数紹介していますが、それだけでなく 画面上で遊べるゲームアプリ の作り方を紹介たコースも用意してあります。
Pythonの実践力強化にもなりますので、ぜひ挑戦して下さい^^
解答と解説|宝探しゲームのサンプルコード
この問題の一つの正解例とそのコードの解説を以下に示します。
解答例|宝探しゲームプログラム
例えば以下のようなプログラムが考えられます。
- 正解コード
-
import random # グリッドサイズを定義 GRID_SIZE = 5 # グリッドを初期化する関数(リスト使用) def initialize_grid(): grid = [] count = 1 for i in range(GRID_SIZE): row = [] for j in range(GRID_SIZE): row.append(f"{count:02d}") # 01, 02, 03...の形式で初期化 count += 1 grid.append(row) return grid # グリッドを表示する関数 def display_grid(grid): print("現在のグリッド:") for row in grid: print(" ".join(row)) print() # ヒントを提供する関数 def give_hint(player_x, player_y, treasure_x, treasure_y): hint = [] if player_x < treasure_x: hint.append("右") elif player_x > treasure_x: hint.append("左") if player_y < treasure_y: hint.append("下") elif player_y > treasure_y: hint.append("上") return " ".join(hint) + " に宝があります。" # 宝探しゲームを実行する関数 def treasure_hunt_game(): # グリッド(リスト)と宝の位置を初期化 grid = initialize_grid() treasure_x = random.randint(0, GRID_SIZE - 1) treasure_y = random.randint(0, GRID_SIZE - 1) # 選択済みマスを集合で記録 chosen_cells = set() print("宝探しゲームへようこそ!1から25までの数字で宝を探し当ててください。") print("宝を探すチャンスは3回です。") display_grid(grid) attempts = 0 found_treasure = False while attempts < 3 and not found_treasure: try: choice = int(input("1から25のマス番号を入力してください: ")) - 1 if choice < 0 or choice >= GRID_SIZE * GRID_SIZE: print("無効な番号です。1から25までの数字を入力してください。") continue player_x = choice % GRID_SIZE player_y = choice // GRID_SIZE # 集合で既に選んだか確認 if (player_x, player_y) in chosen_cells: print("そのマスは既に選択されています。別のマスを選んでください。") continue # 選択済みに追加 chosen_cells.add((player_x, player_y)) # 宝を見つけた場合 if player_x == treasure_x and player_y == treasure_y: print("🎉 おめでとうございます!宝を見つけました! 🎉") grid[player_y][player_x] = "☆" # 宝の位置を☆で表示 found_treasure = True else: # 選んだ場所を*でマークし、ヒントを出す grid[player_y][player_x] = "*" print(give_hint(player_x, player_y, treasure_x, treasure_y)) attempts += 1 print(f"残りのチャンス: {3 - attempts}回") display_grid(grid) except ValueError: print("無効な入力です。1から25までの数字を入力してください。") if not found_treasure: print("💀 残念ながら、チャンスを使い切りました。宝は見つかりませんでした。") # ゲームを実行 if __name__ == "__main__": treasure_hunt_game()
解答例の解説|宝探しゲームの考え方
コードの各部分の解説を以下に示します。
- 詳細解説
-
乱数機能を読み込み、グリッドの一辺の大きさを定数として決める。
import random # グリッドサイズを定義 GRID_SIZE = 5
まず宝の位置をランダムに決めるために標準ライブラリの乱数機能を使えるようにします。
次にゲーム全体で共通して使うグリッドの大きさを、変更しやすいように一定の値として宣言します。グリッドを初期化する関数
# グリッドを初期化する関数(リスト使用) def initialize_grid(): grid = [] count = 1 for i in range(GRID_SIZE): row = [] for j in range(GRID_SIZE): row.append(f"{count:02d}") # 01, 02, 03...の形式で初期化 count += 1 grid.append(row) return grid
この関数は、ゲームで使う盤面データを最初に用意します。
まず空の入れ物を用意し、そこに行単位のリストを順番に積み上げていきます。
行の中では列を左から右へたどり、連番を文字として追加します。
番号は途中で桁数が変わっても形がそろうよう、常に二桁で表示できる形式に整えます。
行ができたら盤面の入れ物に追加し、すべての行がそろったら完成した盤面を呼び出し元へ返します。
こうしておくことで、のちほどの表示や更新が簡単になります。グリッドを表示する関数
# グリッドを表示する関数 def display_grid(grid): print("現在のグリッド:") for row in grid: print(" ".join(row)) print()
この関数は、現在の盤面を人間が読みやすい形で画面に表示します。
最初に見出しの文を出してから、盤面を構成する各行を順番に処理し、行の中の要素同士の間に空白を入れて一続きの文字に整えます。
行ごとに表示することで、5×5のレイアウトがそのまま視覚的に分かるようになります。
最後に余白となる空行を入れて、次の表示や入力との区切りを分かりやすくします。ヒントを提供する関数
# ヒントを提供する関数 def give_hint(player_x, player_y, treasure_x, treasure_y): hint = [] if player_x < treasure_x: hint.append("右") elif player_x > treasure_x: hint.append("左") if player_y < treasure_y: hint.append("下") elif player_y > treasure_y: hint.append("上") return " ".join(hint) + " に宝があります。"
この関数は、選んだマスが宝に対してどちらの方向にあるかを言葉で伝える役割です。
はじめに空の入れ物を用意し、水平方向について「右にあるのか」「左にあるのか」を判定して対応する語を追加します。
つぎに垂直方向についても同様に「下にあるのか」「上にあるのか」を判定して語を追加します。同じ列や同じ行にいる場合は、その軸の語は追加しません。
最後に、集めた語をスペースでつないで、一番うしろに固定の文言を付けた文字列として返します。
状況によっては語が1つのときも2つのときもあり、宝の位置と重なる状況はゲーム側で別扱いにする前提です。宝探しゲームを実行する関数
# 宝探しゲームを実行する関数 def treasure_hunt_game(): # グリッド(リスト)と宝の位置を初期化 grid = initialize_grid() treasure_x = random.randint(0, GRID_SIZE - 1) treasure_y = random.randint(0, GRID_SIZE - 1) # 選択済みマスを集合で記録 chosen_cells = set() print("宝探しゲームへようこそ!1から25までの数字で宝を探し当ててください。") print("宝を探すチャンスは3回です。") display_grid(grid) attempts = 0 found_treasure = False
この部分ではゲームを始めるための初期設定をまとめて行います。
まず、盤面として使う5×5のグリッドを用意します。
宝の位置は、行と列を表す2つの座標を乱数で決めて、どのゲームでも場所が変わるようにします。
プレイヤーがすでに選んだマスを記録するために集合を用意して、同じ場所を選んだときに気づけるようにします。
続いて、ゲームの案内メッセージを表示し、現在のグリッドを最初に見せておきます。
最後に、残り回数の管理に使うカウンタと、宝を発見したかどうかを表す真偽値を初期状態にして、後続の処理で参照できるようにします。1ターン分の処理
while attempts < 3 and not found_treasure: try: choice = int(input("1から25のマス番号を入力してください: ")) - 1 if choice < 0 or choice >= GRID_SIZE * GRID_SIZE: print("無効な番号です。1から25までの数字を入力してください。") continue player_x = choice % GRID_SIZE player_y = choice // GRID_SIZE # 集合で既に選んだか確認 if (player_x, player_y) in chosen_cells: print("そのマスは既に選択されています。別のマスを選んでください。") continue # 選択済みに追加 chosen_cells.add((player_x, player_y)) # 宝を見つけた場合 if player_x == treasure_x and player_y == treasure_y: print("🎉 おめでとうございます!宝を見つけました! 🎉") grid[player_y][player_x] = "☆" # 宝の位置を☆で表示 found_treasure = True else: # 選んだ場所を*でマークし、ヒントを出す grid[player_y][player_x] = "*" print(give_hint(player_x, player_y, treasure_x, treasure_y)) attempts += 1 print(f"残りのチャンス: {3 - attempts}回") display_grid(grid) except ValueError: print("無効な入力です。1から25までの数字を入力してください。")
この部分はゲームのメインループで、残り回数がある間だけ繰り返します。
まずプレイヤーにマス番号を入力してもらい、数字でない入力はやり直しを求めます。数字であっても範囲外なら無効として次の入力を促します。
妥当な番号は内部の座標に変換し、すでに選んだ場所かどうかを集合で確認します。未選択なら集合に登録し、宝の位置と一致するかを判定します。
当たりなら盤面に特別な記号を置いて発見フラグを立て、外れなら印を付けて方角ヒントを表示し、残り回数を1つ減らします。
最後に毎回、最新の盤面を表示して次のターンへ進みます。宝が見つからなかった場合の処理
if not found_treasure: print("💀 残念ながら、チャンスを使い切りました。宝は見つかりませんでした。")
この部分は、最後まで当たりが出なかったときの後処理です。
繰り返しが終わった段階で、発見したかどうかを表す真偽値を確認し、未発見ならその旨をユーザーに分かりやすく伝える文を表示します。
ここでは結果の通知だけに役割を絞ることで、当たりのときと外れのときで処理の見通しを良くしています。ゲームのエントリーポイント
# ゲームを実行 if __name__ == "__main__": treasure_hunt_game()
ここはプログラムの起動スイッチです。
Python ではファイルが「直接実行された場合」と「モジュールとして他のファイルから読み込まれた場合」で__name__
の値が変わります。
この条件を使うことで、直接実行のときだけゲームを1回起動し、学習用の関数や定数は読み込み時に勝手に動かないようにできます。
こうしておくと、後からテストを書いたり、別の教材から関数を再利用したりしても、副作用が出ずに扱いやすくなります。
Pythonのゲームコード一覧は こちらをクリック
Pythonでのゲームアプリ開発は こちらをクリック
- サイト改善アンケート|ご意見をお聞かせください(1分で終わります)
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、みなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。1分だけ、ご協力いただけますと幸いです。
【Python】サイト改善アンケート