2020年7月26日19:06, プログラミング
前回まででドメモのシミュレーションが手動で出来るところまで実装しました。ただ、手動で何万回もシミュレーションを行いたくはありません。またあるモデル(戦略)を試したいときに人間だと間違いを犯してしまう可能性もあります。
ここからはプレイヤーモデルを実装し、自動でドメモのシミュレーションができるようにしましょう。
新しくplayer_model.pyを作成します。
from player import Player
import random
import collections
class NormalPlayerModel(Player):
def think(self, open_cards=[]):
self.think_number_of_open_cards(open_cards)
while True:
self.card = random.choice(self.call_card_list)
if len(set([True]) & set(self.my_call_judge)) == 0:
return self.card
if self.my_call_judge[self.card - 1]:
return self.card
def think_number_of_open_cards(self, open_cards):
self.open_card_collections = collections.Counter(open_cards)
for open_card in self.open_card_collections.keys():
if int(self.open_card_collections[open_card]) == int(open_card):
self.my_call_judge[open_card - 1] = False
NormalPlayerModelというクラスオブジェクトを作成しています。これはドメモを行う一般的な思考をしたプレイヤーのモデルです。
このモデルはPlayerクラスを継承しています。継承とは継承するクラス(ここではPlayerクラス)が持つフィールド、メソッドを継承したクラス(ここではNormalPlayerModelクラス)で使えるようにすることです。
具体的に見ていきましょう。
class Player():
def A():
...
...
# Playerクラスを継承
class NormalPlayerModel(Player):
def think():
self.A() # 継承しているためPlayerクラスのメソッドAが使える。
このようにあるクラスを継承したクラスは、継承元のクラスが持つメソッドを使用することが出来ます。
このような継承を使用する理由は、表現をより単純にするためにあります。
もし、Playerクラスが持つAというメソッドが、これから数10個作るプレイヤーモデルクラスすべてに必要なメソッドだった場合を考えてみましょう。継承を使わない場合はAというメソッドをすべてのクラスへ書く必要があります。また、Aというメソッドに間違いが発生した場合、数10個のプレイヤーモデルクラスすべてのAメソッドを書き換えなければなりません。これはとても面倒くさいですよね。
そのため継承という便利な表現方法があるのです。
think_number_of_open_cardsメソッド内で使用しているcollectionsについて説明します。
collectionsとはリスト型オブジェクトに格納されている各値を種類ごとに分け、ディクショナリ型で返してくれるモジュールです。
例をみましょう。
>>> import collections
# コレクションオブジェクトを作成
>>> collection = collections.Counter([1, 2, 2, 3, 3, 4, 4, 4])
# コレクションのキーを確認
>>> collection.keys()
dict_keys([1, 2, 3, 4])
# コレクションを表示
>>> collection
Counter({4: 3, 2: 2, 3: 2, 1: 1})
collections.Counter(リスト型オブジェクト)でコレクションオブジェクトを作ります。このようにすることで、リスト型オブジェクト内にある値の種類がキーとなります。そのキーに対応しているのが、その値がリスト型オブジェクト内にある個数です。
例では1が1個、2と3が2個、4が3個あるため、コレクションを表示すると、Counter({4: 3, 2: 2, 3: 2, 1: 1})になっています。
このメソッドは宣言する数字を返すメソッドです。1行目でthink_number_of_open_cardsメソッドを使用して、宣言できる数字のリスト(my_call_judge)を更新します。
2行目以降ではランダムに数字を選択し、それが宣言できる数字のリストにあるかどうかを確認します。ない場合はもう一度ランダムに数字を選択、あればその数字を返します。
1行目で、現在公開されている情報(open_cards)を使用してコレクションを作成します。
このコレクションから公開されている情報のうち、数字と公開されているその数字の個数が同じもの(つまり7なら7個、1なら1個)を宣言できる数字のリストから除外します(TrueをFalseにする)。
このようにすることで7が7枚見えているのに7を宣言する、という状況を回避します。
さて、これでプレイヤーモデルの実装は完了しました。
次にこれを使用してドメモのシミュレーションをします。
新しくmain.pyを以下のように記載します。
from player_model import *
from game import Game
for i in range(1):
player_instances = [
NormalPlayerModel("A_Normal"),
NormalPlayerModel("B_Normal"),
NormalPlayerModel("C_Normal"),
NormalPlayerModel("D_Normal"),
NormalPlayerModel("E_Normal"),
]
game = Game(player_instances)
loop_number = 0
while True:
loop_number = loop_number + 1
if game.judge_game_run():
game.play()
else:
break
if loop_number > len(player_instances) * 28 + 100:
break
print(i)
print("finish !")
やっていることは単純で、Gameクラスに渡すプレイヤーインスタンスリストをここで作ったNormalPlayerModelにしています。
そして無限ループを発生させ(while True:の部分)、ドメモを自動的に実行させます。
無限ループから抜けるための条件としては、game.judge_game_runメソッドの戻り値がFalseだった場合(すべてのプレイヤーが自分の数字を当て終えたとき)です。
また、永遠にドメモが終わらなくならないように、loop_numberがある一定を超えた時点でも終了するようにしています。
ドメモシミュレーションの回数は先頭にあるfor文のrangeメソッド内にある数字で管理します。ここが1であれば1回、1000であれば1000回ドメモをします。
それでは実行しましょう。
途中経過を見たいならgame.pyに以下の記載をします。
Game:
...
def play():
...
print(self.turn_player.player_name)
print("宣言した数字 {0}".format(self.turn_player.get_my_call_cards()))
print("残り枚数 {0}\n".format(self.turn_player.get_my_cards()))
...
この状態でmain.pyを実行します。
A_Normal
宣言した数字 [7]
残り枚数 [6, 4, 6]
B_Normal
宣言した数字 [6]
残り枚数 [7, 1, 7]
C_Normal
宣言した数字 [3]
残り枚数 [7, 3, 3]
D_Normal
宣言した数字 [7]
残り枚数 [5, 2, 5]
E_Normal
宣言した数字 [7]
残り枚数 [6, 5, 4]
...
こんな感じに出力されるのが見えれば成功です!
これで自動的にドメモをシミュレーションすることができました!
ただこれではシミュレーションの結果が分かりにくいですよね。
これを可視化する方法を見て行きましょう。