Pythonでブラックジャック作ってみた（その2）

https://qiita.com/sooz32904010/items/54adc625bc46442eb938

前回投稿したブラックジャックのコードについて、コメント欄でご指摘いただいた点を修正してみました。@shiracamusさまご意見ありがとうございました。

以下、修正内容になります。

```python:blacjack.py
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クラスに継承させています(このへんの構成は[出題者の方が想定した形](https://qiita.com/hirossyi73/items/cf8648c31898216312e5)に近いようです)。

Userクラスにはカードを引くか止めるか問う関数hit_standを、Dealerクラスには17以上になるまでカードを引き続ける関数auto_drawを設定しています。その他、プレイヤーの動作のひとつひとつをなるべく関数化して、Gameクラスではその関数を実行するだけにしてみました。

もうひとつは、ゲームのコンティニューで再帰関数を使わないようにしたこと。

関数game_continueではTrue,Falseを返すだけの形にしました。加えて関数play_gameの全体をwhile文で囲んでループ化しました。この他の関数でもやたらwhile文を多用しちゃっているような気がしますが、こんなものでしょうか。それとも再帰関数を避けようと思ったら、while文に頼るしかないものなのか。他に方法が思い浮かびませんでした。

```python:blackjack.py

       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つのことだけさせているか。クラス毎に関連性の高い関数が属しているか等。
 - なるべくメモリ消費の少ない、効率的な方法であるか
無駄な動きをさせない。データを溜め込まない等。

とはいえ、では何が適切であるか、何が効率的であるか、という部分は相変わらず手探りの状況です。

そんなわけで、引き続きコメント欄にて、ご意見を広く募集しております。