前回投稿したブラックジャックのコードについて、コメント欄でご指摘いただいた点を修正してみました。@shiracamusさまご意見ありがとうございました。
以下、修正内容になります。
from random import shuffle
import sys
class Card:
suits = ['♥','♠','♦','♣']
ranks = [None, "A","2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
def __init__(self, s, r):
self.suit = s
self.rank = r
def __int__(self):
r = self.ranks[self.rank]
if r == "J" or r == "Q" or r == "K":
r = 10
elif r == "A":
r = 1
return int(r)
def __str__(self):
v = self.ranks[self.rank] + " of " + self.suits[self.suit]
return v
class Deck:
def __init__(self):
self.cards = []
for i in range(1, 14):
for j in range (4):
self.cards.append(Card(j, i))
shuffle(self.cards)
def draw(self):
if len(self.cards) == 0:
return
return self.cards.pop()
class PlayerBase():
def __init__(self, name):
self.total = 0
self.name = name
self.deck = Deck()
def draw_msg(self,card):
d = '{} は {} を引いた'
print(d.format(self.name,card))
def score_msg(self):
s = '{} の合計スコア {} '
print(s.format(self.name,self.total))
def match(self):
if self.total == 21:
s = '{} の合計スコア {} '
print(s.format(self.name,self.total))
w = 'BLACKJACK! {}の勝ち'
print(w.format(self.name))
return True
elif self.total > 21:
s = '{} の合計スコア {} '
print(s.format(self.name,self.total))
l = 'BUST! {}の負け'
print(l.format(self.name,self.total))
return True
else:
return False
class User(PlayerBase):
def first_draw(self):
pc1 = self.deck.draw()
pc2 = self.deck.draw()
self.total += int(pc1) + int(pc2)
self.draw_msg(str(pc1))
self.draw_msg(str(pc2))
def hit_stand(self):
while True:
hs = input("ヒット or スタンド(h/s):")
if hs == "h":
pc = self.deck.draw()
self.total += int(pc)
self.draw_msg(str(pc))
return True
elif hs == "s":
return False
class Dealer(PlayerBase):
def first_draw(self):
dc1 = self.deck.draw()
dc2 = self.deck.draw()
self.total += int(dc1) + int(dc2)
self.draw_msg(str(dc1))
self.draw_msg("(伏せ札)")
return str(dc2)
def auto_draw(self):
while True:
if self.total < 17:
dc = self.deck.draw()
self.total += int(dc)
self.draw_msg(str(dc))
return True
else:
return False
class Game():
def __init__(self):
self.u = User('あなた')
self.d = Dealer('ディーラー')
def game_continue(self):
while True:
yn = input('ゲームを続けますか?(y/n):')
if yn == "y":
self.u.total = 0
self.d.total = 0
return True
elif yn == "n":
print('ゲームを終了します')
sys.exit(-1)
def play_game(self):
while True:
continue_flg = False
self.u.first_draw()
holecard = self.d.first_draw()
while self.u.hit_stand():
if self.u.match():
continue_flg = self.game_continue()
break
if continue_flg:
continue
self.u.score_msg()
o = '{} の伏せ札は {}'
print(o.format(self.d.name, holecard))
while self.d.auto_draw():
if self.d.match():
continue_flg = self.game_continue()
break
if continue_flg:
continue
self.d.score_msg()
if self.d.total < self.u.total:
print('{} の勝ち'.format(self.u.name))
elif self.d.total > self.u.total:
print('{} の負け'.format(self.u.name))
else:
print('引き分け')
if self.game_continue():
continue
if __name__ == '__main__':
game = Game()
game.play_game()
前回からの変更点
大きく2点、変更しました。
ひとつは、Gameクラスからプレイヤー関連のクラスに主な関数を移したこと。
プレイヤー関連のクラスはまずPlayerBaseのクラスを作成。その後でUserクラスとDealerクラスに継承させています(このへんの構成は出題者の方が想定した形に近いようです)。
Userクラスにはカードを引くか止めるか問う関数hit_standを、Dealerクラスには17以上になるまでカードを引き続ける関数auto_drawを設定しています。その他、プレイヤーの動作のひとつひとつをなるべく関数化して、Gameクラスではその関数を実行するだけにしてみました。
もうひとつは、ゲームのコンティニューで再帰関数を使わないようにしたこと。
関数game_continueではTrue,Falseを返すだけの形にしました。加えて関数play_gameの全体をwhile文で囲んでループ化しました。この他の関数でもやたらwhile文を多用しちゃっているような気がしますが、こんなものでしょうか。それとも再帰関数を避けようと思ったら、while文に頼るしかないものなのか。他に方法が思い浮かびませんでした。
while True:
continue_flg = False
self.u.first_draw()
holecard = self.d.first_draw()
while self.u.hit_stand():
if self.u.match():
continue_flg = self.game_continue()
break
if continue_flg:
continue
while文の多重ループを抜けるためにcontinue_flg変数を設定してます。シンプルそうに見えますが、この形にたどり着くまでにけっこう試行錯誤しました。うまくコンテニューできず変なところで終わったり、変なところから始まったり。
これまでごく単純な関数しか書いたことなかったので、今回いろいろチャレンジできてプラスになりました。反面「果たしてこれが正解なんだろうか」と思う部分もいくつかありました。こんなことを考えるようになったこと自体、「とりあえず動いているから良し」と思っていた以前とはえらい違いですが。「とりあえず動く」だけでも何通りもあることがわかったので、「AとBの方法があったら、どっちを採用すべきか」と考えてしまうわけです。
@shiracamusさまからいただいたご指摘から推測するに、以下のような要素が基準になると考えています。
- オブジェクトの責務が適切であるか
1つの関数に1つのことだけさせているか。クラス毎に関連性の高い関数が属しているか等。 - なるべくメモリ消費の少ない、効率的な方法であるか
無駄な動きをさせない。データを溜め込まない等。
とはいえ、では何が適切であるか、何が効率的であるか、という部分は相変わらず手探りの状況です。
そんなわけで、引き続きコメント欄にて、ご意見を広く募集しております。