LoginSignup
1
2

More than 1 year has passed since last update.

【Python3】0から作るPython初心者プログラミング【03】-大人数でじゃんけんプログラム-

Posted at

【ご挨拶】こんにちは! ぬかさんエンジニアリングです。(3回目‼)

前回の投稿にまたまたありがたいことにコメント頂きまして、クラスの使い方について教えて頂きました。
今回は、それらの内容も含めた上で複数人同時にじゃんけんが出来るようにアップデートした内容になっております。
クラスを使って実際に遊べる程度のプログラムを作ってみたいと思っていた初心者の方がいらっしゃいましたら是非挑戦してみてください!また、複数のインスタンスをまとめて扱う方法や特殊メソッド__str__など少し応用的な話もありますので、既にクラスを理解されている方も興味があれば見ていってくださいね!
LGTMも是非よろしくお願いします‼

本シリーズ初めての方へ

【趣旨】
Python初心者プログラマーが入門書で学んだ知識を実践的なコーディングを通じて身に着けていくためのお題を提供します。
お題を基に各自コーディングに挑戦していただいた後、この記事でコーディングの過程を答え合わせします。

【対象】
Pythonの入門書を読んで理解はしたけど何か目的をもって実践的にコーディングをしたい方。
ProgateでPythonコースをLv5まで勉強したけど応用力が身に着いていないと感じている方。

【初心者とは】
この記事シリーズでの初心者は下記の項目を理解済みであることが目安となっています。
[演算子, 標準ライブラリ, 条件分岐, 繰り返し処理, 例外処理, リスト, タプル, セット, 辞書, オブジェクト指向]

【利用ライブラリ & Python3 --version】

・Google Colaboratory
以下のリンクからアクセスして使い方を確認した後、左上のファイルタブから「ノートブックを新規作成」を選択して自分のプログラムを作りましょう。ファイルはGoogleDriveに保存されるため、自分のGoogleアカウントと連携させるのを忘れないようにしましょう。

Colaboratory へようこそ ←ここからリンクへ飛ぶ

・Python 3.7.12

$$$$

それではさっそく本題に入っていきましょう:muscle:

第【03】回 -大人数でじゃんけんプログラム-

このシリーズ第三回目の今回は、前回の「じゃんけんプログラムのクラス化」を応用して「大人数じゃんけんプログラム」を実装します!
前回クラス化した内容を更に進化させ、じゃんけんに3回勝利した人が優勝する仕様にアップデートしていきます。入力を受け取ってゲームをスタートし、一連の流れを止めずにどの順番でどう処理を実行してゴールまで行きつくのか、その中身を皆さんと一緒に作っていきたいと思います。

【お題】じゃんけんプログラムを複数人同時プレイできる様にしよう!

janken()

#出力
参加人数を整数で入力してください >4
参加人数は4人です
あなたの名前を入力してください >nukasan
nukasanさんですねエントリー完了しました

nukasanの現在の獲得勝利ポイント0

コンピュータ1の現在の獲得勝利ポイント0

コンピュータ2の現在の獲得勝利ポイント0

コンピュータ3の現在の獲得勝利ポイント0


じゃんけんぽん
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手チョキ
コンピュータ2さんの手パー
コンピュータ3さんの手パー


あいこでしょ
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手パー
コンピュータ2さんの手パー
コンピュータ3さんの手グー


コンピュータ1とコンピュータ2の勝ち!1ポイントゲット


nukasanの現在の獲得勝利ポイント0

コンピュータ1の現在の獲得勝利ポイント1

コンピュータ2の現在の獲得勝利ポイント1

コンピュータ3の現在の獲得勝利ポイント0


じゃんけんぽん
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手パー
コンピュータ2さんの手グー
コンピュータ3さんの手パー


コンピュータ1とコンピュータ3の勝ち!1ポイントゲット


nukasanの現在の獲得勝利ポイント0

コンピュータ1の現在の獲得勝利ポイント2

コンピュータ2の現在の獲得勝利ポイント1

コンピュータ3の現在の獲得勝利ポイント1


じゃんけんぽん
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手パー
コンピュータ2さんの手チョキ
コンピュータ3さんの手パー


                .
                .
                .
                .
                .


nukasanの現在の獲得勝利ポイント1

コンピュータ1の現在の獲得勝利ポイント2

コンピュータ2の現在の獲得勝利ポイント2

コンピュータ3の現在の獲得勝利ポイント1


じゃんけんぽん
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手チョキ
コンピュータ2さんの手チョキ
コンピュータ3さんの手パー


