FINAL問題 カードゲーム
from collections import deque
class Game:
def __init__(self):
self.cards = []
self.turn = 0
def has_keycard(self):
self.turn += 1
self.run_query()
return any(self.cards)
def run_query(self):
query, x = self.input_event()
for _ in range(int(x)):
getattr(self, query)()
def input_event(self):
event = input().split()
if self.turn == 1:
event = ['draw', 5]
return event
def draw(self):
self.cards.append(self.deck.pop())
def discard(self):
self.grave_yard.append(self.deck.pop())
def return_from_the_graveyard(self):
self.deck.appendleft(self.grave_yard.pop())
def exclude(self):
self.exclusion.append(self.deck.pop())
def return_from_the_exclusion(self):
self.deck.appendleft(self.exclusion.pop())
@classmethod
def initialize(cls, n):
# 下 <--- [ 山札 ] --- > 上
cls.deck = deque([False] * 40)
cls.deck[-n] = True
cls.grave_yard = []
cls.exclusion = []
def setup():
N, K = map(int, input().split())
Game.initialize(N)
paiza = Game()
return K, paiza
def main(K, paiza):
result = 'Lose'
for _ in range(K):
if paiza.has_keycard():
result = paiza.turn
break
print(result)
if __name__ == '__main__':
main(*setup())
Bランク問題としてふさわしい複雑さです。順を追って解説します。
まず setup() 関数とクラスメソッドです。クラスメソッドには「山札」「墓地」「除外」の山を用意します。山札以外は空の状態です。
山札の上(末尾)から N 番目にキーカードを配置します。
@classmethod
def initialize(cls, n):
# 下 <--- [ 山札 ] --- > 上
cls.deck = deque([False] * 40) # 山札
cls.deck[-n] = True # 末尾から n 番目にキーカードを配置する
cls.grave_yard = [] # 墓地
cls.exclusion = [] # 除外
paiza君のインスタンス変数には paiza君の手札とターン数を持たせています。ターン数は main() 関数の でもカウントできますが、わかりやすくインスタンス内でカウントすることにします。
def __init__(self):
self.cards = []
self.turn = 0
次に main() 関数を一時飛ばして has_keycard() メソッドから説明します。
def has_keycard(self):
self.turn += 1
self.run_query()
return any(self.cards)
このメソッドはクエリ処理をしたあと、paiza君の手札にキーカードがあるかどうかをチェックします。
まず run_query() メソッドを実行します。
def run_query(self):
query, x = self.input_event()
for _ in range(int(x)):
getattr(self, query)()
def input_event(self):
event = input().split()
if self.turn == 1:
event = ['draw', 5]
return event
input_event() メソッドで入力から得た値を返します。その時、'game_start' の時だけ、山札から5枚カードを取る指示に変えて返します。これは draw() メソッドを5回実行することと同等ですので、そのように値を返します。
再び run_query() メソッドに戻り、値を query
と x
に振り分けて 文を実行します。この文では getattr(self, query)()
を x 回実行します。
getattr() は簡単に言えば、ただの文字列 query
を関数名(メソッド名も可)として変換する機能です。
-
getattr(オブジェクト, str)
str は
「文字列 = 関数名」
「文字列 = メソッド名」
と、なること。
今回の例で言うと、オブジェクトは paiza
です。インスタンスをターゲットとしていますので、オブジェクトは self です。
str はそのインスタンスのメソッド名と同じ文字列であること。
最後に引数を、無ければ ()
だけを付ければ正常に処理できるはずです。
この方法を使えば、どんなにイベントの数が多かろうとこの一文だけで済んでしまいます。イベント名とメソッド名を一致させることだけ注意すれば、文よりも簡単に思えてくるでしょう。
そしてメインディッシュのクエリ処理です。問題にも書かれていますが、各メソッドの機能を言葉にしてみます。
def draw(self):
self.cards.append(self.deck.pop())
山札からカードを1枚取って、手札に追加する。
def discard(self):
self.grave_yard.append(self.deck.pop())
山札からカードを1枚取り、そのカードを墓地の山の一番上に置く。
def return_from_the_graveyard(self):
self.deck.appendleft(self.grave_yard.pop())
墓地の山からカードを1枚取り、そのカードを山札の一番下に置く。
def exclude(self):
self.exclusion.append(self.deck.pop())
山札のカードを1枚取り、そのカードを除外の山の一番上に置く。
def return_from_the_exclusion(self):
self.deck.appendleft(self.exclusion.pop())
除外の山からカードを1枚取り、そのカードを山札の一番下に置く。
これらメソッドはそれぞれ1枚分の処理しかしていません。それを run_query() 内の で x 回処理するようにしています。
def run_query(self):
query, x = self.input_event()
for _ in range(int(x)):
getattr(self, query)()
これらの処理をすべて終えると has_keycard() メソッドへ戻ります。
def has_keycard(self):
self.turn += 1
self.run_query()
return any(self.cards)
main() 関数に戻るのですが、戻り値として現在 paiza君の手札にキーカードがあるかをチェックします。any() 関数は、引数の中に1つでも True がある場合は True を返し、全て False の場合は False を返します。
def main(K, paiza):
result = 'Lose'
for _ in range(K):
if paiza.has_keycard():
result = paiza.turn
break
print(result)
結果出力のための result
には初め 'Lose'
を代入して初期化しておきます。
.has_keycard() メソッドから返ってきた値を の評価にかけ、もしキーカードを持っていたなら現在のターン数を result
に再代入し、ループを抜けます。
そして最終結果を画面に出力して完了となります。お疲れさまでした。🏄
クラス型がまだよくわからないようでしたら、このプログラム例を参考に手続型で組んでみてください。作っていくうちに意味がわかってくると思います。