Thirteenth floor

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

2020年6月30日20:09, プログラミング

ドメモシミュレーション

ドメモクラスとプレイヤークラスの連携を前回行いました。

ではようやく、ドメモのシミュレーションを行っていきましょう!

STEP1

新しくgame.pyを作成します。

以下のコードはドメモのシミュレーションを自動で行うことを表現しています。

これは全体の抜粋であるため、これ以外にもいくつかのメソッドを実装します。 ただ大きな流れを先に説明したいと思います。


class Game:
    def play(self):
        self.turn_player = self.get_turn_player()
        if self.turn_player.get_hand_num() > 0:
            self.call_card = self.turn_player.think(self.get_all_open_cards(self.turn_player))
            self.is_card_in_the_hand = self.turn_player.call_card(self.call_card)
            if self.is_card_in_the_hand:
                self.domemo.add_open_card(self.call_card)

        if self.turn_player.get_hand_num() == 0:
            self.add_finish_player(self.turn_player)

self.turn_player = self.get_turn_player()

この行は現在のターンプレイヤー、つまり数字を宣言するプレイヤーを設定しています。

self.get_turn_player()というメソッドを後で実装しますが、このメソッドは現在のターンプレイヤーを返します。

if self.turn_player.get_hand_num() > 0:

ここで、先程取得した現在のターンプレイヤーが持っている札の枚数を確認します。

プレイヤークラスはget_hand_num()という持っている札の枚数を返すメソッドを持っています。turn_playerはプレイヤークラスのインスタンスとして作られるため、このメソッドを使うことが出来ます。

もし札の枚数が0枚以上であれば、そのプレイヤーはまだドメモを続行しなければなりません。

self.call_card = self.turn_player.think(...)

札の枚数が0枚以上であれば、プレイヤーは思考を行い、数字を宣言します。

プレイヤークラスに実装されているthink()メソッドを用います。これはプレイヤークラスが思考を行い、その結果を返します。

self.is_card_in_the_hand = self.turn_player.call_card(self.call_card)

思考を行った後、実際にその数字を宣言します。プレイヤークラスが持っているcall_card()メソッドで数字を宣言させます。引数にはthink()メソッドで返した数字を入れます。

call_card()メソッドの返り値は、宣言した数字がある(True)か、ない(False)かのどちらかです。それを変数へ代入します。

if self.is_card_in_the_hand:

宣言した数字があるか、ないかを判断し、あった場合の処理を行います。

self.domemo.add_open_card(self.call_card)

宣言した数字があった場合、その数字は公開情報になります。そのためドメモインスタンスのadd_open_card()メソッドを使用して追加します。

このメソッドも未実装のため後で加えましょう。

if self.turn_player.get_hand_num() == 0:

ターンプレイヤーが持っている札が0枚だった場合の処理を記載します。

self.add_finish_player(self.turn_player)

持っている札が0枚であればゲーム終了のため、ターンプレイヤーをadd_finish_playerメソッドで終了プレイヤーとして登録します。

STEP2

STEP1で紹介したplay()というメソッドを繰り返し呼び出すことで、ターンプレイヤーは巡ります。最終的にすべてのプレイヤーが持つ札が0枚になるまで呼び出せば、ドメモのシミュレーションができますね。

ここからは細かいメソッドを追加していきましょう。


class Game:
    def play(self):
        ...

    def judge_game_run(self):
        if len(self.finish_player_list) < len(self.player_instances):
            return True
        else:
            return False

    def get_turn_player(self):
        loop_index = 0
        while loop_index < len(self.player_instances):
            self.turn_player = self.player_instances[self.turn_number % len(self.player_instances)]
            self.turn_number = self.turn_number + 1
            if self.turn_player.get_hand_num() > 0:
                break
            loop_index = loop_index + 1

        return self.turn_player

    def add_finish_player(self, player_instance):
        self.finish_player_list.append(player_instance)

    def get_field_open_cards(self):
        return self.domemo.open_cards

    def get_field_closed_cards(self):
        return self.domemo.closed_cards

    def get_all_open_cards(self, player_instance):
        number_list_of_card = [1, 2, 3, 4, 5, 6, 7]
        closed_field_cards = self.get_field_closed_cards()
        player_have_cards = player_instance.get_my_cards()
        closed_card_collections = collections.Counter(closed_field_cards + player_have_cards)

        for closed_card in closed_card_collections.keys():
            number_list_of_card[closed_card - 1] = \
                number_list_of_card[closed_card - 1] - closed_card_collections[closed_card]

        open_card_list = []
        for card, number in enumerate(number_list_of_card):
            open_card_list = open_card_list + [card + 1] * number

        return open_card_list