あいこでしょ
-------------------------------------------------------
じゃんけんの選択肢{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数1, 2, 3) > 1
nukasanさんの手グー
コンピュータ1さんの手グー
コンピュータ2さんの手グー
コンピュータ3さんの手チョキ


nukasanとコンピュータ1とコンピュータ2の勝ち!1ポイントゲット



*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+
>>>>>よってコンピュータ1とコンピュータ2の優勝です!!<<<<<
*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+

1.3人以上の参加人数を入力してゲームに反映できるようにしてください。
2.プレイヤー名を入力してゲームに反映できるようにしてください。
3.じゃんけんの前に各参加者の現在の得点を出力してください。
4.各参加者のじゃんけんの手を出力してください。
5.じゃんけんの勝者の名前を出力してください。
6.じゃんけん勝利ポイントを3点先取した参加者の名前を出力してください。

〔お助けヒント〕

ヒント1
参加者のインスタンスは一つ一つ変数に代入せずに配列にまとめて格納し、取り出す場合はfor文を使うようにすると便利です。

ヒント2
新たにクラスを作成し、初期化メソッドインスタンスの格納された配列を変数に代入し、じゃんけんの一連の流れを実行できるインスタンスメソッドを作成してまとめると複数人同時じゃんけんのプログラムが作りやすくなります。

ヒント3
3人以上でじゃんけんする場合、場に出ている手の種類が1種と3種である場合は必ず「あいこ」になり、2種の場合は必ずどちらかが勝ちます。

ヒント4
インスタンスメソッド内で他のインスタンスメソッドを実行することで、一つのインスタンスメソッドを実行するだけで様々な処理を連鎖的にさせることができます。

【解答】

解答は以下の通りです。
※この解答はあくまで私の回答です。各々の方法でお題が解けていればそれで全然かまいません。むしろ、もっと簡潔な方法があればご教示頂けると助かります:bow_tone1:

import random

#コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Computer:

  def __init__(self, name, point=0):
    self.name = name
    self.point = point

  def pon(self):
    self.hand = random.choice(tuple(HANDS.values()))


#ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Human:

  def __init__(self, name, point=0):
    self.name = name
    self.point = point

  def pon(self):
    while True:
      try:
        print("-"*55 + f"\nじゃんけんの選択肢:{HANDS}\n" + "-"*55)
        human_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > "))
        if human_hand in (1, 2, 3):
          self.hand = HANDS[human_hand]            
          return
      except:
        print("整数1, 2, 3を入力をして下さい")


#じゃんけんクラス(じゃんけんをする際の手の選択と出力、判定、ポイントの加算と出力)
class Janken:

  def __init__(self, players):
    self.players = players

  ##じゃんけんの一連の流れを実行
  def play(self):
    for player in self.players:
      print(f"\n{player.name}の現在の獲得勝利ポイント:{player.point}")
    print("\n\nじゃんけんぽん!")
    self.select_hand()
    while True:
      self.winners = self.judge()
      if not self.winners:
        print("\n\nあいこでしょ!")
        self.select_hand()
      else:
        break
    print(f"\n\n{'と'.join([winner.name for winner in self.winners])}の勝ち!1ポイントゲット\n")
    for winner in self.winners: 
      winner.point += 1
    return [winner.name for winner in self.winners if winner.point >= 3]

  ##ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手を出力
  def select_hand(self):
    for player in self.players:
      player.pon()    
      print(f"{player.name}さんの手:{player.hand}")  

  ##参加者のじゃんけんの手があいこか否かを判定、あいこでなければ勝者を決定
  def judge(self):
    hands = set(player.hand for player in self.players)
    if len(hands) != 2:
      return None
    hand1, hand2 = hands
    win = hand1 if hand1.stronger_than(hand2) else hand2
    return [player for player in self.players if player.hand == win]

#ハンドクラス(判定で2つの手の勝敗を決定)        
class Hand:

  def __init__(self, shape, stronger_than):
    self.shape = shape
    self._stronger_than = stronger_than

  ##デバッグ時にインスタンスを指定の形で出力する用
  def __repr__(self):
    return repr(self.shape)

  ##インスタンスをstr型で出力する際にself.shapeを出力
  def __str__(self):
    return self.shape

  ##2つの手のうち、一方の手が勝つ時の相手の手と実際の相手の手が同じだという返り値を返す
  def stronger_than(self, enemy):
    return self._stronger_than == enemy.shape

#ハンドクラスのインスタンスを要素とする辞書
HANDS = {1: Hand("グー", "チョキ"),
         2: Hand("チョキ", "パー"),
         3: Hand("パー", "グー")}


