演習 - ゲームにビヘイビアーを追加する

完了

じゃんけんゲームへのクラスとデータの追加が済んだので、最後の部分であるビヘイビアーを追加する番です。 メソッドの形式でビヘイビアーを追加します。 クラスにメソッドを追加した最終的な結果は、動作するじゃんけんゲームになります。

ビヘイビアーを実装する

オブジェクト指向プログラミング (OOP) スタイルでプログラムを構築する場合、最初にモデルを作成してからコードを作成することを学習しました。 モデリングにより、プログラムを構成するオブジェクト、データ、ビヘイビアーが表されたテーブル出力が生成されました。 同じテーブルをもう一度示しておきます。

段階 Actor 動作 Data
入力 参加者 手を選択する Participant(choice) で "選択" として保存された手
処理中 GameRound 選択内容とゲーム ルールを比較する 調べられた "結果"
処理中 GameRound 結果の値に基づいてポイントを与える 勝った Participant(point) に追加された "ポイント"
処理中 ゲーム 続行の回答を確認する 回答は true、continue、quit
出力 ゲーム 新しいゲーム ラウンドまたはゲーム終了のクレジット

ここでは、Behavior 列に注目し、クラスに追加されるメソッドをそれに設定します。 また、それらのメソッドにコードを追加し、必要な動作が実行されるようにします。

これまでのコードは次のようになります。 以下の手順でそれを拡張してみましょう。

  1. ファイル rock-paper-scissor.py があるはずです。 ない場合、次の手順を実行します。

  2. ファイル rock-paper-scissor を作成し、エディターを開きます。

    touch rock-paper-scissor.py
    code .
    

    そして、次のコードを追加します。

    class Participant:
        def __init__(self):
            self.points = 0
            self.choice = ""
    
    class GameRound:
    
    class Game:
        def __init__(self):
            self.endGame = False
            self.participant = Participant()
            self.secondParticipant = Participant()
    

ゲームを開始する

ゲームの最初の部分にはそのセットアップが含まれます。これは、ゲーム自体をインスタンス化し、参加者の操作を待機するポイントまでゲームを進めることを意味します。

  1. rock-paper-scissor.py の内容を次のコードに置き換え、ファイルを保存します。

    class Participant:
        def __init__(self, name):
            self.name = name
            self.points = 0
            self.choice = ""
        def choose(self):
            self.choice = input("{name}, select rock, paper or scissor: ".format(name= self.name))
            print("{name} selects {choice}".format(name=self.name, choice = self.choice))
    
    class GameRound:
        def __init__(self, p1, p2):
            p1.choose()
            p2.choose()
        def compareChoices(self):
            print("implement")
        def awardPoints(self):
            print("implement")
    
    class Game:
        def __init__(self):
            self.endGame = False
            self.participant = Participant("Spock")
            self.secondParticipant = Participant("Kirk")
        def start(self):
            game_round = GameRound(self.participant, self.secondParticipant)
    
        def checkEndCondition(self):
            print("implement")
        def determineWinner(self):
            print("implement")
    
    game = Game()
    game.start()
    

    テーブルから各オブジェクトにメソッドを追加しました。 行った変更をテーブルに表しておくと、どのビヘイビアーによってどのメソッドが追加されたのかがわかりやすくなります。

    動作 メソッド Actor
    手を選択する choose() 参加者
    選択内容を比較する compareChoices() GameRound
    ポイントを与える awardPoints() GameRound
    続行の回答を確認する checkEndCondition() ゲーム
    ゲーム終了クレジット determineWinner() ゲーム

    上のテーブルのほとんどのビヘイビアーは、似た名前のメソッドに対応しています。 例外は "ゲーム終了クレジット" で、determineWinner() になります。 その理由は、ゲームを終了する過程で、勝者を確認してその情報を出力できるようにするためです。 このメソッドに何か別の名前を付けてもかまいません。

  2. python3 を呼び出してコードを実行します。

    python3 rock-paper-scissor.py
    

    プログラムにより、次のような出力が生成されます。

    Spock, select rock, paper or scissor:
    

    この出力は、プログラムがユーザーの操作を待機していることを意味します。

  3. rock を選択して Enter キーを押し、プログラムが動作することを確認します。 paper を選択して、もう一度 Enter キーを押します。

    出力は次のテキストのようになります。

    Spock, select rock, paper or scissor: rock
    Spock selects rock
    Kirk, select rock, paper or scissor: paper
    Kirk selects paper
    

Note

この演習のソリューションは、「Start a game - solution code」にあります。

ルールを実装する

問題の説明から、特定の選択が他の選択に勝つことがわかります。 たとえば、石はハサミに勝ち、ハサミは紙に勝ちます。 次のようなコードを記述するとよさそうに見えます。

if choice1 == "rock" and choice2 == "scissor":
    return 1
elif choice1 == "paper" and choice2 == "scissor":
    return -1
else:
    # something else

# and so on

結果として大量のコードが作成され、少し扱いにくくなります。 ゲームのルール セットを拡張する必要があり、コードの保守がさらに困難になったらどうしますか。

さいわいなことに、もっと良い方法があります。 より優れたアプローチは、ルールをマトリックスとして考えることです。 マトリックスを使用すると、他の組み合わせに勝つ組み合わせを表現できます。 勝つ手には 1 を、引き分けには 0 を、負ける手には -1 を設定します。 次のマトリックスは、石、紙、ハサミについてのものです。