def judge_game_run()

これはドメモの終了を判断するメソッドです。

ドメモの終了とは、すべてのプレイヤーが自身の札を当て終わった場合です。

そのためfinish_player_listの数とplayer_instancesの数を比較しています。

finish_player_listplayer_instances未満の場合はTrueを、違う場合はFalseを返しています。

def get_turn_player()

このメソッドはターンプレイヤーを返します。

行っていることは単純です。

while文はloop_index < len(player_instances)Trueのとき、つまり最大でプレイヤー数だけ実行されます。

while文の内部は、ターンプレイヤーを決定するためのロジックです。

turn_numberlen(player_instances)で割った余りをインデックスとして、player_instancesの要素を取り出しています。それがターンプレイヤーになります。

例を見てみましょう。


player_instances = [A, B, C, D]

len(player_instances) # A, B, C, Dの4つになりますね。

turn_number # 最初は0でこのメソッドが呼ばれるたびに1増えます。

turn_number % len(player_instances) # 0を4で割った余りなので0です。

player_instances[turn_number % len(player_instances)] # インデックスが0なのでAが取り出されます。

turn_number # 1になりました。

turn_number % len(player_instances) # 1を4で割った余りなので1です。

player_instances[turn_number % len(player_instances)] # インデックスが1なのでBが取り出されます。

上記を繰り返していくとターンプレイヤーが巡っていくのがわかるでしょう。

ターンプレイヤーの持っている札が0枚以上であればすぐにwhile文をbreakで抜けます。 0だった場合はloop_indexを1増やして、再度while文を実行します。

最終的にreturnでターンプレイヤーを返します。

def add_finish_player()

これはプレイヤーインスタンスを受け取り、finish_player_listへ追加するメソッドです。

finish_player_listは終了したプレイヤーのリストで__init__で定義しておきます。

def get_field_open_cards()

ドメモクラスにある属性open_cardsを返すメソッドです。__init__でドメモインスタンスを作成しておきます。

def get_field_closed_cards()

ドメモクラスにある属性closed_cardsを返すメソッドです。__init__でドメモインスタンスを作成しておきます。

def get_all_open_cards()

これは、プレイヤーインスタンスを受け取り、そのプレイヤーから見えるすべての札を返すメソッドです。

特定のプレイヤーから見える札というのは、すべての札(number_list_of_cards)から場に伏せられている札(closed_field_cards)とプレイヤー自身が持っている札(player_have_cards)を除いたものです。

内部の詳しいロジックはぜひ考えてみてください。もっとわかりやすくできるかもしれないですし。

STEP3

最後に__init__メソッドとimport文を追加しましょう。


import collections
import random
import copy
from domemo import Domemo
from player import Player

class Game:
    def __init__(self, player_instances):
        self.player_instances = player_instances
        self.domemo = Domemo(self.player_instances)
        self.domemo.give_card_for_player()
        self.domemo.give_card_for_field()
        self.turn_player = self.player_instances[0]
        self.finish_player_list = []
        self.turn_number = 0
        self.is_card_in_the_hand = True

    def play(self):
        ...
    def judge_game_run(self):
        ...
    def get_turn_player(self):
        ...
    def add_finish_player(self, player_instance):
        ...
    def get_field_open_cards(self):
        ...
    def get_field_closed_cards(self):
        ...
    def get_all_open_cards(self, player_instance):
        ...

domemo.pyへ実装していなかったメソッドも追加します。


class Domemo:
    ...

    def add_open_card(self, card):
        self.open_cards.append(card)
        return None

STEP4

実行してみます。

以下(>>>の行)をPyCharmのPythonコンソールへ入力します。


>>> from player import Player
>>> from game import Game
>>> game = Game([Player("A"), Player("B"), Player("C"), Player("D"), Player("E")])

>>> while True:
        print(game.get_all_open_cards(game.turn_player))
        game.play()
        if not game.judge_game_run():
            break        

[1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7]
call num (1 ~ 7)>>> >? 4
[1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 7]
call num (1 ~ 7)>>> >? 5

...


実行すると、call numと聞かれるので、数字を入れます。

これを繰り返していくといつかはドメモが終了しループから抜けられます。

これでシミュレーションができるようになりましたね。

次回のお話

これだと、人の手を使って数字を入力しなければなりません。10000回もやりたくないですよね。

次回はすべてをコンピュータで完結させましょう!

コメント

コメント投稿ページへ