#じゃんけんの実施&出力
def janken():

  ##参加人数を選択
  while True:
    try :
      all_player_num = int(input("参加人数を整数で入力してください >"))
      if all_player_num > 0:
        break
    except:
      print("正の整数を入力してください。")    
  print(f"参加人数は{all_player_num}人です。")

  ##名前の入力
  while True:
    my_name = (input("あなたの名前を入力してください >"))
    if my_name:
      print(f"{my_name}さんですね。エントリー完了しました。")
      break

  ##参加者のクラスをplayer配列に格納
  players = Human(my_name), *[Computer(f"コンピュータ{n}") for n in range(1, all_player_num)]

  ##じゃんけんクラスのplayインスタンスメソッドを実行し、優勝者が出るまでじゃんけんを繰り返す
  while True:
      result = Janken(players).play() 
      if len(result) > 0:
          print("\n\n" + "*+"*30 + f"\n>>>>>よって{'と'.join(result)}の優勝です!!<<<<<\n" + "*+"*30 + "\n\n\n")
          break

#じゃんけんメソッドを実行
janken()

【解説】

今回は、じゃんけんプログラムの応用編と言うことで人間一人とコンピュータ複数人でじゃんけんが出来るプログラムにアップデートしてみたいと思います。

主な変更点は3つあります。
一つ目。各プレイヤーの名前と勝利ポイントをインスタンス変数に保持できるようにクラスを変更します。
二つ目。複数人で同時にじゃんけんをして誰が勝利したか判定できるように変更します。
三つ目。各プレイヤーの勝利ポイントを記録し、3点先取した人が優勝する仕組みに変更します。

この変更点を満たす形でクラスインスタンスメソッドを作っていきたいと思います。

どんなクラスを作るのか、どんなインスタンスメソッドを作るのかは自由度が高すぎてむやみに作ってもこんがらがります。まずは、どんな機能が必要でどんなインスタンスメソッドクラスにまとめると分かりやすくて効率の良いコードになるのか自分で仮説を立てて作ってみます。たいていはエラーを吐くのでそこから試行錯誤と情報収集を繰り返しながら自分なりの解法を見つけていきます。その過程で得る知見は実践経験と結びついてしっかりと身に着いていきます。今回の解説も情報収集として使っていただくために書いておりますが、なるべく自力で作ってみるのをおすすめします。

〔作り方〕

①プレイ人数とプレイヤー名を入力できる機構を作成

⑴プレイ人数の入力機構を作成

##参加人数を選択
  while True:
    try :
      all_player_num = int(input("参加人数を整数で入力してください >"))
      if all_player_num > 0:
        break
    except:
      print("正の整数を入力してください。")    
  print(f"参加人数は{all_player_num}人です。")

⑵プレイヤーの名前の入力機構を作成

##名前の入力
  while True:
    my_name = (input("あなたの名前を入力してください >"))
    if my_name:
      print(f"{my_name}さんですね。エントリー完了しました。")
      break

②ヒューマンクラスとコンピュータクラスに名前と勝利ポイントを受け取る初期化メソッドを追加

インスタンス作成時に名前を代入するself.nameを追加

#コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Computer:

  def __init__(self, name):
    self.name = name #<<<<<<<<<

#ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Human:

  def __init__(self, name):
    self.name = name #<<<<<<<<<

⑵勝利ポイントを代入するself.pointを追加

#コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Computer:

  def __init__(self, name, point=0):
    self.name = name
    self.point = point #<<<<<<<<<

#ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択)
class Human:

  def __init__(self, name, point=0):
    self.name = name
    self.point = point #<<<<<<<<<

インスタンス生成時にはポイントは0なので、引数に初期値として0を設定しておきます。

③参加者が何人でも処理しやすいようにインスタンスを配列(タプル)に格納

⑴ヒューマンクラスはプレイヤーの名前を、コンピュータは番号を受け取りplayersに格納

  ##参加者のクラスをplayer配列に格納
  players = Human(my_name), *[Computer(f"コンピュータ{n}") for n in range(1, all_player_num)]

  print(players)
  #出力(参加人数4人の場合)
  '''
  (<__main__.Human object at 0x7f842fe0a3d0>, <__main__.Computer object at 0x7f842fe98e90>, 
   <__main__.Computer object at 0x7f842fe98f90>, <__main__.Computer object at 0x7f84330fa410>)
  '''

