Thirteenth floor

【Pythonでボードゲームシミュレーション】DOMEMO その5

2020年6月12日21:58, プログラミング

プレイヤークラス作成!

前回まではdomemo.pyへDomemoクラスを作成しました。

今回は別のファイルを使用してプレイヤークラスを作成していきます。

さて、クラスオブジェクトはどんなデータを持っていて、どんな操作が出来るかを表現するものだと説明しました。そこでドメモというゲームをプレイすることを考えると、ドメモの箱だけあってもしょうがないですね。

それをプレイするプレイヤーがどうしても必要になります。

プレイヤーも、Aさんというプレイヤーさんがいるとすれば、Zさんというプレイヤーさんもいるかもしれません。重要なのはAさんもZさんも全く異なるデータを持っていたり、操作を行ったりするわけではないということです。

ボードゲームには必ずルールがあります。ですので、プレイヤーは一定の行動を行うわけです。

例を見ましょう。

次の図はプレイヤーが持つデータを示しています。もちろん、ここには書いていないようなデータもあるでしょう。

プレイヤーが持つデータ

これをPythonで表現しましょう。

STEP1

player.pyを作成して以下のコードを記載します。


class Player:
    def __init__(self, name):
        self.player_name = name
        self.my_cards = []
        self.original_cards = []
        self.my_call_cards = []
        self.my_call_judge = [True, True, True, True, True, True, True]
        self.other_call_cards = []
        self.call_card_list = [1, 2, 3, 4, 5, 6, 7]

文法の話

クラスオブジェクトで使用する__init__メソッドについてはその3で説明しましたね。

1点違うのは、nameという引数を受け取っているという点です。

Domemoクラスのインスタンス化はDomemo()のようにかっこ()の中には何も入れませんでした。 これは、Domemoクラスの__init__メソッドが何も(self以外)引数を受け取らないためです。

Playerクラスではnameを受け取るため、インスタンス化する場合はPlayer("player_name")と何かしらの名前を入れる必要があります。渡されたnameself.name = nameによってインスタンス変数self.player_nameに格納されます。

このようにすることで、インスタンス化するときにプレイヤー名を渡せば、異なる名前を持つPlayerクラスを作成できるのです。

__init__メソッド

各変数の意味を記載していきます。

  • self.player_name:プレイヤー名(文字列型)
  • self.my_cards:自分が持っている札(リスト型)
  • self.original_cards:自分が最初に持っていた札(リスト型)
  • self.my_call_cards:自分が宣言した数字(リスト型)
  • self.my_call_judge:自分が宣言した数字が当たっていたか、外れていたか(リスト型)
  • self.other_call_cards:相手が宣言した数字(リスト型)
  • self.call_card_list:宣言できる数字(リスト型)

私がシミュレーションする過程で必要になったデータを、Playerクラスに持たせています。

これは改良することができて、実際のゲームでプレイヤーが持っている情報をPythonで表現すればよいのです。他に考えられるとすれば、ブラフを言った回数や、声の大きさ、等も加えてみると違ったシミュレーションが出来るかもしれません。

雰囲気を変数で表現できたら素晴らしいですね。

STEP2

メソッドを追加していきましょう。

次のコードをplayer.pyへ追記します。


import copy

class Player:
    def __init__(self, name):
        ....

    def set_my_cards(self, cards):
        self.my_cards = cards

    def get_my_cards(self):
        return self.my_cards

    def set_original_cards(self):
        self.original_cards = copy.copy(self.get_my_cards())

    def get_original_cards(self):
        return self.original_cards

    def get_hand_num(self):
        return len(self.my_cards)

    def get_my_call_cards(self):
        return self.my_call_cards


文法の話(セッター・ゲッター)

ここで設定したメソッドはセッター、ゲッターと一般的に呼ばれているものです。

__init__メソッドで作成したmy_cardsoriginal_cardsへ値を代入するのがセッター、それらの値をおreturnで返すのがゲッターです。

Pythonでは別にセッター、ゲッターを使用せずとも、インスタンス名.データ名で各データを参照することができます。

ただ、データと操作を分離したほうが私は良いと考えています。

例えば、後でmy_cardsに値をセットするときは、その前にprint文でセットする値を出力したい、となったとします。

このような場合、セッターを使っていればセッターを変更するだけで済みます。

インスタンス名.データ名で参照している場合は、その前にprint文を使う必要がありますね。これが1箇所だけならばよいのですが、10箇所、20箇所で使っていたら、直すのも大変ですね。

ですので、セッター、ゲッターを使用したほうが良いかなと思います。

もちろん、どんな書き方をしても大丈夫です。私だって変な書き方をしていることもありますし。要するに動けばいいという精神を持つことが大事でしょう。言語は伝えるためにあるわけで、下手でも伝えるほうが、伝えられないより、よっぽどいいと思います。

文法の話(copy)

もう1つ文法の話です。

import copycopyというモジュールを読み込んでいます。

これはリスト型オブジェクトのコピーを返すメソッドを使用するために読み込みました。

さて、ここでどうして=ではなくてcopyを使うのか、確認しましょう。


>>> a = [1,2,3]
>>> b = a
>>> b
[1, 2, 3]    # コピーされている
>>> del b[1]
>>> b
[1, 3]    # bから2を削除
>>> a
[1, 3]    # aからも2が消えてしまっている!

上記の例は=を使用した場合に起こる問題を示しています。

aという変数へリスト型オブジェクト[1,2,3]を格納しました。その後、bという変数へaを格納しました。

ここで行っている変数への格納という処理が、数値型や文字列型を格納する場合と違うのです。


# a = [1,2,3]に相当
a >>>参照>>> [1,2,3]

# b = aに相当
a >>>参照>>> [1,2,3] <<<参照<<< b

# del b[1]に相当
a >>>参照>>> [1,3] <<<参照<<< b

上記のように、リスト型オブジェクトは変数へ入れられているというよりも、変数がリスト型オブジェクトを見ている(参照している)と言えます。

したがって、同じリスト型オブジェクトを見ているa, bは同じ結果を返します。

bがオブジェクトの中身を消せば、aから見た場合も消えた後のリスト型オブジェクトが見えるわけです。

続いてcopyのしていることを確認しましょう。


# a = [1,2,3]に相当
a >>>参照>>> [1,2,3]

# b = copy.copy(a)に相当
a >>>参照>>> [1,2,3]
                |  
                |<<< コピー
                v
b >>>参照>>> [1,2,3]

# del b[1]に相当
a >>>参照>>> [1,2,3]
b >>>参照>>> [1,3]

copyはリスト型オブジェクトのコピーを作成します。そのため、aとbは中身は同じでも別のオブジェクトを見ているのです。

そのため、bから2を消しても、aからは消えません。

次回のお話

次回はセッター、ゲッター以外のメソッドを追加していきます!

コメント

コメント投稿ページへ