【Python】レッスン4-S2:ナインゲームを作ろう

一つ前のLessonでは宝探しゲームを作成しました。
今回はナインゲームを作成しましょう。
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でのゲームアプリ開発は こちらをクリック
ナインゲームを作ろう|リスト・タプル・辞書を使う練習問題
プレイヤーとコンピュータが、1〜9の牌から交互に1枚ずつ選んで数の大きさで競うコマンドラインゲームを作成しましょう。
各ラウンドの結果を表示しながら進行し、すべての牌を出し終えたら最終得点と勝者を表示します。
ナインゲームのルールとプログラムの仕様
以下の要件に従ってコードを完成させてください。
- ゲーム開始時に 「ナインゲームを開始します!」 と表示すること。
- プレイヤーとコンピュータは 1〜9 の牌 を所持し、全9回戦 を行うこと。
- 各ターン開始前に、両者の 現在の得点 と 持ち牌一覧 を表示すること。未所持の位置は「-」で示すこと。
- プレイヤーは 入力 で打牌を選び、コンピュータの打牌は ランダム に選ぶこと。
- 出した牌を比較し、大きい数字を出した側がその数字と同じ点数 を獲得すること。
- 同値は引き分け(得点なし)。いずれの場合も 出した牌は以後使えない ものとすること。
- すべての牌を出し終えたら 最終得点を比較して勝者 を表示すること。
また、以下のようにデータ構造を使用すること。
データ構造 | 使い方 |
---|---|
リスト | 各プレイヤーの 持ち牌 をリストで管理すること(初期値は [1,2,3,4,5,6,7,8,9] )。使用済みの牌は -1 に置き換えて保持 し、表示時は 「-」 として出力すること。ランダム選択や入力の妥当性確認では -1 を除外したリスト を用いること。 |
辞書 | ゲーム状態は 辞書 で管理すること。 例: players = {"player": {"tiles": [...], "score": 0}, "computer": {"tiles": [...], "score": 0}} (各プレイヤーは tiles (持ち牌のリスト) と score (得点) を持つ) |
タプル | 各ラウンドの 対戦履歴 を タプル (プレイヤーの打牌, コンピュータの打牌) として記録し、ゲーム終了時に 第1回戦から順に表示 すること。 |
ただし、以下のような実行結果となるコードを書くこと。
ナインゲームを開始します! 【第1回戦】 プレイヤーの得点 : 0点 コンピュータの得点: 0点 ☆プレイヤーの持ち牌⇒【1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9】 コンピュータの持ち牌⇒【1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9】 持ち牌の中から出す牌を選択してください > 1 プレイヤーの打牌:1 < 5:コンピュータの打牌 コンピュータは5点獲得 【第2回戦】 プレイヤーの得点 : 0点 コンピュータの得点: 5点 ... ゲーム終了! プレイヤーの得点 : 20点 コンピュータの得点: 31点 コンピュータの勝利です! === 対戦履歴(タプル) === 第1回戦: プレイヤー=1, コンピュータ=5 第2回戦: プレイヤー=2, コンピュータ=7 ... 第9回戦: プレイヤー=9, コンピュータ=1
【ヒント】難しいと感じる人だけ見よう
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント1【コードの構成を見る】
-
正解のコードは上から順に以下のような構成となっています。
1:持ち牌のリストを、人が読みやすい一行の表示に整えて出力する。
・関数はタイトル(文字列)と牌のリストを受け取り、冒頭と末尾の記号の出力を分ける。
・牌のリストは、未所持(-1)は「-」、それ以外は文字列に変換して並べる。
・変換後の要素を「 ,」で結合して出力し、最後に閉じの記号を表示して改行する。2:プレイヤーとコンピュータの現在の得点を、2行の定型フォーマットで表示する。
・引数にはゲーム状態の辞書を受け取り、player
とcomputer
のそれぞれの中にあるscore
を参照する設計にする。
・f文字列でラベルと数値、そして「点」を連結し、見やすい定型の2行に整えて出力する。
・表示順は「プレイヤー → コンピュータ」で固定し、各ターンの開始時に呼び出して最新の得点を示す。3:プレイヤーに入力させ、妥当な牌が選ばれるまで繰り返し確認して受け付ける。
・妥当な入力が得られるまで繰り返し処理を続ける仕組みにする。
・入力文字は数値に変換を試み、失敗したら例外処理でメッセージを出してやり直させる。
・変換後の数が持ち牌リストに含まれているかを確認し、有効ならその値を返し、無効なら再入力を促す。4:コンピュータの打牌を、与えられた候補から等確率で1枚ランダムに選ぶ。
・標準ライブラリの乱数機能を使って、与えられた一覧から要素を等確率で1つ取り出す。
・関数は「候補となる牌のリスト」を受け取り、「選ばれた1つの数値」を返すだけにする。
・入力される一覧は、すでに使用済みの値を含まない想定で処理を簡潔に保つ。5:ナインゲームのメイン関数。開始メッセージを表示して準備を整える。
・プレイヤーごとに「持ち牌」と「得点」をまとめて扱えるよう、状態を階層化して管理する。
・持ち牌は連続した数の集合としてリストで初期化し、得点はゼロからスタートさせる。
・各回の出し手を後で振り返れるよう、履歴を格納する入れ物を用意し、開始の案内文を最初に表示する。6:1回戦ごとに表示・入力・判定・更新・記録を行い、全9回戦を進行させる。
・9回分のラウンドを順番に処理し、各回の冒頭で「第n回戦」の見出しと現在の得点・持ち牌を表示する。
・入力やランダム選択の候補は「未使用だけ」を対象にし、勝敗は3通り(大・小・同値)で分岐して加点ルールを適用する。
・使った牌は持ち牌の該当位置を使用済みに更新し、(プレイヤーの出牌, コンピュータの出牌)のタプルを履歴に追加する。7:最終得点を表示して勝者を判定し、全ラウンドの対戦履歴を番号付きで一覧表示する。
・終了時は区切りのメッセージを最初に出し、直後に現在の得点をまとめて表示して全体を把握しやすくする。
・勝者判定は得点の比較で三分岐(大きい/小さい/同じ)に分け、それぞれ対応する文言を出力する。
・対戦履歴は先頭から順に番号を振って表示し、各要素のタプルからプレイヤーとコンピュータの出目を取り出して整形する。8:このファイルを直接実行したときだけ、ゲームを起動するためのエントリーポイントを用意する。
・慣用句を使って「直接実行時のみ実行、インポート時は実行しない」条件分岐を作る。
・エントリーポイントでは、ゲーム進行を担うmain()
を呼ぶだけにして責務を分離する。
・テストや再利用を考え、インポートされた場合は副作用(勝手な起動)が発生しない設計にする。
- ヒント2【穴埋め問題にする】
-
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
import random # タイル(持ち牌)を表示する関数 def display_tiles(title, tiles): print(f"{title}⇒【", end="") '''(穴埋め)リスト内包表記で -1 を "-" に置換し、文字列に変換した要素を " ," で join して一行表示する print 文を書く''' print("】") # プレイヤーとコンピュータの得点を表示する関数 def display_scores(players): '''(穴埋め)players 辞書からプレイヤー側の score を参照して表示する print 文を書く''' '''(穴埋め)players 辞書からコンピュータ側の score を参照して表示する print 文を書く''' # プレイヤーが手持ちから牌を選ぶ関数(入力処理付き) def get_player_choice(tiles): while True: try: choice = int(input("持ち牌の中から出す牌を選択してください > ")) if choice in tiles: return choice else: print("無効な選択です。再度選択してください。") except ValueError: print("数字を入力してください。") # コンピュータがランダムに牌を選ぶ関数 def get_computer_choice(tiles): return random.choice(tiles) # ナインゲームのメイン処理を行う関数 def main(): # プレイヤーとコンピュータの状態を辞書で管理 '''(穴埋め)players 辞書を初期化するブロックを書く。 "player" と "computer" をキーに持ち、各々に "tiles": [1,2,3,4,5,6,7,8,9] と "score": 0 を設定する''' # タプルで対戦履歴を残す '''(穴埋め)対戦履歴を格納するリスト history を空で初期化する行を書く''' print("ナインゲームを開始します!") for round_num in range(1, 10): print(f"\n【第{round_num}回戦】") display_scores(players) display_tiles("☆プレイヤーの持ち牌", players["player"]["tiles"]) display_tiles("コンピュータの持ち牌", players["computer"]["tiles"]) '''(穴埋め)プレイヤーの持ち牌から -1 を除外する内包表記で新しいリストを作り、 入力関数に渡して選択値を受け取る代入文を書く''' '''(穴埋め)コンピュータの持ち牌から -1 を除外する内包表記で新しいリストを作り、 ランダム選択関数に渡して選択値を受け取る代入文を書く''' print(f"プレイヤーの打牌:{player_choice}", end="") if player_choice > computer_choice: print(f" > {computer_choice}:コンピュータの打牌") players["player"]["score"] += player_choice print(f"プレイヤーは{player_choice}点獲得") elif player_choice < computer_choice: print(f" < {computer_choice}:コンピュータの打牌") players["computer"]["score"] += computer_choice print(f"コンピュータは{computer_choice}点獲得") else: print(f" = {computer_choice}:コンピュータの打牌") print("引き分けです。得点はなし") # 使用済み牌を -1 に '''(穴埋め)プレイヤーの持ち牌リストで、選んだ値の位置を index で特定し、 その位置に -1 を代入して使用済みに更新する行を書く''' '''(穴埋め)コンピュータの持ち牌リストで、選んだ値の位置を index で特定し、 その位置に -1 を代入して使用済みに更新する行を書く''' # タプルで履歴保存 '''(穴埋め)(player_choice, computer_choice) のタプルを作り、history リストに追加する行を書く''' print("\nゲーム終了!") display_scores(players) if players["player"]["score"] > players["computer"]["score"]: print("プレイヤーの勝利です!") elif players["player"]["score"] < players["computer"]["score"]: print("コンピュータの勝利です!") else: print("引き分けです!") print("\n=== 対戦履歴(タプル) ===") for i, (p, c) in enumerate(history, 1): print(f"第{i}回戦: プレイヤー={p}, コンピュータ={c}") if __name__ == "__main__": main()
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
当サイトではテキストベースのゲームコードを多数紹介していますが、それだけでなく 画面上で遊べるゲームアプリ の作り方を紹介たコースも用意してあります。
Pythonの実践力強化にもなりますので、ぜひ挑戦して下さい^^
解答と解説|ナインゲームのサンプルコード
この問題の一つの正解例とそのコードの解説を以下に示します。
解答例|ナインゲームプログラム
例えば以下のようなプログラムが考えられます。
- 正解コード
-
import random # タイル(持ち牌)を表示する関数 def display_tiles(title, tiles): print(f"{title}⇒【", end="") print(" ,".join([str(tile) if tile != -1 else "-" for tile in tiles]), end="") print("】") # プレイヤーとコンピュータの得点を表示する関数 def display_scores(players): print(f"プレイヤーの得点 : {players['player']['score']}点") print(f"コンピュータの得点: {players['computer']['score']}点") # プレイヤーが手持ちから牌を選ぶ関数(入力処理付き) def get_player_choice(tiles): while True: try: choice = int(input("持ち牌の中から出す牌を選択してください > ")) if choice in tiles: return choice else: print("無効な選択です。再度選択してください。") except ValueError: print("数字を入力してください。") # コンピュータがランダムに牌を選ぶ関数 def get_computer_choice(tiles): return random.choice(tiles) # ナインゲームのメイン処理を行う関数 def main(): # プレイヤーとコンピュータの状態を辞書で管理 players = { "player": {"tiles": [1,2,3,4,5,6,7,8,9], "score": 0}, "computer": {"tiles": [1,2,3,4,5,6,7,8,9], "score": 0} } # タプルで対戦履歴を残す history = [] print("ナインゲームを開始します!") for round_num in range(1, 10): print(f"\n【第{round_num}回戦】") display_scores(players) display_tiles("☆プレイヤーの持ち牌", players["player"]["tiles"]) display_tiles("コンピュータの持ち牌", players["computer"]["tiles"]) player_choice = get_player_choice([tile for tile in players["player"]["tiles"] if tile != -1]) computer_choice = get_computer_choice([tile for tile in players["computer"]["tiles"] if tile != -1]) print(f"プレイヤーの打牌:{player_choice}", end="") if player_choice > computer_choice: print(f" > {computer_choice}:コンピュータの打牌") players["player"]["score"] += player_choice print(f"プレイヤーは{player_choice}点獲得") elif player_choice < computer_choice: print(f" < {computer_choice}:コンピュータの打牌") players["computer"]["score"] += computer_choice print(f"コンピュータは{computer_choice}点獲得") else: print(f" = {computer_choice}:コンピュータの打牌") print("引き分けです。得点はなし") # 使用済み牌を -1 に players["player"]["tiles"][players["player"]["tiles"].index(player_choice)] = -1 players["computer"]["tiles"][players["computer"]["tiles"].index(computer_choice)] = -1 # タプルで履歴保存 history.append((player_choice, computer_choice)) print("\nゲーム終了!") display_scores(players) if players["player"]["score"] > players["computer"]["score"]: print("プレイヤーの勝利です!") elif players["player"]["score"] < players["computer"]["score"]: print("コンピュータの勝利です!") else: print("引き分けです!") print("\n=== 対戦履歴(タプル) ===") for i, (p, c) in enumerate(history, 1): print(f"第{i}回戦: プレイヤー={p}, コンピュータ={c}") if __name__ == "__main__": main()
解答例の解説|ナインゲームの考え方
解答例の詳細解説は以下の通りです。
- 詳細解説
-
タイル(持ち牌)を表示する関数
import random # タイル(持ち牌)を表示する関数 def display_tiles(title, tiles): print(f"{title}⇒【", end="") print(" ,".join([str(tile) if tile != -1 else "-" for tile in tiles]), end="") print("】")
最初に乱数を扱うための標準ライブラリを読み込みます。
続いて、タイトルと持ち牌のリストを受け取る関数を定義します。
表示は三段階で行い、冒頭にタイトルと始まりの記号を出し、次に牌の並びを区切り文字でつないで出し、最後に閉じの記号を出します。
牌の一覧を作るときは、未所持を表す値だけ「-」という見た目に置き換え、それ以外は文字として並べます。
また、途中では改行せずにつなげて表示するために、出力の終端の扱いを調整しています。プレイヤーとコンピュータの得点を表示する関数
# プレイヤーとコンピュータの得点を表示する関数 def display_scores(players): print(f"プレイヤーの得点 : {players['player']['score']}点") print(f"コンピュータの得点: {players['computer']['score']}点")
この関数は、ゲーム全体の状態をまとめた辞書から、プレイヤーとコンピュータそれぞれの得点を取り出して表示します。
辞書の中にさらに辞書が入っている「ネストした構造」になっているため、まず外側で対象(プレイヤー/コンピュータ)を選び、内側で得点の値を参照します。
表示は日本語のラベルと数値を組み合わせ、末尾に「点」を付ける決まった形で2行に出します。
毎ターンの冒頭など、現在の状況を確認したいタイミングで呼び出すことで、進行を分かりやすくします。手持ちから牌を選ぶ関数
# プレイヤーが手持ちから牌を選ぶ関数(入力処理付き) def get_player_choice(tiles): while True: try: choice = int(input("持ち牌の中から出す牌を選択してください > ")) if choice in tiles: return choice else: print("無効な選択です。再度選択してください。") except ValueError: print("数字を入力してください。")
この関数はプレイヤーに出す牌を尋ね、正しい値が入力されるまで何度でも聞き直します。
入力は最初は文字として受け取るため、数として扱える形に変換しようとします。
数字以外が入ってしまうこともあるので、変換に失敗したときはエラーを捕まえて「数字を入力してください」と伝えます。
数に変換できたら、実際にその数が持ち牌の中にあるかを確かめます。
持っていない数を指定した場合は無効であることを知らせ、もう一度入力してもらいます。
条件を満たす有効な数が入力されたら、その数を返して処理を終えます。コンピュータがランダムに牌を選ぶ関数
# コンピュータがランダムに牌を選ぶ関数 def get_computer_choice(tiles): return random.choice(tiles)
この関数は、コンピュータが次に出す牌を自動で決めます。
受け取った持ち牌の一覧から、どれを選ぶかを人間の意思ではなく乱数に任せることで、毎回の対戦に偶然性と予測不能さを加えます。
ここで使う乱数は標準ライブラリに含まれており、あらかじめ読み込んでおけば、一覧の中から要素を均等な確率でひとつ取り出す処理を簡単に実現できます。
関数としては、候補の一覧を受け取り、選ばれた牌の数値をそのまま返すだけの単純な作りです(候補は呼び出し側で未所持を除外済みの前提)。ナインゲームのメイン処理を行う関数の前半
# ナインゲームのメイン処理を行う関数 def main(): # プレイヤーとコンピュータの状態を辞書で管理 players = { "player": {"tiles": [1,2,3,4,5,6,7,8,9], "score": 0}, "computer": {"tiles": [1,2,3,4,5,6,7,8,9], "score": 0} } # タプルで対戦履歴を残す history = [] print("ナインゲームを開始します!")
ここでは、ゲーム全体を動かすための土台を作ります。
まず、プレイヤーとコンピュータの状態をひとまとまりに管理できるように、名前ごとに持ち牌と得点をセットにして保存します。
持ち牌は並びや更新に向いたリスト、得点は増減しやすい数値で保持します。
あわせて、各回の出し手を記録していくための履歴用の入れ物も用意しておきます。
これらの準備ができたら、開始の合図となる文言を表示して、次の処理に進める状態にします。ゲームの進行(全9回戦)
for round_num in range(1, 10): print(f"\n【第{round_num}回戦】") display_scores(players) display_tiles("☆プレイヤーの持ち牌", players["player"]["tiles"]) display_tiles("コンピュータの持ち牌", players["computer"]["tiles"]) player_choice = get_player_choice([tile for tile in players["player"]["tiles"] if tile != -1]) computer_choice = get_computer_choice([tile for tile in players["computer"]["tiles"] if tile != -1]) print(f"プレイヤーの打牌:{player_choice}", end="") if player_choice > computer_choice: print(f" > {computer_choice}:コンピュータの打牌") players["player"]["score"] += player_choice print(f"プレイヤーは{player_choice}点獲得") elif player_choice < computer_choice: print(f" < {computer_choice}:コンピュータの打牌") players["computer"]["score"] += computer_choice print(f"コンピュータは{computer_choice}点獲得") else: print(f" = {computer_choice}:コンピュータの打牌") print("引き分けです。得点はなし") # 使用済み牌を -1 に players["player"]["tiles"][players["player"]["tiles"].index(player_choice)] = -1 players["computer"]["tiles"][players["computer"]["tiles"].index(computer_choice)] = -1 # タプルで履歴保存 history.append((player_choice, computer_choice))
ここではゲームの本体となるラウンド処理をまとめて行います。
最初に何回戦かを示す見出しを出し、その時点の得点と持ち牌を表示して状況を確認します。
続いて、プレイヤーには今使える牌の中から入力してもらい、コンピュータは同じく残っている候補からランダムに選びます。
両者の出した数を比べて、大きいほうだけが自分の出目と同じ点数を獲得します。同じ数なら引き分けで加点はありません。
どの場合でも、その回で使った牌は「使用済み」として扱い、持ち牌の一覧では以後「-」と表示されるように更新します。
最後に、その回の出し手をタプルで履歴に追加し、次の回戦へ進みます。最終得点を表示して勝者を判定
print("\nゲーム終了!") display_scores(players) if players["player"]["score"] > players["computer"]["score"]: print("プレイヤーの勝利です!") elif players["player"]["score"] < players["computer"]["score"]: print("コンピュータの勝利です!") else: print("引き分けです!") print("\n=== 対戦履歴(タプル) ===") for i, (p, c) in enumerate(history, 1): print(f"第{i}回戦: プレイヤー={p}, コンピュータ={c}")
全てのラウンドが終わったら区切りのメッセージを出し、最新の得点をもう一度表示します。
続いて、プレイヤーとコンピュータの得点を比べて、勝ち・負け・引き分けのいずれかを分かりやすい日本語で案内します。
最後に、これまでの各回で出した組み合わせを最初から順番に並べ、何回戦の出来事かが一目で分かるよう連番を付けて一覧化します。
履歴はタプルの並びなので、各要素からプレイヤー側とコンピュータ側の出目を取り出して、人が読みやすい形式で表示します。ゲームを起動するためのエントリーポイント
if __name__ == "__main__": main()
ここでは、プログラムの「起動スイッチ」を用意しています。
Pythonでは、ファイルを「直接実行」した場合と「他のファイルから読み込んだ(インポートした)」場合で、動作を切り替えることができます。
直接実行のときだけゲームを始め、インポート時には何も始めないようにすることで、関数の再利用やテストがしやすくなります。
実際の起動処理は、ゲーム全体を進行させる関数を1回呼び出すだけにして、役割をすっきり分けています。
Pythonのゲームコード一覧は こちらをクリック
Pythonでのゲームアプリ開発は こちらをクリック
- サイト改善アンケート|ご意見をお聞かせください(1分で終わります)
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、みなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。1分だけ、ご協力いただけますと幸いです。
【Python】サイト改善アンケート