プレイヤー名入力機構で入力した名前がmy_nameに代入されているので、ヒューマンクラス作成時に受け取ります。
コンピュータの数は参加人数からプレイヤー数1人を除いた分になるので、参加人数が代入されているall_player_numを使ってリスト内包表記で人数分のインスタンスを作成します。配列に代入する際には、「*」を使いリストから順番に要素を取り出す方法を使います。
実際にplayersの中身を見るとクラス型のオブジェクト(インスタンス)が格納されているのが分かります。
ここで疑問に思う方がいるかもしれません。クラスを作成するときは
インスタンス名 = クラス名()
と書きますよね??と。
私もそう思っていました。
だた、実はインスタンスを作成する際に変数に代入する必要はありません。
通例で変数に代入しているのは、インスタンスを作成した際にそのインスタンスを分かりやすく識別するという理由があるからです。変数に代入しなければむき出しのクラス型オブジェクトが出力されてしまい、同じクラスから作ったインスタンスを瞬時に識別するのは難しいのです。上記のコードを見ると変数に代入する理由がよくわかると思います。
今回の様にインスタンスをまとめて扱いたい場合は、変数への代入が要らないことを理解していればわざわざ
インスタンス作成→変数に代入→リストを作成→変数をリストに追加
せずとも
インスタンス作成→配列へ代入
とコードを省略できます。是非覚えておいてください。

④参加者のインスタンス配列を受け取りじゃんけんを実行するJankenクラスを作成

⑴初期化メソッドを作成

#じゃんけんクラス(じゃんけんをする際の手の選択と出力、判定、ポイントの加算と出力)
class Janken:

  def __init__(self, players):
    self.players = players

⑵じゃんけんの一連の流れを実行するplayインスタンスメソッドを作成

##じゃんけんの一連の流れを実行
  def play(self):
    ###現在の参加者全員のそれぞれの勝利ポイントを出力
    for player in self.players:
      print(f"\n{player.name}の現在の獲得勝利ポイント:{player.point}")
    ###じゃんけんスタート
    print("\n\nじゃんけんぽん!")
    ###ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手の記録と出力
    self.select_hand()
    ###各参加者の手からjudgeインスタンスメソッドであいこか否か判別。あいこを抜け出すまでじゃんけんを繰り返す
    while True:
      self.winners = self.judge()
      if not self.winners:
        print("\n\nあいこでしょ!")
        self.select_hand()
      else:
        break
    ###じゃんけんの勝者の名前を出力
    print(f"\n\n{'と'.join([winner.name for winner in self.winners])}の勝ち!1ポイントゲット\n")
    ###じゃんけんの勝者に勝利ポイントを加算し、もし3ポイント獲得した参加者がいれば名前を返り値として返す
    for winner in self.winners: 
      winner.point += 1
    return [winner.name for winner in self.winners if winner.point >= 3]

⑶各参加者のじゃんけんの手を記録し出力するselect_handインスタンスメソッドを作成

  ##ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手を出力
  def select_hand(self):
    for player in self.players:
      player.pon()    
      print(f"{player.name}さんの手:{player.hand}")  

ponインスタンスメソッドを実行するとself.handに3種のHandインスタンスのどれかが代入される様にヒューマンクラスとコンピュータクラスを一部変更します。
print(self.hand)を実行すると、特殊メソッド__str__の効果で"グーチョキパー"の文字列が出力されますが、type(self.hand)では<class '__main__.Hand'>が出力されるためHandクラスインスタンスが代入されていると分かります。)

⑷参加者のじゃんけんの手からあいこと勝敗を判定

  ##参加者のじゃんけんの手があいこか否かを判定、あいこでなければ勝者を決定
  def judge(self):
    ###参加者のじゃんけんの手をセットにまとめると場に出ている手の種類が分かる
    hands = set(player.hand for player in self.players)
    ###手が1種、3種の場合は必ずあいこになるので返り値でNoneを返す
    if len(hands) != 2:
      return None
    ###手が2種の場合は、一方の手でstronger_thanインスタンスメソッドを実行し、強い手を判別する
    hand1, hand2 = hands
    win = hand1 if hand1.stronger_than(hand2) else hand2
    ###強い手を出していた参加者を勝者として返り値で返す
    return [player for player in self.players if player.hand == win]

⑤の⑵で詳しく説明

⑤複数人の勝敗判定に対応するためじゃんけんの手の辞書を変更しHandクラスを作成

⑴じゃんけんの手の辞書をJudgeインスタンスメソッドの判定に使いやすいように変更