選択肢 論文 ハサミ
0 -1 1
論文 1 0 -1
ハサミ -1 1 0

次のような多次元配列を使用して、上記のテーブルを Python で実装できます。

rules = [
    [0, -1, 1],
    [1, 0, -1],
    [-1, 1, 0]
]

rules[0][1] # Rock vs Paper = -1, Paper wins over Rock
  1. Participant クラスを見つけ、toNumericalChoice() メソッドを追加します。

        def toNumericalChoice(self):
            switcher = {
                "rock": 0,
                "paper": 1,
                "scissor": 2
            }
            return switcher[self.choice]
    

    上記のメソッドにより、コマンド ラインの文字列入力が整数に変換されます。 そうすることで、その回の勝者を簡単に決定できるようになります。

    ヒント

    コードを正しくインデントするには、必ずカーソルを適切に置いてください。

  2. GameRound クラスを見つけ、compareChoices() メソッドを更新します。

        def compareChoices(self, p1, p2):
            return self.rules[p1.toNumericalChoice()][p2.toNumericalChoice()]
    

    このコードにより、2 つの選択を比較して、勝者を決定することができます。

  3. 同じクラスに、getResultAsString() メソッドを追加します。

        def getResultAsString(self, result):
            res = {
                0: "draw",
                1: "win",
                -1: "loss"
            }       
            return res[result]
    

    このメソッドは、結果を判断し、わかりやすいテキストを画面に出力するのに役立ちます。

  4. 引き続き同じクラスで、__init__() の内容を次のコードに置き換えます。

        def __init__(self, p1, p2):
            self.rules = [
                [0, -1, 1],
                [1, 0, -1],
                [-1, 1, 0]
            ]
    
            p1.choose()
            p2.choose()
            result = self.compareChoices(p1,p2)
            print("Round resulted in a {result}".format(result = self.getResultAsString(result) ))
    

    前のコードで導入された rules フィールドには、石、紙、ハサミのルールの実装が含まれています。 さらに、self.compareChoices() を呼び出すことで、行われた 2 つの選択が "比較" されます。 最後に、見る人にわかりやすい結果を画面に出力する行があります。

Note

この演習のソリューションは、「Implement rules - solution code」にあります。

ゲームのスコアを付ける

ゲームのスコアを付けるには、プレイが終了した後で、適切なプレーヤーにポイントを割り当てます。 勝ったプレーヤーは 1 ポイント獲得し、引き分けと負けのときはポイントはありません。

  1. Participant クラスを見つけ、incrementPoint() メソッドを追加します。

        def incrementPoint(self):
            self.points += 1
    

    ヒント

    属性を変更するメソッドの導入は、カプセル化に向けた最初のステップです。 必要であれば、points フィールドを __points__ に変更して、外側からメンバーを "隠ぺい" し、カプセル化を練習してください。

  2. GameRound クラスを見つけて、__init()__ メソッドの最後に次のコードを追加します。

            if result > 0:
               p1.incrementPoint()
            elif result < 0:
               p2.incrementPoint()
    

Note

この演習のソリューションは、「Score game - solution code」にあります。

継続の確認を追加する

継続の確認は、ゲームの終わりにある質問で、続行するかどうかをプレーヤーにたずねます。 ユーザーが続行しないことを選択した場合は、現在の結果と勝者 (存在する場合) を出力できます。

  1. Game クラスを見つけ、メソッド determineWinner() を更新します。

        def determineWinner(self):
            resultString = "It's a Draw"
            if self.participant.points > self.secondParticipant.points:
                resultString = "Winner is {name}".format(name=self.participant.name)
            elif self.participant.points < self.secondParticipant.points:
                resultString = "Winner is {name}".format(name=self.secondParticipant.name)
            print(resultString)
    
  2. 同じクラス内で、メソッド checkEndCondition() を更新します。

        def checkEndCondition(self):
            answer = input("Continue game y/n: ")
            if answer == 'y':
                GameRound(self.participant, self.secondParticipant)
                self.checkEndCondition()
            else:
                print("Game ended, {p1name} has {p1points}, and {p2name} has {p2points}".format(p1name = self.participant.name, p1points= self.participant.points, p2name=self.secondParticipant.name, p2points=self.secondParticipant.points))
                self.determineWinner()
                self.endGame = True
    
  3. 同じクラスで、start() メソッドのコードを次のコードに置き換えます。

        def start(self):
            while not self.endGame:
                GameRound(self.participant, self.secondParticipant)
                self.checkEndCondition()
    
  4. ファイルを保存します。

  5. コマンド python3 rock-paper-scissor.py を実行して、プログラムをテストします。

    python3 rock-paper-scissor.py
    

    入力として rockpaper を選択し、続行を確認するメッセージが表示されたら n を入力します。

    出力は次のテキストのようになります。

    Spock, select rock, paper or scissor: rock
    Spock selects rock
    Kirk, select rock, paper or scissor: paper
    Kirk selects paper
    Round resulted in a loss
    Continue game y/n: n
    Game ended, Spock has 0, and Kirk has 1
    Winner is Kirk
    

Note

この演習のソリューションは、「Continuation query - solution code」にあります。

お疲れさまでした。 じゃんけんゲームの OOP バージョンを実装できました。