Pythonでブラックジャック作ってみた

#はじめに

[プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし](https://qiita.com/hirossyi73/items/cf8648c31898216312e5)に挑戦してみました

書き終わるまで他の人のコードは見てません
みなさんも開発する気があるなら見る前に書いてみることをおすすめします！

#開発
## 開発するブラックジャックのルール
元記事のままです
>- 初期カードは52枚。引く際にカードの重複は無いようにする
- プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
- 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、**ディーラーの2枚目のカードは分からないようにする**
- その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
- プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
- プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
- プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
- JとQとKは10として扱う
- **<font color="red">Aはとりあえず「1」としてだけ扱う。</font>**「11」にはしない
- ダブルダウンなし、スピリットなし、サレンダーなし、その他特殊そうなルールなし


##開発してみて
元記事に書いてある通り、カードの柄と番号をどう扱うかがキモでした
私は上一桁を柄、下二桁を数字として
310 は 3 と 10 に分けてダイヤの 10 のように実装してみました
表示する際は辞書を使って変換します

##コード

```python:blackjack.py
from random import shuffle


SUIT = {
    1: 'ハート',
    2: 'スペード',
    3: 'ダイヤ',
    4: 'クローバー'
}

RANK = {
    1: 'A',
    11: 'J',
    12: 'Q',
    13: 'K'
}


class Deck:
    def __init__(self):
        self.deck = []
        for suit in range(1, 5):
            for rank in range(1, 14):
                self.deck.append(suit*100 + rank)
        shuffle(self.deck)

    def draw_card(self):
        return self.deck.pop()


class Participant:
    def __init__(self, name):
        self.name = name
        self.rank = []
        self.draw_card_history = []

    def get_sum(self):
        return sum(self.rank)

    def get_suit_rank(self, card):
        num_suit = card // 100
        num_rank = card % 100
        display_suit = SUIT[num_suit]
        display_rank = RANK.get(num_rank, str(num_rank))
        return num_suit, num_rank, display_suit, display_rank

    def set_hand(self, card, *, display=True):
        _, num_rank, display_suit, display_rank = self.get_suit_rank(card)
        if display:
            print('{} の引いたカードは {} の {} です'.format(self.name, display_suit, display_rank))
        else:
            print('{} の引いたカードはわかりません'.format(self.name))
        self.rank.append(min(num_rank, 10))
        self.draw_card_history.append(card)

    def over_twenty_one(self):
        if sum(self.rank) > 21:
            return True
        return False

    def display_suit_rank(self, n):
        card = self.draw_card_history[n-1]
        _, _, display_suit, display_rank = self.get_suit_rank(card)
        print('{} が引いた {} 枚目のカードは {} の {} です'.format(self.name, n, display_suit, display_rank))


class Player(Participant):
    def is_continue(self):
        print('{} のスコアは {}'.format(self.name, sum(self.rank)))
        if input('引く場合は y, やめる場合は n\n>') == 'y':
            return True
        return False


class Dealer(Participant):
    def is_continue(self):
        print('{} のスコアは {}'.format(self.name, sum(self.rank)))
        if self.get_sum() < 17:
            return True
        return False


def main():
    deck = Deck()
    player = Player('player')
    dealer = Dealer('dealer')

    player.set_hand(deck.draw_card())
    player.set_hand(deck.draw_card())
    dealer.set_hand(deck.draw_card())
    dealer.set_hand(deck.draw_card(), display=False)

    print()

    while player.is_continue():
        player.set_hand(deck.draw_card())
        if player.over_twenty_one():
            print('21 を越えました')
            print('あなたの負けです')
            break

    print()

    if not player.over_twenty_one():
        dealer.display_suit_rank(2)
        while dealer.is_continue():
            dealer.set_hand(deck.draw_card())

        if dealer.over_twenty_one() or player.get_sum() >= dealer.get_sum():
            print('あなたの勝ちです')
        else:
            print('あなたの負けです')


main()


```

##実行画面
パターンは4つ
###プレイヤーが21を超えたとき
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186471/cf3a4b8c-693c-07d7-5781-a813efc30cfa.png)