#ハンドクラスのインスタンスを要素とする辞書
HANDS = {1: Hand("グー", "チョキ"),
         2: Hand("チョキ", "パー"),
         3: Hand("パー", "グー")}

⑤の⑵で詳しく説明

⑵Handクラスを作成する

#ハンドクラス(判定で2つの手の勝敗を決定)        
class Hand:

  def __init__(self, shape, stronger_than):
    self.shape = shape
    self._stronger_than = stronger_than

  ##デバッグ時にインスタンスを指定の形で出力する用
  def __repr__(self):
    return repr(self.shape)

  ##インスタンスをstr型で出力する際にself.shapeを出力
  def __str__(self):
    return self.shape

  ##2つの手のうち、一方の手が勝つ時の相手の手と実際の相手の手が同じだという返り値を返す
  def stronger_than(self, enemy):
    return self._stronger_than == enemy.shape

初期化メソッドでは、self.shapeに選んだ手、self._stronger_thanに選んだ手が勝てる相手の手(グーに対するチョキ)を代入します。

Handのインスタンス__str__という特殊メソッドによって、str型(文字列型)で表示するときにself.shape、つまり選んだ手に対応する文字列"グーチョキパー"のどれかを出力します。
この特殊メソッド__str__についてはTechAcademyマガジンのPythonのstrreprの違いを現役エンジニアが解説【初心者向け】をご覧ください。

stronger_thanインスタンスメソッドは、judgeインスタンスメソッドで2種の手から勝ち手を判別する際に用いられます。
まず2種の手の集合をhand1hand2に分けます。
次にhand1.stronger_than(hand2)で実行し、hand1が選んだ手(グー)が勝てる相手の手(チョキ)であるself._stronger_thanと相手の手であるenemy.shapeが同じだという返り値を返します。

あとはjudgeインスタンスメソッドで返り値がTrueの場合はhand1winに代入、返り値がFalseの場合はhand2winに代入します。
winに代入した手の種が勝ち手なので、各プレイヤーの手と照らし合わせ、同じ手を選んでいた場合はそのプレイヤーを勝者として返り値を返します。

ちなみに、それぞれの手の種はprintで出力してもただの文字列に見えますがtypeを見るとHandクラスインスタンスだと分かります。これは特殊メソッド__str__の効果で文字列が出力されているだけで本質は<class '__main__.Hand'>だからと言うことです。だから、引数enemyhand2を渡してenemy.shapeを書いてもエラーにならずに処理することができます。

⑥プレイヤーの誰かが3ポイントを取るまでじゃんけんを繰り返す機構を作成

Jankenクラスplayインスタンスメソッドの返り値が0以上なら優勝者を出力

  ##じゃんけんクラスのplayインスタンスメソッドを実行し、優勝者が出るまでじゃんけんを繰り返す
  while True:
      result = Janken(players).play() 
      if len(result) > 0:
          print("\n\n" + "*+"*30 + f"\n>>>>>よって{'と'.join(result)}の優勝です!!<<<<<\n" + "*+"*30 + "\n\n\n")
          break

Janken(players).play()によってJankenクラスの「属性」として参加者のインスタンスの配列が代入され、同時にplayインスタンスメソッドが実行されることで誰かが勝つまでじゃんけんをしてくれます。たった一文ですがほぼすべての処理が実行されます。
playインスタンスメソッドは返り値として3ポイント以上勝利ポイントを獲得した人の名前が帰ってきます。ですので、返り値をresultに代入してlen()で要素数が0より多くなった瞬間じゃんけんを終わりにして名前を出力することで優勝者を出力出来ます。

【終わりに】

今回は、前回のじゃんけんプログラムを応用して複数人同時にじゃんけんできるプログラムを作ってきました。いかがだったでしょうか。
何も見ずに作るのは結構難しかったのではないでしょうか。特にインスタンスを変数に代入せずに使う感覚を掴むまでには時間を要したのではないでしょうか。次回はもっと難易度は上がりますが一緒に頑張っていきましょう!反対に今回の課題をすらすらと作ることが出来た方はもう中級者に手がかかり始めていると言ってもいいかもしれません。どちらにせよ今回の課題で何か成長を感じてくださったのであれば嬉しいです。

もしもコーディングの部分で間違いなどありましたらコメントでご指摘いただけると幸いです。
また、その他質問などございましたらコメントをお願い致します。

次回は、今回作った大人数じゃんけんプログラムを応用して「あっち向いてほいプログラム」を作りたいと思います。下記リンクからどうぞ!
鋭意制作中

この記事が良かったと感じたらLGTMを宜しくお願いします!それではまた次回!:wave:

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2