###プレイヤーが勝ったとき
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186471/d85733e4-6b5e-282f-7c42-f10099d22dd4.png)

###ディーラーが21を超えたとき
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186471/753e5836-0b87-d301-f63a-866feb30443c.png)


###ディーラーが勝ったとき
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186471/5290e701-f8ac-cd0d-0b79-d89111ecc267.png)




#おわりに
今回は上記の開発に書いてある通りに開発してみました
またエラーとか全く調べてないので何か起こるかもしれませんし何か足りないかもしれません
今後細かいとこの見直し、色々な機能を追加を考えると楽しいですね
その前にブラックジャックのルールを覚えなきゃ、、、

#追記（2018.07.25)

暇なときにちょこちょことやっています
<del>[GitHub]</del>

#追追記
もうやりません
全体のソースコードは以下です
スプリット・ダブルダウンに対応しています
(汚いのは許してください）

```python:blackjack.py

from itertools import product
from random import shuffle


class Card:
    SUITS = {
        1: 'ハート',
        2: 'スペード',
        3: 'ダイヤ',
        4: 'クラブ'
    }

    RANKS = {
        1: 'A',
        11: 'J',
        12: 'Q',
        13: 'K'
    }

    def __init__(self, number):
        self.num_suit = number // 100
        self.num_rank = [min(10, number % 100), (1, 11)][number % 100 == 1]
        self.display_suit = Card.SUITS[number // 100]
        self.display_rank = Card.RANKS.get(number % 100, str(number % 100))


class Deck:
    def __init__(self):
        self.__deck = []
        for suit in range(1, 5):
            for rank in range(1, 14):
                self.__deck.append(Card(suit * 100 + rank))
        shuffle(self.__deck)

    def draw_card(self):
        try:
            return self.__deck.pop()
        except IndexError:
            self.__init__()


class Hand:
    def __init__(self):
        self.hand = []

    def set_hand(self, card, *, display=True):
        if display:
            print("{} - {}".format(card.display_suit, card.display_rank))
        else:
            print("you can't see HOLE CARD")

        self.hand.append(card)

    def calculate_total_score(self):
        list_for_calculating_score = [card.num_rank for card in self.hand]
        ace = [rank for rank in list_for_calculating_score if type(rank) is tuple]
        if not ace:
            return sum(list_for_calculating_score)
        else:
            except_ace_score = sum([rank for rank in list_for_calculating_score if type(rank) is not tuple])
            ace_score = [sum(x) for x in product(*ace)]
            score = [ace_score[i] + except_ace_score for i in range(len(ace_score))]
            under_21_score = [i for i in score if i <= 21]
            if under_21_score:
                return max(under_21_score)
            else:
                return min(score)

    def is_busted(self):
        return self.calculate_total_score() > 21

    def is_natural_blackjack(self):
        if len(self.hand) == 2:
            first_card = self.hand[0].num_rank
            second_card = self.hand[1].num_rank
            if (first_card == 10 and second_card == (1, 11)) or (first_card == (1, 11) and second_card == 10):
                return True
        return False


class Dealer:
    def __init__(self, name):
        self.name = name
        self.hand = Hand()

    def set_hand(self, card, display=True):
        self.hand.set_hand(card, display=display)

    def display_hole_card(self):
        card = self.hand.hand[1]
        print("{} の HOLE CARD は {} の {} です。".format(self.name, card.display_suit, card.display_rank))

    def is_continue(self):
        return self.hand.calculate_total_score() < 17

    def display_score(self):
        print("{} のスコアは {}".format(self.name, self.hand.calculate_total_score()))


class Player:
    def __init__(self, name):
        self.name = name
        self.hand = Hand()
        self.done_split = False

    def set_hand(self, card):
        self.hand.set_hand(card)

    def can_split(self):
        if self.done_split or len(self.hand.hand) > 2:
            return False
        return self.hand.hand[0].num_rank == self.hand.hand[1].num_rank

    def can_double_down(self):
        return len(self.hand.hand) == 2

    def input_player_intention(self):
        display_words = {(True, True): 'HIT or STAND or Double Down or Split?\n>',
                         (False, True): 'HIT or STAND or Double Down?\n>',
                         (False, False): 'HIT or STAND?\n>'}

        correct_response = {(True, True): ['hit', 'stand', 'double down', 'doubledown', 'double', 'split'],
                            (False, True): ['hit', 'stand', 'double down', 'doubledown', 'double'],
                            (False, False): ['hit', 'stand']}

        while True:
            can_split = self.can_split()
            can_double_down = self.can_double_down()
            player_intention = input(display_words[(can_split, can_double_down)]).lower()

            if player_intention in correct_response[(can_split, can_double_down)]:
                return player_intention

    def display_score(self):
        print("{} のスコアは {}".format(self.name, self.hand.calculate_total_score()))

```


```python:game.py

from Blackjack.src.sorse import Hand, Deck, Dealer, Player


class Game:
    def __init__(self):
        self.player = Player('player')
        self.dealer = Dealer('dealer')
        self.deck = Deck()
        self.player_left = self.player_right = None

    def play(self):
        self.first_turn(self.player)
        self.first_turn(self.dealer)

        self.player_turn(self.player)
        self.dealer_turn()

        self.result()

    def first_turn(self, player):
        player.hand.set_hand(self.deck.draw_card())
        if type(player) == Dealer:
            player.hand.set_hand(self.deck.draw_card(), display=False)
        else:
            player.hand.set_hand(self.deck.draw_card())

    def split_func(self, player):
        self.player_left = Player('player left')
        self.player_right = Player('player right')

        print('\n---left')
        self.player_left.hand.set_hand(player.hand.hand[0])
        self.player_left.hand.set_hand(self.deck.draw_card())
        self.player_left.hand.done_split = True
        self.player_turn(self.player_left)

        print('\n--right')
        self.player_right.hand.set_hand(player.hand.hand[1])
        self.player_right.hand.set_hand(self.deck.draw_card())
        self.player_right.hand.done_split = True
        self.player_turn(self.player_right)

    def player_turn(self, player):
        while True:
            player.display_score()
            if player.hand.is_busted():
                break
            print()

            player_intention = player.input_player_intention()

            if player_intention == 'hit':
                player.hand.set_hand(self.deck.draw_card())
            elif player_intention in ['double down', 'doubledown', 'double']:
                player.hand.set_hand(self.deck.draw_card())
                player.display_score()
                break
            elif player_intention == 'split':
                player.done_split = True
                self.split_func(player)
                break
            elif player_intention == 'stand':
                player.display_score()
                break

    def dealer_turn(self):
        self.dealer.display_hole_card()

        while self.dealer.is_continue():
            self.dealer.display_score()
            self.dealer.hand.set_hand(self.deck.draw_card())
        self.dealer.display_score()

    def display_result(self, player1):
        player1.display_score()
        self.dealer.display_score()
        print('-' * 100)

        if player1.hand.is_natural_blackjack() and self.dealer.hand.is_natural_blackjack():
            print('引き分け')

        elif player1.hand.is_natural_blackjack():
            print('{} is natural black jack'.format(player1.name))
            print('あなたの勝ちです')

        elif self.dealer.hand.is_natural_blackjack():
            print('{} is natural black jack'.format(self.dealer.name))
            print('あなたの負けです')

        elif player1.hand.is_busted():
            print('あなたの負けです')

        elif self.dealer.hand.is_busted():
            print('あなたの勝ちです')

        elif player1.hand.calculate_total_score() > self.dealer.hand.calculate_total_score():
            print('あなたの勝ちです')

        elif self.dealer.hand.calculate_total_score() > player1.hand.calculate_total_score():
            print('あなたの負けです')

        elif player1.hand.calculate_total_score() == self.dealer.hand.calculate_total_score():
            print('引き分け')

    def result(self):
        if self.player.done_split:
            self.display_result(self.player_left)
            print('-' * 100)
            print()
            self.display_result(self.player_right)

        else:
            self.display_result(self.player)


if __name__ == '__main__':
    Game().play()

```